supython 0.1.1__tar.gz → 0.1.3__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.1 → supython-0.1.3}/CHANGELOG.md +2 -2
- {supython-0.1.1 → supython-0.1.3}/PKG-INFO +50 -20
- {supython-0.1.1 → supython-0.1.3}/README.md +49 -19
- {supython-0.1.1 → supython-0.1.3}/pyproject.toml +1 -1
- {supython-0.1.1 → supython-0.1.3}/src/supython/app.py +2 -0
- supython-0.1.3/src/supython/auth/claims.py +72 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/service.py +9 -2
- {supython-0.1.1 → supython-0.1.3}/.gitignore +0 -0
- {supython-0.1.1 → supython-0.1.3}/LICENSE +0 -0
- {supython-0.1.1 → supython-0.1.3}/SECURITY.md +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth_templates.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth_users.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/db.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/functions.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/jobs.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/ops.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/realtime.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth_templates.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth_users.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_db.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_functions.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_jobs.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_ops.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_realtime.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_storage.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/storage.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/system.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/audit.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/deps.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/errors.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/session.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/spa.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Alert-dluGVkos.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Alert-dluGVkos.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Audit-Njung3HI.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Audit-Njung3HI.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Backups-DzPlFgrm.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Backups-DzPlFgrm.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Buckets-ByacGkU1.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Buckets-ByacGkU1.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Channels-BoIuTtam.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Channels-BoIuTtam.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Crons-B67vc39F.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Crons-B67vc39F.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DataTable-COAAWEft.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DataTable-COAAWEft.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Empty-cr2r7e2u.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Empty-cr2r7e2u.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Grid-hFkp9F4P.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Grid-hFkp9F4P.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Input-DppYTq9C.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Input-DppYTq9C.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/JsonField-DibyJgun.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/JsonField-DibyJgun.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-BjLyE3Ds.css +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-CoOjECT_.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-CoOjECT_.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-D9WYrnIT.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-D9WYrnIT.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-DS1XPa0h.css +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Queue-CywZs6vI.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Queue-CywZs6vI.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-BiLXE49D.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-BiLXE49D.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-C-ianIGD.css +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-DKy2_KQi.css +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Select-DIzZyRZb.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Select-DIzZyRZb.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Space-n5-XcguU.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Space-n5-XcguU.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/TableData-CQIagLKn.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/TableData-CQIagLKn.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Tag-D1fOKpTH.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Tag-D1fOKpTH.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Templates-BS-ugkdq.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Templates-BS-ugkdq.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Thing-CEAniuMg.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Thing-CEAniuMg.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Users-wzwajhlh.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Users-wzwajhlh.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/_plugin-vue_export-helper-DGA9ry_j.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/dist-VXIJLCYq.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/dist-VXIJLCYq.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/format-length-CGCY1rMh.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/format-length-CGCY1rMh.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/get-Ca6unauB.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/get-Ca6unauB.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/index-CeE6v959.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/index-CeE6v959.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/pinia-COXwfrOX.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/pinia-COXwfrOX.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/resources-Bt6thQCD.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/resources-Bt6thQCD.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useResource-C_rJCY8C.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useResource-C_rJCY8C.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-CnZc5zhi.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-CnZc5zhi.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-Dg0XlRlq.css +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useToast-DsZKx0IX.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useToast-DsZKx0IX.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/utils-sbXoq7Ir.js +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/utils-sbXoq7Ir.js.map +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/favicon.svg +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/icons.svg +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/index.html +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/_email_job.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/github.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/google.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/oauth.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/registry.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/ratelimit.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/router.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/auth/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/backups/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/backups/_backup_job.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/backups/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/backups/service.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/body_size.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/cli.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/_auth.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/_client.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/_config.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/_functions.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/_storage.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/client/py.typed +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/db.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/db_admin.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/extensions.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/functions/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/functions/context.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/functions/loader.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/functions/router.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/functions/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/gen/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/gen/_introspect.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/gen/types_py.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/gen/types_ts.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/health.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/hooks.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/backends.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/context.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/cron.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/cron_inproc.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/decorators.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/registry.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/router.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/service.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/worker.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/jwks.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/keyset.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/logging_config.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/mail.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/mailer.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrate.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0001_extensions_and_roles.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0002_auth_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0003_demo_todos.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0004_auth_v0_2.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0005_storage_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0006_realtime_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0007_jobs_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0008_jobs_last_error.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0009_auth_rate_limits.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0010_worker_heartbeat.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0011_admin_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0012_auth_banned_until.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0013_email_templates.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0014_realtime_payload_warning.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0015_backups_schema.sql +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/passwords.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/broker.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/protocol.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/router.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/service.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/topics.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/websocket.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/init_project.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/Caddyfile.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/README.md.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/apps_hooks.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/apps_jobs.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/asgi.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker-compose.prod.yml.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker-compose.yml.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker_postgres_Dockerfile.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker_postgres_postgresql.conf.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/env.example.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/functions_README.md.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/gitignore.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/manage.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/migrations/.gitkeep +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/package_init.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/settings.py.tmpl +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/secretset.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/security_headers.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/settings.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/settings_module.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/__init__.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/backends.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/router.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/schemas.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/service.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/storage/signing.py +0 -0
- {supython-0.1.1 → supython-0.1.3}/src/supython/tokens.py +0 -0
|
@@ -31,7 +31,7 @@ Each entry links the relevant `PROJECT.md` section and decision-log row
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
-
## [0.1.0] — 2026-05-
|
|
34
|
+
## [0.1.0] — 2026-05-09
|
|
35
35
|
|
|
36
36
|
The first public release. Everything currently on `main` collapses
|
|
37
37
|
into this single ZeroVer entry — auth, storage, functions, realtime,
|
|
@@ -200,5 +200,5 @@ v0.1–v0.7 plus a v1.1.x admin track; see §19 decision log
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
[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.3
|
|
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,10 +58,10 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
|
|
59
59
|
# supython
|
|
60
60
|
|
|
61
|
-
> A lightweight, Postgres-first BaaS framework for Python. **v0.1.
|
|
61
|
+
> A lightweight, Postgres-first BaaS framework for Python. **v0.1.2 release**
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
**the database owns the schema, Python owns the things SQL is bad at**.
|
|
64
|
+
It leans on [PostgREST](https://postgrest.org)
|
|
65
65
|
for auto-generated REST APIs and on Postgres' own RLS for authorization,
|
|
66
66
|
while a small FastAPI service in Python handles auth, JWT issuance, realtime,
|
|
67
67
|
storage, functions, workers, and an optional admin control plane.
|
|
@@ -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
|
-
Shipped [v0.1.
|
|
72
|
+
Shipped [v0.1.2]:
|
|
73
73
|
|
|
74
74
|
**Core platform**
|
|
75
75
|
- **Email/password auth** — signup, login, refresh-token rotation with **reuse detection**
|
|
@@ -88,14 +88,14 @@ Shipped [v0.1.0]:
|
|
|
88
88
|
|
|
89
89
|
**Realtime**
|
|
90
90
|
- **WebSocket Realtime** — `postgres_changes`, `broadcast`, `presence` with per-subscriber RLS filtering
|
|
91
|
-
- **Phoenix Channels wire format**
|
|
91
|
+
- **Phoenix Channels wire format**
|
|
92
92
|
- **Generic trigger** — `realtime.enable('public.todos')` opts any table in
|
|
93
93
|
- **Two-browser chat demo** — `examples/chat.html` (zero build step)
|
|
94
94
|
|
|
95
95
|
**Jobs & cron**
|
|
96
96
|
- **Job queue** — Postgres-backed (`SELECT FOR UPDATE SKIP LOCKED`), idempotent enqueue, retry with backoff
|
|
97
97
|
- **Cron scheduling** — `pg_cron` (primary) or in-process `croniter` fallback
|
|
98
|
-
- **Generic hooks** — `@app.on_signup` / `@app.on_login` lifecycle hooks
|
|
98
|
+
- **Generic hooks** — `@app.on_signup` / `@app.on_login` lifecycle hooks; `@app.claims_provider` for custom JWT claims
|
|
99
99
|
- **`supython worker run`** — long-running worker with graceful SIGTERM drain
|
|
100
100
|
|
|
101
101
|
**Operations & security**
|
|
@@ -106,7 +106,7 @@ Shipped [v0.1.0]:
|
|
|
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** (shipped in v0.1.
|
|
109
|
+
**Admin control plane** (shipped in v0.1.2)
|
|
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
|
|
@@ -159,7 +159,7 @@ cp .env.example .env
|
|
|
159
159
|
# the value PostgREST will use (docker-compose.yml injects it via env).
|
|
160
160
|
#
|
|
161
161
|
# The scaffold creates:
|
|
162
|
-
# manage.py —
|
|
162
|
+
# manage.py — Optional CLI entrypoint (sets SUPYTHON_SETTINGS_MODULE)
|
|
163
163
|
# myapp/settings.py — declare EXTENSIONS, EXTRA_ROUTERS, EXTRA_MIDDLEWARE
|
|
164
164
|
# myapp/jobs.py — example @job seed (register your background jobs here)
|
|
165
165
|
# myapp/hooks.py — example @on("signup") seed (lifecycle hooks)
|
|
@@ -219,8 +219,7 @@ including filtering, sorting, refresh, and isolation between users.
|
|
|
219
219
|
## Realtime quickstart
|
|
220
220
|
|
|
221
221
|
supython ships a WebSocket engine that speaks the **Phoenix Channels 5-tuple
|
|
222
|
-
protocol
|
|
223
|
-
`supabase-py` clients connect without any shim.
|
|
222
|
+
protocol**.
|
|
224
223
|
|
|
225
224
|
### 1. Opt a table into realtime
|
|
226
225
|
|
|
@@ -419,6 +418,37 @@ SMTP_PASSWORD`.
|
|
|
419
418
|
**OAuth** — add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` (and/or GitHub
|
|
420
419
|
equivalents) to `.env`. Providers without credentials are silently disabled.
|
|
421
420
|
|
|
421
|
+
### Custom JWT claims
|
|
422
|
+
|
|
423
|
+
Register a `claims_provider` to inject application-specific claims into every
|
|
424
|
+
access token minted by the auth endpoints. Each provider is an async callable
|
|
425
|
+
`(user, conn) -> dict` whose return value is merged into the token payload —
|
|
426
|
+
on signup, password login, refresh, magic-link, OTP, and OAuth callbacks.
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
from supython.app import app
|
|
430
|
+
|
|
431
|
+
@app.claims_provider
|
|
432
|
+
async def add_org(user, conn):
|
|
433
|
+
org_id = await conn.fetchval(
|
|
434
|
+
"select org_id from public.memberships where user_id = $1", user.id
|
|
435
|
+
)
|
|
436
|
+
return {"org_id": str(org_id)} if org_id else {}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Notes:
|
|
440
|
+
|
|
441
|
+
- Reserved JWT claims (`sub`, `email`, `role`, `aud`, `iat`, `exp`, `jti`)
|
|
442
|
+
cannot be overridden — they are filtered out automatically.
|
|
443
|
+
- Providers run on the **service-role** connection used by the auth flow, so
|
|
444
|
+
they can read tables that RLS would block during issuance. Treat the
|
|
445
|
+
function as privileged code (same posture as a Postgres `security definer`
|
|
446
|
+
routine — sanitize any user-supplied input).
|
|
447
|
+
- A provider that raises aborts issuance: a missing claim is a silent authz
|
|
448
|
+
bug, not a missing welcome email.
|
|
449
|
+
- Refresh re-collects claims, so a token rotated via `/auth/v1/refresh`
|
|
450
|
+
reflects current state.
|
|
451
|
+
|
|
422
452
|
### Auth hardening settings (`.env`)
|
|
423
453
|
|
|
424
454
|
| Variable | Default | Purpose |
|
|
@@ -444,7 +474,7 @@ need to edit SQL.
|
|
|
444
474
|
|
|
445
475
|
```
|
|
446
476
|
supython/
|
|
447
|
-
├── manage.py #
|
|
477
|
+
├── manage.py # optional cli entrypoint (sets SUPYTHON_SETTINGS_MODULE)
|
|
448
478
|
├── docker-compose.yml # Postgres + PostgREST (dev stack)
|
|
449
479
|
├── docker-compose.prod.yml # hardened single-host production stack
|
|
450
480
|
├── docker-compose.test.yml # dedicated test Postgres on port 54323
|
|
@@ -521,7 +551,7 @@ need to edit SQL.
|
|
|
521
551
|
├── app.py # FastAPI factory
|
|
522
552
|
├── cli.py # typer: up, dev, keygen, admin, worker, test, …
|
|
523
553
|
├── extensions.py # eager-import dotted module paths at boot
|
|
524
|
-
├── settings_module.py #
|
|
554
|
+
├── settings_module.py # user settings (EXTENSIONS, EXTRA_ROUTERS, …)
|
|
525
555
|
├── health.py # /livez, /readyz, /health endpoints
|
|
526
556
|
├── logging_config.py # structured JSON log setup
|
|
527
557
|
├── security_headers.py # HSTS, CSP, etc.
|
|
@@ -570,7 +600,7 @@ need to edit SQL.
|
|
|
570
600
|
|
|
571
601
|
## Plugins & extensions
|
|
572
602
|
|
|
573
|
-
supython uses a
|
|
603
|
+
supython uses a settings module to declare your app's extensions:
|
|
574
604
|
|
|
575
605
|
```python
|
|
576
606
|
# <name>/settings.py — scaffolded by `supython init`
|
|
@@ -669,7 +699,7 @@ supython test reset # stop test DB + delete volume
|
|
|
669
699
|
`auth.uid()` returns the user's id inside RLS policies.
|
|
670
700
|
4. RLS policies on `public.todos` use `auth.uid()` to scope every query.
|
|
671
701
|
|
|
672
|
-
|
|
702
|
+
|
|
673
703
|
|
|
674
704
|
## Docker image
|
|
675
705
|
|
|
@@ -731,7 +761,7 @@ unit tests always run in isolation.
|
|
|
731
761
|
**CI:** runners with Docker run `supython test up && supython test run`;
|
|
732
762
|
runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
733
763
|
|
|
734
|
-
## Roadmap [shipped v0.1.
|
|
764
|
+
## Roadmap [shipped v0.1.2]
|
|
735
765
|
|
|
736
766
|
- ✅ Email/password auth, PostgREST contract, RLS demo
|
|
737
767
|
- ✅ OAuth, password reset, magic link, OTP, reuse detection, email backend, test suite
|
|
@@ -742,12 +772,12 @@ runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
|
742
772
|
- ✅ Production observable: structured JSON logs, `/livez`/`/readyz`/`/health`, security headers, input size guards, audit log completeness, OAuth PKCE, secret rotation runbooks
|
|
743
773
|
- ✅ (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
|
|
744
774
|
- *(deferred)* — Realtime v2 over logical replication
|
|
745
|
-
- v0.1.
|
|
746
|
-
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
775
|
+
- v0.1.2 Release — final sweep, tag, publish wheel, production deployment with no patches
|
|
776
|
+
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
747
777
|
|
|
748
|
-
### Post v0.1.
|
|
778
|
+
### Post v0.1.2
|
|
749
779
|
|
|
750
|
-
- **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.
|
|
780
|
+
- **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.2; tests + remaining DoD items deferred)
|
|
751
781
|
- **Realtime v2** — logical replication (demand-driven; swap when trigger overhead or >8KB payload data warrants it)
|
|
752
782
|
- **Prometheus `/metrics`** + **OpenTelemetry** — optional extras
|
|
753
783
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# supython
|
|
2
2
|
|
|
3
|
-
> A lightweight, Postgres-first BaaS framework for Python. **v0.1.
|
|
3
|
+
> A lightweight, Postgres-first BaaS framework for Python. **v0.1.2 release**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
**the database owns the schema, Python owns the things SQL is bad at**.
|
|
6
|
+
It leans on [PostgREST](https://postgrest.org)
|
|
7
7
|
for auto-generated REST APIs and on Postgres' own RLS for authorization,
|
|
8
8
|
while a small FastAPI service in Python handles auth, JWT issuance, realtime,
|
|
9
9
|
storage, functions, workers, and an optional admin control plane.
|
|
@@ -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
|
-
Shipped [v0.1.
|
|
14
|
+
Shipped [v0.1.2]:
|
|
15
15
|
|
|
16
16
|
**Core platform**
|
|
17
17
|
- **Email/password auth** — signup, login, refresh-token rotation with **reuse detection**
|
|
@@ -30,14 +30,14 @@ Shipped [v0.1.0]:
|
|
|
30
30
|
|
|
31
31
|
**Realtime**
|
|
32
32
|
- **WebSocket Realtime** — `postgres_changes`, `broadcast`, `presence` with per-subscriber RLS filtering
|
|
33
|
-
- **Phoenix Channels wire format**
|
|
33
|
+
- **Phoenix Channels wire format**
|
|
34
34
|
- **Generic trigger** — `realtime.enable('public.todos')` opts any table in
|
|
35
35
|
- **Two-browser chat demo** — `examples/chat.html` (zero build step)
|
|
36
36
|
|
|
37
37
|
**Jobs & cron**
|
|
38
38
|
- **Job queue** — Postgres-backed (`SELECT FOR UPDATE SKIP LOCKED`), idempotent enqueue, retry with backoff
|
|
39
39
|
- **Cron scheduling** — `pg_cron` (primary) or in-process `croniter` fallback
|
|
40
|
-
- **Generic hooks** — `@app.on_signup` / `@app.on_login` lifecycle hooks
|
|
40
|
+
- **Generic hooks** — `@app.on_signup` / `@app.on_login` lifecycle hooks; `@app.claims_provider` for custom JWT claims
|
|
41
41
|
- **`supython worker run`** — long-running worker with graceful SIGTERM drain
|
|
42
42
|
|
|
43
43
|
**Operations & security**
|
|
@@ -48,7 +48,7 @@ Shipped [v0.1.0]:
|
|
|
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** (shipped in v0.1.
|
|
51
|
+
**Admin control plane** (shipped in v0.1.2)
|
|
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
|
|
@@ -101,7 +101,7 @@ cp .env.example .env
|
|
|
101
101
|
# the value PostgREST will use (docker-compose.yml injects it via env).
|
|
102
102
|
#
|
|
103
103
|
# The scaffold creates:
|
|
104
|
-
# manage.py —
|
|
104
|
+
# manage.py — Optional CLI entrypoint (sets SUPYTHON_SETTINGS_MODULE)
|
|
105
105
|
# myapp/settings.py — declare EXTENSIONS, EXTRA_ROUTERS, EXTRA_MIDDLEWARE
|
|
106
106
|
# myapp/jobs.py — example @job seed (register your background jobs here)
|
|
107
107
|
# myapp/hooks.py — example @on("signup") seed (lifecycle hooks)
|
|
@@ -161,8 +161,7 @@ including filtering, sorting, refresh, and isolation between users.
|
|
|
161
161
|
## Realtime quickstart
|
|
162
162
|
|
|
163
163
|
supython ships a WebSocket engine that speaks the **Phoenix Channels 5-tuple
|
|
164
|
-
protocol
|
|
165
|
-
`supabase-py` clients connect without any shim.
|
|
164
|
+
protocol**.
|
|
166
165
|
|
|
167
166
|
### 1. Opt a table into realtime
|
|
168
167
|
|
|
@@ -361,6 +360,37 @@ SMTP_PASSWORD`.
|
|
|
361
360
|
**OAuth** — add `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` (and/or GitHub
|
|
362
361
|
equivalents) to `.env`. Providers without credentials are silently disabled.
|
|
363
362
|
|
|
363
|
+
### Custom JWT claims
|
|
364
|
+
|
|
365
|
+
Register a `claims_provider` to inject application-specific claims into every
|
|
366
|
+
access token minted by the auth endpoints. Each provider is an async callable
|
|
367
|
+
`(user, conn) -> dict` whose return value is merged into the token payload —
|
|
368
|
+
on signup, password login, refresh, magic-link, OTP, and OAuth callbacks.
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from supython.app import app
|
|
372
|
+
|
|
373
|
+
@app.claims_provider
|
|
374
|
+
async def add_org(user, conn):
|
|
375
|
+
org_id = await conn.fetchval(
|
|
376
|
+
"select org_id from public.memberships where user_id = $1", user.id
|
|
377
|
+
)
|
|
378
|
+
return {"org_id": str(org_id)} if org_id else {}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Notes:
|
|
382
|
+
|
|
383
|
+
- Reserved JWT claims (`sub`, `email`, `role`, `aud`, `iat`, `exp`, `jti`)
|
|
384
|
+
cannot be overridden — they are filtered out automatically.
|
|
385
|
+
- Providers run on the **service-role** connection used by the auth flow, so
|
|
386
|
+
they can read tables that RLS would block during issuance. Treat the
|
|
387
|
+
function as privileged code (same posture as a Postgres `security definer`
|
|
388
|
+
routine — sanitize any user-supplied input).
|
|
389
|
+
- A provider that raises aborts issuance: a missing claim is a silent authz
|
|
390
|
+
bug, not a missing welcome email.
|
|
391
|
+
- Refresh re-collects claims, so a token rotated via `/auth/v1/refresh`
|
|
392
|
+
reflects current state.
|
|
393
|
+
|
|
364
394
|
### Auth hardening settings (`.env`)
|
|
365
395
|
|
|
366
396
|
| Variable | Default | Purpose |
|
|
@@ -386,7 +416,7 @@ need to edit SQL.
|
|
|
386
416
|
|
|
387
417
|
```
|
|
388
418
|
supython/
|
|
389
|
-
├── manage.py #
|
|
419
|
+
├── manage.py # optional cli entrypoint (sets SUPYTHON_SETTINGS_MODULE)
|
|
390
420
|
├── docker-compose.yml # Postgres + PostgREST (dev stack)
|
|
391
421
|
├── docker-compose.prod.yml # hardened single-host production stack
|
|
392
422
|
├── docker-compose.test.yml # dedicated test Postgres on port 54323
|
|
@@ -463,7 +493,7 @@ need to edit SQL.
|
|
|
463
493
|
├── app.py # FastAPI factory
|
|
464
494
|
├── cli.py # typer: up, dev, keygen, admin, worker, test, …
|
|
465
495
|
├── extensions.py # eager-import dotted module paths at boot
|
|
466
|
-
├── settings_module.py #
|
|
496
|
+
├── settings_module.py # user settings (EXTENSIONS, EXTRA_ROUTERS, …)
|
|
467
497
|
├── health.py # /livez, /readyz, /health endpoints
|
|
468
498
|
├── logging_config.py # structured JSON log setup
|
|
469
499
|
├── security_headers.py # HSTS, CSP, etc.
|
|
@@ -512,7 +542,7 @@ need to edit SQL.
|
|
|
512
542
|
|
|
513
543
|
## Plugins & extensions
|
|
514
544
|
|
|
515
|
-
supython uses a
|
|
545
|
+
supython uses a settings module to declare your app's extensions:
|
|
516
546
|
|
|
517
547
|
```python
|
|
518
548
|
# <name>/settings.py — scaffolded by `supython init`
|
|
@@ -611,7 +641,7 @@ supython test reset # stop test DB + delete volume
|
|
|
611
641
|
`auth.uid()` returns the user's id inside RLS policies.
|
|
612
642
|
4. RLS policies on `public.todos` use `auth.uid()` to scope every query.
|
|
613
643
|
|
|
614
|
-
|
|
644
|
+
|
|
615
645
|
|
|
616
646
|
## Docker image
|
|
617
647
|
|
|
@@ -673,7 +703,7 @@ unit tests always run in isolation.
|
|
|
673
703
|
**CI:** runners with Docker run `supython test up && supython test run`;
|
|
674
704
|
runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
675
705
|
|
|
676
|
-
## Roadmap [shipped v0.1.
|
|
706
|
+
## Roadmap [shipped v0.1.2]
|
|
677
707
|
|
|
678
708
|
- ✅ Email/password auth, PostgREST contract, RLS demo
|
|
679
709
|
- ✅ OAuth, password reset, magic link, OTP, reuse detection, email backend, test suite
|
|
@@ -684,12 +714,12 @@ runners without Docker run `pytest tests/unit` for a meaningful subset.
|
|
|
684
714
|
- ✅ Production observable: structured JSON logs, `/livez`/`/readyz`/`/health`, security headers, input size guards, audit log completeness, OAuth PKCE, secret rotation runbooks
|
|
685
715
|
- ✅ (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
|
|
686
716
|
- *(deferred)* — Realtime v2 over logical replication
|
|
687
|
-
- v0.1.
|
|
688
|
-
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
717
|
+
- v0.1.2 Release — final sweep, tag, publish wheel, production deployment with no patches
|
|
718
|
+
- ✅ **TypeScript SDK** — `@supython/sdk` wrapping `@supabase/postgrest-js` + `@supabase/realtime-js`
|
|
689
719
|
|
|
690
|
-
### Post v0.1.
|
|
720
|
+
### Post v0.1.2
|
|
691
721
|
|
|
692
|
-
- **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.
|
|
722
|
+
- **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.2; tests + remaining DoD items deferred)
|
|
693
723
|
- **Realtime v2** — logical replication (demand-driven; swap when trigger overhead or >8KB payload data warrants it)
|
|
694
724
|
- **Prometheus `/metrics`** + **OpenTelemetry** — optional extras
|
|
695
725
|
|
|
@@ -11,6 +11,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
11
11
|
from . import __version__, db, jwks
|
|
12
12
|
from .admin import router as admin_api_router
|
|
13
13
|
from .admin import spa as admin_spa
|
|
14
|
+
from .auth import claims as auth_claims
|
|
14
15
|
from .auth.router import router as auth_router
|
|
15
16
|
from .extensions import load_extensions
|
|
16
17
|
from .settings_module import UserSettings, load_user_settings
|
|
@@ -155,6 +156,7 @@ def create_app() -> FastAPI:
|
|
|
155
156
|
app.on_signup = _make_hook_decorator("signup")
|
|
156
157
|
app.on_login = _make_hook_decorator("login")
|
|
157
158
|
app.on_logout = _make_hook_decorator("logout")
|
|
159
|
+
app.claims_provider = auth_claims.register
|
|
158
160
|
|
|
159
161
|
return app
|
|
160
162
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Custom-claim providers for access-token issuance.
|
|
2
|
+
|
|
3
|
+
Library users register an async callable via ``@app.claims_provider`` (or
|
|
4
|
+
``claims.register``) and the auth service invokes it inside ``_issue_pair``
|
|
5
|
+
just before minting the JWT. Each provider returns a ``dict`` that is
|
|
6
|
+
merged into the access token's payload.
|
|
7
|
+
|
|
8
|
+
Contract (intentionally narrower than ``hooks.fire``):
|
|
9
|
+
|
|
10
|
+
- Providers run on the **service-role** connection used by the auth flow,
|
|
11
|
+
so they can read tables that RLS would block during issuance (e.g. a
|
|
12
|
+
``user_roles`` lookup keyed by a brand-new user). Treat the function as
|
|
13
|
+
privileged — same posture as a Postgres ``security definer`` routine.
|
|
14
|
+
- A provider raising propagates: a missing claim is a silent authz bug,
|
|
15
|
+
not a missing welcome email. ``hooks.fire`` swallows on purpose;
|
|
16
|
+
``collect`` does not.
|
|
17
|
+
- Reserved JWT claims (``sub``, ``email``, ``role``, ``aud``, ``iat``,
|
|
18
|
+
``exp``, ``jti``) cannot be overridden — they are filtered out of every
|
|
19
|
+
provider's return value so a misbehaving provider can't mint a token
|
|
20
|
+
that contradicts its own header or expiry.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from collections.abc import Awaitable, Callable
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
import asyncpg
|
|
28
|
+
|
|
29
|
+
from .schemas import UserResponse
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
ClaimsProvider = Callable[[UserResponse, asyncpg.Connection], Awaitable[dict[str, Any]]]
|
|
34
|
+
|
|
35
|
+
_RESERVED: frozenset[str] = frozenset(
|
|
36
|
+
{"sub", "email", "role", "aud", "iat", "exp", "jti"}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_providers: list[ClaimsProvider] = []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register(fn: ClaimsProvider) -> ClaimsProvider:
|
|
43
|
+
"""Register *fn* as a claims provider. Usable as a decorator."""
|
|
44
|
+
_providers.append(fn)
|
|
45
|
+
return fn
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def collect(user: UserResponse, conn: asyncpg.Connection) -> dict[str, Any]:
|
|
49
|
+
"""Run every registered provider and return the merged claim dict.
|
|
50
|
+
|
|
51
|
+
Providers run in registration order; later providers win on key
|
|
52
|
+
collisions. Reserved JWT claims are stripped from each return value
|
|
53
|
+
before merging.
|
|
54
|
+
"""
|
|
55
|
+
merged: dict[str, Any] = {}
|
|
56
|
+
for fn in _providers:
|
|
57
|
+
out = await fn(user, conn)
|
|
58
|
+
if not out:
|
|
59
|
+
continue
|
|
60
|
+
for key in _RESERVED.intersection(out):
|
|
61
|
+
logger.warning(
|
|
62
|
+
"claims provider %r returned reserved claim %r; dropping",
|
|
63
|
+
getattr(fn, "__qualname__", fn),
|
|
64
|
+
key,
|
|
65
|
+
)
|
|
66
|
+
merged.update({k: v for k, v in out.items() if k not in _RESERVED})
|
|
67
|
+
return merged
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def reset() -> None:
|
|
71
|
+
"""Clear all registered providers. Test-only."""
|
|
72
|
+
_providers.clear()
|
|
@@ -14,6 +14,7 @@ from itsdangerous import BadSignature, URLSafeTimedSerializer
|
|
|
14
14
|
from .. import mail, passwords, tokens
|
|
15
15
|
from ..mailer import EmailMessage
|
|
16
16
|
from ..settings import get_settings
|
|
17
|
+
from . import claims
|
|
17
18
|
from .schemas import UserResponse
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
@@ -63,7 +64,10 @@ async def _audit_log(
|
|
|
63
64
|
async def _issue_pair(
|
|
64
65
|
conn: asyncpg.Connection, user: UserResponse
|
|
65
66
|
) -> tuple[str, str, int]:
|
|
66
|
-
|
|
67
|
+
extra = await claims.collect(user, conn)
|
|
68
|
+
access, ttl = tokens.issue_access_token(
|
|
69
|
+
user.id, user.email, extra_claims=extra or None
|
|
70
|
+
)
|
|
67
71
|
refresh = tokens.issue_refresh_token()
|
|
68
72
|
await conn.execute(
|
|
69
73
|
"insert into auth.refresh_tokens (user_id, token) values ($1, $2)",
|
|
@@ -273,7 +277,10 @@ async def refresh_grant(
|
|
|
273
277
|
new_refresh,
|
|
274
278
|
refresh_token,
|
|
275
279
|
)
|
|
276
|
-
|
|
280
|
+
extra = await claims.collect(user, conn)
|
|
281
|
+
access, ttl = tokens.issue_access_token(
|
|
282
|
+
user.id, user.email, extra_claims=extra or None
|
|
283
|
+
)
|
|
277
284
|
return user, access, new_refresh, ttl
|
|
278
285
|
|
|
279
286
|
|
|
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.1 → supython-0.1.3}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js.map
RENAMED
|
File without changes
|
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js
RENAMED
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js.map
RENAMED
|
File without changes
|
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DataTable-COAAWEft.js.map
RENAMED
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js
RENAMED
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js.map
RENAMED
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js
RENAMED
|
File without changes
|
{supython-0.1.1 → supython-0.1.3}/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.1 → supython-0.1.3}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js.map
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|