fastapi-rtk 2.17.0__tar.gz → 2.18.1__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 (339) hide show
  1. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/PKG-INFO +1 -1
  2. fastapi_rtk-2.18.1/fastapi_rtk/_version.py +1 -0
  3. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/guide/backend.md +5 -3
  4. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/model_rest_api.py +10 -16
  5. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_download_service.py +31 -17
  6. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/filters.py +44 -10
  7. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/interface.py +8 -0
  8. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/const.py +1 -0
  9. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/setting.py +18 -0
  10. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/api/test_model_rest_api.py +30 -6
  11. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/model_rest_api/test_service_delegators.py +0 -1
  12. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/conftest.py +27 -3
  13. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_filters.py +70 -0
  14. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_interface.py +39 -0
  15. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_const.py +1 -0
  16. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_setting.py +19 -0
  17. fastapi_rtk-2.17.0/fastapi_rtk/_version.py +0 -1
  18. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/.gitignore +0 -0
  19. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/LICENSE +0 -0
  20. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/README.md +0 -0
  21. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/__init__.py +0 -0
  22. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/adapters/agents-md/AGENTS.md +0 -0
  23. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/adapters/claude/skill.frontmatter +0 -0
  24. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/guide/frontend.md +0 -0
  25. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/guide/legacy.md +0 -0
  26. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/agent_docs/guide/overview.md +0 -0
  27. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/__init__.py +0 -0
  28. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/base_api.py +0 -0
  29. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
  30. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/params.py +0 -0
  31. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/__init__.py +0 -0
  32. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_add_service.py +0 -0
  33. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_bulk_service.py +0 -0
  34. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_delete_service.py +0 -0
  35. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_edit_service.py +0 -0
  36. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_file_service.py +0 -0
  37. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_info_service.py +0 -0
  38. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_list_service.py +0 -0
  39. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_schema_service.py +0 -0
  40. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/api/model_rest_api/services/_show_service.py +0 -0
  41. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/apis/__init__.py +0 -0
  42. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/apis/info.py +0 -0
  43. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/apis/license.py +0 -0
  44. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/__init__.py +0 -0
  45. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/auth.py +0 -0
  46. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  47. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  48. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  49. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/ldap.py +0 -0
  50. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  51. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  52. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  53. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/strategies/config.py +0 -0
  54. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/strategies/db.py +0 -0
  55. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  56. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/__init__.py +0 -0
  57. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/__init__.py +0 -0
  58. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/column.py +0 -0
  59. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/db.py +0 -0
  60. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  61. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/filters.py +0 -0
  62. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/interface.py +0 -0
  63. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/model.py +0 -0
  64. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/generic/session.py +0 -0
  65. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  66. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/column.py +0 -0
  67. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/db.py +0 -0
  68. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  69. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  70. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  71. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_service.py +0 -0
  72. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_service.py +0 -0
  73. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
  74. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/_processing_service.py +0 -0
  75. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  76. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  77. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  78. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  79. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  80. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/model.py +0 -0
  81. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/backends/sqla/session.py +0 -0
  82. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/__init__.py +0 -0
  83. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/cache.py +0 -0
  84. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/db.py +0 -0
  85. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/file_manager.py +0 -0
  86. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/filter.py +0 -0
  87. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/interface.py +0 -0
  88. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/loop_manager.py +0 -0
  89. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/model.py +0 -0
  90. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/session.py +0 -0
  91. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/bases/user_cache.py +0 -0
  92. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/caches/__init__.py +0 -0
  93. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/caches/cache.py +0 -0
  94. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/caches/serializers.py +0 -0
  95. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/caches/storages.py +0 -0
  96. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/__init__.py +0 -0
  97. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/cli.py +0 -0
  98. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/__init__.py +0 -0
  99. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
  100. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/__init__.py +0 -0
  101. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/adapters/__init__.py +0 -0
  102. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/adapters/agents_md.py +0 -0
  103. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/adapters/claude.py +0 -0
  104. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/base.py +0 -0
  105. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/agent/docs.py +0 -0
  106. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  107. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  108. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  109. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  110. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  111. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  112. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  113. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  114. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  115. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/export.py +0 -0
  116. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/security.py +0 -0
  117. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/commands/translate.py +0 -0
  118. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/const.py +0 -0
  119. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/decorators.py +0 -0
  120. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/types.py +0 -0
  121. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/cli/utils.py +0 -0
  122. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/config.py +0 -0
  123. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/db.py +0 -0
  124. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/decorators.py +0 -0
  125. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/dependencies.py +0 -0
  126. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/exceptions.py +0 -0
  127. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  128. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/file_managers/__init__.py +0 -0
  129. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/file_managers/file_manager.py +0 -0
  130. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/file_managers/image_manager.py +0 -0
  131. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  132. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  133. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/filters.py +0 -0
  134. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/globals.py +0 -0
  135. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/__init__.py +0 -0
  136. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/babel/__init__.py +0 -0
  137. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/babel/cli.py +0 -0
  138. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/babel/config.py +0 -0
  139. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/babel.cfg +0 -0
  140. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/lazy_text.py +0 -0
  141. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/messages.pot +0 -0
  142. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  143. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  144. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  145. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  146. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/loop_managers/__init__.py +0 -0
  147. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/loop_managers/loop_manager.py +0 -0
  148. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/manager.py +0 -0
  149. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/middlewares.py +0 -0
  150. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/mixins.py +0 -0
  151. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/models.py +0 -0
  152. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/py.typed +0 -0
  153. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/registry.py +0 -0
  154. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/routers.py +0 -0
  155. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/schemas.py +0 -0
  156. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/__init__.py +0 -0
  157. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/__init__.py +0 -0
  158. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/apis.py +0 -0
  159. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/models.py +0 -0
  160. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
  161. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_builtin_role_service.py +0 -0
  162. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_entities.py +0 -0
  163. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
  164. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
  165. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
  166. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_permission_service.py +0 -0
  167. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_results.py +0 -0
  168. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_role_service.py +0 -0
  169. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/_user_service.py +0 -0
  170. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/security_manager/security_manager.py +0 -0
  171. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/security/sqla/user_cache.py +0 -0
  172. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/types.py +0 -0
  173. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/__init__.py +0 -0
  174. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/async_task_runner.py +0 -0
  175. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/class_factory.py +0 -0
  176. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  177. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/deep_merge.py +0 -0
  178. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/extender_mixin.py +0 -0
  179. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  180. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/formatter.py +0 -0
  181. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/hooks.py +0 -0
  182. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/lazy.py +0 -0
  183. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/merge_schema.py +0 -0
  184. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  185. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/pydantic.py +0 -0
  186. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/run_utils.py +0 -0
  187. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/self_dependencies.py +0 -0
  188. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  189. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/sqla.py +0 -0
  190. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/timezone.py +0 -0
  191. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/update_signature.py +0 -0
  192. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  193. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/fastapi_rtk/version.py +0 -0
  194. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/pyproject.toml +0 -0
  195. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/conftest.py +0 -0
  196. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/docker-compose.gis.yml +0 -0
  197. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/__init__.py +0 -0
  198. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/api/__init__.py +0 -0
  199. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/api/test_add_edit_relation_response.py +0 -0
  200. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/apis/__init__.py +0 -0
  201. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/apis/test_info_callable_schema.py +0 -0
  202. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/apis/test_info_contract.py +0 -0
  203. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/auth/__init__.py +0 -0
  204. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/auth/test_auth_flow.py +0 -0
  205. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/auth/test_oauth.py +0 -0
  206. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/conftest.py +0 -0
  207. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/file_managers/__init__.py +0 -0
  208. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/file_managers/test_file_routes.py +0 -0
  209. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/__init__.py +0 -0
  210. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/__init__.py +0 -0
  211. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/conftest.py +0 -0
  212. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/test_permission_service.py +0 -0
  213. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/test_role_service.py +0 -0
  214. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/test_security_manager.py +0 -0
  215. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/security/sqla/test_user_service.py +0 -0
  216. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/integration/test_app_smoke.py +0 -0
  217. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/__init__.py +0 -0
  218. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/__init__.py +0 -0
  219. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/conftest.py +0 -0
  220. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/model_rest_api/__init__.py +0 -0
  221. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/model_rest_api/test_columns.py +0 -0
  222. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
  223. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/model_rest_api/test_init.py +0 -0
  224. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/api/test_base_api.py +0 -0
  225. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/apis/__init__.py +0 -0
  226. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/apis/test_info.py +0 -0
  227. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/apis/test_license.py +0 -0
  228. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/__init__.py +0 -0
  229. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/hashers/__init__.py +0 -0
  230. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
  231. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/hashers/test_scrypt.py +0 -0
  232. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/password_helpers/__init__.py +0 -0
  233. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/password_helpers/test_fab.py +0 -0
  234. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/strategies/__init__.py +0 -0
  235. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/strategies/test_config.py +0 -0
  236. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/strategies/test_db.py +0 -0
  237. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/strategies/test_jwt.py +0 -0
  238. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/auth/test_auth.py +0 -0
  239. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/__init__.py +0 -0
  240. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/__init__.py +0 -0
  241. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_column.py +0 -0
  242. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_db.py +0 -0
  243. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_exceptions.py +0 -0
  244. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_filters.py +0 -0
  245. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_interface.py +0 -0
  246. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_model.py +0 -0
  247. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/generic/test_session.py +0 -0
  248. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/__init__.py +0 -0
  249. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_audit.py +0 -0
  250. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_audit_service_delegators.py +0 -0
  251. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_audit_types.py +0 -0
  252. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_column.py +0 -0
  253. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_db.py +0 -0
  254. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
  255. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_model.py +0 -0
  256. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/backends/sqla/test_session.py +0 -0
  257. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/__init__.py +0 -0
  258. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_db.py +0 -0
  259. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_file_manager.py +0 -0
  260. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_filter.py +0 -0
  261. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_interface.py +0 -0
  262. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_model.py +0 -0
  263. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/bases/test_session.py +0 -0
  264. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/__init__.py +0 -0
  265. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/__init__.py +0 -0
  266. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/conftest.py +0 -0
  267. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/test_db.py +0 -0
  268. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/test_export.py +0 -0
  269. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/test_security.py +0 -0
  270. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/test_security_crypto.py +0 -0
  271. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/commands/test_translate.py +0 -0
  272. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/conftest.py +0 -0
  273. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/test_cli.py +0 -0
  274. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/test_decorators.py +0 -0
  275. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/test_types.py +0 -0
  276. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/cli/test_utils.py +0 -0
  277. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/__init__.py +0 -0
  278. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/conftest.py +0 -0
  279. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/test_file_manager.py +0 -0
  280. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/test_image_manager.py +0 -0
  281. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
  282. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
  283. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/lang/__init__.py +0 -0
  284. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/lang/test_babel_cli.py +0 -0
  285. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/lang/test_babel_config.py +0 -0
  286. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/lang/test_lazy_text.py +0 -0
  287. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/__init__.py +0 -0
  288. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/__init__.py +0 -0
  289. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/conftest.py +0 -0
  290. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/__init__.py +0 -0
  291. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_builtin_role_service.py +0 -0
  292. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_entities.py +0 -0
  293. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_permission_service.py +0 -0
  294. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_results.py +0 -0
  295. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_role_service.py +0 -0
  296. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/security_manager/test_user_service.py +0 -0
  297. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/test_apis.py +0 -0
  298. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/test_models.py +0 -0
  299. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/security/sqla/test_security_manager.py +0 -0
  300. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_caches.py +0 -0
  301. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_config.py +0 -0
  302. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_db.py +0 -0
  303. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_decorators.py +0 -0
  304. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_dependencies.py +0 -0
  305. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_exceptions.py +0 -0
  306. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_fastapi_react_toolkit.py +0 -0
  307. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_filters.py +0 -0
  308. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_globals.py +0 -0
  309. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_manager.py +0 -0
  310. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_middlewares.py +0 -0
  311. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_mixins.py +0 -0
  312. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_models.py +0 -0
  313. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_registry.py +0 -0
  314. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_routers.py +0 -0
  315. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_schemas.py +0 -0
  316. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_types.py +0 -0
  317. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_user_cache.py +0 -0
  318. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/test_version.py +0 -0
  319. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/__init__.py +0 -0
  320. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_async_task_runner.py +0 -0
  321. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_class_factory.py +0 -0
  322. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_csv_json_converter.py +0 -0
  323. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_deep_merge.py +0 -0
  324. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_extender_mixin.py +0 -0
  325. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
  326. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_formatter.py +0 -0
  327. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_hooks.py +0 -0
  328. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_lazy.py +0 -0
  329. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_merge_schema.py +0 -0
  330. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
  331. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_pydantic.py +0 -0
  332. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_run_utils.py +0 -0
  333. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_self_dependencies.py +0 -0
  334. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_smartdefaultdict.py +0 -0
  335. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_sqla.py +0 -0
  336. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_timezone.py +0 -0
  337. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_update_signature.py +0 -0
  338. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/tests/unit/utils/test_use_default_when_none.py +0 -0
  339. {fastapi_rtk-2.17.0 → fastapi_rtk-2.18.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-rtk
3
- Version: 2.17.0
3
+ Version: 2.18.1
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
@@ -0,0 +1 @@
1
+ __version__ = "2.18.1"
@@ -551,7 +551,7 @@ The task runs later, possibly after the loop has advanced or the enclosing funct
551
551
 
552
552
  ### Sizing the `/download` stream
553
553
 
554
- `/download` exports every row matching the query, so it ignores `page` and `page_size` from the request; they reach the handler and are overwritten. It reads the rows in chunks, and `download_page_size` on the Api decides how large a chunk is (since the first release after v2.15.0). The default is an async method that counts the matching rows and takes a tenth of them, floored at the `API_DOWNLOAD_MIN_PAGE_SIZE` config key (default `100`).
554
+ `/download` exports every row matching the query, so it ignores `page` and `page_size` from the request; they reach the handler and are overwritten. It reads the rows in chunks, and the `download_page_size` attribute on the Api decides how large a chunk is (since the first release after v2.15.0). It is either an int (a fixed chunk) or a resolver `(api, session, params) -> int`. It defaults to the `API_DOWNLOAD_PAGE_SIZE` config key, which is itself the built-in resolver: it counts the matching rows and takes a tenth of them, floored at the `API_DOWNLOAD_MIN_PAGE_SIZE` config key (default `100`) and capped at `API_DOWNLOAD_MAX_PAGE_SIZE` (default `1000`) so a large table cannot size the chunk into an out-of-memory read.
555
555
 
556
556
  Set it to an int for a fixed chunk, which also skips the count query the default runs:
557
557
 
@@ -561,16 +561,18 @@ class ReadingApi(ModelRestApi):
561
561
  download_page_size = 5000
562
562
  ```
563
563
 
564
- Override the method when the size depends on the query. It receives the session and the `DBQueryParams` the export runs with. Sync and async both work, resolved through `safe_call`, so a sync one runs inline rather than on a worker thread:
564
+ Set it to a resolver when the size depends on the query. It receives the Api, the session, and the `DBQueryParams` the export runs with. Sync and async both work, resolved through `safe_call`, so a sync one runs inline rather than on a worker thread:
565
565
 
566
566
  ```python
567
567
  class ReadingApi(ModelRestApi):
568
568
  datamodel = SQLAInterface(Reading)
569
569
 
570
- def download_page_size(self, session, params):
570
+ def download_page_size(api, session, params):
571
571
  return 1000 if params.get("filters") else 250
572
572
  ```
573
573
 
574
+ Set the `API_DOWNLOAD_PAGE_SIZE` config key (an int or a `(api, session, params)` resolver) to change the default for every Api at once.
575
+
574
576
  The chunk size is a read size, not a limit: every matching row is exported whatever it is set to.
575
577
 
576
578
  ## 14. Permissions, roles, and auth
@@ -15,7 +15,7 @@ plus one shared column / schema / body service:
15
15
  - ``put`` route + ``pre_update_merge`` / ``pre_update`` / ``post_update`` live on ``ModelRestApi``, backed by :class:`~.services.EditService` (``edit_service``).
16
16
  - ``delete`` route + ``pre_delete`` / ``post_delete`` live on ``ModelRestApi``, backed by :class:`~.services.DeleteService` (``delete_service``).
17
17
  - ``bulk`` route + ``bulk_handler`` stub live on ``ModelRestApi``, backed by :class:`~.services.BulkService` (``bulk_service``).
18
- - ``download`` route + ``download_page_size`` heuristic + CSV ``_export_data`` / ``_import_data`` helpers live on ``ModelRestApi``, backed by :class:`~.services.DownloadService` (``download_service``).
18
+ - ``download`` route + CSV ``_export_data`` / ``_import_data`` helpers live on ``ModelRestApi``, backed by :class:`~.services.DownloadService` (``download_service``); the ``download_page_size`` config attribute holds an int or a ``(api, session, params)`` resolver, defaulting to ``default_download_page_size``.
19
19
  - Column / schema / body delegators (``_process_body``, ``_generate_jsonforms_schema``, ...) live on ``ModelRestApi``, backed by :class:`~.services.SchemaService` (``schema_service``).
20
20
 
21
21
  Class attributes (datamodel, ``*_columns``, ``*_schema``, lifecycle hooks)
@@ -87,6 +87,9 @@ from .services import (
87
87
  ShowService,
88
88
  )
89
89
 
90
+ if typing.TYPE_CHECKING:
91
+ from .services._download_service import DownloadPageSizeResolver
92
+
90
93
  __all__ = ["ModelRestApi"]
91
94
 
92
95
 
@@ -166,6 +169,12 @@ class ModelRestApi(BaseApi):
166
169
  """
167
170
  The maximum page size for the related fields in add_columns, edit_columns, and search_columns properties.
168
171
  """
172
+ download_page_size: "int | DownloadPageSizeResolver" = lazy(
173
+ lambda: Setting.API_DOWNLOAD_PAGE_SIZE
174
+ )
175
+ """
176
+ How many rows the download stream reads per chunk. An int fixes the chunk size and skips the count query; a resolver `(api, session, params) -> int` computes it per request. Defaults to the `API_DOWNLOAD_PAGE_SIZE` config key, itself the built-in heuristic (a tenth of the matching rows, bounded by `API_DOWNLOAD_MIN_PAGE_SIZE` / `API_DOWNLOAD_MAX_PAGE_SIZE`).
177
+ """
169
178
  routes: list[AVAILABLE_ROUTES] = lazy(
170
179
  lambda self: [
171
180
  x
@@ -1944,21 +1953,6 @@ class ModelRestApi(BaseApi):
1944
1953
  q, session, export_mode, delimiter, quotechar, label, check_validity
1945
1954
  )
1946
1955
 
1947
- async def download_page_size(self, session: SQLASession, params: DBQueryParams):
1948
- """
1949
- Decide how many rows the download stream reads per chunk.
1950
-
1951
- Set the attribute to an int instead to fix the chunk size and skip the count query this default runs.
1952
-
1953
- Args:
1954
- session (SQLASession): A database scoped session.
1955
- params (DBQueryParams): The query parameters the export runs with.
1956
-
1957
- Returns:
1958
- int: The number of rows to read per chunk.
1959
- """
1960
- return await self.download_service.download_page_size(session, params)
1961
-
1962
1956
  def _export_data(
1963
1957
  self,
1964
1958
  data: list[Model],
@@ -7,6 +7,7 @@ Reachable as ``self.download_service`` on the manager. Holds the
7
7
  streamer, and the disabled ``import_data`` upload helper.
8
8
  """
9
9
 
10
+ import collections.abc
10
11
  import csv
11
12
  import typing
12
13
 
@@ -27,6 +28,12 @@ if typing.TYPE_CHECKING:
27
28
  from .. import ModelRestApi
28
29
 
29
30
 
31
+ DownloadPageSizeResolver = collections.abc.Callable[
32
+ ["ModelRestApi", SQLASession, DBQueryParams],
33
+ "int | collections.abc.Awaitable[int]",
34
+ ]
35
+
36
+
30
37
  class DownloadService:
31
38
  """
32
39
  Download-route logic shared by the download route.
@@ -103,7 +110,7 @@ class DownloadService:
103
110
  params = self.api._handle_query_params(q)
104
111
  page_size = self.api.download_page_size
105
112
  if callable(page_size):
106
- page_size = await safe_call(page_size(session, params))
113
+ page_size = await safe_call(page_size(self.api, session, params))
107
114
  params["page_size"] = page_size
108
115
  return fastapi.responses.StreamingResponse(
109
116
  self.api._export_data(
@@ -121,22 +128,6 @@ class DownloadService:
121
128
  },
122
129
  )
123
130
 
124
- async def download_page_size(self, session: SQLASession, params: DBQueryParams):
125
- """
126
- Decide how many rows the download stream reads per chunk.
127
-
128
- Set the attribute to an int instead to fix the chunk size and skip the count query this default runs.
129
-
130
- Args:
131
- session (SQLASession): A database scoped session.
132
- params (DBQueryParams): The query parameters the export runs with.
133
-
134
- Returns:
135
- int: The number of rows to read per chunk.
136
- """
137
- count = await smart_run(self.api.datamodel.count, session, params)
138
- return max(count // 10, Setting.API_DOWNLOAD_MIN_PAGE_SIZE)
139
-
140
131
  async def export_data(
141
132
  self,
142
133
  data: list[Model],
@@ -286,3 +277,26 @@ class DownloadService:
286
277
  await smart_run(self.api.datamodel.commit, session)
287
278
 
288
279
  return list_data
280
+
281
+
282
+ async def default_download_page_size(
283
+ api: "ModelRestApi", session: SQLASession, params: DBQueryParams
284
+ ) -> int:
285
+ """
286
+ Decide how many rows the download stream reads per chunk.
287
+
288
+ Takes a tenth of the matching rows, floored at `API_DOWNLOAD_MIN_PAGE_SIZE` and capped at `API_DOWNLOAD_MAX_PAGE_SIZE` so a large table cannot size the chunk into an out-of-memory read. Set `download_page_size` to an int to fix the chunk size and skip the count query this runs, or to another resolver with this signature to replace the heuristic.
289
+
290
+ Args:
291
+ api (ModelRestApi): The manager whose datamodel the export runs against.
292
+ session (SQLASession): A database scoped session.
293
+ params (DBQueryParams): The query parameters the export runs with.
294
+
295
+ Returns:
296
+ int: The number of rows to read per chunk.
297
+ """
298
+ count = await smart_run(api.datamodel.count, session, params)
299
+ return min(
300
+ max(count // 10, Setting.API_DOWNLOAD_MIN_PAGE_SIZE),
301
+ Setting.API_DOWNLOAD_MAX_PAGE_SIZE,
302
+ )
@@ -145,6 +145,30 @@ class BaseFilter(AbstractBaseFilter[sqlalchemy.Select[Model]]):
145
145
  )
146
146
  return value
147
147
 
148
+ def _upper_bound(self, value: typing.Any, original_value: typing.Any):
149
+ """
150
+ Extend an already-cast value to its inclusive upper bound.
151
+
152
+ A datetime cast from a date-only string reaches the end of its day
153
+ (23:59:59.999999); any other value is its own upper bound.
154
+
155
+ Args:
156
+ value (typing.Any): The value already cast by `_cast_value`.
157
+ original_value (typing.Any): The raw filter value before casting.
158
+
159
+ Returns:
160
+ typing.Any: The upper bound value.
161
+ """
162
+ if not isinstance(value, datetime.datetime) or not isinstance(
163
+ original_value, str
164
+ ):
165
+ return value
166
+ try:
167
+ datetime.date.fromisoformat(original_value)
168
+ except ValueError:
169
+ return value
170
+ return value.replace(hour=23, minute=59, second=59, microsecond=999999)
171
+
148
172
 
149
173
  class BaseFilterTextContains(BaseFilter):
150
174
  def _build_concat_expr(self, col_objs: list[sqlalchemy.ColumnElement]):
@@ -297,8 +321,12 @@ class FilterEqual(BaseFilter):
297
321
  arg_name = "eq"
298
322
 
299
323
  def apply(self, statement, col: str, value):
300
- col, value = self._cast_col_and_value(col, value)
301
- return statement.filter(col == value)
324
+ lower = self._cast_value(col, value)
325
+ upper = self._upper_bound(lower, value)
326
+ col_obj = self._cast_column(col, lower)
327
+ if lower == upper:
328
+ return statement.filter(col_obj == lower)
329
+ return statement.filter(col_obj.between(lower, upper))
302
330
 
303
331
 
304
332
  class FilterNotEqual(BaseFilter):
@@ -306,8 +334,12 @@ class FilterNotEqual(BaseFilter):
306
334
  arg_name = "neq"
307
335
 
308
336
  def apply(self, statement, col: str, value):
309
- col, value = self._cast_col_and_value(col, value)
310
- return statement.filter(col != value)
337
+ lower = self._cast_value(col, value)
338
+ upper = self._upper_bound(lower, value)
339
+ col_obj = self._cast_column(col, lower)
340
+ if lower == upper:
341
+ return statement.filter(col_obj != lower)
342
+ return statement.filter(~col_obj.between(lower, upper))
311
343
 
312
344
 
313
345
  class FilterStartsWith(BaseFilter):
@@ -396,8 +428,9 @@ class FilterSmallerEqual(BaseFilter):
396
428
  arg_name = "le"
397
429
 
398
430
  def apply(self, statement, col: str, value):
399
- col, value = self._cast_col_and_value(col, value)
400
- return statement.filter(col <= value)
431
+ upper = self._upper_bound(self._cast_value(col, value), value)
432
+ col_obj = self._cast_column(col, upper)
433
+ return statement.filter(col_obj <= upper)
401
434
 
402
435
 
403
436
  class FilterIn(BaseFilter):
@@ -427,12 +460,13 @@ class FilterBetween(BaseFilter):
427
460
  value,
428
461
  )
429
462
 
430
- value = [self._cast_value(col, v) for v in value]
431
- if value[0] is None or value[1] is None:
463
+ lower = self._cast_value(col, value[0])
464
+ upper = self._upper_bound(self._cast_value(col, value[1]), value[1])
465
+ if lower is None or upper is None:
432
466
  return statement
433
467
 
434
- col = self._cast_column(col, value)
435
- return statement.filter(col.between(value[0], value[1]))
468
+ col = self._cast_column(col, [lower, upper])
469
+ return statement.filter(col.between(lower, upper))
436
470
 
437
471
 
438
472
  class FilterRelationOneToOneOrManyToOneEqual(
@@ -157,6 +157,14 @@ class SQLAInterface(
157
157
  if not chunk:
158
158
  break
159
159
  yield chunk
160
+
161
+ # Expunge consumed rows one by one; expunge_all() swaps the identity
162
+ # map the streaming result still holds, killing mid-stream fetches.
163
+ def expunge_chunk() -> None:
164
+ for item in chunk:
165
+ session.expunge(item)
166
+
167
+ await smart_run(expunge_chunk)
160
168
  await safe_call(self.close(session))
161
169
 
162
170
  async def add(self, session, item, *, flush=True, commit=True, refresh=True):
@@ -73,6 +73,7 @@ DEFAULT_PUBLIC_ROLE = "Public"
73
73
  DEFAULT_API_PAGE_SIZE = 25
74
74
  DEFAULT_API_MAX_PAGE_SIZE = 25
75
75
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE = 100
76
+ DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE = 1000
76
77
  DEFAULT_TOKEN_URL = "/api/v1/auth/jwt/login"
77
78
  DEFAULT_SECRET = "SUPERSECRET"
78
79
  DEFAULT_COOKIE_NAME = "dataTactics"
@@ -2,6 +2,7 @@ import typing
2
2
 
3
3
  from .const import (
4
4
  DEFAULT_ADMIN_ROLE,
5
+ DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE,
5
6
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE,
6
7
  DEFAULT_API_MAX_PAGE_SIZE,
7
8
  DEFAULT_API_PAGE_SIZE,
@@ -21,6 +22,9 @@ from .exceptions import FastAPIReactToolkitException
21
22
  from .globals import g
22
23
 
23
24
  if typing.TYPE_CHECKING:
25
+ from .api.model_rest_api.services._download_service import (
26
+ DownloadPageSizeResolver,
27
+ )
24
28
  from .bases.file_manager import AbstractFileManager, AbstractImageManager
25
29
  from .bases.loop_manager import AbstractLoopManager
26
30
  from .bases.user_cache import AbstractUserSerializer
@@ -90,6 +94,20 @@ class _Setting:
90
94
  "API_DOWNLOAD_MIN_PAGE_SIZE", DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE
91
95
  )
92
96
 
97
+ @property
98
+ def API_DOWNLOAD_MAX_PAGE_SIZE(self) -> int:
99
+ return g.config.get(
100
+ "API_DOWNLOAD_MAX_PAGE_SIZE", DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE
101
+ )
102
+
103
+ @property
104
+ def API_DOWNLOAD_PAGE_SIZE(self) -> "int | DownloadPageSizeResolver":
105
+ from .api.model_rest_api.services._download_service import (
106
+ default_download_page_size,
107
+ )
108
+
109
+ return g.config.get("API_DOWNLOAD_PAGE_SIZE", default_download_page_size)
110
+
93
111
  @property
94
112
  def TEXT_FILTER_SEPARATOR(self) -> str:
95
113
  return g.config.get("TEXT_FILTER_SEPARATOR", ";")
@@ -15,7 +15,10 @@ import types
15
15
  import pytest
16
16
  import sqlalchemy
17
17
 
18
- from fastapi_rtk.const import DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE
18
+ from fastapi_rtk.const import (
19
+ DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE,
20
+ DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE,
21
+ )
19
22
  from fastapi_rtk.schemas import GeneralResponse
20
23
  from fastapi_rtk.utils import smart_run
21
24
 
@@ -713,6 +716,26 @@ class TestDownloadPageSize:
713
716
  assert response.status_code == 200
714
717
  assert streamed_params["page_size"] == 500
715
718
 
719
+ async def test_page_size_is_capped_at_maximum(
720
+ self,
721
+ client,
722
+ admin_token,
723
+ seeded_widgets,
724
+ widgets_api,
725
+ monkeypatch,
726
+ streamed_params,
727
+ ):
728
+ async def count(session, params=None):
729
+ return 200000
730
+
731
+ monkeypatch.setattr(widgets_api.datamodel, "count", count)
732
+ response = await client.get(
733
+ "/api/v1/widgets/download",
734
+ headers={"Authorization": f"Bearer {admin_token}"},
735
+ )
736
+ assert response.status_code == 200
737
+ assert streamed_params["page_size"] == DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE
738
+
716
739
  async def test_int_page_size_skips_the_count(
717
740
  self,
718
741
  client,
@@ -754,8 +777,8 @@ class TestDownloadPageSize:
754
777
  counted.append(params)
755
778
  return 5000
756
779
 
757
- def download_page_size(session, params):
758
- seen.append((session, params))
780
+ def download_page_size(api, session, params):
781
+ seen.append((api, session, params))
759
782
  return 42
760
783
 
761
784
  monkeypatch.setattr(widgets_api.datamodel, "count", count)
@@ -767,7 +790,8 @@ class TestDownloadPageSize:
767
790
  assert response.status_code == 200
768
791
  assert streamed_params["page_size"] == 42
769
792
  assert counted == []
770
- assert seen[0][0] is not None and "list_columns" in seen[0][1]
793
+ assert seen[0][0] is widgets_api
794
+ assert seen[0][1] is not None and "list_columns" in seen[0][2]
771
795
 
772
796
  async def test_async_callable_page_size(
773
797
  self,
@@ -778,8 +802,8 @@ class TestDownloadPageSize:
778
802
  monkeypatch,
779
803
  streamed_params,
780
804
  ):
781
- async def download_page_size(session, params):
782
- return await smart_run(widgets_api.datamodel.count, session, params) + 7
805
+ async def download_page_size(api, session, params):
806
+ return await smart_run(api.datamodel.count, session, params) + 7
783
807
 
784
808
  monkeypatch.setattr(widgets_api, "download_page_size", download_page_size)
785
809
  response = await client.get(
@@ -51,7 +51,6 @@ DELEGATOR_PARITY.append(("bulk_headless", BulkService, "dispatch"))
51
51
  from fastapi_rtk.api.model_rest_api.services._download_service import DownloadService
52
52
 
53
53
  DELEGATOR_PARITY.append(("download_headless", DownloadService, "download"))
54
- DELEGATOR_PARITY.append(("download_page_size", DownloadService, "download_page_size"))
55
54
  DELEGATOR_PARITY.append(("_export_data", DownloadService, "export_data"))
56
55
  DELEGATOR_PARITY.append(("_import_data", DownloadService, "import_data"))
57
56
 
@@ -4,6 +4,8 @@ Models defined once at module load to avoid SAWarning on re-declaration
4
4
  and to keep relationship registry consistent across tests.
5
5
  """
6
6
 
7
+ import datetime
8
+
7
9
  import pytest
8
10
  import sqlalchemy
9
11
  import sqlalchemy.orm
@@ -24,6 +26,7 @@ class Person(Model):
24
26
  name = sqlalchemy.Column(sqlalchemy.String(50), nullable=False)
25
27
  age = sqlalchemy.Column(sqlalchemy.Integer)
26
28
  active = sqlalchemy.Column(sqlalchemy.Boolean, default=True)
29
+ born_at = sqlalchemy.Column(sqlalchemy.DateTime)
27
30
  country_id = sqlalchemy.Column(
28
31
  sqlalchemy.Integer, sqlalchemy.ForeignKey("tier8_country.id")
29
32
  )
@@ -109,9 +112,30 @@ def session(engine):
109
112
  )
110
113
  s.add_all(
111
114
  [
112
- Person(id=1, name="Alice", age=20, active=True, country_id=1),
113
- Person(id=2, name="Bob", age=30, active=True, country_id=1),
114
- Person(id=3, name="Carol", age=40, active=False, country_id=2),
115
+ Person(
116
+ id=1,
117
+ name="Alice",
118
+ age=20,
119
+ active=True,
120
+ country_id=1,
121
+ born_at=datetime.datetime(2026, 9, 10, 9, 0, 0),
122
+ ),
123
+ Person(
124
+ id=2,
125
+ name="Bob",
126
+ age=30,
127
+ active=True,
128
+ country_id=1,
129
+ born_at=datetime.datetime(2026, 9, 10, 23, 30, 0),
130
+ ),
131
+ Person(
132
+ id=3,
133
+ name="Carol",
134
+ age=40,
135
+ active=False,
136
+ country_id=2,
137
+ born_at=datetime.datetime(2026, 9, 11, 12, 0, 0),
138
+ ),
115
139
  ]
116
140
  )
117
141
  s.commit()
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  import unittest.mock
2
3
 
3
4
  import pytest
@@ -131,6 +132,75 @@ class TestFilterBetween:
131
132
  assert len(result) == 3 # NULL bounds skip filter
132
133
 
133
134
 
135
+ class TestFilterDatetimeDaySpan:
136
+ """A date-only value on a datetime column spans the whole day; a value
137
+ carrying a time stays an exact instant. Alice=2026-09-10 09:00, Bob=2026-09-10
138
+ 23:30, Carol=2026-09-11 12:00."""
139
+
140
+ def test_equal_date_only_spans_day(self, session, iface):
141
+ result = _exec(session, iface, FilterEqual, "born_at", "2026-09-10")
142
+ assert sorted(p.name for p in result) == ["Alice", "Bob"]
143
+
144
+ def test_equal_with_time_is_exact(self, session, iface):
145
+ result = _exec(session, iface, FilterEqual, "born_at", "2026-09-10T09:00:00")
146
+ assert [p.name for p in result] == ["Alice"]
147
+
148
+ def test_not_equal_date_only_excludes_whole_day(self, session, iface):
149
+ result = _exec(session, iface, FilterNotEqual, "born_at", "2026-09-10")
150
+ assert [p.name for p in result] == ["Carol"]
151
+
152
+ def test_between_date_only_upper_includes_end_of_day(self, session, iface):
153
+ result = _exec(
154
+ session, iface, FilterBetween, "born_at", ["2026-09-01", "2026-09-10"]
155
+ )
156
+ assert sorted(p.name for p in result) == ["Alice", "Bob"]
157
+
158
+ def test_between_upper_with_time_does_not_span(self, session, iface):
159
+ result = _exec(
160
+ session,
161
+ iface,
162
+ FilterBetween,
163
+ "born_at",
164
+ ["2026-09-10", "2026-09-10T20:00:00"],
165
+ )
166
+ assert [p.name for p in result] == ["Alice"]
167
+
168
+ def test_smaller_equal_date_only_includes_end_of_day(self, session, iface):
169
+ result = _exec(session, iface, FilterSmallerEqual, "born_at", "2026-09-10")
170
+ assert sorted(p.name for p in result) == ["Alice", "Bob"]
171
+
172
+ def test_greater_equal_date_only_starts_at_midnight(self, session, iface):
173
+ result = _exec(session, iface, FilterGreaterEqual, "born_at", "2026-09-11")
174
+ assert [p.name for p in result] == ["Carol"]
175
+
176
+
177
+ class TestUpperBound:
178
+ def test_date_only_string_reaches_end_of_day(self, iface):
179
+ f = FilterSmallerEqual(iface)
180
+ cast = datetime.datetime(2026, 9, 10, 0, 0, 0)
181
+ assert f._upper_bound(cast, "2026-09-10") == datetime.datetime(
182
+ 2026, 9, 10, 23, 59, 59, 999999
183
+ )
184
+
185
+ def test_datetime_string_is_exact(self, iface):
186
+ f = FilterSmallerEqual(iface)
187
+ cast = datetime.datetime(2026, 9, 10, 9, 0, 0)
188
+ assert f._upper_bound(cast, "2026-09-10T09:00:00") == cast
189
+
190
+ def test_non_string_original_value_is_not_spanned(self, iface):
191
+ f = FilterSmallerEqual(iface)
192
+ cast = datetime.datetime(2026, 9, 10, 9, 0, 0)
193
+ assert f._upper_bound(cast, cast) == cast
194
+
195
+ def test_none_value_returns_none(self, iface):
196
+ f = FilterSmallerEqual(iface)
197
+ assert f._upper_bound(None, "NULL") is None
198
+
199
+ def test_non_datetime_value_is_unchanged(self, iface):
200
+ f = FilterSmallerEqual(iface)
201
+ assert f._upper_bound(30, "30") == 30
202
+
203
+
134
204
  class TestFilterTextContains:
135
205
  def test_basic(self, session, iface):
136
206
  result = _exec(session, iface, FilterTextContains, "name", "lic")
@@ -4,6 +4,7 @@ import enum
4
4
 
5
5
  import pytest
6
6
  import sqlalchemy
7
+ import sqlalchemy.ext.asyncio
7
8
  import sqlalchemy.orm
8
9
 
9
10
  from fastapi_rtk import (
@@ -35,6 +36,12 @@ class _RichModel(Model):
35
36
  score: Mapped[float | None] = mapped_column(sqlalchemy.Float)
36
37
 
37
38
 
39
+ class _StreamRow(Model):
40
+ __tablename__ = "tier8_stream"
41
+ id: Mapped[int] = mapped_column(primary_key=True)
42
+ name: Mapped[str | None] = mapped_column(sqlalchemy.String(50))
43
+
44
+
38
45
  @pytest.fixture
39
46
  def rich_iface():
40
47
  return SQLAInterface(_RichModel)
@@ -372,6 +379,38 @@ class TestCRUD:
372
379
  total = sum(len(c) for c in chunks)
373
380
  assert total == 3
374
381
 
382
+ async def test_yield_per_expunges_consumed_rows(self, session, iface, monkeypatch):
383
+ expunged = []
384
+ original = session.expunge
385
+
386
+ def expunge(item):
387
+ expunged.append(item)
388
+ original(item)
389
+
390
+ monkeypatch.setattr(session, "expunge", expunge)
391
+ async for _chunk in iface.yield_per(session, {"page_size": 2}):
392
+ pass
393
+ assert len(expunged) == 3
394
+
395
+ async def test_yield_per_async_survives_chunk_boundary(self):
396
+ engine = sqlalchemy.ext.asyncio.create_async_engine("sqlite+aiosqlite://")
397
+ try:
398
+ async with engine.begin() as conn:
399
+ await conn.run_sync(_StreamRow.__table__.create)
400
+ interface = SQLAInterface(_StreamRow)
401
+ async with sqlalchemy.ext.asyncio.AsyncSession(engine) as write_session:
402
+ write_session.add_all(
403
+ [_StreamRow(id=i, name=f"n{i}") for i in range(1, 7)]
404
+ )
405
+ await write_session.commit()
406
+ async with sqlalchemy.ext.asyncio.AsyncSession(engine) as read_session:
407
+ total = 0
408
+ async for chunk in interface.yield_per(read_session, {"page_size": 2}):
409
+ total += len(chunk)
410
+ assert total == 6
411
+ finally:
412
+ await engine.dispose()
413
+
375
414
  async def test_add(self, session, iface, Person_cls):
376
415
  new = Person_cls(id=99, name="Dave", age=50, country_id=1)
377
416
  await iface.add(session, new)
@@ -59,6 +59,7 @@ class TestDefaults:
59
59
  assert const.DEFAULT_API_PAGE_SIZE == 25
60
60
  assert const.DEFAULT_API_MAX_PAGE_SIZE == 25
61
61
  assert const.DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE == 100
62
+ assert const.DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE == 1000
62
63
 
63
64
  def test_default_token_url(self):
64
65
  assert const.DEFAULT_TOKEN_URL.startswith("/")
@@ -2,6 +2,7 @@ import pytest
2
2
 
3
3
  from fastapi_rtk.const import (
4
4
  DEFAULT_ADMIN_ROLE,
5
+ DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE,
5
6
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE,
6
7
  DEFAULT_API_MAX_PAGE_SIZE,
7
8
  DEFAULT_API_PAGE_SIZE,
@@ -62,6 +63,24 @@ class TestGeneralDefaults:
62
63
  def test_api_download_min_page_size_default(self):
63
64
  assert Setting.API_DOWNLOAD_MIN_PAGE_SIZE == DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE
64
65
 
66
+ def test_api_download_max_page_size(self, config_setter):
67
+ config_setter("API_DOWNLOAD_MAX_PAGE_SIZE", 5000)
68
+ assert Setting.API_DOWNLOAD_MAX_PAGE_SIZE == 5000
69
+
70
+ def test_api_download_max_page_size_default(self):
71
+ assert Setting.API_DOWNLOAD_MAX_PAGE_SIZE == DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE
72
+
73
+ def test_api_download_page_size_default_is_builtin_resolver(self):
74
+ from fastapi_rtk.api.model_rest_api.services._download_service import (
75
+ default_download_page_size,
76
+ )
77
+
78
+ assert Setting.API_DOWNLOAD_PAGE_SIZE is default_download_page_size
79
+
80
+ def test_api_download_page_size_override(self, config_setter):
81
+ config_setter("API_DOWNLOAD_PAGE_SIZE", 1234)
82
+ assert Setting.API_DOWNLOAD_PAGE_SIZE == 1234
83
+
65
84
  def test_text_filter_separator_default(self):
66
85
  assert Setting.TEXT_FILTER_SEPARATOR == ";"
67
86
 
@@ -1 +0,0 @@
1
- __version__ = "2.17.0"
File without changes
File without changes
File without changes