fastapi-rtk 2.21.2__tar.gz → 2.22.0__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 (347) hide show
  1. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/PKG-INFO +25 -1
  2. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/README.md +22 -0
  3. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/__init__.py +4 -0
  4. fastapi_rtk-2.22.0/fastapi_rtk/_version.py +1 -0
  5. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/backend.md +29 -0
  6. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/__init__.py +8 -4
  7. fastapi_rtk-2.22.0/fastapi_rtk/auth/saml.py +376 -0
  8. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/decorators.py +5 -1
  9. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/const.py +2 -0
  10. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/routers.py +201 -2
  11. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/apis.py +14 -2
  12. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/async_task_runner.py +9 -16
  13. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/pyproject.toml +1 -0
  14. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/conftest.py +205 -0
  15. fastapi_rtk-2.22.0/tests/integration/auth/test_saml.py +383 -0
  16. fastapi_rtk-2.22.0/tests/unit/auth/test_saml.py +277 -0
  17. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_decorators.py +31 -0
  18. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_const.py +2 -0
  19. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_async_task_runner.py +98 -0
  20. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/uv.lock +207 -1
  21. fastapi_rtk-2.21.2/fastapi_rtk/_version.py +0 -1
  22. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/.gitignore +0 -0
  23. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/LICENSE +0 -0
  24. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/adapters/agents-md/AGENTS.md +0 -0
  25. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/adapters/claude/skill.frontmatter +0 -0
  26. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/frontend.md +0 -0
  27. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/jsonforms.md +0 -0
  28. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/legacy.md +0 -0
  29. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/overview.md +0 -0
  30. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/__init__.py +0 -0
  31. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/base_api.py +0 -0
  32. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
  33. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/model_rest_api.py +0 -0
  34. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/params.py +0 -0
  35. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/__init__.py +0 -0
  36. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_add_service.py +0 -0
  37. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_bulk_service.py +0 -0
  38. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_delete_service.py +0 -0
  39. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_download_service.py +0 -0
  40. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_edit_service.py +0 -0
  41. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_file_service.py +0 -0
  42. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_info_service.py +0 -0
  43. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_list_service.py +0 -0
  44. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_lookup_service.py +0 -0
  45. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_schema_service.py +0 -0
  46. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_show_service.py +0 -0
  47. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/__init__.py +0 -0
  48. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/info.py +0 -0
  49. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/license.py +0 -0
  50. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/auth.py +0 -0
  51. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  52. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  53. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  54. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/ldap.py +0 -0
  55. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  56. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  57. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  58. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/config.py +0 -0
  59. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/db.py +0 -0
  60. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  61. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/__init__.py +0 -0
  62. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/__init__.py +0 -0
  63. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/column.py +0 -0
  64. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/db.py +0 -0
  65. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  66. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/filters.py +0 -0
  67. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/interface.py +0 -0
  68. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/model.py +0 -0
  69. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/session.py +0 -0
  70. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  71. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/column.py +0 -0
  72. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/db.py +0 -0
  73. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  74. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  75. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  76. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_service.py +0 -0
  77. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_service.py +0 -0
  78. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
  79. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_processing_service.py +0 -0
  80. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  81. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  82. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  83. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  84. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  85. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/filters.py +0 -0
  86. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/interface.py +0 -0
  87. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/model.py +0 -0
  88. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/session.py +0 -0
  89. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/__init__.py +0 -0
  90. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/cache.py +0 -0
  91. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/db.py +0 -0
  92. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/file_manager.py +0 -0
  93. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/filter.py +0 -0
  94. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/interface.py +0 -0
  95. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/loop_manager.py +0 -0
  96. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/model.py +0 -0
  97. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/session.py +0 -0
  98. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/user_cache.py +0 -0
  99. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/__init__.py +0 -0
  100. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/cache.py +0 -0
  101. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/serializers.py +0 -0
  102. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/storages.py +0 -0
  103. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/__init__.py +0 -0
  104. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/cli.py +0 -0
  105. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/__init__.py +0 -0
  106. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
  107. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/__init__.py +0 -0
  108. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/__init__.py +0 -0
  109. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/agents_md.py +0 -0
  110. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/claude.py +0 -0
  111. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/base.py +0 -0
  112. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/docs.py +0 -0
  113. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  114. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  115. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  116. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  117. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  118. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  119. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  120. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  121. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  122. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/export.py +0 -0
  123. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/security.py +0 -0
  124. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/translate.py +0 -0
  125. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/const.py +0 -0
  126. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/types.py +0 -0
  127. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/utils.py +0 -0
  128. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/config.py +0 -0
  129. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/db.py +0 -0
  130. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/decorators.py +0 -0
  131. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/dependencies.py +0 -0
  132. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/exceptions.py +0 -0
  133. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  134. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/__init__.py +0 -0
  135. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/file_manager.py +0 -0
  136. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/image_manager.py +0 -0
  137. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  138. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  139. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/filters.py +0 -0
  140. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/globals.py +0 -0
  141. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/__init__.py +0 -0
  142. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/__init__.py +0 -0
  143. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/cli.py +0 -0
  144. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/config.py +0 -0
  145. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel.cfg +0 -0
  146. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/lazy_text.py +0 -0
  147. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/messages.pot +0 -0
  148. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  149. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  150. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  151. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  152. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/loop_managers/__init__.py +0 -0
  153. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/loop_managers/loop_manager.py +0 -0
  154. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/manager.py +0 -0
  155. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/middlewares.py +0 -0
  156. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/mixins.py +0 -0
  157. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/models.py +0 -0
  158. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/py.typed +0 -0
  159. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/registry.py +0 -0
  160. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/schemas.py +0 -0
  161. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/__init__.py +0 -0
  162. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/__init__.py +0 -0
  163. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/models.py +0 -0
  164. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
  165. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_builtin_role_service.py +0 -0
  166. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_entities.py +0 -0
  167. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
  168. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
  169. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
  170. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_permission_service.py +0 -0
  171. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_results.py +0 -0
  172. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_role_service.py +0 -0
  173. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_user_service.py +0 -0
  174. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/security_manager.py +0 -0
  175. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/user_cache.py +0 -0
  176. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/setting.py +0 -0
  177. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/types.py +0 -0
  178. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/__init__.py +0 -0
  179. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/class_factory.py +0 -0
  180. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  181. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/deep_merge.py +0 -0
  182. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/extender_mixin.py +0 -0
  183. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  184. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/formatter.py +0 -0
  185. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/hooks.py +0 -0
  186. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/lazy.py +0 -0
  187. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/merge_schema.py +0 -0
  188. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  189. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/pydantic.py +0 -0
  190. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/run_utils.py +0 -0
  191. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/self_dependencies.py +0 -0
  192. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  193. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/sqla.py +0 -0
  194. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/timezone.py +0 -0
  195. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/update_signature.py +0 -0
  196. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  197. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/version.py +0 -0
  198. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/docker-compose.gis.yml +0 -0
  199. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/__init__.py +0 -0
  200. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/__init__.py +0 -0
  201. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_add_edit_relation_response.py +0 -0
  202. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_lookup.py +0 -0
  203. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_model_rest_api.py +0 -0
  204. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/__init__.py +0 -0
  205. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/test_info_callable_schema.py +0 -0
  206. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/test_info_contract.py +0 -0
  207. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/__init__.py +0 -0
  208. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/test_auth_flow.py +0 -0
  209. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/test_oauth.py +0 -0
  210. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/conftest.py +0 -0
  211. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/file_managers/__init__.py +0 -0
  212. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/file_managers/test_file_routes.py +0 -0
  213. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/__init__.py +0 -0
  214. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/__init__.py +0 -0
  215. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/conftest.py +0 -0
  216. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_permission_service.py +0 -0
  217. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_role_service.py +0 -0
  218. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_security_manager.py +0 -0
  219. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_user_service.py +0 -0
  220. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/test_app_smoke.py +0 -0
  221. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/__init__.py +0 -0
  222. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/__init__.py +0 -0
  223. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/conftest.py +0 -0
  224. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/__init__.py +0 -0
  225. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_columns.py +0 -0
  226. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_download_service.py +0 -0
  227. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
  228. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_init.py +0 -0
  229. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_lookup_service.py +0 -0
  230. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_service_delegators.py +0 -0
  231. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/test_base_api.py +0 -0
  232. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/__init__.py +0 -0
  233. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/test_info.py +0 -0
  234. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/test_license.py +0 -0
  235. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/__init__.py +0 -0
  236. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/__init__.py +0 -0
  237. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
  238. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/test_scrypt.py +0 -0
  239. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/password_helpers/__init__.py +0 -0
  240. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/password_helpers/test_fab.py +0 -0
  241. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/__init__.py +0 -0
  242. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_config.py +0 -0
  243. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_db.py +0 -0
  244. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_jwt.py +0 -0
  245. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/test_auth.py +0 -0
  246. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/__init__.py +0 -0
  247. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/__init__.py +0 -0
  248. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_column.py +0 -0
  249. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_db.py +0 -0
  250. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_exceptions.py +0 -0
  251. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_filters.py +0 -0
  252. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_interface.py +0 -0
  253. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_model.py +0 -0
  254. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_session.py +0 -0
  255. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/__init__.py +0 -0
  256. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/conftest.py +0 -0
  257. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit.py +0 -0
  258. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit_service_delegators.py +0 -0
  259. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit_types.py +0 -0
  260. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_column.py +0 -0
  261. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_db.py +0 -0
  262. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_filters.py +0 -0
  263. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
  264. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_interface.py +0 -0
  265. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_model.py +0 -0
  266. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_session.py +0 -0
  267. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/__init__.py +0 -0
  268. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_db.py +0 -0
  269. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_file_manager.py +0 -0
  270. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_filter.py +0 -0
  271. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_interface.py +0 -0
  272. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_model.py +0 -0
  273. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_session.py +0 -0
  274. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/__init__.py +0 -0
  275. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/__init__.py +0 -0
  276. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/conftest.py +0 -0
  277. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_db.py +0 -0
  278. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_export.py +0 -0
  279. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_security.py +0 -0
  280. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_security_crypto.py +0 -0
  281. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_translate.py +0 -0
  282. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/conftest.py +0 -0
  283. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_cli.py +0 -0
  284. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_types.py +0 -0
  285. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_utils.py +0 -0
  286. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/__init__.py +0 -0
  287. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/conftest.py +0 -0
  288. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_file_manager.py +0 -0
  289. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_image_manager.py +0 -0
  290. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
  291. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
  292. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/__init__.py +0 -0
  293. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_babel_cli.py +0 -0
  294. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_babel_config.py +0 -0
  295. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_lazy_text.py +0 -0
  296. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/__init__.py +0 -0
  297. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/__init__.py +0 -0
  298. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/conftest.py +0 -0
  299. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/__init__.py +0 -0
  300. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_builtin_role_service.py +0 -0
  301. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_entities.py +0 -0
  302. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_permission_service.py +0 -0
  303. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_results.py +0 -0
  304. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_role_service.py +0 -0
  305. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_user_service.py +0 -0
  306. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_apis.py +0 -0
  307. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_models.py +0 -0
  308. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_security_manager.py +0 -0
  309. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_caches.py +0 -0
  310. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_config.py +0 -0
  311. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_db.py +0 -0
  312. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_decorators.py +0 -0
  313. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_dependencies.py +0 -0
  314. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_exceptions.py +0 -0
  315. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_fastapi_react_toolkit.py +0 -0
  316. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_filters.py +0 -0
  317. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_globals.py +0 -0
  318. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_manager.py +0 -0
  319. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_middlewares.py +0 -0
  320. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_mixins.py +0 -0
  321. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_models.py +0 -0
  322. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_registry.py +0 -0
  323. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_routers.py +0 -0
  324. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_schemas.py +0 -0
  325. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_setting.py +0 -0
  326. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_types.py +0 -0
  327. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_user_cache.py +0 -0
  328. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_version.py +0 -0
  329. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/__init__.py +0 -0
  330. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_class_factory.py +0 -0
  331. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_csv_json_converter.py +0 -0
  332. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_deep_merge.py +0 -0
  333. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_extender_mixin.py +0 -0
  334. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
  335. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_formatter.py +0 -0
  336. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_hooks.py +0 -0
  337. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_lazy.py +0 -0
  338. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_merge_schema.py +0 -0
  339. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
  340. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_pydantic.py +0 -0
  341. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_run_utils.py +0 -0
  342. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_self_dependencies.py +0 -0
  343. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_smartdefaultdict.py +0 -0
  344. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_sqla.py +0 -0
  345. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_timezone.py +0 -0
  346. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_update_signature.py +0 -0
  347. {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_use_default_when_none.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-rtk
3
- Version: 2.21.2
3
+ Version: 2.22.0
4
4
  Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
5
5
  Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
6
6
  Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
@@ -50,6 +50,8 @@ Requires-Dist: pyinstrument>=5.1.2; extra == 'profiler'
50
50
  Provides-Extra: s3
51
51
  Requires-Dist: smart-open[s3]>=7.5.0; extra == 's3'
52
52
  Requires-Dist: urllib3>=2.7.0; extra == 's3'
53
+ Provides-Extra: saml
54
+ Requires-Dist: python3-saml>=1.16.0; extra == 'saml'
53
55
  Provides-Extra: test
54
56
  Requires-Dist: aiosqlite>=0.21.0; extra == 'test'
55
57
  Requires-Dist: pytest-asyncio>=1.3.0; extra == 'test'
@@ -351,6 +353,28 @@ A single adapter can also be swapped on the web `Provider` via `adapters={{ stor
351
353
 
352
354
  See the wiki page [React Native and Adapters](https://codeberg.org/datatactics/fastapi-rtk/wiki/06.04-React-Native-and-Adapters) for the full adapter reference (interfaces, per-adapter web/RN table, auth transports).
353
355
 
356
+ ## Signatures
357
+
358
+ The wheel and sdist of every PyPI release and the tarball of every npm release are signed with [cosign](https://github.com/sigstore/cosign). These files and their signature bundles (`<file>.sigstore.json`) are attached to the [release on Codeberg](https://codeberg.org/datatactics/fastapi-rtk/releases). Verify a downloaded file against the public key in this repo ([`cosign.pub`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/cosign.pub)):
359
+
360
+ ```sh
361
+ pip download --no-deps fastapi-rtk==<version>
362
+ cosign verify-blob --key cosign.pub \
363
+ --bundle fastapi_rtk-<version>-py3-none-any.whl.sigstore.json \
364
+ fastapi_rtk-<version>-py3-none-any.whl
365
+
366
+ npm pack fastapi-rtk@<version>
367
+ cosign verify-blob --key cosign.pub \
368
+ --bundle fastapi-rtk-<version>.tgz.sigstore.json \
369
+ fastapi-rtk-<version>.tgz
370
+ ```
371
+
372
+ [`scripts/verify-release.sh`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/scripts/verify-release.sh) runs that check for all three files of a release, downloading them from PyPI and npm and their bundles from the Codeberg release (needs `curl`, `jq` and `cosign`):
373
+
374
+ ```sh
375
+ scripts/verify-release.sh <version>
376
+ ```
377
+
354
378
  ## License
355
379
 
356
380
  FastAPI-RTK is licensed under the [MIT license](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/LICENSE).
@@ -292,6 +292,28 @@ A single adapter can also be swapped on the web `Provider` via `adapters={{ stor
292
292
 
293
293
  See the wiki page [React Native and Adapters](https://codeberg.org/datatactics/fastapi-rtk/wiki/06.04-React-Native-and-Adapters) for the full adapter reference (interfaces, per-adapter web/RN table, auth transports).
294
294
 
295
+ ## Signatures
296
+
297
+ The wheel and sdist of every PyPI release and the tarball of every npm release are signed with [cosign](https://github.com/sigstore/cosign). These files and their signature bundles (`<file>.sigstore.json`) are attached to the [release on Codeberg](https://codeberg.org/datatactics/fastapi-rtk/releases). Verify a downloaded file against the public key in this repo ([`cosign.pub`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/cosign.pub)):
298
+
299
+ ```sh
300
+ pip download --no-deps fastapi-rtk==<version>
301
+ cosign verify-blob --key cosign.pub \
302
+ --bundle fastapi_rtk-<version>-py3-none-any.whl.sigstore.json \
303
+ fastapi_rtk-<version>-py3-none-any.whl
304
+
305
+ npm pack fastapi-rtk@<version>
306
+ cosign verify-blob --key cosign.pub \
307
+ --bundle fastapi-rtk-<version>.tgz.sigstore.json \
308
+ fastapi-rtk-<version>.tgz
309
+ ```
310
+
311
+ [`scripts/verify-release.sh`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/scripts/verify-release.sh) runs that check for all three files of a release, downloading them from PyPI and npm and their bundles from the Codeberg release (needs `curl`, `jq` and `cosign`):
312
+
313
+ ```sh
314
+ scripts/verify-release.sh <version>
315
+ ```
316
+
295
317
  ## License
296
318
 
297
319
  FastAPI-RTK is licensed under the [MIT license](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/LICENSE).
@@ -61,6 +61,9 @@ __all__ = [
61
61
  "BearerConfig",
62
62
  "CookieConfig",
63
63
  "StrategyConfig",
64
+ "SAMLClient",
65
+ "SAMLIdentity",
66
+ "SAMLResponseError",
64
67
  # .backends.generic
65
68
  "GenericPKAutoIncrement",
66
69
  "GenericUnset",
@@ -245,6 +248,7 @@ __all__ = [
245
248
  # .routers
246
249
  "get_auth_router",
247
250
  "get_oauth_router",
251
+ "get_saml_router",
248
252
  "get_oauth_associate_router",
249
253
  # .schemas
250
254
  "PRIMARY_KEY",
@@ -0,0 +1 @@
1
+ __version__ = "2.22.0"
@@ -714,6 +714,35 @@ The toolkit wires the login/callback routes; the frontend's OAuth sign-in hook k
714
714
 
715
715
  A `"backend": "jwt"` client (for clients without a browser cookie store, such as React Native; web apps default to `cookie` and can opt in) answers the callback with `{"access_token", "token_type": "bearer"}` JSON instead of a cookie, so the client has to make the callback request itself: point `redirect_url` (or `redirect_url_factory`) at the URL the app receives the redirect on (on React Native the same URL goes to `createReactNativeAdapters({ oauthRedirectUrl })`, and `oauthSignin` then completes the sign-in itself), or call `oauthCallback("<name>", redirectQuery)` from `useAuth()` with that URL's query string, which forwards `code` and `state` to `GET /api/v1/auth/oauth-authorized/<name>` and stores the returned token. A JWT client rejects `redirect_url_after_callback` with a `ValueError` at startup and a popup login (`?popup=true`) with 400 `OAUTH_POPUP_NOT_SUPPORTED`, since both would drop the token.
716
716
 
717
+ Entry names must be unique across `OAUTH_CLIENTS`; a duplicate raises `ValueError` at startup.
718
+
719
+ A SAML 2.0 IdP (Microsoft Entra ID, for example) is an ordinary entry whose `oauth_client` is a `SAMLClient` (`from fastapi_rtk import SAMLClient`, needs `pip install fastapi-rtk[saml]`). Give it `name`, `sp_entity_id`, and either `idp_metadata_url` or all of `idp_entity_id`, `idp_sso_url`, `idp_cert`; `account_id_attribute` / `email_attribute` name the assertion attributes to read (the NameID when `None`). The other entry keys keep their OAuth meaning: `redirect_url` is the ACS URL registered at the IdP, `backend` must be `cookie` (`jwt` raises `ValueError`, since the IdP posts to the browser), and the hooks receive `oauth_token={"attributes": {...}, "nameid": ..., "assertion_id": ...}`, so roles come from returning `role_keys`, e.g. the Entra groups claim. Routes: `GET /api/v1/auth/login/<name>` (popup supported, so `oauthSignin('<name>')` works unchanged) and the ACS `POST /api/v1/auth/oauth-authorized/<name>`, which answers 303 to `redirect_url_after_callback`. A failed validation is 400 `SAML_RESPONSE_INVALID`, a reused assertion 400 `SAML_ASSERTION_REPLAYED` (tracked in `g.cache` until the assertion expires). The default `InMemoryCacheStorage` is per process, so with several workers or replicas a replay that lands on another worker passes: such a deployment must set `CACHE` to a storage every worker shares (a custom `AbstractCacheStorage`, since the toolkit ships none). The check and the record are separate calls: the default in-memory storage never suspends between them, but with a storage whose `get` awaits a network round trip (Redis, say) two simultaneous posts of one assertion can both pass. IdP-initiated logins are rejected, and login CSRF is not prevented, as for OAuth.
720
+
721
+ ```python
722
+ from fastapi_rtk import SAMLClient
723
+
724
+ OAUTH_PROVIDERS = [
725
+ {
726
+ "oauth_client": SAMLClient(
727
+ name="entra",
728
+ sp_entity_id="my-app",
729
+ idp_entity_id="https://sts.windows.net/<tenant>/",
730
+ idp_sso_url="https://login.microsoftonline.com/<tenant>/saml2",
731
+ idp_cert="MIIC...",
732
+ account_id_attribute="http://schemas.microsoft.com/identity/claims/objectidentifier",
733
+ email_attribute="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
734
+ ),
735
+ "redirect_url": "https://<host>/api/v1/auth/oauth-authorized/entra",
736
+ "associate_by_email": True,
737
+ "on_before_login": lambda user, oauth_token: {
738
+ "role_keys": oauth_token["attributes"].get(
739
+ "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups", []
740
+ )
741
+ },
742
+ },
743
+ ]
744
+ ```
745
+
717
746
  ## 16. Migrations and user management CLI
718
747
 
719
748
  ```bash
@@ -7,13 +7,17 @@ from .auth import (
7
7
  StrategyConfig,
8
8
  )
9
9
  from .password_helpers import FABPasswordHelper
10
+ from .saml import SAMLClient, SAMLIdentity, SAMLResponseError
10
11
 
11
12
  __all__ = [
12
13
  "FABPasswordHelper",
13
- "CookieConfig",
14
- "BearerConfig",
15
- "StrategyConfig",
16
- "Auth",
17
14
  "Authenticator",
18
15
  "AuthConfigurator",
16
+ "Auth",
17
+ "BearerConfig",
18
+ "CookieConfig",
19
+ "StrategyConfig",
20
+ "SAMLClient",
21
+ "SAMLIdentity",
22
+ "SAMLResponseError",
19
23
  ]
@@ -0,0 +1,376 @@
1
+ """SAML 2.0 service provider client, registered as an `OAUTH_CLIENTS` entry."""
2
+
3
+ import collections.abc
4
+ import dataclasses
5
+ import importlib
6
+ import math
7
+ import time
8
+ import types
9
+ import typing
10
+ import urllib.parse
11
+
12
+ from ..utils import smart_run
13
+
14
+ if typing.TYPE_CHECKING:
15
+ import onelogin.saml2.settings
16
+
17
+ __all__ = ["SAMLClient", "SAMLIdentity", "SAMLResponseError"]
18
+
19
+
20
+ @dataclasses.dataclass
21
+ class SAMLIdentity:
22
+ """
23
+ The identity read off a validated SAML response.
24
+ """
25
+
26
+ account_id: str
27
+ account_email: str | None
28
+ assertion_id: str
29
+ not_on_or_after: int | None
30
+ nameid: str
31
+ attributes: dict[str, list[str]]
32
+
33
+
34
+ class SAMLResponseError(Exception):
35
+ """
36
+ A SAML response failed validation.
37
+ """
38
+
39
+ def __init__(self, errors: list[str], reason: str | None):
40
+ """
41
+ Create the error from python3-saml's error codes and reason.
42
+
43
+ Args:
44
+ errors: The python3-saml error codes.
45
+ reason: The python3-saml reason for the last error.
46
+ """
47
+ super().__init__(f"{', '.join(errors)}: {reason}")
48
+ self.errors = errors
49
+ self.reason = reason
50
+
51
+
52
+ class SAMLClient:
53
+ """
54
+ A SAML 2.0 identity provider, used as the `oauth_client` of an `OAUTH_CLIENTS` entry.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ name: str,
60
+ sp_entity_id: str,
61
+ idp_entity_id: str | None = None,
62
+ idp_sso_url: str | None = None,
63
+ idp_cert: str | None = None,
64
+ idp_metadata_url: str | None = None,
65
+ account_id_attribute: str | None = None,
66
+ email_attribute: str | None = None,
67
+ ):
68
+ """
69
+ Configure the client; IdP fields left None come from `idp_metadata_url`.
70
+
71
+ Args:
72
+ name: Provider name, the last path segment of the login and ACS routes.
73
+ sp_entity_id: Entity ID of this service provider at the IdP.
74
+ idp_entity_id: Entity ID (issuer) of the IdP.
75
+ idp_sso_url: HTTP-Redirect single sign-on URL of the IdP.
76
+ idp_cert: Base64 X.509 signing certificate of the IdP.
77
+ idp_metadata_url: IdP metadata URL, source of every IdP field left None.
78
+ account_id_attribute: Attribute holding the account id; the NameID when None.
79
+ email_attribute: Attribute holding the email; the NameID when None.
80
+
81
+ Raises:
82
+ ValueError: No metadata URL and one of the three IdP fields is missing.
83
+ ImportError: The `saml` extra is not installed.
84
+ """
85
+ if not idp_metadata_url and not (idp_entity_id and idp_sso_url and idp_cert):
86
+ raise ValueError(
87
+ f"SAML client {name!r} needs idp_metadata_url or all of "
88
+ "idp_entity_id, idp_sso_url and idp_cert"
89
+ )
90
+ self.name = name
91
+ self.sp_entity_id = sp_entity_id
92
+ self.idp_entity_id = idp_entity_id
93
+ self.idp_sso_url = idp_sso_url
94
+ self.idp_cert = idp_cert
95
+ self.idp_metadata_url = idp_metadata_url
96
+ self.account_id_attribute = account_id_attribute
97
+ self.email_attribute = email_attribute
98
+ self._idp: dict | None = None
99
+ # Fails at startup rather than at the first login when the extra is missing.
100
+ _onelogin("onelogin.saml2.settings")
101
+
102
+ async def get_authorization_url(
103
+ self, acs_url: str, relay_state: collections.abc.Callable[[str], str]
104
+ ) -> str:
105
+ """
106
+ Build the IdP redirect URL for a new AuthnRequest.
107
+
108
+ Args:
109
+ acs_url: The ACS URL the IdP posts the response to.
110
+ relay_state: Called with the AuthnRequest ID, returns the RelayState.
111
+
112
+ Returns:
113
+ str: The IdP URL to redirect the browser to.
114
+ """
115
+ return await smart_run(self._authorization_url, acs_url, relay_state)
116
+
117
+ async def process_response(
118
+ self, acs_url: str, post_data: dict[str, str], request_id: str
119
+ ) -> SAMLIdentity:
120
+ """
121
+ Validate a posted SAML response.
122
+
123
+ Args:
124
+ acs_url: The ACS URL the response must be addressed to.
125
+ post_data: The posted form fields, `SAMLResponse` included.
126
+ request_id: The AuthnRequest ID the response must answer.
127
+
128
+ Returns:
129
+ SAMLIdentity: The authenticated identity.
130
+
131
+ Raises:
132
+ SAMLResponseError: The response failed validation.
133
+ """
134
+ return await smart_run(self._process_response, acs_url, post_data, request_id)
135
+
136
+ def _authorization_url(
137
+ self, acs_url: str, relay_state: collections.abc.Callable[[str], str]
138
+ ) -> str:
139
+ """
140
+ Build the IdP redirect URL; the blocking half of `get_authorization_url`.
141
+
142
+ Args:
143
+ acs_url: The ACS URL the IdP posts the response to.
144
+ relay_state: Called with the AuthnRequest ID, returns the RelayState.
145
+
146
+ Returns:
147
+ str: The IdP URL to redirect the browser to.
148
+ """
149
+ authn_request_module = _onelogin("onelogin.saml2.authn_request")
150
+ utils = _onelogin("onelogin.saml2.utils")
151
+ settings = self._settings(acs_url)
152
+ authn_request = authn_request_module.OneLogin_Saml2_Authn_Request(settings)
153
+ return utils.OneLogin_Saml2_Utils.redirect(
154
+ settings.get_idp_sso_url(),
155
+ {
156
+ "SAMLRequest": authn_request.get_request(),
157
+ "RelayState": relay_state(authn_request.get_id()),
158
+ },
159
+ )
160
+
161
+ def _process_response(
162
+ self, acs_url: str, post_data: dict[str, str], request_id: str
163
+ ) -> SAMLIdentity:
164
+ """
165
+ Validate a posted SAML response; the blocking half of `process_response`.
166
+
167
+ Args:
168
+ acs_url: The ACS URL the response must be addressed to.
169
+ post_data: The posted form fields, `SAMLResponse` included.
170
+ request_id: The AuthnRequest ID the response must answer.
171
+
172
+ Returns:
173
+ SAMLIdentity: The authenticated identity.
174
+
175
+ Raises:
176
+ SAMLResponseError: The response failed validation.
177
+ """
178
+ response_module = _onelogin("onelogin.saml2.response")
179
+ settings = self._settings(acs_url)
180
+ try:
181
+ response = response_module.OneLogin_Saml2_Response(
182
+ settings, post_data.get("SAMLResponse", "")
183
+ )
184
+ valid = response.is_valid(_request_data(acs_url), request_id)
185
+ except Exception as e:
186
+ # The response is decoded and parsed in the constructor, so garbage raises here.
187
+ raise SAMLResponseError(["invalid_response"], repr(e)) from e
188
+ if not valid:
189
+ raise SAMLResponseError(["invalid_response"], response.get_error())
190
+ # The outer Response InResponseTo is unsigned, and python3-saml checks the signed one only when both are present.
191
+ signed_in_response_to = [
192
+ node.get("InResponseTo")
193
+ for node in response._query_assertion(
194
+ "/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData"
195
+ )
196
+ ]
197
+ if request_id not in signed_in_response_to:
198
+ raise SAMLResponseError(
199
+ ["invalid_in_response_to"],
200
+ f"signed InResponseTo {signed_in_response_to!r} does not match {request_id!r}",
201
+ )
202
+ try:
203
+ # is_valid leaves NameID and duplicate-attribute checks to these getters.
204
+ attributes = response.get_attributes()
205
+ nameid = response.get_nameid()
206
+ assertion_id = response.get_assertion_id()
207
+ not_on_or_after = response.get_assertion_not_on_or_after()
208
+ except Exception as e:
209
+ raise SAMLResponseError(["invalid_response"], repr(e)) from e
210
+ account_id = nameid
211
+ if self.account_id_attribute:
212
+ account_id = _first(attributes, self.account_id_attribute)
213
+ if account_id is None:
214
+ raise SAMLResponseError(
215
+ ["missing_account_id"],
216
+ f"attribute {self.account_id_attribute!r} is missing",
217
+ )
218
+ return SAMLIdentity(
219
+ account_id=account_id,
220
+ account_email=_first(attributes, self.email_attribute)
221
+ if self.email_attribute
222
+ else nameid,
223
+ assertion_id=assertion_id,
224
+ not_on_or_after=not_on_or_after,
225
+ nameid=nameid,
226
+ attributes=attributes,
227
+ )
228
+
229
+ def _settings(
230
+ self, acs_url: str
231
+ ) -> "onelogin.saml2.settings.OneLogin_Saml2_Settings":
232
+ """
233
+ Build the python3-saml settings for one ACS URL.
234
+
235
+ Args:
236
+ acs_url: The ACS URL of this service provider.
237
+
238
+ Returns:
239
+ OneLogin_Saml2_Settings: The validated settings.
240
+ """
241
+ settings_module = _onelogin("onelogin.saml2.settings")
242
+ return settings_module.OneLogin_Saml2_Settings(
243
+ {
244
+ "strict": True,
245
+ "debug": False,
246
+ "sp": {
247
+ "entityId": self.sp_entity_id,
248
+ "assertionConsumerService": {
249
+ "url": acs_url,
250
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
251
+ },
252
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
253
+ },
254
+ "idp": self._idp_data(),
255
+ "security": {
256
+ "wantAssertionsSigned": True,
257
+ "wantMessagesSigned": False,
258
+ "wantNameId": True,
259
+ "rejectDeprecatedAlgorithm": True,
260
+ "requestedAuthnContext": False,
261
+ "wantAttributeStatement": False,
262
+ "allowSingleLabelDomains": True,
263
+ },
264
+ },
265
+ sp_validation_only=False,
266
+ )
267
+
268
+ def _idp_data(self) -> dict:
269
+ """
270
+ Return the python3-saml `idp` settings, fetching the metadata on first use.
271
+
272
+ Returns:
273
+ dict: The `idp` settings block, cached on the instance.
274
+ """
275
+ if self._idp is not None:
276
+ return self._idp
277
+ idp = {
278
+ "entityId": self.idp_entity_id,
279
+ "singleSignOnService": {
280
+ "url": self.idp_sso_url,
281
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
282
+ },
283
+ "x509cert": self.idp_cert,
284
+ }
285
+ if not (self.idp_entity_id and self.idp_sso_url and self.idp_cert):
286
+ parser = _onelogin("onelogin.saml2.idp_metadata_parser")
287
+ metadata = parser.OneLogin_Saml2_IdPMetadataParser.parse_remote(
288
+ self.idp_metadata_url, timeout=10
289
+ )["idp"]
290
+ idp["entityId"] = self.idp_entity_id or metadata.get("entityId")
291
+ idp["x509cert"] = self.idp_cert or metadata.get("x509cert")
292
+ # Metadata listing several signing certificates (rollover) carries them here instead.
293
+ if not idp["x509cert"] and metadata.get("x509certMulti"):
294
+ idp["x509certMulti"] = metadata["x509certMulti"]
295
+ del idp["x509cert"]
296
+ # Raised before caching, so a later login fetches the metadata again.
297
+ if not (idp.get("x509cert") or idp.get("x509certMulti")):
298
+ raise ValueError(
299
+ f"IdP metadata at {self.idp_metadata_url!r} lists no signing certificate"
300
+ )
301
+ if not self.idp_sso_url:
302
+ idp["singleSignOnService"] = metadata["singleSignOnService"]
303
+ self._idp = idp
304
+ return idp
305
+
306
+
307
+ def seconds_until(timestamp: int | None) -> int:
308
+ """
309
+ Seconds from now until a unix timestamp.
310
+
311
+ Args:
312
+ timestamp: The unix timestamp; None means one hour.
313
+
314
+ Returns:
315
+ int: The seconds left, at least 1.
316
+ """
317
+ if timestamp is None:
318
+ return 3600
319
+ return max(1, math.ceil(timestamp - time.time()))
320
+
321
+
322
+ def _first(attributes: dict[str, list[str]], name: str) -> str | None:
323
+ """
324
+ Return the first value of an assertion attribute.
325
+
326
+ Args:
327
+ attributes: The assertion attributes, name to values.
328
+ name: The attribute name.
329
+
330
+ Returns:
331
+ str | None: The first value, None when the attribute is absent or empty.
332
+ """
333
+ values = attributes.get(name) or []
334
+ return values[0] if values else None
335
+
336
+
337
+ def _request_data(acs_url: str) -> dict:
338
+ """
339
+ Build the python3-saml request data for a response posted to the ACS URL.
340
+
341
+ Args:
342
+ acs_url: The ACS URL the IdP posted to.
343
+
344
+ Returns:
345
+ dict: The request data python3-saml validates `Destination` against.
346
+ """
347
+ # Built from the ACS URL, not the request: behind a proxy prefix the app sees a different path than the IdP posted to.
348
+ acs = urllib.parse.urlsplit(acs_url)
349
+ return {
350
+ "https": "on" if acs.scheme == "https" else "off",
351
+ "http_host": acs.netloc,
352
+ "script_name": acs.path,
353
+ "get_data": {},
354
+ "post_data": {},
355
+ }
356
+
357
+
358
+ def _onelogin(module: str) -> types.ModuleType:
359
+ """
360
+ Import a python3-saml module, naming the extra when it is missing.
361
+
362
+ Args:
363
+ module: The dotted module name.
364
+
365
+ Returns:
366
+ types.ModuleType: The imported module.
367
+
368
+ Raises:
369
+ ImportError: The `saml` extra is not installed.
370
+ """
371
+ try:
372
+ return importlib.import_module(module)
373
+ except ImportError as e:
374
+ raise ImportError(
375
+ "SAML login needs the 'saml' extra: pip install fastapi-rtk[saml]"
376
+ ) from e
@@ -68,7 +68,11 @@ def _current_command_path() -> str | None:
68
68
  Returns:
69
69
  str | None: The space-joined command names, or `None` when there is no active Click context.
70
70
  """
71
- ctx = click.get_current_context(silent=True)
71
+ # typer >= 0.26 runs commands on its own vendored click, with its own context stack.
72
+ typer_get_current_context = getattr(typer.main, "get_current_context", None)
73
+ ctx = typer_get_current_context(silent=True) if typer_get_current_context else None
74
+ if ctx is None:
75
+ ctx = click.get_current_context(silent=True)
72
76
  if ctx is None:
73
77
  return None
74
78
  names = []
@@ -164,6 +164,8 @@ class ErrorCode(str, enum.Enum):
164
164
  FILE_NOT_FOUND = "FILE_NOT_FOUND"
165
165
  IMAGE_NOT_FOUND = "IMAGE_NOT_FOUND"
166
166
  OAUTH_POPUP_NOT_SUPPORTED = "OAUTH_POPUP_NOT_SUPPORTED"
167
+ SAML_RESPONSE_INVALID = "SAML_RESPONSE_INVALID"
168
+ SAML_ASSERTION_REPLAYED = "SAML_ASSERTION_REPLAYED"
167
169
  COLUMN_NOT_FOUND = "COLUMN_NOT_FOUND"
168
170
 
169
171