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.
Files changed (252) hide show
  1. {supython-0.1.1 → supython-0.1.3}/CHANGELOG.md +2 -2
  2. {supython-0.1.1 → supython-0.1.3}/PKG-INFO +50 -20
  3. {supython-0.1.1 → supython-0.1.3}/README.md +49 -19
  4. {supython-0.1.1 → supython-0.1.3}/pyproject.toml +1 -1
  5. {supython-0.1.1 → supython-0.1.3}/src/supython/app.py +2 -0
  6. supython-0.1.3/src/supython/auth/claims.py +72 -0
  7. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/service.py +9 -2
  8. {supython-0.1.1 → supython-0.1.3}/.gitignore +0 -0
  9. {supython-0.1.1 → supython-0.1.3}/LICENSE +0 -0
  10. {supython-0.1.1 → supython-0.1.3}/SECURITY.md +0 -0
  11. {supython-0.1.1 → supython-0.1.3}/src/supython/__init__.py +0 -0
  12. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/__init__.py +0 -0
  13. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/__init__.py +0 -0
  14. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth.py +0 -0
  15. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth_templates.py +0 -0
  16. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/auth_users.py +0 -0
  17. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/db.py +0 -0
  18. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/functions.py +0 -0
  19. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/jobs.py +0 -0
  20. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/ops.py +0 -0
  21. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/realtime.py +0 -0
  22. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth.py +0 -0
  23. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth_templates.py +0 -0
  24. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_auth_users.py +0 -0
  25. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_db.py +0 -0
  26. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_functions.py +0 -0
  27. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_jobs.py +0 -0
  28. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_ops.py +0 -0
  29. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_realtime.py +0 -0
  30. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/service_storage.py +0 -0
  31. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/storage.py +0 -0
  32. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/api/system.py +0 -0
  33. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/audit.py +0 -0
  34. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/deps.py +0 -0
  35. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/errors.py +0 -0
  36. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/schemas.py +0 -0
  37. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/session.py +0 -0
  38. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/spa.py +0 -0
  39. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Alert-dluGVkos.js +0 -0
  40. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Alert-dluGVkos.js.map +0 -0
  41. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Audit-Njung3HI.js +0 -0
  42. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Audit-Njung3HI.js.map +0 -0
  43. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Backups-DzPlFgrm.js +0 -0
  44. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Backups-DzPlFgrm.js.map +0 -0
  45. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Buckets-ByacGkU1.js +0 -0
  46. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Buckets-ByacGkU1.js.map +0 -0
  47. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Channels-BoIuTtam.js +0 -0
  48. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Channels-BoIuTtam.js.map +0 -0
  49. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js +0 -0
  50. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ChevronRight-CtQH1EQ1.js.map +0 -0
  51. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js +0 -0
  52. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/CodeViewer-Bqy7-wvH.js.map +0 -0
  53. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Crons-B67vc39F.js +0 -0
  54. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Crons-B67vc39F.js.map +0 -0
  55. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js +0 -0
  56. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DashboardView-CUTFVL6k.js.map +0 -0
  57. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DataTable-COAAWEft.js +0 -0
  58. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DataTable-COAAWEft.js.map +0 -0
  59. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js +0 -0
  60. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DescriptionsItem-P8JUDaBs.js.map +0 -0
  61. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js +0 -0
  62. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/DrawerContent-TpYTFgF1.js.map +0 -0
  63. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Empty-cr2r7e2u.js +0 -0
  64. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Empty-cr2r7e2u.js.map +0 -0
  65. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js +0 -0
  66. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/EmptyState-DeDck-OL.js.map +0 -0
  67. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Grid-hFkp9F4P.js +0 -0
  68. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Grid-hFkp9F4P.js.map +0 -0
  69. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Input-DppYTq9C.js +0 -0
  70. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Input-DppYTq9C.js.map +0 -0
  71. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js +0 -0
  72. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Invoke-DW3Nveeh.js.map +0 -0
  73. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/JsonField-DibyJgun.js +0 -0
  74. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/JsonField-DibyJgun.js.map +0 -0
  75. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-BjLyE3Ds.css +0 -0
  76. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-CoOjECT_.js +0 -0
  77. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/LoginView-CoOjECT_.js.map +0 -0
  78. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-D9WYrnIT.js +0 -0
  79. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-D9WYrnIT.js.map +0 -0
  80. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Logs-DS1XPa0h.css +0 -0
  81. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js +0 -0
  82. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Migrations-DOSC2ddQ.js.map +0 -0
  83. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js +0 -0
  84. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/ObjectBrowser-_5w8vOX8.js.map +0 -0
  85. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Queue-CywZs6vI.js +0 -0
  86. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Queue-CywZs6vI.js.map +0 -0
  87. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js +0 -0
  88. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RefreshTokens-Ccjr53jg.js.map +0 -0
  89. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js +0 -0
  90. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/RlsEditor-BSlH9vSc.js.map +0 -0
  91. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-BiLXE49D.js +0 -0
  92. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-BiLXE49D.js.map +0 -0
  93. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Routes-C-ianIGD.css +0 -0
  94. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-DKy2_KQi.css +0 -0
  95. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js +0 -0
  96. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SchemaBrowser-XFvFbtDB.js.map +0 -0
  97. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Select-DIzZyRZb.js +0 -0
  98. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Select-DIzZyRZb.js.map +0 -0
  99. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Space-n5-XcguU.js +0 -0
  100. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Space-n5-XcguU.js.map +0 -0
  101. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js +0 -0
  102. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlEditor-b8pTsILY.js.map +0 -0
  103. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js +0 -0
  104. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/SqlWorkspace-BUS7IntH.js.map +0 -0
  105. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/TableData-CQIagLKn.js +0 -0
  106. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/TableData-CQIagLKn.js.map +0 -0
  107. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Tag-D1fOKpTH.js +0 -0
  108. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Tag-D1fOKpTH.js.map +0 -0
  109. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Templates-BS-ugkdq.js +0 -0
  110. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Templates-BS-ugkdq.js.map +0 -0
  111. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Thing-CEAniuMg.js +0 -0
  112. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Thing-CEAniuMg.js.map +0 -0
  113. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Users-wzwajhlh.js +0 -0
  114. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/Users-wzwajhlh.js.map +0 -0
  115. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/_plugin-vue_export-helper-DGA9ry_j.js +0 -0
  116. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/dist-VXIJLCYq.js +0 -0
  117. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/dist-VXIJLCYq.js.map +0 -0
  118. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/format-length-CGCY1rMh.js +0 -0
  119. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/format-length-CGCY1rMh.js.map +0 -0
  120. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/get-Ca6unauB.js +0 -0
  121. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/get-Ca6unauB.js.map +0 -0
  122. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/index-CeE6v959.js +0 -0
  123. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/index-CeE6v959.js.map +0 -0
  124. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/pinia-COXwfrOX.js +0 -0
  125. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/pinia-COXwfrOX.js.map +0 -0
  126. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/resources-Bt6thQCD.js +0 -0
  127. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/resources-Bt6thQCD.js.map +0 -0
  128. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js +0 -0
  129. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-locale-mtgM0a3a.js.map +0 -0
  130. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js +0 -0
  131. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/use-merged-state-BvhkaHNX.js.map +0 -0
  132. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js +0 -0
  133. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useConfirm-tMjvBFXR.js.map +0 -0
  134. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useResource-C_rJCY8C.js +0 -0
  135. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useResource-C_rJCY8C.js.map +0 -0
  136. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-CnZc5zhi.js +0 -0
  137. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-CnZc5zhi.js.map +0 -0
  138. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useTable-Dg0XlRlq.css +0 -0
  139. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useToast-DsZKx0IX.js +0 -0
  140. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/useToast-DsZKx0IX.js.map +0 -0
  141. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/utils-sbXoq7Ir.js +0 -0
  142. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/assets/utils-sbXoq7Ir.js.map +0 -0
  143. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/favicon.svg +0 -0
  144. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/icons.svg +0 -0
  145. {supython-0.1.1 → supython-0.1.3}/src/supython/admin/static/index.html +0 -0
  146. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/__init__.py +0 -0
  147. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/_email_job.py +0 -0
  148. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/__init__.py +0 -0
  149. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/github.py +0 -0
  150. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/google.py +0 -0
  151. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/oauth.py +0 -0
  152. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/providers/registry.py +0 -0
  153. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/ratelimit.py +0 -0
  154. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/router.py +0 -0
  155. {supython-0.1.1 → supython-0.1.3}/src/supython/auth/schemas.py +0 -0
  156. {supython-0.1.1 → supython-0.1.3}/src/supython/backups/__init__.py +0 -0
  157. {supython-0.1.1 → supython-0.1.3}/src/supython/backups/_backup_job.py +0 -0
  158. {supython-0.1.1 → supython-0.1.3}/src/supython/backups/schemas.py +0 -0
  159. {supython-0.1.1 → supython-0.1.3}/src/supython/backups/service.py +0 -0
  160. {supython-0.1.1 → supython-0.1.3}/src/supython/body_size.py +0 -0
  161. {supython-0.1.1 → supython-0.1.3}/src/supython/cli.py +0 -0
  162. {supython-0.1.1 → supython-0.1.3}/src/supython/client/__init__.py +0 -0
  163. {supython-0.1.1 → supython-0.1.3}/src/supython/client/_auth.py +0 -0
  164. {supython-0.1.1 → supython-0.1.3}/src/supython/client/_client.py +0 -0
  165. {supython-0.1.1 → supython-0.1.3}/src/supython/client/_config.py +0 -0
  166. {supython-0.1.1 → supython-0.1.3}/src/supython/client/_functions.py +0 -0
  167. {supython-0.1.1 → supython-0.1.3}/src/supython/client/_storage.py +0 -0
  168. {supython-0.1.1 → supython-0.1.3}/src/supython/client/py.typed +0 -0
  169. {supython-0.1.1 → supython-0.1.3}/src/supython/db.py +0 -0
  170. {supython-0.1.1 → supython-0.1.3}/src/supython/db_admin.py +0 -0
  171. {supython-0.1.1 → supython-0.1.3}/src/supython/extensions.py +0 -0
  172. {supython-0.1.1 → supython-0.1.3}/src/supython/functions/__init__.py +0 -0
  173. {supython-0.1.1 → supython-0.1.3}/src/supython/functions/context.py +0 -0
  174. {supython-0.1.1 → supython-0.1.3}/src/supython/functions/loader.py +0 -0
  175. {supython-0.1.1 → supython-0.1.3}/src/supython/functions/router.py +0 -0
  176. {supython-0.1.1 → supython-0.1.3}/src/supython/functions/schemas.py +0 -0
  177. {supython-0.1.1 → supython-0.1.3}/src/supython/gen/__init__.py +0 -0
  178. {supython-0.1.1 → supython-0.1.3}/src/supython/gen/_introspect.py +0 -0
  179. {supython-0.1.1 → supython-0.1.3}/src/supython/gen/types_py.py +0 -0
  180. {supython-0.1.1 → supython-0.1.3}/src/supython/gen/types_ts.py +0 -0
  181. {supython-0.1.1 → supython-0.1.3}/src/supython/health.py +0 -0
  182. {supython-0.1.1 → supython-0.1.3}/src/supython/hooks.py +0 -0
  183. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/__init__.py +0 -0
  184. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/backends.py +0 -0
  185. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/context.py +0 -0
  186. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/cron.py +0 -0
  187. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/cron_inproc.py +0 -0
  188. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/decorators.py +0 -0
  189. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/registry.py +0 -0
  190. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/router.py +0 -0
  191. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/schemas.py +0 -0
  192. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/service.py +0 -0
  193. {supython-0.1.1 → supython-0.1.3}/src/supython/jobs/worker.py +0 -0
  194. {supython-0.1.1 → supython-0.1.3}/src/supython/jwks.py +0 -0
  195. {supython-0.1.1 → supython-0.1.3}/src/supython/keyset.py +0 -0
  196. {supython-0.1.1 → supython-0.1.3}/src/supython/logging_config.py +0 -0
  197. {supython-0.1.1 → supython-0.1.3}/src/supython/mail.py +0 -0
  198. {supython-0.1.1 → supython-0.1.3}/src/supython/mailer.py +0 -0
  199. {supython-0.1.1 → supython-0.1.3}/src/supython/migrate.py +0 -0
  200. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0001_extensions_and_roles.sql +0 -0
  201. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0002_auth_schema.sql +0 -0
  202. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0003_demo_todos.sql +0 -0
  203. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0004_auth_v0_2.sql +0 -0
  204. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0005_storage_schema.sql +0 -0
  205. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0006_realtime_schema.sql +0 -0
  206. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0007_jobs_schema.sql +0 -0
  207. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0008_jobs_last_error.sql +0 -0
  208. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0009_auth_rate_limits.sql +0 -0
  209. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0010_worker_heartbeat.sql +0 -0
  210. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0011_admin_schema.sql +0 -0
  211. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0012_auth_banned_until.sql +0 -0
  212. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0013_email_templates.sql +0 -0
  213. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0014_realtime_payload_warning.sql +0 -0
  214. {supython-0.1.1 → supython-0.1.3}/src/supython/migrations/0015_backups_schema.sql +0 -0
  215. {supython-0.1.1 → supython-0.1.3}/src/supython/passwords.py +0 -0
  216. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/__init__.py +0 -0
  217. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/broker.py +0 -0
  218. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/protocol.py +0 -0
  219. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/router.py +0 -0
  220. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/schemas.py +0 -0
  221. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/service.py +0 -0
  222. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/topics.py +0 -0
  223. {supython-0.1.1 → supython-0.1.3}/src/supython/realtime/websocket.py +0 -0
  224. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/__init__.py +0 -0
  225. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/init_project.py +0 -0
  226. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/Caddyfile.tmpl +0 -0
  227. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/README.md.tmpl +0 -0
  228. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/apps_hooks.py.tmpl +0 -0
  229. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/apps_jobs.py.tmpl +0 -0
  230. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/asgi.py.tmpl +0 -0
  231. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker-compose.prod.yml.tmpl +0 -0
  232. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker-compose.yml.tmpl +0 -0
  233. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker_postgres_Dockerfile.tmpl +0 -0
  234. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/docker_postgres_postgresql.conf.tmpl +0 -0
  235. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/env.example.tmpl +0 -0
  236. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/functions_README.md.tmpl +0 -0
  237. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/gitignore.tmpl +0 -0
  238. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/manage.py.tmpl +0 -0
  239. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/migrations/.gitkeep +0 -0
  240. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/package_init.py.tmpl +0 -0
  241. {supython-0.1.1 → supython-0.1.3}/src/supython/scaffold/templates/settings.py.tmpl +0 -0
  242. {supython-0.1.1 → supython-0.1.3}/src/supython/secretset.py +0 -0
  243. {supython-0.1.1 → supython-0.1.3}/src/supython/security_headers.py +0 -0
  244. {supython-0.1.1 → supython-0.1.3}/src/supython/settings.py +0 -0
  245. {supython-0.1.1 → supython-0.1.3}/src/supython/settings_module.py +0 -0
  246. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/__init__.py +0 -0
  247. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/backends.py +0 -0
  248. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/router.py +0 -0
  249. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/schemas.py +0 -0
  250. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/service.py +0 -0
  251. {supython-0.1.1 → supython-0.1.3}/src/supython/storage/signing.py +0 -0
  252. {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-08
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
- [Unreleased]: https://github.com/Tkeby/supython/compare/v0.1.0...HEAD
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.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.0 release**
61
+ > A lightweight, Postgres-first BaaS framework for Python. **v0.1.2 release**
62
62
 
63
- supython is the inverse of Django: **the database owns the schema, Python owns
64
- the things SQL is bad at**. It leans on [PostgREST](https://postgrest.org)
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.0]:
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** — unmodified `supabase-js` / `supabase-py` SDKs connect
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.0)
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 — Django-style CLI entrypoint (sets SUPYTHON_SETTINGS_MODULE)
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** used by every official Supabase SDK. Unmodified `supabase-js` and
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 # Django-style entrypoint (sets SUPYTHON_SETTINGS_MODULE)
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 # Django-style user settings (EXTENSIONS, EXTRA_ROUTERS, …)
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 Django-style settings module to declare your app's extensions:
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
- This is exactly the model Supabase uses, in ~400 lines of Python.
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.0]
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.0 Release — final sweep, tag, publish wheel, production deployment with no patches
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.0
778
+ ### Post v0.1.2
749
779
 
