diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 39eae4b..0df4b92 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -5,7 +5,7 @@ env:
# reason to support multiple Python versions, so all actions are run with
# this version. Quote the version to avoid interpretation as a floating
# point number.
- PYTHON_VERSION: "3.12"
+ PYTHON_VERSION: "3.13"
"on":
merge_group: {}
diff --git a/.github/workflows/periodic-ci.yaml b/.github/workflows/periodic-ci.yaml
index 65dd8b4..ab121d4 100644
--- a/.github/workflows/periodic-ci.yaml
+++ b/.github/workflows/periodic-ci.yaml
@@ -10,7 +10,7 @@ env:
# reason to support multiple Python versions, so all actions are run with
# this version. Quote the version to avoid interpretation as a floating
# point number.
- PYTHON_VERSION: "3.12"
+ PYTHON_VERSION: "3.13"
"on":
schedule:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca4e5b8..b4a0f42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@ Find changes for the upcoming release in the project's [changelog.d directory](h
+
+## 0.2.0 (2024-12-18)
+
+### New features
+
+- Add a `wobbly expire` command that deletes all jobs from the database that have passed their destruction time. This is run periodically as a Kubernetes `CronJob`.
+
## 0.1.0 (2024-12-12)
diff --git a/Dockerfile b/Dockerfile
index 43333f6..911f98b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@
# - Runs a non-root user.
# - Sets up the entrypoint and port.
-FROM python:3.12.8-slim-bookworm AS base-image
+FROM python:3.13.1-slim-bookworm AS base-image
# Update system packages.
COPY scripts/install-base-packages.sh .
@@ -21,7 +21,7 @@ RUN ./install-base-packages.sh && rm ./install-base-packages.sh
FROM base-image AS install-image
# Install uv.
-COPY --from=ghcr.io/astral-sh/uv:0.5.8 /uv /bin/uv
+COPY --from=ghcr.io/astral-sh/uv:0.5.10 /uv /bin/uv
# Install system packages only needed for building dependencies.
COPY scripts/install-dependency-packages.sh .
diff --git a/changelog.d/20241217_122642_rra_DM_48184.md b/changelog.d/20241217_122642_rra_DM_48184.md
deleted file mode 100644
index fa84efc..0000000
--- a/changelog.d/20241217_122642_rra_DM_48184.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### New features
-
-- Add a `wobbly expire` command that deletes all jobs from the database that have passed their destruction time. This is run periodically as a Kubernetes `CronJob`.
diff --git a/pyproject.toml b/pyproject.toml
index 1a54473..2c31eef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,13 +11,13 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: POSIX",
"Typing :: Typed",
]
-requires-python = ">=3.12"
+requires-python = ">=3.13"
dependencies = [
"alembic[tz]",
"fastapi>=0.100",
diff --git a/requirements/dev.in b/requirements/dev.in
index 828a9f9..0140fd1 100644
--- a/requirements/dev.in
+++ b/requirements/dev.in
@@ -18,6 +18,7 @@ pydantic
pytest
pytest-asyncio
pytest-cov
+pytest-sugar
# Documentation
scriv
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 7ae7090..d3ecf0c 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -16,15 +16,15 @@ asgi-lifespan==2.1.0 \
--hash=sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308 \
--hash=sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f
# via -r requirements/dev.in
-attrs==24.2.0 \
- --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \
- --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
+attrs==24.3.0 \
+ --hash=sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff \
+ --hash=sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308
# via
# -c requirements/main.txt
# scriv
-certifi==2024.8.30 \
- --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \
- --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9
+certifi==2024.12.14 \
+ --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \
+ --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db
# via
# -c requirements/main.txt
# httpcore
@@ -376,6 +376,7 @@ packaging==24.2 \
# via
# -c requirements/main.txt
# pytest
+ # pytest-sugar
pluggy==1.5.0 \
--hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
--hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
@@ -497,14 +498,19 @@ pytest==8.3.4 \
# -r requirements/dev.in
# pytest-asyncio
# pytest-cov
-pytest-asyncio==0.24.0 \
- --hash=sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b \
- --hash=sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276
+ # pytest-sugar
+pytest-asyncio==0.25.0 \
+ --hash=sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609 \
+ --hash=sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3
# via -r requirements/dev.in
pytest-cov==6.0.0 \
--hash=sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35 \
--hash=sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0
# via -r requirements/dev.in
+pytest-sugar==1.0.0 \
+ --hash=sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a \
+ --hash=sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd
+ # via -r requirements/dev.in
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
@@ -543,12 +549,15 @@ sniffio==1.3.1 \
# anyio
# asgi-lifespan
# httpx
+termcolor==2.5.0 \
+ --hash=sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8 \
+ --hash=sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f
+ # via pytest-sugar
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via
# -c requirements/main.txt
- # anyio
# mypy
# pydantic
# pydantic-core
diff --git a/requirements/main.txt b/requirements/main.txt
index b4e3469..887a92d 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -115,9 +115,9 @@ asyncpg==0.30.0 \
--hash=sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50 \
--hash=sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34
# via safir
-attrs==24.2.0 \
- --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \
- --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
+attrs==24.3.0 \
+ --hash=sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff \
+ --hash=sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308
# via
# jsonschema
# referencing
@@ -129,9 +129,9 @@ casefy==1.0.0 \
--hash=sha256:bc99428475c2089c5f6a21297b4cfe4e83dff132cf3bb06655ddcb90632af1ed \
--hash=sha256:c89f96fb0fbd13691073b7a65c1e668e81453247d647479a3db105e86d7b0df9
# via dataclasses-avroschema
-certifi==2024.8.30 \
- --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \
- --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9
+certifi==2024.12.14 \
+ --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \
+ --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db
# via
# httpcore
# httpx
@@ -964,18 +964,18 @@ proto-plus==1.25.0 \
--hash=sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961 \
--hash=sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91
# via google-api-core
-protobuf==5.29.1 \
- --hash=sha256:012ce28d862ff417fd629285aca5d9772807f15ceb1a0dbd15b88f58c776c98c \
- --hash=sha256:027fbcc48cea65a6b17028510fdd054147057fa78f4772eb547b9274e5219331 \
- --hash=sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34 \
- --hash=sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110 \
- --hash=sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0 \
- --hash=sha256:50879eb0eb1246e3a5eabbbe566b44b10348939b7cc1b267567e8c3d07213853 \
- --hash=sha256:5a41deccfa5e745cef5c65a560c76ec0ed8e70908a67cc8f4da5fce588b50d57 \
- --hash=sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb \
- --hash=sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d \
- --hash=sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155 \
- --hash=sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18
+protobuf==5.29.2 \
+ --hash=sha256:13d6d617a2a9e0e82a88113d7191a1baa1e42c2cc6f5f1398d3b054c8e7e714a \
+ --hash=sha256:2d2e674c58a06311c8e99e74be43e7f3a8d1e2b2fdf845eaa347fbd866f23355 \
+ --hash=sha256:36000f97ea1e76e8398a3f02936aac2a5d2b111aae9920ec1b769fc4a222c4d9 \
+ --hash=sha256:494229ecd8c9009dd71eda5fd57528395d1eacdf307dbece6c12ad0dd09e912e \
+ --hash=sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9 \
+ --hash=sha256:a0c53d78383c851bfa97eb42e3703aefdc96d2036a41482ffd55dc5f529466eb \
+ --hash=sha256:b2cc8e8bb7c9326996f0e160137b0861f1a82162502658df2951209d0cb0309e \
+ --hash=sha256:b6b0d416bbbb9d4fbf9d0561dbfc4e324fd522f61f7af0fe0f282ab67b22477e \
+ --hash=sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851 \
+ --hash=sha256:e621a98c0201a7c8afe89d9646859859be97cb22b8bf1d8eacfd90d5bda2eb19 \
+ --hash=sha256:fde4554c0e578a5a0bcc9a276339594848d1e89f9ea47b4427c80e5d72f90181
# via
# google-api-core
# googleapis-common-protos
@@ -1112,9 +1112,9 @@ pydantic-core==2.27.1 \
# pydantic-xml
# safir
# safir-arq
-pydantic-settings==2.6.1 \
- --hash=sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87 \
- --hash=sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0
+pydantic-settings==2.7.0 \
+ --hash=sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66 \
+ --hash=sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5
# via
# wobbly (pyproject.toml)
# safir
@@ -1136,9 +1136,9 @@ python-dotenv==1.0.1 \
# via
# pydantic-settings
# uvicorn
-python-multipart==0.0.19 \
- --hash=sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc \
- --hash=sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d
+python-multipart==0.0.20 \
+ --hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \
+ --hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13
# via safir
python-schema-registry-client==2.6.0 \
--hash=sha256:a0688a9cd6e2a616a79fb46a6615c531cd5f9e2a5145f5c95932f792417731cb \
@@ -1326,17 +1326,17 @@ rsa==4.9 \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
# via google-auth
-safir==9.0.1 \
- --hash=sha256:8235503c7198665417e5645d87639d5c79bebfd9bf8d529154677f622915b97e \
- --hash=sha256:e0e4fb1bb42981ddb1ad5dd8843e4aa3c73cde56500152f2cfdbeaf05bd63617
+safir==9.1.0 \
+ --hash=sha256:38718de0d2e5f9623eddc511700a15fb56fee88da7bba0e1210dc831276aa9aa \
+ --hash=sha256:630b2e59ae87edab25fb2c3d5ccd6ca156c0d525eb543624532bbf4fd3c85536
# via wobbly (pyproject.toml)
-safir-arq==9.0.1 \
- --hash=sha256:14fb89cd0182d64feb9fca214a76642a858aea25210b60790058f4542d0d7fd7 \
- --hash=sha256:ddac70f37dcd78e4f816c2d4a9f8c5d21bb5d861b580bcce9a727b0517f4e9ce
+safir-arq==9.1.0 \
+ --hash=sha256:10a10ceb9ad8c22c316fe79383e8fb019cb08a08ac9e175b5980c30f083d9da5 \
+ --hash=sha256:2958806f6af356d6051a67ee4513ea525f0114f1f48636b116a2be7aab2f9f78
# via safir
-safir-logging==9.0.1 \
- --hash=sha256:775ab8b2c1a62fe5779f8d4504797df7affb5dbebc70fcad00307cda419d637e \
- --hash=sha256:e5dfdfbdafb0a60dd2b8cdd63f8171cb703830cd5d433d60c27028cc9a4044f1
+safir-logging==9.1.0 \
+ --hash=sha256:614cf63ec2f54dec640df8da9ece69d8e5fb43b19f98a943430ceefac7b2f267 \
+ --hash=sha256:c486c4406ed9ffc41e3ff977ac8938cb89cd57ba7f20436a03f9b336bf0008e0
# via safir
six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
@@ -1429,7 +1429,6 @@ typing-extensions==4.12.2 \
# via
# aiokafka
# alembic
- # anyio
# dataclasses-avroschema
# fastapi
# faststream
@@ -1444,9 +1443,9 @@ urllib3==2.2.3 \
--hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \
--hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9
# via requests
-uvicorn==0.32.1 \
- --hash=sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e \
- --hash=sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175
+uvicorn==0.34.0 \
+ --hash=sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4 \
+ --hash=sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9
# via wobbly (pyproject.toml)
uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' \
--hash=sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0 \
diff --git a/requirements/tox.txt b/requirements/tox.txt
index d8d8380..1b08028 100644
--- a/requirements/tox.txt
+++ b/requirements/tox.txt
@@ -6,9 +6,9 @@ cachetools==5.5.0 \
# via
# -c requirements/main.txt
# tox
-certifi==2024.8.30 \
- --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \
- --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9
+certifi==2024.12.14 \
+ --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \
+ --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db
# via
# -c requirements/dev.txt
# -c requirements/main.txt
@@ -230,24 +230,24 @@ urllib3==2.2.3 \
# -c requirements/main.txt
# docker
# requests
-uv==0.5.8 \
- --hash=sha256:0f2bcdd00a49ad1669e217a2787448cac1653c9968d74bfa3732f3c25ca26f69 \
- --hash=sha256:2b3076c79746d4f83257c9dea5ba0833b0711aeff8e6695670eadd140a0cf67f \
- --hash=sha256:2ee40bc9c08fea0e71092838c0fc36df83f741807d8be9acf2fd4c4757b3171e \
- --hash=sha256:365eb6bbb551c5623a73b1ed530f4e69083016f70f0cf5ca1a30ec66413bcda2 \
- --hash=sha256:4a3325af8ed1effa7076967472c063b0000d609fd6f561c7751e43bab30297f1 \
- --hash=sha256:56715389d240ac989af2188cd3bfc2b603d31b42330e915dacfe113b34d8e65b \
- --hash=sha256:5989bbbbca072edc1875036c76aed74ec3dfc4741de7d1f060e181717efea6ac \
- --hash=sha256:8058ab06d2f69355694f6e9a36edc45164474c516b4e2895bd67f8232d9022ed \
- --hash=sha256:84f26ce1736d075d1df34f7c3f6b0b728cecd9a4da3e5160d5d887587830e7ce \
- --hash=sha256:8a8cbe1ffa0ef5c2f1c90622e07211a8f93f48daa2be1bd4592bb8cda52b0285 \
- --hash=sha256:a7956787658fb9253fba49741886409402a48039bee64b1697397d27284919af \
- --hash=sha256:aa03c338e19456d3a6544a94293bd2905837ae22720cc161c83ea0fd13c3b09f \
- --hash=sha256:c56022edc0f61febbdef89e6f699a0e991932c493b7293635b4814e102d040d2 \
- --hash=sha256:c91d0a2b8218af2aa0385b867da8c13a620db22077686793c7231f012cb40619 \
- --hash=sha256:defd5da3685f43f74698634ffc197aaf9b836b8ba0de0e57b34d7bc74d856fa9 \
- --hash=sha256:e146062e4cc39db334cbde38d56d2c6301dd9cf6739ce07ce5a4d71b4cbc2d00 \
- --hash=sha256:f8ade0430b6618ae0e21e52f61f6f3943dd6f3184ef6dc4491087b27940427f9
+uv==0.5.10 \
+ --hash=sha256:064e977957e61aaaf7215bbd8f8566bcb22d7662c8adc929d039010fdb686436 \
+ --hash=sha256:06eb14988a75cc178241747a9437d23faad7d62e2d9b955db7e8a8098853341a \
+ --hash=sha256:253a02e03bf83bc0ec4e17242f54a4af2fef6191fcfb392b2613defd2b2a2f89 \
+ --hash=sha256:27f27eba58b9a71c3a7905ca966c69adf5a4a1df1dd14ef4d064c40cbaabc49e \
+ --hash=sha256:326603d44454a8856a5660bb406e99194f3c8d2cc4504c97c99871da59575022 \
+ --hash=sha256:4e0b91598e67d8c1228b47894a61fffb9d82caf8f1080bb9f21df49530118db6 \
+ --hash=sha256:502d9d10f5f139c850b1f6085a0c5719d49dd39d767504ce7c4245b47531f156 \
+ --hash=sha256:5890ca6703c371cecc88c2a7bf32fc47187a865fc577df0d40d390fcbdec76f0 \
+ --hash=sha256:68a6b992b7ebae9f3fa2f395348c95e6f05745246b067a26e7597a6730fcb690 \
+ --hash=sha256:7337ed40bae6f37d9335bf7f83bb43d08b6c141212b1ca3b15a9194c4d438ffe \
+ --hash=sha256:87dd4473ebf585fcd78a818bf8735ab39a157bef4f712e8b22e753b7344f6290 \
+ --hash=sha256:8bc47bd623b1f8fa883b7afbf480286b946512d9ac7bf23105e7d63ef702ea7b \
+ --hash=sha256:936759d8de8f78969756ee2b1558b4e9bd4b059922d0840cdd162a190c95ac50 \
+ --hash=sha256:adc0dad56118127b3a1cc0126149a9b8c643fd4e4c5fa37be6af4bd84d33d30c \
+ --hash=sha256:b61812ee4765f07db02ff616d4aac9c514857c0648459242a286243fe92d6223 \
+ --hash=sha256:d0d0e75a4337076f43936b11d6cc4cb11e261948c719adb8e208b78454a122a0 \
+ --hash=sha256:fa8607cc07cc9e666e531a9533b02d45bbb376ae314721434643c328298709b4
# via tox-uv
virtualenv==20.28.0 \
--hash=sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0 \
diff --git a/src/wobbly/handlers/service.py b/src/wobbly/handlers/service.py
index 091511c..85d3983 100644
--- a/src/wobbly/handlers/service.py
+++ b/src/wobbly/handlers/service.py
@@ -9,7 +9,7 @@
from typing import Annotated
-from fastapi import APIRouter, Depends, Header, Path, Response
+from fastapi import APIRouter, Body, Depends, Header, Path, Response
from safir.dependencies.gafaelfawr import auth_dependency
from safir.models import ErrorLocation
from safir.slack.webhook import SlackRouteErrorHandler
@@ -140,7 +140,7 @@ async def delete_job(
async def patch_job(
*,
job_id: Annotated[JobIdentifier, Depends(job_identifier_dependency)],
- update: JobUpdate,
+ update: Annotated[JobUpdate, Body()],
context: Annotated[RequestContext, Depends(context_dependency)],
) -> SerializedJob:
job_service = context.factory.create_job_service()
diff --git a/src/wobbly/models.py b/src/wobbly/models.py
index 758648e..c990959 100644
--- a/src/wobbly/models.py
+++ b/src/wobbly/models.py
@@ -5,7 +5,7 @@
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
-from typing import Annotated, Self, TypeAlias
+from typing import Annotated, Self, override
from pydantic import BaseModel, Field
from safir.database import DatetimeIdCursor
@@ -80,14 +80,17 @@ class JobIdentifier:
class JobCursor(DatetimeIdCursor[SerializedJob]):
"""Cursor for paginated lists of jobs."""
+ @override
@staticmethod
def id_column() -> InstrumentedAttribute:
return SQLJob.id
+ @override
@staticmethod
def time_column() -> InstrumentedAttribute:
return SQLJob.creation_time
+ @override
@classmethod
def from_entry(
cls, entry: SerializedJob, *, reverse: bool = False
@@ -114,7 +117,7 @@ class JobSearch:
"""Limit the number of jobs returned to at most this count."""
-JobUpdate: TypeAlias = Annotated[
+type JobUpdate = Annotated[
JobUpdateAborted
| JobUpdateCompleted
| JobUpdateError
diff --git a/tests/handlers/service_test.py b/tests/handlers/service_test.py
index 36a9867..7702689 100644
--- a/tests/handlers/service_test.py
+++ b/tests/handlers/service_test.py
@@ -427,6 +427,12 @@ async def test_errors(client: AsyncClient) -> None:
r = await client.get("/wobbly/jobs", params={"limit": -1}, headers=headers)
assert r.status_code == 422
+ # Unsupported updates are rejected.
+ r = await client.patch(
+ "/wobbly/jobs/1", json={"phase": "SUSPENDED"}, headers=headers
+ )
+ assert r.status_code == 422
+
async def create_jobs(
client: AsyncClient, headers: dict[str, str], count: int