supython 0.1.8__tar.gz → 0.1.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {supython-0.1.8 → supython-0.1.9}/CHANGELOG.md +34 -0
- {supython-0.1.8 → supython-0.1.9}/PKG-INFO +10 -11
- {supython-0.1.8 → supython-0.1.9}/README.md +9 -10
- {supython-0.1.8 → supython-0.1.9}/pyproject.toml +1 -1
- {supython-0.1.8 → supython-0.1.9}/src/supython/cli.py +31 -2
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/cron.py +31 -4
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/decorators.py +37 -2
- {supython-0.1.8 → supython-0.1.9}/.gitignore +0 -0
- {supython-0.1.8 → supython-0.1.9}/LICENSE +0 -0
- {supython-0.1.8 → supython-0.1.9}/SECURITY.md +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/auth.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/auth_templates.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/auth_users.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/db.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/functions.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/jobs.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/ops.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/realtime.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_auth.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_auth_templates.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_auth_users.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_db.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_functions.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_jobs.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_ops.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_realtime.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/service_storage.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/storage.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/api/system.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/audit.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/deps.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/errors.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/session.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/spa.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Alert-dluGVkos.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Alert-dluGVkos.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Audit-Njung3HI.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Audit-Njung3HI.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Backups-DzPlFgrm.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Backups-DzPlFgrm.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Buckets-ByacGkU1.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Buckets-ByacGkU1.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Channels-BoIuTtam.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Channels-BoIuTtam.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Crons-B67vc39F.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Crons-B67vc39F.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DataTable-COAAWEft.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DataTable-COAAWEft.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Empty-cr2r7e2u.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Empty-cr2r7e2u.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Grid-hFkp9F4P.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Grid-hFkp9F4P.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Input-DppYTq9C.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Input-DppYTq9C.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/JsonField-DibyJgun.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/JsonField-DibyJgun.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/LoginView-BjLyE3Ds.css +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/LoginView-CoOjECT_.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/LoginView-CoOjECT_.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Logs-D9WYrnIT.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Logs-D9WYrnIT.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Logs-DS1XPa0h.css +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Queue-CywZs6vI.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Queue-CywZs6vI.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Routes-BiLXE49D.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Routes-BiLXE49D.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Routes-C-ianIGD.css +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-DKy2_KQi.css +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Select-DIzZyRZb.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Select-DIzZyRZb.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Space-n5-XcguU.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Space-n5-XcguU.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/TableData-CQIagLKn.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/TableData-CQIagLKn.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Tag-D1fOKpTH.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Tag-D1fOKpTH.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Templates-BS-ugkdq.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Templates-BS-ugkdq.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Thing-CEAniuMg.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Thing-CEAniuMg.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Users-wzwajhlh.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Users-wzwajhlh.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/_plugin-vue_export-helper-DGA9ry_j.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/dist-VXIJLCYq.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/dist-VXIJLCYq.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/format-length-CGCY1rMh.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/format-length-CGCY1rMh.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/get-Ca6unauB.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/get-Ca6unauB.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/index-CeE6v959.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/index-CeE6v959.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/pinia-COXwfrOX.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/pinia-COXwfrOX.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/resources-Bt6thQCD.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/resources-Bt6thQCD.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useResource-C_rJCY8C.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useResource-C_rJCY8C.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useTable-CnZc5zhi.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useTable-CnZc5zhi.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useTable-Dg0XlRlq.css +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useToast-DsZKx0IX.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/useToast-DsZKx0IX.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/utils-sbXoq7Ir.js +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/utils-sbXoq7Ir.js.map +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/favicon.svg +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/icons.svg +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/index.html +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/app.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/_email_job.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/claims.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/deps.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/providers/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/providers/github.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/providers/google.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/providers/oauth.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/providers/registry.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/ratelimit.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/router.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/auth/service.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/backups/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/backups/_backup_job.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/backups/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/backups/service.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/body_size.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/_auth.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/_client.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/_config.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/_functions.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/_storage.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/client/py.typed +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/db.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/db_admin.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/extensions.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/functions/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/functions/context.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/functions/loader.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/functions/router.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/functions/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/gen/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/gen/_introspect.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/gen/types_py.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/gen/types_ts.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/health.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/hooks.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/backends.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/context.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/cron_inproc.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/registry.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/router.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/service.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jobs/worker.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/jwks.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/keyset.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/logging_config.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/mail.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/mailer.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrate.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0001_extensions_and_roles.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0002_auth_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0003_demo_todos.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0004_auth_v0_2.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0005_storage_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0006_realtime_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0007_jobs_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0008_jobs_last_error.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0009_auth_rate_limits.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0010_worker_heartbeat.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0011_admin_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0012_auth_banned_until.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0013_email_templates.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0014_realtime_payload_warning.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/migrations/0015_backups_schema.sql +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/passwords.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/broker.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/protocol.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/router.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/service.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/topics.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/realtime/websocket.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/init_project.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/Caddyfile.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/README.md.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/apps_hooks.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/apps_jobs.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/asgi.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/docker-compose.prod.yml.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/docker-compose.yml.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/docker_postgres_Dockerfile.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/docker_postgres_postgresql.conf.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/env.example.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/functions_README.md.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/gitignore.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/manage.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/migrations/.gitkeep +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/package_init.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/scaffold/templates/settings.py.tmpl +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/secretset.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/security_headers.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/settings.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/settings_module.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/__init__.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/backends.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/router.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/schemas.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/service.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/storage/signing.py +0 -0
- {supython-0.1.8 → supython-0.1.9}/src/supython/tokens.py +0 -0
|
@@ -31,6 +31,39 @@ Each entry links the relevant `PROJECT.md` section and decision-log row
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
+
## [0.1.9] — 2026-05-29
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- `@cron(...)` now also registers the decorated function as the job
|
|
39
|
+
handler for `job_name`. Previously the decorator only wrote a
|
|
40
|
+
`CronDefinition`, so when pg_cron fired `jobs.enqueue(p_name :=
|
|
41
|
+
<job_name>)` the worker rejected the job with `unknown job:
|
|
42
|
+
<job_name>` on every tick. Pass `register_handler=False` to keep
|
|
43
|
+
the pre-fix behaviour (schedule fires a handler declared elsewhere
|
|
44
|
+
with `@job`).
|
|
45
|
+
- `supython cron list` and `supython cron sync` now load the user's
|
|
46
|
+
settings module and `EXTENSIONS` before reading the registry,
|
|
47
|
+
mirroring `worker run` and the FastAPI startup path. Without the
|
|
48
|
+
bootstrap, `cron list` always printed `no crons registered` and a
|
|
49
|
+
subsequent `cron sync` would silently wipe every row from
|
|
50
|
+
`jobs.cron_schedules` and unschedule every matching pg_cron entry.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- `@cron(...)` accepts `register_handler` (default `True`) plus the
|
|
55
|
+
job kwargs forwarded to the auto-registered `JobDefinition`:
|
|
56
|
+
`max_attempts`, `backoff`, `backoff_base_s`, `backoff_max_s`,
|
|
57
|
+
`role`, `claims_from`, `accepts_payload`.
|
|
58
|
+
- `supython cron sync --allow-empty` (and the matching
|
|
59
|
+
`sync_pg_cron(conn, allow_empty=True)` argument). Without the flag,
|
|
60
|
+
`sync_pg_cron` now refuses to act when the in-process registry is
|
|
61
|
+
empty but `jobs.cron_schedules` is non-empty — guards against a
|
|
62
|
+
forgotten extension bootstrap silently wiping production schedules.
|
|
63
|
+
The empty-registry-and-empty-table cold-start case stays a no-op.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
34
67
|
## [0.1.0] — 2026-05-09
|
|
35
68
|
|
|
36
69
|
The first public release. Everything currently on `main` collapses
|
|
@@ -201,4 +234,5 @@ v0.1–v0.7 plus a v1.1.x admin track; see §19 decision log
|
|
|
201
234
|
---
|
|
202
235
|
|
|
203
236
|
|
|
237
|
+
[0.1.9]: https://github.com/Tkeby/supython/releases/tag/v0.1.9
|
|
204
238
|
[0.1.0]: https://github.com/Tkeby/supython/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supython
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: A lightweight Postgres-first BaaS framework for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/Tkeby/supython
|
|
6
6
|
Project-URL: Repository, https://github.com/Tkeby/supython
|
|
@@ -58,7 +58,7 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
|
|
59
59
|
# supython
|
|
60
60
|
|
|
61
|
-
> A lightweight, Postgres-first BaaS framework for Python.
|
|
61
|
+
> A lightweight, Postgres-first BaaS framework for Python.
|
|
62
62
|
|
|
63
63
|
**the database owns the schema, Python owns the things SQL is bad at**.
|
|
64
64
|
It leans on [PostgREST](https://postgrest.org)
|
|
@@ -69,7 +69,7 @@ storage, functions, workers, and an optional admin control plane.
|
|
|
69
69
|
supython is for a specific person with a specific problem:
|
|
70
70
|
> A developer who wants to build a CRUD-heavy web app (most apps are), who thinks in SQL, who wants Postgres to own authorization, and who wants auth + storage + custom logic without assembling the integration themselves.
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
|
|
74
74
|
**Core platform**
|
|
75
75
|
- **Email/password auth** — signup, login, refresh-token rotation with **reuse detection**
|
|
@@ -106,7 +106,7 @@ Shipped [v0.1.2]:
|
|
|
106
106
|
- **Secret rotation** — JWT keys, symmetric secrets, Postgres passwords; all with zero-downtime runbooks
|
|
107
107
|
- **Multi-arch Docker image** — `linux/amd64` + `linux/arm64`, non-root user, `tini` PID 1, ~64 MB
|
|
108
108
|
|
|
109
|
-
**Admin control plane**
|
|
109
|
+
**Admin control plane**
|
|
110
110
|
- **Vue 3 + Vite SPA** at `/admin` — no runtime Node deps; pre-built static bundle in the wheel
|
|
111
111
|
- **Database surface** — schema browser, table data with role switcher, SQL workspace (read-only default + write toggle), RLS policy editor with dry-run, migrations panel
|
|
112
112
|
- **Auth surface** — user search, ban/unban/force-logout, refresh-token inspector, audit log, email template editing
|
|
@@ -393,7 +393,7 @@ supython dev
|
|
|
393
393
|
| `JOBS_DRAIN_TIMEOUT_S` | `30.0` | Graceful shutdown drain (seconds) |
|
|
394
394
|
| `JOBS_DEV_INPROCESS` | `false` | Spawn worker in-process during `supython dev` |
|
|
395
395
|
|
|
396
|
-
##
|
|
396
|
+
## Auth endpoints
|
|
397
397
|
|
|
398
398
|
| Endpoint | Method | Purpose |
|
|
399
399
|
|---|---|---|
|
|
@@ -436,7 +436,7 @@ denylist without breaking that contract. The mitigation is a **short
|
|
|
436
436
|
long-lived (`REFRESH_TOKEN_TTL=30d`) so users don't get prompted to log
|
|
437
437
|
in every few minutes — clients refresh transparently.
|
|
438
438
|
|
|
439
|
-
### Custom JWT claims
|
|
439
|
+
### Custom JWT claims
|
|
440
440
|
|
|
441
441
|
Register a claims provider to inject application-specific claims into every
|
|
442
442
|
access token minted by the auth endpoints. Each provider is an async callable
|
|
@@ -472,7 +472,7 @@ Notes:
|
|
|
472
472
|
- Refresh re-collects claims, so a token rotated via `/auth/v1/refresh`
|
|
473
473
|
reflects current state.
|
|
474
474
|
|
|
475
|
-
### Reading the caller in your routes
|
|
475
|
+
### Reading the caller in your routes
|
|
476
476
|
|
|
477
477
|
Application code mounts its own routers alongside supython's. To gate an
|
|
478
478
|
endpoint on a valid bearer token — or to read custom claims out of it —
|
|
@@ -818,7 +818,7 @@ unit tests always run in isolation.
|
|
|
818
818
|
**CI:** runners with Docker run `supython test up && supython test run`;
|
|
819
819
|
runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
820
820
|
|
|
821
|
-
## Roadmap
|
|
821
|
+
## Roadmap
|
|
822
822
|
|
|
823
823
|
- ✅ Email/password auth, PostgREST contract, RLS demo
|
|
824
824
|
- ✅ OAuth, password reset, magic link, OTP, reuse detection, email backend, test suite
|
|
@@ -829,12 +829,11 @@ runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
|
829
829
|
- ✅ Production observable: structured JSON logs, `/livez`/`/readyz`/`/health`, security headers, input size guards, audit log completeness, OAuth PKCE, secret rotation runbooks
|
|
830
830
|
- ✅ (partial) Multi-arch Docker images, admin control plane (Vue 3 SPA — database, auth, storage, functions, realtime, jobs, backups, log tail), CI buildx workflow; benchmarks + security audit pass + dependency budget CI remaining
|
|
831
831
|
- *(deferred)* — Realtime v2 over logical replication
|
|
832
|
-
- v0.1.2 Release — final sweep, tag, publish wheel, production deployment with no patches
|
|
833
832
|
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
834
833
|
|
|
835
|
-
###
|
|
834
|
+
### Future
|
|
836
835
|
|
|
837
|
-
- **
|
|
836
|
+
- **Admin control plane** polish (tests + remaining DoD items)
|
|
838
837
|
- **Realtime v2** — logical replication (demand-driven; swap when trigger overhead or >8KB payload data warrants it)
|
|
839
838
|
- **Prometheus `/metrics`** + **OpenTelemetry** — optional extras
|
|
840
839
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# supython
|
|
2
2
|
|
|
3
|
-
> A lightweight, Postgres-first BaaS framework for Python.
|
|
3
|
+
> A lightweight, Postgres-first BaaS framework for Python.
|
|
4
4
|
|
|
5
5
|
**the database owns the schema, Python owns the things SQL is bad at**.
|
|
6
6
|
It leans on [PostgREST](https://postgrest.org)
|
|
@@ -11,7 +11,7 @@ storage, functions, workers, and an optional admin control plane.
|
|
|
11
11
|
supython is for a specific person with a specific problem:
|
|
12
12
|
> A developer who wants to build a CRUD-heavy web app (most apps are), who thinks in SQL, who wants Postgres to own authorization, and who wants auth + storage + custom logic without assembling the integration themselves.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
|
|
16
16
|
**Core platform**
|
|
17
17
|
- **Email/password auth** — signup, login, refresh-token rotation with **reuse detection**
|
|
@@ -48,7 +48,7 @@ Shipped [v0.1.2]:
|
|
|
48
48
|
- **Secret rotation** — JWT keys, symmetric secrets, Postgres passwords; all with zero-downtime runbooks
|
|
49
49
|
- **Multi-arch Docker image** — `linux/amd64` + `linux/arm64`, non-root user, `tini` PID 1, ~64 MB
|
|
50
50
|
|
|
51
|
-
**Admin control plane**
|
|
51
|
+
**Admin control plane**
|
|
52
52
|
- **Vue 3 + Vite SPA** at `/admin` — no runtime Node deps; pre-built static bundle in the wheel
|
|
53
53
|
- **Database surface** — schema browser, table data with role switcher, SQL workspace (read-only default + write toggle), RLS policy editor with dry-run, migrations panel
|
|
54
54
|
- **Auth surface** — user search, ban/unban/force-logout, refresh-token inspector, audit log, email template editing
|
|
@@ -335,7 +335,7 @@ supython dev
|
|
|
335
335
|
| `JOBS_DRAIN_TIMEOUT_S` | `30.0` | Graceful shutdown drain (seconds) |
|
|
336
336
|
| `JOBS_DEV_INPROCESS` | `false` | Spawn worker in-process during `supython dev` |
|
|
337
337
|
|
|
338
|
-
##
|
|
338
|
+
## Auth endpoints
|
|
339
339
|
|
|
340
340
|
| Endpoint | Method | Purpose |
|
|
341
341
|
|---|---|---|
|
|
@@ -378,7 +378,7 @@ denylist without breaking that contract. The mitigation is a **short
|
|
|
378
378
|
long-lived (`REFRESH_TOKEN_TTL=30d`) so users don't get prompted to log
|
|
379
379
|
in every few minutes — clients refresh transparently.
|
|
380
380
|
|
|
381
|
-
### Custom JWT claims
|
|
381
|
+
### Custom JWT claims
|
|
382
382
|
|
|
383
383
|
Register a claims provider to inject application-specific claims into every
|
|
384
384
|
access token minted by the auth endpoints. Each provider is an async callable
|
|
@@ -414,7 +414,7 @@ Notes:
|
|
|
414
414
|
- Refresh re-collects claims, so a token rotated via `/auth/v1/refresh`
|
|
415
415
|
reflects current state.
|
|
416
416
|
|
|
417
|
-
### Reading the caller in your routes
|
|
417
|
+
### Reading the caller in your routes
|
|
418
418
|
|
|
419
419
|
Application code mounts its own routers alongside supython's. To gate an
|
|
420
420
|
endpoint on a valid bearer token — or to read custom claims out of it —
|
|
@@ -760,7 +760,7 @@ unit tests always run in isolation.
|
|
|
760
760
|
**CI:** runners with Docker run `supython test up && supython test run`;
|
|
761
761
|
runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
762
762
|
|
|
763
|
-
## Roadmap
|
|
763
|
+
## Roadmap
|
|
764
764
|
|
|
765
765
|
- ✅ Email/password auth, PostgREST contract, RLS demo
|
|
766
766
|
- ✅ OAuth, password reset, magic link, OTP, reuse detection, email backend, test suite
|
|
@@ -771,12 +771,11 @@ runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
|
771
771
|
- ✅ Production observable: structured JSON logs, `/livez`/`/readyz`/`/health`, security headers, input size guards, audit log completeness, OAuth PKCE, secret rotation runbooks
|
|
772
772
|
- ✅ (partial) Multi-arch Docker images, admin control plane (Vue 3 SPA — database, auth, storage, functions, realtime, jobs, backups, log tail), CI buildx workflow; benchmarks + security audit pass + dependency budget CI remaining
|
|
773
773
|
- *(deferred)* — Realtime v2 over logical replication
|
|
774
|
-
- v0.1.2 Release — final sweep, tag, publish wheel, production deployment with no patches
|
|
775
774
|
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
776
775
|
|
|
777
|
-
###
|
|
776
|
+
### Future
|
|
778
777
|
|
|
779
|
-
- **
|
|
778
|
+
- **Admin control plane** polish (tests + remaining DoD items)
|
|
780
779
|
- **Realtime v2** — logical replication (demand-driven; swap when trigger overhead or >8KB payload data warrants it)
|
|
781
780
|
- **Prometheus `/metrics`** + **OpenTelemetry** — optional extras
|
|
782
781
|
|
|
@@ -115,6 +115,22 @@ def _run_async(coro): # type: ignore[no-untyped-def]
|
|
|
115
115
|
return asyncio.run(coro)
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def _bootstrap_user_modules() -> None:
|
|
119
|
+
"""Import the user's settings module + extensions so decorators register.
|
|
120
|
+
|
|
121
|
+
Mirrors ``worker_run`` and ``create_app``. CLI subcommands that read the
|
|
122
|
+
in-process registry (e.g. ``cron list``, ``cron sync``) must call this
|
|
123
|
+
first, otherwise the registry is empty and a ``cron sync`` would wipe
|
|
124
|
+
every row from ``jobs.cron_schedules``.
|
|
125
|
+
"""
|
|
126
|
+
from .extensions import load_extensions
|
|
127
|
+
from .settings_module import UserSettings, load_user_settings
|
|
128
|
+
|
|
129
|
+
s = get_settings()
|
|
130
|
+
user = load_user_settings(s.settings_module) if s.settings_module else UserSettings()
|
|
131
|
+
load_extensions([*s.extensions, *user.extensions])
|
|
132
|
+
|
|
133
|
+
|
|
118
134
|
@gen_cli.command("types")
|
|
119
135
|
def gen_types(
|
|
120
136
|
lang: str = typer.Option("py", "--lang", help="Target language (`py` or `ts`)."),
|
|
@@ -418,6 +434,7 @@ def cron_list() -> None:
|
|
|
418
434
|
"""List registered cron schedules."""
|
|
419
435
|
from .jobs.registry import get_registry
|
|
420
436
|
|
|
437
|
+
_bootstrap_user_modules()
|
|
421
438
|
crons = list(get_registry().iter_crons())
|
|
422
439
|
if not crons:
|
|
423
440
|
typer.echo("no crons registered.")
|
|
@@ -427,17 +444,29 @@ def cron_list() -> None:
|
|
|
427
444
|
|
|
428
445
|
|
|
429
446
|
@cron_cli.command("sync")
|
|
430
|
-
def cron_sync(
|
|
447
|
+
def cron_sync(
|
|
448
|
+
allow_empty: bool = typer.Option(
|
|
449
|
+
False,
|
|
450
|
+
"--allow-empty",
|
|
451
|
+
help=(
|
|
452
|
+
"Allow sync to proceed when the in-process registry has no @cron "
|
|
453
|
+
"definitions but `jobs.cron_schedules` has rows. Without this "
|
|
454
|
+
"flag the sync refuses, because it would otherwise wipe every "
|
|
455
|
+
"row and unschedule every pg_cron entry."
|
|
456
|
+
),
|
|
457
|
+
),
|
|
458
|
+
) -> None:
|
|
431
459
|
"""Sync registered crons with pg_cron."""
|
|
432
460
|
from .jobs.cron import sync_pg_cron
|
|
433
461
|
|
|
462
|
+
_bootstrap_user_modules()
|
|
434
463
|
s = get_settings()
|
|
435
464
|
|
|
436
465
|
async def _run():
|
|
437
466
|
conn = await asyncpg.connect(s.database_url)
|
|
438
467
|
try:
|
|
439
468
|
await conn.execute("set role service_role")
|
|
440
|
-
await sync_pg_cron(conn)
|
|
469
|
+
await sync_pg_cron(conn, allow_empty=allow_empty)
|
|
441
470
|
finally:
|
|
442
471
|
await conn.close()
|
|
443
472
|
|
|
@@ -51,7 +51,11 @@ async def _as_login_role(conn: asyncpg.Connection) -> AsyncIterator[None]:
|
|
|
51
51
|
await conn.execute(f'set {scope}role "{prev_role}"')
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
async def sync_pg_cron(
|
|
54
|
+
async def sync_pg_cron(
|
|
55
|
+
conn: asyncpg.Connection,
|
|
56
|
+
*,
|
|
57
|
+
allow_empty: bool = False,
|
|
58
|
+
) -> None:
|
|
55
59
|
"""Upsert ``pg_cron`` schedule rows from the registry and remove stale ones.
|
|
56
60
|
|
|
57
61
|
The caller is expected to have entered ``db.as_service_role()`` (or
|
|
@@ -62,18 +66,41 @@ async def sync_pg_cron(conn: asyncpg.Connection) -> None:
|
|
|
62
66
|
initialise a session at tick time.
|
|
63
67
|
|
|
64
68
|
When pg_cron is installed, errors from ``cron.schedule`` propagate
|
|
65
|
-
to the caller.
|
|
69
|
+
to the caller.
|
|
66
70
|
|
|
67
71
|
When pg_cron is NOT installed the metadata row is still upserted so
|
|
68
72
|
the in-process scheduler (or a manual runner) can pick it up, and
|
|
69
73
|
``cron.schedule`` is skipped entirely.
|
|
74
|
+
|
|
75
|
+
Raises ``ValueError`` when the in-process registry has no ``@cron``
|
|
76
|
+
definitions but ``jobs.cron_schedules`` is non-empty. That mismatch
|
|
77
|
+
almost always means the caller forgot to import the user's
|
|
78
|
+
``EXTENSIONS`` before syncing, and proceeding would wipe every row
|
|
79
|
+
(and unschedule every pg_cron entry). Pass ``allow_empty=True`` to
|
|
80
|
+
intentionally clear the table.
|
|
70
81
|
"""
|
|
71
82
|
registry = get_registry()
|
|
83
|
+
cron_defns = list(registry.iter_crons())
|
|
84
|
+
|
|
72
85
|
has_pg_cron = await conn.fetchval(
|
|
73
86
|
"select exists(select 1 from pg_extension where extname = 'pg_cron')"
|
|
74
87
|
)
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
if not cron_defns and not allow_empty:
|
|
90
|
+
existing_count = await conn.fetchval(
|
|
91
|
+
"select count(*) from jobs.cron_schedules"
|
|
92
|
+
)
|
|
93
|
+
if existing_count:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"registry has no @cron definitions but jobs.cron_schedules "
|
|
96
|
+
f"holds {existing_count} row(s) — refusing to sync, since "
|
|
97
|
+
f"this would wipe the table and unschedule every pg_cron "
|
|
98
|
+
f"entry. Pass allow_empty=True to confirm, or check that "
|
|
99
|
+
f"the module containing your @cron decorators is listed in "
|
|
100
|
+
f"EXTENSIONS / SUPYTHON_SETTINGS_MODULE."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
for cron_defn in cron_defns:
|
|
77
104
|
await conn.execute(
|
|
78
105
|
"""
|
|
79
106
|
insert into jobs.cron_schedules
|
|
@@ -123,7 +150,7 @@ async def sync_pg_cron(conn: asyncpg.Connection) -> None:
|
|
|
123
150
|
command,
|
|
124
151
|
)
|
|
125
152
|
|
|
126
|
-
registered_names = {c.name for c in
|
|
153
|
+
registered_names = {c.name for c in cron_defns}
|
|
127
154
|
existing = await conn.fetch("select name from jobs.cron_schedules")
|
|
128
155
|
for row in existing:
|
|
129
156
|
if row["name"] in registered_names:
|
|
@@ -59,18 +59,53 @@ def cron(
|
|
|
59
59
|
job_version: int = 1,
|
|
60
60
|
payload: dict | None = None,
|
|
61
61
|
queue: str = "default",
|
|
62
|
+
register_handler: bool = True,
|
|
63
|
+
max_attempts: int = 3,
|
|
64
|
+
backoff: Backoff | str = Backoff.EXPONENTIAL,
|
|
65
|
+
backoff_base_s: float = 5.0,
|
|
66
|
+
backoff_max_s: float = 300.0,
|
|
67
|
+
role: str = "service_role",
|
|
68
|
+
claims_from: str | None = None,
|
|
69
|
+
accepts_payload: bool = True,
|
|
62
70
|
) -> Callable[..., Any]:
|
|
71
|
+
"""Schedule the decorated function on a cron expression.
|
|
72
|
+
|
|
73
|
+
By default the decorated function is also registered as the handler for
|
|
74
|
+
``job_name`` so pg_cron's tick → ``jobs.enqueue`` → worker dispatch
|
|
75
|
+
round-trip works without a separate ``@job`` declaration. Pass
|
|
76
|
+
``register_handler=False`` when the schedule should fire a job whose
|
|
77
|
+
handler lives elsewhere (declared with ``@job``).
|
|
78
|
+
"""
|
|
79
|
+
resolved_job_name = job_name or name
|
|
80
|
+
|
|
63
81
|
def decorator(fn: Handler) -> Handler:
|
|
64
|
-
get_registry()
|
|
82
|
+
registry = get_registry()
|
|
83
|
+
registry.register_cron(
|
|
65
84
|
CronDefinition(
|
|
66
85
|
name=name,
|
|
67
86
|
cron_expr=cron_expr,
|
|
68
|
-
job_name=
|
|
87
|
+
job_name=resolved_job_name,
|
|
69
88
|
job_version=job_version,
|
|
70
89
|
payload=payload or {},
|
|
71
90
|
queue=queue,
|
|
72
91
|
)
|
|
73
92
|
)
|
|
93
|
+
if register_handler:
|
|
94
|
+
registry.register_job(
|
|
95
|
+
JobDefinition(
|
|
96
|
+
name=resolved_job_name,
|
|
97
|
+
version=job_version,
|
|
98
|
+
handler=fn,
|
|
99
|
+
max_attempts=max_attempts,
|
|
100
|
+
backoff=backoff if isinstance(backoff, str) else backoff.value,
|
|
101
|
+
backoff_base_s=backoff_base_s,
|
|
102
|
+
backoff_max_s=backoff_max_s,
|
|
103
|
+
queue=queue,
|
|
104
|
+
role=role,
|
|
105
|
+
claims_from=claims_from,
|
|
106
|
+
accepts_payload=accepts_payload,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
74
109
|
return fn
|
|
75
110
|
|
|
76
111
|
return decorator
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js.map
RENAMED
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js.map
RENAMED
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DataTable-COAAWEft.js.map
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js.map
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/JsonField-DibyJgun.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/LoginView-CoOjECT_.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js.map
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js.map
RENAMED
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-DKy2_KQi.css
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js
RENAMED
|
File without changes
|
{supython-0.1.8 → supython-0.1.9}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js.map
RENAMED
|
File without changes
|
|
File without changes
|