750
- - **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.0; tests + remaining DoD items deferred)
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.0 release**
3
+ > A lightweight, Postgres-first BaaS framework for Python. **v0.1.2 release**
4
4
 
5
- supython is the inverse of Django: **the database owns the schema, Python owns
6
- the things SQL is bad at**. It leans on [PostgREST](https://postgrest.org)
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.0]:
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** — unmodified `supabase-js` / `supabase-py` SDKs connect
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.0)
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 — Django-style CLI entrypoint (sets SUPYTHON_SETTINGS_MODULE)
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** used by every official Supabase SDK. Unmodified `supabase-js` and
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 # Django-style entrypoint (sets SUPYTHON_SETTINGS_MODULE)
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 # Django-style user settings (EXTENSIONS, EXTRA_ROUTERS, …)
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 Django-style settings module to declare your app's extensions:
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
- This is exactly the model Supabase uses, in ~400 lines of Python.
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.0]
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.0 Release — final sweep, tag, publish wheel, production deployment with no patches
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.0
720
+ ### Post v0.1.2
691
721
 
692
- - **v1.1+** — Admin control plane polish (backend + frontend shipped in v0.1.0; tests + remaining DoD items deferred)
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "supython"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "A lightweight Postgres-first BaaS framework for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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
- access, ttl = tokens.issue_access_token(user.id, user.email)
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
- access, ttl = tokens.issue_access_token(user.id, user.email)
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