fastapi-rtk 2.12.2__tar.gz → 2.12.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 (313) hide show
  1. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/PKG-INFO +1 -1
  2. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/__init__.py +4 -0
  3. fastapi_rtk-2.12.3/fastapi_rtk/_version.py +1 -0
  4. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/__init__.py +2 -0
  5. fastapi_rtk-2.12.3/fastapi_rtk/bases/loop_manager.py +47 -0
  6. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/globals.py +14 -0
  7. fastapi_rtk-2.12.3/fastapi_rtk/loop_managers/__init__.py +3 -0
  8. fastapi_rtk-2.12.3/fastapi_rtk/loop_managers/loop_manager.py +232 -0
  9. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/setting.py +8 -0
  10. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/run_utils.py +92 -21
  11. fastapi_rtk-2.12.3/tests/unit/utils/test_run_utils.py +775 -0
  12. fastapi_rtk-2.12.2/fastapi_rtk/_version.py +0 -1
  13. fastapi_rtk-2.12.2/tests/unit/utils/test_run_utils.py +0 -369
  14. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/.gitignore +0 -0
  15. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/LICENSE +0 -0
  16. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/README.md +0 -0
  17. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/__init__.py +0 -0
  18. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/base_api.py +0 -0
  19. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
  20. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
  21. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
  22. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +0 -0
  23. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
  24. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
  25. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
  26. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
  27. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_info_mixin.py +0 -0
  28. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
  29. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
  30. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
  31. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_typed_base.py +0 -0
  32. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
  33. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/__init__.py +0 -0
  34. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/info.py +0 -0
  35. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/license.py +0 -0
  36. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/__init__.py +0 -0
  37. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/auth.py +0 -0
  38. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  39. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  40. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  41. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/ldap.py +0 -0
  42. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  43. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  44. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  45. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/config.py +0 -0
  46. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/db.py +0 -0
  47. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  48. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/__init__.py +0 -0
  49. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/__init__.py +0 -0
  50. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/column.py +0 -0
  51. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/db.py +0 -0
  52. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  53. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/filters.py +0 -0
  54. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/interface.py +0 -0
  55. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/model.py +0 -0
  56. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/session.py +0 -0
  57. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  58. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/column.py +0 -0
  59. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/db.py +0 -0
  60. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  61. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  62. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  63. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
  64. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
  65. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
  66. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
  67. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  68. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  69. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  70. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  71. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  72. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/filters.py +0 -0
  73. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/interface.py +0 -0
  74. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/model.py +0 -0
  75. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/session.py +0 -0
  76. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/cache.py +0 -0
  77. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/db.py +0 -0
  78. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/file_manager.py +0 -0
  79. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/filter.py +0 -0
  80. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/interface.py +0 -0
  81. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/model.py +0 -0
  82. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/session.py +0 -0
  83. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/user_cache.py +0 -0
  84. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/__init__.py +0 -0
  85. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/cache.py +0 -0
  86. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/serializers.py +0 -0
  87. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/storages.py +0 -0
  88. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/__init__.py +0 -0
  89. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/cli.py +0 -0
  90. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/__init__.py +0 -0
  91. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
  92. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  93. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  94. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  95. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  96. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  97. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  98. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  99. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  100. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  101. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/export.py +0 -0
  102. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/security.py +0 -0
  103. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/translate.py +0 -0
  104. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/const.py +0 -0
  105. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/decorators.py +0 -0
  106. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/types.py +0 -0
  107. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/utils.py +0 -0
  108. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/config.py +0 -0
  109. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/const.py +0 -0
  110. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/db.py +0 -0
  111. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/decorators.py +0 -0
  112. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/dependencies.py +0 -0
  113. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/exceptions.py +0 -0
  114. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  115. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/__init__.py +0 -0
  116. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/file_manager.py +0 -0
  117. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/image_manager.py +0 -0
  118. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  119. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  120. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/filters.py +0 -0
  121. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/__init__.py +0 -0
  122. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/__init__.py +0 -0
  123. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/cli.py +0 -0
  124. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/config.py +0 -0
  125. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel.cfg +0 -0
  126. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/lazy_text.py +0 -0
  127. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/messages.pot +0 -0
  128. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  129. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  130. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  131. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  132. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/manager.py +0 -0
  133. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/middlewares.py +0 -0
  134. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/mixins.py +0 -0
  135. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/models.py +0 -0
  136. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/py.typed +0 -0
  137. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/registry.py +0 -0
  138. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/routers.py +0 -0
  139. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/schemas.py +0 -0
  140. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/__init__.py +0 -0
  141. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/__init__.py +0 -0
  142. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/apis.py +0 -0
  143. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/models.py +0 -0
  144. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
  145. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
  146. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
  147. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
  148. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
  149. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
  150. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
  151. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
  152. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
  153. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/user_cache.py +0 -0
  154. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/types.py +0 -0
  155. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/__init__.py +0 -0
  156. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/async_task_runner.py +0 -0
  157. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/class_factory.py +0 -0
  158. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  159. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/deep_merge.py +0 -0
  160. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/extender_mixin.py +0 -0
  161. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  162. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/formatter.py +0 -0
  163. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/hooks.py +0 -0
  164. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/lazy.py +0 -0
  165. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/merge_schema.py +0 -0
  166. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  167. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/pydantic.py +0 -0
  168. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/self_dependencies.py +0 -0
  169. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  170. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/sqla.py +0 -0
  171. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/timezone.py +0 -0
  172. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/update_signature.py +0 -0
  173. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  174. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/version.py +0 -0
  175. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/pyproject.toml +0 -0
  176. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/conftest.py +0 -0
  177. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/docker-compose.gis.yml +0 -0
  178. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/__init__.py +0 -0
  179. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/api/__init__.py +0 -0
  180. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/api/test_model_rest_api.py +0 -0
  181. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/__init__.py +0 -0
  182. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/test_info_callable_schema.py +0 -0
  183. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/test_info_contract.py +0 -0
  184. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/__init__.py +0 -0
  185. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/test_auth_flow.py +0 -0
  186. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/test_oauth.py +0 -0
  187. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/conftest.py +0 -0
  188. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/file_managers/__init__.py +0 -0
  189. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/file_managers/test_file_routes.py +0 -0
  190. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/__init__.py +0 -0
  191. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/sqla/__init__.py +0 -0
  192. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/sqla/test_security_manager.py +0 -0
  193. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/test_app_smoke.py +0 -0
  194. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/__init__.py +0 -0
  195. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/__init__.py +0 -0
  196. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/conftest.py +0 -0
  197. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/__init__.py +0 -0
  198. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_columns.py +0 -0
  199. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
  200. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_init.py +0 -0
  201. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
  202. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/test_base_api.py +0 -0
  203. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/__init__.py +0 -0
  204. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/test_info.py +0 -0
  205. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/test_license.py +0 -0
  206. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/__init__.py +0 -0
  207. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/__init__.py +0 -0
  208. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
  209. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/test_scrypt.py +0 -0
  210. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/password_helpers/__init__.py +0 -0
  211. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/password_helpers/test_fab.py +0 -0
  212. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/__init__.py +0 -0
  213. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_config.py +0 -0
  214. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_db.py +0 -0
  215. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_jwt.py +0 -0
  216. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/test_auth.py +0 -0
  217. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/__init__.py +0 -0
  218. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/__init__.py +0 -0
  219. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_column.py +0 -0
  220. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_db.py +0 -0
  221. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_exceptions.py +0 -0
  222. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_filters.py +0 -0
  223. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_interface.py +0 -0
  224. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_model.py +0 -0
  225. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_session.py +0 -0
  226. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/__init__.py +0 -0
  227. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/conftest.py +0 -0
  228. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_audit.py +0 -0
  229. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_audit_types.py +0 -0
  230. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_column.py +0 -0
  231. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_db.py +0 -0
  232. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_filters.py +0 -0
  233. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
  234. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_interface.py +0 -0
  235. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_model.py +0 -0
  236. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_session.py +0 -0
  237. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/__init__.py +0 -0
  238. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_db.py +0 -0
  239. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_file_manager.py +0 -0
  240. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_filter.py +0 -0
  241. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_interface.py +0 -0
  242. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_model.py +0 -0
  243. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_session.py +0 -0
  244. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/__init__.py +0 -0
  245. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/__init__.py +0 -0
  246. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/conftest.py +0 -0
  247. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_db.py +0 -0
  248. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_export.py +0 -0
  249. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_security.py +0 -0
  250. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_security_crypto.py +0 -0
  251. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_translate.py +0 -0
  252. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/conftest.py +0 -0
  253. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_cli.py +0 -0
  254. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_decorators.py +0 -0
  255. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_types.py +0 -0
  256. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_utils.py +0 -0
  257. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/__init__.py +0 -0
  258. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/conftest.py +0 -0
  259. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_file_manager.py +0 -0
  260. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_image_manager.py +0 -0
  261. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
  262. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
  263. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/__init__.py +0 -0
  264. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_babel_cli.py +0 -0
  265. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_babel_config.py +0 -0
  266. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_lazy_text.py +0 -0
  267. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/__init__.py +0 -0
  268. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/__init__.py +0 -0
  269. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/conftest.py +0 -0
  270. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_apis.py +0 -0
  271. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_models.py +0 -0
  272. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_security_manager.py +0 -0
  273. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_caches.py +0 -0
  274. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_config.py +0 -0
  275. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_const.py +0 -0
  276. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_db.py +0 -0
  277. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_decorators.py +0 -0
  278. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_dependencies.py +0 -0
  279. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_exceptions.py +0 -0
  280. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_fastapi_react_toolkit.py +0 -0
  281. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_filters.py +0 -0
  282. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_globals.py +0 -0
  283. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_manager.py +0 -0
  284. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_middlewares.py +0 -0
  285. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_mixins.py +0 -0
  286. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_models.py +0 -0
  287. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_registry.py +0 -0
  288. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_routers.py +0 -0
  289. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_schemas.py +0 -0
  290. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_setting.py +0 -0
  291. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_types.py +0 -0
  292. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_user_cache.py +0 -0
  293. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_version.py +0 -0
  294. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/__init__.py +0 -0
  295. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_async_task_runner.py +0 -0
  296. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_class_factory.py +0 -0
  297. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_csv_json_converter.py +0 -0
  298. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_deep_merge.py +0 -0
  299. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_extender_mixin.py +0 -0
  300. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
  301. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_formatter.py +0 -0
  302. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_hooks.py +0 -0
  303. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_lazy.py +0 -0
  304. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_merge_schema.py +0 -0
  305. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
  306. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_pydantic.py +0 -0
  307. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_self_dependencies.py +0 -0
  308. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_smartdefaultdict.py +0 -0
  309. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_sqla.py +0 -0
  310. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_timezone.py +0 -0
  311. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_update_signature.py +0 -0
  312. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_use_default_when_none.py +0 -0
  313. {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-rtk
3
- Version: 2.12.2
3
+ Version: 2.12.3
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
@@ -17,6 +17,7 @@ from .fastapi_react_toolkit import *
17
17
  from .file_managers import *
18
18
  from .globals import *
19
19
  from .lang import *
20
+ from .loop_managers import *
20
21
  from .manager import *
21
22
  from .mixins import *
22
23
  from .routers import *
@@ -158,6 +159,7 @@ __all__ = [
158
159
  "AbstractBaseOprFilter",
159
160
  "PydanticGenerationSchema",
160
161
  "AbstractInterface",
162
+ "AbstractLoopManager",
161
163
  "MissingGreenletError",
162
164
  "BasicModel",
163
165
  "AbstractSession",
@@ -209,6 +211,8 @@ __all__ = [
209
211
  "lazy_text",
210
212
  "translate",
211
213
  "_",
214
+ # .loop_managers
215
+ "LoopManager",
212
216
  # .manager
213
217
  "UserManager",
214
218
  # .mixins
@@ -0,0 +1 @@
1
+ __version__ = "2.12.3"
@@ -3,6 +3,7 @@ from .db import *
3
3
  from .file_manager import *
4
4
  from .filter import *
5
5
  from .interface import *
6
+ from .loop_manager import *
6
7
  from .model import *
7
8
  from .session import *
8
9
  from .user_cache import *
@@ -21,6 +22,7 @@ __all__ = [
21
22
  "AbstractBaseOprFilter",
22
23
  "PydanticGenerationSchema",
23
24
  "AbstractInterface",
25
+ "AbstractLoopManager",
24
26
  "MissingGreenletError",
25
27
  "BasicModel",
26
28
  "AbstractSession",
@@ -0,0 +1,47 @@
1
+ import abc
2
+ import asyncio
3
+
4
+ __all__ = ["AbstractLoopManager"]
5
+
6
+
7
+ class AbstractLoopManager(abc.ABC):
8
+ """
9
+ Supplies the event loop that synchronous callers submit coroutines to.
10
+
11
+ Implement this to control which loop `smart_run_sync` / `safe_call_sync` reach for when no loop is passed, e.g. to hand them a loop the host application already runs. The loop must be running in another thread, because the calling thread blocks on the result.
12
+ """
13
+
14
+ @property
15
+ @abc.abstractmethod
16
+ def loop(self) -> asyncio.AbstractEventLoop:
17
+ """
18
+ The loop to submit coroutines to, started if it was not running yet.
19
+
20
+ Returns:
21
+ asyncio.AbstractEventLoop: A running event loop owned by another thread.
22
+ """
23
+
24
+ @abc.abstractmethod
25
+ def shutdown(self) -> None:
26
+ """
27
+ Stop the loops this manager owns, if it started any.
28
+
29
+ Returns:
30
+ None: The next `loop` access builds a fresh loop.
31
+ """
32
+
33
+ def loop_for(
34
+ self, current: asyncio.AbstractEventLoop | None
35
+ ) -> asyncio.AbstractEventLoop:
36
+ """
37
+ The loop to use for a caller already running on ``current``.
38
+
39
+ Blocking a thread for a task on the loop that thread runs would deadlock, since nothing is left to run the task. Override to hand such a caller a different loop; this default ignores ``current`` and is therefore only safe for callers that never run on the returned loop.
40
+
41
+ Args:
42
+ current (asyncio.AbstractEventLoop | None): The loop running in the calling thread, or None outside one.
43
+
44
+ Returns:
45
+ asyncio.AbstractEventLoop: The loop to submit to.
46
+ """
47
+ return self.loop
@@ -56,6 +56,7 @@ from .utils import lazy, lazy_import
56
56
  if typing.TYPE_CHECKING:
57
57
  from .auth import AuthConfigurator
58
58
  from .bases.file_manager import AbstractFileManager, AbstractImageManager
59
+ from .bases.loop_manager import AbstractLoopManager
59
60
  from .caches import Cache
60
61
  from .fastapi_react_toolkit import FastAPIReactToolkit
61
62
  from .security.sqla.models import User
@@ -117,6 +118,10 @@ class Globals:
117
118
  """
118
119
  The cache object used by caching features such as `AUTH_USER_CACHE_TTL`. Defaults to `Cache` from `fastapi_rtk.caches.cache`, which keeps entries in an `InMemoryCacheStorage`.
119
120
  """
121
+ loop_manager: "AbstractLoopManager"
122
+ """
123
+ The object supplying the event loop `smart_run_sync` / `safe_call_sync` submit to when given no loop. Defaults to the `LOOP_MANAGER` config key, or `LoopManager` from `fastapi_rtk.loop_managers.loop_manager`, which runs one long-lived loop per nesting depth, each on its own daemon thread.
124
+ """
120
125
  user_cache: "UserCache"
121
126
  """
122
127
  The object that stores and rebuilds authenticated users in `g.cache`. Defaults to the `AUTH_USER_CACHE` config key, or `UserCache` from `fastapi_rtk.security.sqla.user_cache`.
@@ -341,6 +346,15 @@ g.set_default(
341
346
  )
342
347
  ),
343
348
  )
349
+ g.set_default(
350
+ "loop_manager",
351
+ lazy(
352
+ lambda: (
353
+ lazy_import("fastapi_rtk.setting", lambda mod: mod.Setting.LOOP_MANAGER)()
354
+ or lazy_import("fastapi_rtk.loop_managers", lambda mod: mod.LoopManager())()
355
+ )
356
+ ),
357
+ )
344
358
  g.config.add_callback(basic_callback)
345
359
 
346
360
  # Local Proxies
@@ -0,0 +1,3 @@
1
+ from .loop_manager import *
2
+
3
+ __all__ = ["LoopManager"]
@@ -0,0 +1,232 @@
1
+ import asyncio
2
+ import os
3
+ import threading
4
+ import typing
5
+ import weakref
6
+
7
+ from ..bases.loop_manager import AbstractLoopManager
8
+ from ..const import logger as _root_logger
9
+
10
+ __all__ = ["LoopManager"]
11
+
12
+ logger = _root_logger.getChild("loop_manager")
13
+
14
+
15
+ class LoopManager(AbstractLoopManager):
16
+ """
17
+ Runs long-lived event loops on daemon threads, one per nesting depth.
18
+
19
+ Reusing loops keeps loop-bound resources (asyncpg pools, redis asyncio clients, `asyncio.Lock`) valid across calls and stops every synchronous call from leaving a closed loop behind. Depth 0 serves ordinary callers; a caller already running on the depth-N loop gets depth N+1, so a synchronous event handler whose session commits inside a coroutine on one loop can still submit work without blocking that loop against itself. Loops are created on first need, restarted when one stops on its own, and forgotten in a forked child (Celery's prefork pool), whose inherited loops have no thread to drain them.
20
+
21
+ Attributes:
22
+ thread_name (str): Prefix of the thread names, suffixed with the depth.
23
+ """
24
+
25
+ thread_name = "fastapi-rtk-loop"
26
+
27
+ _managers: typing.ClassVar["weakref.WeakSet[LoopManager]"] = weakref.WeakSet()
28
+ """
29
+ Every live manager, so the fork handler can reset each one in the child. Weakly held, so a manager nobody uses is collected instead of being reset on every later fork.
30
+ """
31
+
32
+ def __init__(self):
33
+ self._loops: list[_Loop] = []
34
+ self._lock = threading.Lock()
35
+ self._managers.add(self)
36
+
37
+ @property
38
+ def loop(self) -> asyncio.AbstractEventLoop:
39
+ """
40
+ The loop ordinary callers submit to.
41
+
42
+ Returns:
43
+ asyncio.AbstractEventLoop: The depth-0 loop, started if it was not running yet.
44
+ """
45
+ return self._loop_at(0)
46
+
47
+ def loop_for(
48
+ self, current: asyncio.AbstractEventLoop | None
49
+ ) -> asyncio.AbstractEventLoop:
50
+ """
51
+ The loop to use for a caller running on ``current``.
52
+
53
+ Args:
54
+ current (asyncio.AbstractEventLoop | None): The loop running in the calling thread, or None outside one.
55
+
56
+ Returns:
57
+ asyncio.AbstractEventLoop: The next depth down when ``current`` is one of this manager's loops, else the depth-0 loop.
58
+ """
59
+ for depth, entry in enumerate(self._loops):
60
+ if entry.loop is current:
61
+ return self._loop_at(depth + 1)
62
+ return self._loop_at(0)
63
+
64
+ def shutdown(self) -> None:
65
+ """
66
+ Stop every loop this manager started and let their threads finish.
67
+
68
+ Coroutines still running are cancelled first, so a caller blocked on one raises `concurrent.futures.CancelledError` instead of waiting forever on a loop that stopped under it.
69
+
70
+ Returns:
71
+ None: The next `loop` access builds a fresh loop.
72
+ """
73
+ with self._lock:
74
+ entries, self._loops = self._loops, []
75
+ for entry in entries:
76
+ entry.stopped.set()
77
+ _cancel_and_stop(entry.loop)
78
+
79
+ def _loop_at(self, depth: int) -> asyncio.AbstractEventLoop:
80
+ """
81
+ Return the loop for one nesting depth, creating loops up to it.
82
+
83
+ Args:
84
+ depth (int): The nesting depth to serve.
85
+
86
+ Returns:
87
+ asyncio.AbstractEventLoop: The running loop held at that depth.
88
+ """
89
+ entries = self._loops
90
+ if depth < len(entries) and entries[depth].serving:
91
+ return entries[depth].loop
92
+ with self._lock:
93
+ while len(self._loops) <= depth:
94
+ self._loops.append(self._start_loop(len(self._loops)))
95
+ if not self._loops[depth].serving:
96
+ self._loops[depth] = self._start_loop(depth)
97
+ return self._loops[depth].loop
98
+
99
+ def _start_loop(self, depth: int) -> "_Loop":
100
+ """
101
+ Start a loop on its own daemon thread.
102
+
103
+ Args:
104
+ depth (int): The nesting depth the loop serves, used in the thread name.
105
+
106
+ Returns:
107
+ _Loop: The loop now running on that thread, with the thread and its stop flag.
108
+ """
109
+ loop = asyncio.new_event_loop()
110
+ stopped = threading.Event()
111
+ thread = threading.Thread(
112
+ target=_serve,
113
+ args=(loop, stopped),
114
+ name=f"{self.thread_name}-{depth}",
115
+ daemon=True,
116
+ )
117
+ thread.start()
118
+ return _Loop(loop, thread, stopped)
119
+
120
+ @classmethod
121
+ def _reset_all_after_fork(cls) -> None:
122
+ """
123
+ Reset every live manager in a freshly forked child.
124
+
125
+ Runs in the child's only thread before anything else can reach a manager, so nothing waits on a lock the fork inherited locked, and no child ever submits to a parent loop whose thread it did not inherit.
126
+
127
+ Returns:
128
+ None: Each manager starts fresh loops on its next use.
129
+ """
130
+ for manager in cls._managers:
131
+ manager._reset_after_fork()
132
+
133
+ def _reset_after_fork(self) -> None:
134
+ """
135
+ Forget the parent's loops, whose threads do not exist in this child.
136
+
137
+ The lock is replaced rather than taken: only the forking thread survives a fork, so a lock another thread held at that moment is inherited locked with nobody left to release it.
138
+
139
+ Returns:
140
+ None: The next `loop` access starts fresh loops.
141
+ """
142
+ self._loops = []
143
+ self._lock = threading.Lock()
144
+
145
+
146
+ class _Loop(typing.NamedTuple):
147
+ """
148
+ One loop a `LoopManager` holds, with the thread serving it.
149
+
150
+ Attributes:
151
+ loop (asyncio.AbstractEventLoop): The loop coroutines are submitted to.
152
+ thread (threading.Thread): The daemon thread running it.
153
+ stopped (threading.Event): Set by `LoopManager.shutdown` to let that thread finish.
154
+ """
155
+
156
+ loop: asyncio.AbstractEventLoop
157
+ thread: threading.Thread
158
+ stopped: threading.Event
159
+
160
+ @property
161
+ def serving(self) -> bool:
162
+ """
163
+ Whether this loop can still accept coroutines.
164
+
165
+ Returns:
166
+ bool: True while its thread is alive and the loop is open.
167
+ """
168
+ return self.thread.is_alive() and not self.loop.is_closed()
169
+
170
+
171
+ def _serve(loop: asyncio.AbstractEventLoop, stopped: threading.Event) -> None:
172
+ """
173
+ Run one loop until its manager stops it, restarting it if it stops on its own.
174
+
175
+ A `KeyboardInterrupt` or `SystemExit` raised inside a submitted coroutine propagates out of `run_forever`, and outside code can call `loop.stop()`. Either would otherwise end this thread while the loop still looks usable, and every later submission to it would block forever. A loop closed from outside cannot be restarted, so the thread gives it up and the manager builds a new one on next use.
176
+
177
+ Args:
178
+ loop (asyncio.AbstractEventLoop): The loop to run.
179
+ stopped (threading.Event): Set when the loop should be given up rather than restarted.
180
+
181
+ Returns:
182
+ None: The loop is closed once this returns.
183
+ """
184
+ asyncio.set_event_loop(loop)
185
+ try:
186
+ while not stopped.is_set() and not loop.is_closed():
187
+ try:
188
+ loop.run_forever()
189
+ except BaseException:
190
+ logger.exception("Event loop %s crashed, restarting it", loop)
191
+ finally:
192
+ loop.close()
193
+
194
+
195
+ def _cancel_and_stop(loop: asyncio.AbstractEventLoop) -> None:
196
+ """
197
+ Cancel everything running on a loop, then stop it.
198
+
199
+ Args:
200
+ loop (asyncio.AbstractEventLoop): The loop to drain, running in another thread.
201
+
202
+ Returns:
203
+ None: The loop stops once the cancelled coroutines have finished.
204
+ """
205
+ if loop.is_closed():
206
+ return
207
+ try:
208
+ asyncio.run_coroutine_threadsafe(_cancel_all(), loop)
209
+ except RuntimeError:
210
+ logger.debug("Event loop %s was already gone when stopping it", loop)
211
+
212
+
213
+ async def _cancel_all() -> None:
214
+ """
215
+ Cancel every other task on this loop, wait for them, then stop the loop.
216
+
217
+ Returns:
218
+ None: The loop is stopped when this returns.
219
+ """
220
+ loop = asyncio.get_running_loop()
221
+ tasks = [
222
+ task for task in asyncio.all_tasks(loop) if task is not asyncio.current_task()
223
+ ]
224
+ for task in tasks:
225
+ task.cancel()
226
+ if tasks:
227
+ await asyncio.gather(*tasks, return_exceptions=True)
228
+ loop.stop()
229
+
230
+
231
+ if hasattr(os, "register_at_fork"):
232
+ os.register_at_fork(after_in_child=LoopManager._reset_all_after_fork)
@@ -21,6 +21,7 @@ from .globals import g
21
21
 
22
22
  if typing.TYPE_CHECKING:
23
23
  from .bases.file_manager import AbstractFileManager, AbstractImageManager
24
+ from .bases.loop_manager import AbstractLoopManager
24
25
  from .bases.user_cache import AbstractUserSerializer
25
26
  from .caches import Cache
26
27
  from .security.sqla.user_cache import UserCache
@@ -151,6 +152,13 @@ class _Setting:
151
152
  """
152
153
  return g.config.get("CACHE")
153
154
 
155
+ @property
156
+ def LOOP_MANAGER(self) -> "AbstractLoopManager | None":
157
+ """
158
+ The manager supplying the event loop `smart_run_sync` / `safe_call_sync` submit to when given no loop.
159
+ """
160
+ return g.config.get("LOOP_MANAGER")
161
+
154
162
  @property
155
163
  def FILE_ALLOWED_EXTENSIONS(self) -> list[str] | None:
156
164
  return g.config.get("FILE_ALLOWED_EXTENSIONS")
@@ -59,7 +59,7 @@ def smart_run_sync(
59
59
  Args:
60
60
  func (collections.abc.Callable[..., T | collections.abc.Awaitable[T]]): The function to be executed.
61
61
  *args: Positional arguments to be passed to the function.
62
- loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit an async ``func`` to. Defaults to None, which runs it on a fresh loop in a thread pool.
62
+ loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit an async ``func`` to. Defaults to None, which uses the loop `g.loop_manager` supplies.
63
63
  **kwargs: Keyword arguments to be passed to the function.
64
64
 
65
65
  Returns:
@@ -98,9 +98,9 @@ def safe_call_sync(
98
98
 
99
99
  Args:
100
100
  result_or_coro (T | collections.abc.Coroutine[typing.Any, typing.Any, T]): The result or coroutine to be executed.
101
- max_workers (int, optional): The maximum number of workers in the thread pool. Defaults to 1.
102
- loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which runs the coroutine on a fresh loop in a thread pool.
103
- **kwargs: Additional keyword arguments to pass to the `ThreadPoolExecutor`.
101
+ max_workers (int, optional): Unused, kept so existing callers still type-check. Defaults to 1.
102
+ loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which uses the loop `g.loop_manager` supplies.
103
+ **kwargs: Additional keyword arguments forwarded to `run_coroutine_in_threadpool`, which ignores them.
104
104
 
105
105
  Returns:
106
106
  T: The result of the function call or coroutine.
@@ -119,31 +119,28 @@ def run_coroutine_in_threadpool(
119
119
  **kwargs,
120
120
  ):
121
121
  """
122
- Run a coroutine in a thread pool executor.
122
+ Run a coroutine on a long-lived event loop from a synchronous caller.
123
+
124
+ ``loop`` is submitted to when given; otherwise ``g.loop_manager`` picks a
125
+ long-lived loop, avoiding the one the calling thread already runs. Reusing
126
+ loops keeps loop-bound resources (asyncpg pools, redis asyncio clients,
127
+ ``asyncio.Lock``) valid across calls and stops every call from leaving a
128
+ closed loop behind. The caller's context is copied so contextvars set
129
+ before the call are visible inside the coroutine.
123
130
 
124
- When ``loop`` is given, the coroutine is submitted to that already-running
125
- loop via ``asyncio.run_coroutine_threadsafe`` instead of a throwaway loop.
126
- This keeps loop-bound resources (asyncpg pools, redis asyncio clients,
127
- ``asyncio.Lock``) valid across calls; ``max_workers`` and ``**kwargs`` are
128
- then unused. The caller owns the loop and must run it in another thread.
131
+ Passing a ``loop`` the calling thread itself runs deadlocks, since blocking
132
+ that thread leaves nothing to run the task.
129
133
 
130
134
  Args:
131
135
  coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to run.
132
- max_workers (int, optional): The maximum number of workers in the thread pool. Defaults to 1.
133
- loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which runs the coroutine on a fresh loop in a thread pool.
134
- **kwargs: Additional keyword arguments to pass to the `ThreadPoolExecutor`.
136
+ max_workers (int, optional): Unused, kept so existing callers still type-check. Defaults to 1.
137
+ loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which uses the loop `g.loop_manager` supplies.
138
+ **kwargs: Unused, kept so existing callers still type-check.
135
139
 
136
140
  Returns:
137
141
  T: The result of the coroutine.
138
142
  """
139
- if loop is not None:
140
- return asyncio.run_coroutine_threadsafe(coro, loop).result()
141
- with concurrent.futures.ThreadPoolExecutor(
142
- max_workers=max_workers, **kwargs
143
- ) as executor:
144
- ctx = contextvars.copy_context()
145
- future = executor.submit(ctx.run, asyncio.run, coro)
146
- return future.result()
143
+ return _submit_to_loop(coro, loop if loop is not None else _managed_loop())
147
144
 
148
145
 
149
146
  def run_function_in_threadpool(
@@ -228,3 +225,77 @@ def parse_from_values_or_func(
228
225
  if callable(value_or_func):
229
226
  return value_or_func(*args, **kwargs)
230
227
  return value_or_func
228
+
229
+
230
+ def _managed_loop() -> asyncio.AbstractEventLoop:
231
+ """
232
+ Return the loop the configured loop manager picks for this caller.
233
+
234
+ The loop already running in this thread is passed along, so the manager can
235
+ hand back a different one: blocking a thread for a task on its own loop
236
+ would deadlock, since nothing is left to run the task. ``g`` is imported
237
+ here rather than at module scope, because ``globals`` imports this package
238
+ while loading.
239
+
240
+ Returns:
241
+ asyncio.AbstractEventLoop: The loop `g.loop_manager` supplies for this caller.
242
+ """
243
+ from ..globals import g
244
+
245
+ return g.loop_manager.loop_for(_running_loop())
246
+
247
+
248
+ def _submit_to_loop(
249
+ coro: collections.abc.Coroutine[typing.Any, typing.Any, T],
250
+ loop: asyncio.AbstractEventLoop,
251
+ ) -> T:
252
+ """
253
+ Run a coroutine on another thread's loop and block for its result.
254
+
255
+ The coroutine runs as a task on that loop, so it starts with the loop
256
+ thread's contextvars rather than the caller's. The caller's values are
257
+ copied in first, inside the task's own context, so nothing leaks between
258
+ concurrent callers.
259
+
260
+ Args:
261
+ coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to run.
262
+ loop (asyncio.AbstractEventLoop): The loop to run it on, running in another thread.
263
+
264
+ Returns:
265
+ T: The result of the coroutine.
266
+ """
267
+ return asyncio.run_coroutine_threadsafe(
268
+ _with_context(contextvars.copy_context(), coro), loop
269
+ ).result()
270
+
271
+
272
+ def _running_loop() -> asyncio.AbstractEventLoop | None:
273
+ """
274
+ Return the loop running in this thread, if any.
275
+
276
+ Returns:
277
+ asyncio.AbstractEventLoop | None: The running loop, or None outside one.
278
+ """
279
+ try:
280
+ return asyncio.get_running_loop()
281
+ except RuntimeError:
282
+ return None
283
+
284
+
285
+ async def _with_context(
286
+ context: contextvars.Context,
287
+ coro: collections.abc.Coroutine[typing.Any, typing.Any, T],
288
+ ) -> T:
289
+ """
290
+ Await a coroutine with another context's variables applied.
291
+
292
+ Args:
293
+ context (contextvars.Context): The context whose variables to apply.
294
+ coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to await.
295
+
296
+ Returns:
297
+ T: The result of the coroutine.
298
+ """
299
+ for variable, value in context.items():
300
+ variable.set(value)
301
+ return await coro