fastapi-rtk 2.18.1__tar.gz → 2.18.2__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 (340) hide show
  1. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/PKG-INFO +1 -1
  2. fastapi_rtk-2.18.2/fastapi_rtk/_version.py +1 -0
  3. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/guide/backend.md +3 -1
  4. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_download_service.py +47 -1
  5. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/interface.py +1 -8
  6. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/const.py +1 -0
  7. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/setting.py +8 -0
  8. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/csv_json_converter.py +67 -26
  9. fastapi_rtk-2.18.2/tests/unit/api/model_rest_api/test_download_service.py +204 -0
  10. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_interface.py +5 -12
  11. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_const.py +1 -1
  12. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_setting.py +11 -0
  13. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_csv_json_converter.py +97 -0
  14. fastapi_rtk-2.18.1/fastapi_rtk/_version.py +0 -1
  15. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/.gitignore +0 -0
  16. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/LICENSE +0 -0
  17. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/README.md +0 -0
  18. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/__init__.py +0 -0
  19. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/adapters/agents-md/AGENTS.md +0 -0
  20. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/adapters/claude/skill.frontmatter +0 -0
  21. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/guide/frontend.md +0 -0
  22. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/guide/legacy.md +0 -0
  23. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/agent_docs/guide/overview.md +0 -0
  24. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/__init__.py +0 -0
  25. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/base_api.py +0 -0
  26. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
  27. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/model_rest_api.py +0 -0
  28. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/params.py +0 -0
  29. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/__init__.py +0 -0
  30. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_add_service.py +0 -0
  31. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_bulk_service.py +0 -0
  32. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_delete_service.py +0 -0
  33. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_edit_service.py +0 -0
  34. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_file_service.py +0 -0
  35. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_info_service.py +0 -0
  36. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_list_service.py +0 -0
  37. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_schema_service.py +0 -0
  38. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/api/model_rest_api/services/_show_service.py +0 -0
  39. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/apis/__init__.py +0 -0
  40. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/apis/info.py +0 -0
  41. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/apis/license.py +0 -0
  42. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/__init__.py +0 -0
  43. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/auth.py +0 -0
  44. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  45. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  46. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  47. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/ldap.py +0 -0
  48. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  49. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  50. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  51. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/strategies/config.py +0 -0
  52. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/strategies/db.py +0 -0
  53. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  54. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/__init__.py +0 -0
  55. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/__init__.py +0 -0
  56. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/column.py +0 -0
  57. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/db.py +0 -0
  58. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  59. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/filters.py +0 -0
  60. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/interface.py +0 -0
  61. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/model.py +0 -0
  62. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/generic/session.py +0 -0
  63. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  64. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/column.py +0 -0
  65. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/db.py +0 -0
  66. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  67. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  68. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  69. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_service.py +0 -0
  70. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_service.py +0 -0
  71. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
  72. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/_processing_service.py +0 -0
  73. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  74. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  75. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  76. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  77. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  78. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/filters.py +0 -0
  79. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/model.py +0 -0
  80. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/backends/sqla/session.py +0 -0
  81. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/__init__.py +0 -0
  82. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/cache.py +0 -0
  83. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/db.py +0 -0
  84. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/file_manager.py +0 -0
  85. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/filter.py +0 -0
  86. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/interface.py +0 -0
  87. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/loop_manager.py +0 -0
  88. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/model.py +0 -0
  89. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/session.py +0 -0
  90. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/bases/user_cache.py +0 -0
  91. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/caches/__init__.py +0 -0
  92. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/caches/cache.py +0 -0
  93. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/caches/serializers.py +0 -0
  94. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/caches/storages.py +0 -0
  95. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/__init__.py +0 -0
  96. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/cli.py +0 -0
  97. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/__init__.py +0 -0
  98. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
  99. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/__init__.py +0 -0
  100. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/adapters/__init__.py +0 -0
  101. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/adapters/agents_md.py +0 -0
  102. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/adapters/claude.py +0 -0
  103. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/base.py +0 -0
  104. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/agent/docs.py +0 -0
  105. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  106. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  107. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  108. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  109. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  110. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  111. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  112. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  113. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  114. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/export.py +0 -0
  115. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/security.py +0 -0
  116. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/commands/translate.py +0 -0
  117. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/const.py +0 -0
  118. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/decorators.py +0 -0
  119. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/types.py +0 -0
  120. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/cli/utils.py +0 -0
  121. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/config.py +0 -0
  122. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/db.py +0 -0
  123. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/decorators.py +0 -0
  124. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/dependencies.py +0 -0
  125. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/exceptions.py +0 -0
  126. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  127. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/file_managers/__init__.py +0 -0
  128. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/file_managers/file_manager.py +0 -0
  129. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/file_managers/image_manager.py +0 -0
  130. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  131. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  132. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/filters.py +0 -0
  133. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/globals.py +0 -0
  134. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/__init__.py +0 -0
  135. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/babel/__init__.py +0 -0
  136. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/babel/cli.py +0 -0
  137. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/babel/config.py +0 -0
  138. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/babel.cfg +0 -0
  139. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/lazy_text.py +0 -0
  140. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/messages.pot +0 -0
  141. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  142. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  143. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  144. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  145. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/loop_managers/__init__.py +0 -0
  146. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/loop_managers/loop_manager.py +0 -0
  147. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/manager.py +0 -0
  148. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/middlewares.py +0 -0
  149. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/mixins.py +0 -0
  150. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/models.py +0 -0
  151. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/py.typed +0 -0
  152. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/registry.py +0 -0
  153. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/routers.py +0 -0
  154. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/schemas.py +0 -0
  155. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/__init__.py +0 -0
  156. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/__init__.py +0 -0
  157. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/apis.py +0 -0
  158. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/models.py +0 -0
  159. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
  160. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_builtin_role_service.py +0 -0
  161. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_entities.py +0 -0
  162. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
  163. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
  164. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
  165. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_permission_service.py +0 -0
  166. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_results.py +0 -0
  167. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_role_service.py +0 -0
  168. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/_user_service.py +0 -0
  169. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/security_manager/security_manager.py +0 -0
  170. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/security/sqla/user_cache.py +0 -0
  171. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/types.py +0 -0
  172. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/__init__.py +0 -0
  173. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/async_task_runner.py +0 -0
  174. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/class_factory.py +0 -0
  175. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/deep_merge.py +0 -0
  176. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/extender_mixin.py +0 -0
  177. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  178. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/formatter.py +0 -0
  179. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/hooks.py +0 -0
  180. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/lazy.py +0 -0
  181. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/merge_schema.py +0 -0
  182. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  183. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/pydantic.py +0 -0
  184. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/run_utils.py +0 -0
  185. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/self_dependencies.py +0 -0
  186. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  187. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/sqla.py +0 -0
  188. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/timezone.py +0 -0
  189. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/update_signature.py +0 -0
  190. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  191. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/fastapi_rtk/version.py +0 -0
  192. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/pyproject.toml +0 -0
  193. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/conftest.py +0 -0
  194. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/docker-compose.gis.yml +0 -0
  195. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/__init__.py +0 -0
  196. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/api/__init__.py +0 -0
  197. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/api/test_add_edit_relation_response.py +0 -0
  198. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/api/test_model_rest_api.py +0 -0
  199. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/apis/__init__.py +0 -0
  200. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/apis/test_info_callable_schema.py +0 -0
  201. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/apis/test_info_contract.py +0 -0
  202. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/auth/__init__.py +0 -0
  203. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/auth/test_auth_flow.py +0 -0
  204. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/auth/test_oauth.py +0 -0
  205. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/conftest.py +0 -0
  206. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/file_managers/__init__.py +0 -0
  207. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/file_managers/test_file_routes.py +0 -0
  208. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/__init__.py +0 -0
  209. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/__init__.py +0 -0
  210. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/conftest.py +0 -0
  211. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/test_permission_service.py +0 -0
  212. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/test_role_service.py +0 -0
  213. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/test_security_manager.py +0 -0
  214. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/security/sqla/test_user_service.py +0 -0
  215. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/integration/test_app_smoke.py +0 -0
  216. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/__init__.py +0 -0
  217. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/__init__.py +0 -0
  218. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/conftest.py +0 -0
  219. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/model_rest_api/__init__.py +0 -0
  220. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/model_rest_api/test_columns.py +0 -0
  221. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
  222. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/model_rest_api/test_init.py +0 -0
  223. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/model_rest_api/test_service_delegators.py +0 -0
  224. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/api/test_base_api.py +0 -0
  225. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/apis/__init__.py +0 -0
  226. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/apis/test_info.py +0 -0
  227. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/apis/test_license.py +0 -0
  228. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/__init__.py +0 -0
  229. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/hashers/__init__.py +0 -0
  230. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
  231. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/hashers/test_scrypt.py +0 -0
  232. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/password_helpers/__init__.py +0 -0
  233. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/password_helpers/test_fab.py +0 -0
  234. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/strategies/__init__.py +0 -0
  235. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/strategies/test_config.py +0 -0
  236. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/strategies/test_db.py +0 -0
  237. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/strategies/test_jwt.py +0 -0
  238. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/auth/test_auth.py +0 -0
  239. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/__init__.py +0 -0
  240. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/__init__.py +0 -0
  241. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_column.py +0 -0
  242. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_db.py +0 -0
  243. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_exceptions.py +0 -0
  244. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_filters.py +0 -0
  245. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_interface.py +0 -0
  246. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_model.py +0 -0
  247. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/generic/test_session.py +0 -0
  248. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/__init__.py +0 -0
  249. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/conftest.py +0 -0
  250. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_audit.py +0 -0
  251. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_audit_service_delegators.py +0 -0
  252. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_audit_types.py +0 -0
  253. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_column.py +0 -0
  254. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_db.py +0 -0
  255. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_filters.py +0 -0
  256. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
  257. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_model.py +0 -0
  258. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/backends/sqla/test_session.py +0 -0
  259. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/__init__.py +0 -0
  260. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_db.py +0 -0
  261. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_file_manager.py +0 -0
  262. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_filter.py +0 -0
  263. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_interface.py +0 -0
  264. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_model.py +0 -0
  265. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/bases/test_session.py +0 -0
  266. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/__init__.py +0 -0
  267. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/__init__.py +0 -0
  268. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/conftest.py +0 -0
  269. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/test_db.py +0 -0
  270. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/test_export.py +0 -0
  271. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/test_security.py +0 -0
  272. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/test_security_crypto.py +0 -0
  273. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/commands/test_translate.py +0 -0
  274. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/conftest.py +0 -0
  275. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/test_cli.py +0 -0
  276. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/test_decorators.py +0 -0
  277. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/test_types.py +0 -0
  278. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/cli/test_utils.py +0 -0
  279. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/__init__.py +0 -0
  280. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/conftest.py +0 -0
  281. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/test_file_manager.py +0 -0
  282. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/test_image_manager.py +0 -0
  283. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
  284. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
  285. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/lang/__init__.py +0 -0
  286. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/lang/test_babel_cli.py +0 -0
  287. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/lang/test_babel_config.py +0 -0
  288. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/lang/test_lazy_text.py +0 -0
  289. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/__init__.py +0 -0
  290. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/__init__.py +0 -0
  291. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/conftest.py +0 -0
  292. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/__init__.py +0 -0
  293. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_builtin_role_service.py +0 -0
  294. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_entities.py +0 -0
  295. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_permission_service.py +0 -0
  296. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_results.py +0 -0
  297. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_role_service.py +0 -0
  298. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/security_manager/test_user_service.py +0 -0
  299. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/test_apis.py +0 -0
  300. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/test_models.py +0 -0
  301. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/security/sqla/test_security_manager.py +0 -0
  302. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_caches.py +0 -0
  303. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_config.py +0 -0
  304. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_db.py +0 -0
  305. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_decorators.py +0 -0
  306. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_dependencies.py +0 -0
  307. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_exceptions.py +0 -0
  308. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_fastapi_react_toolkit.py +0 -0
  309. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_filters.py +0 -0
  310. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_globals.py +0 -0
  311. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_manager.py +0 -0
  312. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_middlewares.py +0 -0
  313. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_mixins.py +0 -0
  314. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_models.py +0 -0
  315. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_registry.py +0 -0
  316. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_routers.py +0 -0
  317. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_schemas.py +0 -0
  318. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_types.py +0 -0
  319. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_user_cache.py +0 -0
  320. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/test_version.py +0 -0
  321. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/__init__.py +0 -0
  322. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_async_task_runner.py +0 -0
  323. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_class_factory.py +0 -0
  324. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_deep_merge.py +0 -0
  325. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_extender_mixin.py +0 -0
  326. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
  327. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_formatter.py +0 -0
  328. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_hooks.py +0 -0
  329. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_lazy.py +0 -0
  330. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_merge_schema.py +0 -0
  331. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
  332. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_pydantic.py +0 -0
  333. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_run_utils.py +0 -0
  334. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_self_dependencies.py +0 -0
  335. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_smartdefaultdict.py +0 -0
  336. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_sqla.py +0 -0
  337. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_timezone.py +0 -0
  338. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_update_signature.py +0 -0
  339. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/tests/unit/utils/test_use_default_when_none.py +0 -0
  340. {fastapi_rtk-2.18.1 → fastapi_rtk-2.18.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-rtk
3
- Version: 2.18.1
3
+ Version: 2.18.2
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.2"
@@ -135,6 +135,8 @@ class Item(Model):
135
135
 
136
136
  `__repr__` is load-bearing: the frontend uses it as the label for related items in dropdowns and grid cells. If it returns nothing meaningful, users see `<Item object at 0x...>`.
137
137
 
138
+ A bare relation in `list_columns` (`"category"`, no sub-column) loads every column of the related row, so a related model carrying a large column (a JSONB snapshot, a blob) is fetched in full once per row. Declare `__repr_columns__ = ["name"]` on the related model, naming the columns its `__repr__` reads: `/_info` dropdown values and the `/download` CSV then fetch only the primary key plus those columns, for a nested path ending in a relation (`"order.customer"`) as well as a top-level one. The list and show JSON still serialize the full related row, so a heavy column that no response needs is best marked `deferred=True` on its `mapped_column`.
139
+
138
140
  ### List columns
139
141
 
140
142
  For plain `Mapped[list[T] | None]` columns, use `ListColumn` (JSON-backed) or `JSONBListColumn` (Postgres JSONB-backed) from `fastapi_rtk` instead of `sqlalchemy.dialects.postgresql.JSONB` directly. The `TypeDecorator` enforces list-shape at bind time, optionally coerces each entry to a target Python type, and lets `ModelRestApi` recognize the column as a list (rendered as a tag-array field by the frontend, not a free-form JSON blob).
@@ -551,7 +553,7 @@ The task runs later, possibly after the loop has advanced or the enclosing funct
551
553
 
552
554
  ### Sizing the `/download` stream
553
555
 
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.
556
+ `/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 from the database 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); the HTTP response is written separately, in pieces of `API_DOWNLOAD_RESPONSE_CHUNK_SIZE` characters of CSV text (default `8192`; `0` or a negative value sends one chunk per row). 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
557
 
556
558
  Set it to an int for a fixed chunk, which also skips the count query the default runs:
557
559
 
@@ -108,6 +108,7 @@ class DownloadService:
108
108
  return GeneralResponse(detail=translate("OK"))
109
109
 
110
110
  params = self.api._handle_query_params(q)
111
+ params["list_columns"] = self._repr_projection(params["list_columns"])
111
112
  page_size = self.api.download_page_size
112
113
  if callable(page_size):
113
114
  page_size = await safe_call(page_size(self.api, session, params))
@@ -169,6 +170,10 @@ class DownloadService:
169
170
  writer.writerow(labels)
170
171
  yield line.read()
171
172
 
173
+ # Every yielded chunk crosses the globals middleware's memory stream and one ASGI send, so rows are batched.
174
+ chunk_size = Setting.API_DOWNLOAD_RESPONSE_CHUNK_SIZE
175
+ buffer: list[str] = []
176
+ buffered = 0
172
177
  async for chunk in data:
173
178
  for item in chunk:
174
179
  row = await CSVJSONConverter.ajson_to_csv_single(
@@ -178,7 +183,15 @@ class DownloadService:
178
183
  export_mode=export_mode,
179
184
  )
180
185
  writer.writerow(row)
181
- yield line.read()
186
+ text = line.read()
187
+ buffer.append(text)
188
+ buffered += len(text)
189
+ if buffered >= chunk_size:
190
+ yield "".join(buffer)
191
+ buffer.clear()
192
+ buffered = 0
193
+ if buffer:
194
+ yield "".join(buffer)
182
195
 
183
196
  async def import_data( # pragma: no cover
184
197
  self,
@@ -278,6 +291,39 @@ class DownloadService:
278
291
 
279
292
  return list_data
280
293
 
294
+ def _repr_projection(self, columns: list[str]) -> list[str]:
295
+ """
296
+ Narrow a column ending in a relation to the columns its CSV cell needs, when that model declares them.
297
+
298
+ A CSV cell renders a relation as ``str(item)`` (``name_``) or its primary key (``id_``), so a related model declaring ``__repr_columns__`` is fetched with its primary key plus those columns instead of every column, which is what a path ending in a relation loads otherwise. The path may be nested (``a.b.c``); only its last segment is narrowed.
299
+
300
+ Args:
301
+ columns (list[str]): The columns the export query fetches.
302
+
303
+ Returns:
304
+ list[str]: The same columns, with each qualifying relation path replaced by dotted sub-columns.
305
+ """
306
+ projected: list[str] = []
307
+ for col in columns:
308
+ interface = self.api.datamodel
309
+ *path, leaf = col.split(".")
310
+ for segment in path:
311
+ if not interface.is_relation(segment):
312
+ interface = None
313
+ break
314
+ interface = interface.get_related_interface(segment)
315
+ repr_columns = None
316
+ if interface is not None and interface.is_relation(leaf):
317
+ related = interface.get_related_interface(leaf)
318
+ repr_columns = getattr(related.obj, "__repr_columns__", None)
319
+ if not repr_columns:
320
+ projected.append(col)
321
+ continue
322
+ projected.extend(
323
+ f"{col}.{sub}" for sub in [*related.get_pk_attrs(), *repr_columns]
324
+ )
325
+ return projected
326
+
281
327
 
282
328
  async def default_download_page_size(
283
329
  api: "ModelRestApi", session: SQLASession, params: DBQueryParams
@@ -152,19 +152,12 @@ class SQLAInterface(
152
152
  result = result.scalars()
153
153
  else:
154
154
  result = session.scalars(statement)
155
+ # No expunge: the identity map holds rows weakly, so a consumed chunk is freed once the caller drops it, and expunge_all() would swap the map the streaming result still holds.
155
156
  while True:
156
157
  chunk = await smart_run(result.fetchmany, page_size)
157
158
  if not chunk:
158
159
  break
159
160
  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)
168
161
  await safe_call(self.close(session))
169
162
 
170
163
  async def add(self, session, item, *, flush=True, commit=True, refresh=True):
@@ -74,6 +74,7 @@ DEFAULT_API_PAGE_SIZE = 25
74
74
  DEFAULT_API_MAX_PAGE_SIZE = 25
75
75
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE = 100
76
76
  DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE = 1000
77
+ DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE = 8 * 1024
77
78
  DEFAULT_TOKEN_URL = "/api/v1/auth/jwt/login"
78
79
  DEFAULT_SECRET = "SUPERSECRET"
79
80
  DEFAULT_COOKIE_NAME = "dataTactics"
@@ -4,6 +4,7 @@ from .const import (
4
4
  DEFAULT_ADMIN_ROLE,
5
5
  DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE,
6
6
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE,
7
+ DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE,
7
8
  DEFAULT_API_MAX_PAGE_SIZE,
8
9
  DEFAULT_API_PAGE_SIZE,
9
10
  DEFAULT_LANG_FOLDER,
@@ -100,6 +101,13 @@ class _Setting:
100
101
  "API_DOWNLOAD_MAX_PAGE_SIZE", DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE
101
102
  )
102
103
 
104
+ @property
105
+ def API_DOWNLOAD_RESPONSE_CHUNK_SIZE(self) -> int:
106
+ return g.config.get(
107
+ "API_DOWNLOAD_RESPONSE_CHUNK_SIZE",
108
+ DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE,
109
+ )
110
+
103
111
  @property
104
112
  def API_DOWNLOAD_PAGE_SIZE(self) -> "int | DownloadPageSizeResolver":
105
113
  from .api.model_rest_api.services._download_service import (
@@ -1,12 +1,12 @@
1
1
  import abc
2
2
  import csv
3
3
  import enum
4
+ import functools
4
5
  import inspect
5
6
  import json
7
+ import types
6
8
  import typing
7
9
 
8
- from .run_utils import safe_call
9
-
10
10
  if typing.TYPE_CHECKING:
11
11
  from ..bases import BasicModel
12
12
 
@@ -118,7 +118,7 @@ class CSVJSONConverter:
118
118
 
119
119
  @classmethod
120
120
  def json_to_csv_single(
121
- self,
121
+ cls,
122
122
  data: "dict[str, typing.Any] | BasicModel",
123
123
  /,
124
124
  *,
@@ -141,14 +141,12 @@ class CSVJSONConverter:
141
141
  str: The CSV data as a string.
142
142
  """
143
143
  csv_data: list[str] = []
144
- data_pipeline = DataPipeline()
145
- data_pipeline.add_processor(ColumnProcessor(relation_separator))
146
- data_pipeline.add_processor(ModelProcessor())
147
- data_pipeline.add_processor(
148
- ListProcessor(delimiter=delimiter, export_mode=export_mode)
144
+ data_pipeline = cls.get_data_pipeline(
145
+ async_column=False,
146
+ relation_separator=relation_separator,
147
+ delimiter=delimiter,
148
+ export_mode=export_mode,
149
149
  )
150
- data_pipeline.add_processor(EnumProcessor())
151
- data_pipeline.add_processor(FallbackProcessor())
152
150
 
153
151
  for col in list_columns:
154
152
  value = data_pipeline.process(data, col)
@@ -181,14 +179,12 @@ class CSVJSONConverter:
181
179
  str: The CSV data as a string.
182
180
  """
183
181
  csv_data: list[str] = []
184
- data_pipeline = DataPipeline()
185
- data_pipeline.add_processor(AsyncColumnProcessor(relation_separator))
186
- data_pipeline.add_processor(ModelProcessor())
187
- data_pipeline.add_processor(
188
- ListProcessor(delimiter=delimiter, export_mode=export_mode)
182
+ data_pipeline = cls.get_data_pipeline(
183
+ async_column=True,
184
+ relation_separator=relation_separator,
185
+ delimiter=delimiter,
186
+ export_mode=export_mode,
189
187
  )
190
- data_pipeline.add_processor(EnumProcessor())
191
- data_pipeline.add_processor(FallbackProcessor())
192
188
 
193
189
  for col in list_columns:
194
190
  value = await data_pipeline.aprocess(data, col)
@@ -196,6 +192,44 @@ class CSVJSONConverter:
196
192
 
197
193
  return csv_data
198
194
 
195
+ @classmethod
196
+ @functools.lru_cache(maxsize=64)
197
+ def get_data_pipeline(
198
+ cls,
199
+ *,
200
+ async_column: bool,
201
+ relation_separator: str,
202
+ delimiter: str,
203
+ export_mode: ExportMode,
204
+ ) -> "DataPipeline":
205
+ """
206
+ Build the row-conversion pipeline once per parameter set; its processors hold no per-row state.
207
+
208
+ Override to change the processor chain; the cache is keyed per class, so a subclass gets its own pipelines. The delimiter comes from the request, hence the bounded cache.
209
+
210
+ Args:
211
+ async_column (bool): Whether the column processor awaits coroutine-valued attributes.
212
+ relation_separator (str): The separator between a relation and its sub-column.
213
+ delimiter (str): The CSV delimiter, which picks the list separator.
214
+ export_mode (ExportMode): Export mode (simplified or detailed).
215
+
216
+ Returns:
217
+ DataPipeline: The shared pipeline for these parameters.
218
+ """
219
+ data_pipeline = DataPipeline()
220
+ data_pipeline.add_processor(
221
+ AsyncColumnProcessor(relation_separator)
222
+ if async_column
223
+ else ColumnProcessor(relation_separator)
224
+ )
225
+ data_pipeline.add_processor(ModelProcessor())
226
+ data_pipeline.add_processor(
227
+ ListProcessor(delimiter=delimiter, export_mode=export_mode)
228
+ )
229
+ data_pipeline.add_processor(EnumProcessor())
230
+ data_pipeline.add_processor(FallbackProcessor())
231
+ return data_pipeline
232
+
199
233
  @classmethod
200
234
  def _convert_nested_col_into_dict(
201
235
  cls,
@@ -254,15 +288,19 @@ class CSVJSONConverter:
254
288
  class DataPipeline:
255
289
  def __init__(self):
256
290
  self.processors = list[DataProcessor]()
291
+ self._steps = list[tuple[DataProcessor, bool]]()
257
292
 
258
293
  def add_processor(self, processor: "DataProcessor"):
259
294
  """
260
295
  Adds a data processor to the pipeline.
261
296
 
297
+ Whether ``process`` is a coroutine function is decided here, once, so ``aprocess`` awaits only the processors that need it.
298
+
262
299
  Args:
263
300
  processor (DataProcessor): The data processor to add.
264
301
  """
265
302
  self.processors.append(processor)
303
+ self._steps.append((processor, inspect.iscoroutinefunction(processor.process)))
266
304
 
267
305
  def process(self, data: typing.Any, col: str):
268
306
  """
@@ -292,8 +330,11 @@ class DataPipeline:
292
330
  Returns:
293
331
  typing.Any: The processed data.
294
332
  """
295
- for processor in self.processors:
296
- data, col, should_continue = await safe_call(processor.process(data, col))
333
+ for processor, is_async in self._steps:
334
+ if is_async:
335
+ data, col, should_continue = await processor.process(data, col)
336
+ else:
337
+ data, col, should_continue = processor.process(data, col)
297
338
  if not should_continue:
298
339
  break
299
340
  return data
@@ -335,22 +376,23 @@ class ColumnProcessor(DataProcessor):
335
376
  class AsyncColumnProcessor(ColumnProcessor):
336
377
  async def process(self, data, col):
337
378
  data, col, continue_processing = super().process(data, col)
338
- if inspect.iscoroutine(data):
379
+ if isinstance(data, types.CoroutineType):
339
380
  data = await data
340
381
  return data, col, continue_processing
341
382
 
342
383
 
343
384
  class ModelProcessor(DataProcessor):
344
385
  def __init__(self, attr="name_"):
386
+ from ..bases import BasicModel
387
+
345
388
  super().__init__()
346
389
  self.attr = attr
390
+ self.model_class = BasicModel
347
391
 
348
392
  def process(self, data, col):
349
- from ..bases import BasicModel
350
-
351
393
  continue_processing = True
352
394
 
353
- if isinstance(data, BasicModel):
395
+ if isinstance(data, self.model_class):
354
396
  data = getattr(data, self.attr, "")
355
397
  continue_processing = False
356
398
  return data, col, continue_processing
@@ -383,10 +425,9 @@ class ListProcessor(DataProcessor):
383
425
  self.dict_processor = DictProcessor(
384
426
  attr_detailed if export_mode == "detailed" else attr_simplified
385
427
  )
428
+ self.model_class = self.model_processor.model_class
386
429
 
387
430
  def process(self, data, col):
388
- from ..bases import BasicModel
389
-
390
431
  continue_processing = True
391
432
 
392
433
  if isinstance(data, list):
@@ -394,7 +435,7 @@ class ListProcessor(DataProcessor):
394
435
  for item in data:
395
436
  if isinstance(item, dict):
396
437
  item_processed, _, _ = self.dict_processor.process(item, col)
397
- elif isinstance(item, BasicModel):
438
+ elif isinstance(item, self.model_class):
398
439
  item_processed, _, _ = self.model_processor.process(item, col)
399
440
  else:
400
441
  item_processed = str(item)
@@ -0,0 +1,204 @@
1
+ """`DownloadService`: chunked CSV output and the repr-column projection of relation paths."""
2
+
3
+ import csv
4
+ import io
5
+ import types
6
+
7
+ import pydantic
8
+ import pytest
9
+ import sqlalchemy
10
+ import sqlalchemy.orm
11
+ import sqlalchemy.pool
12
+
13
+ from fastapi_rtk import Mapped, mapped_column, relationship
14
+ from fastapi_rtk.api.model_rest_api.services._download_service import DownloadService
15
+ from fastapi_rtk.backends.sqla.interface import SQLAInterface
16
+ from fastapi_rtk.backends.sqla.model import Model
17
+
18
+ LIST_COLUMNS = ["name", "description"]
19
+ LABEL_COLUMNS = {"name": "Name", "description": "Description"}
20
+
21
+
22
+ class _ExportSite(Model):
23
+ __tablename__ = "download_service_site"
24
+ __repr_columns__ = ["code"]
25
+ id: Mapped[int] = mapped_column(primary_key=True)
26
+ code: Mapped[str]
27
+ secret: Mapped[str | None]
28
+ runs: Mapped[list["_ExportRun"]] = relationship(back_populates="site")
29
+
30
+ def __repr__(self):
31
+ return self.code
32
+
33
+
34
+ class _ExportRun(Model):
35
+ __tablename__ = "download_service_run"
36
+ __repr_columns__ = ["label"]
37
+ id: Mapped[int] = mapped_column(primary_key=True)
38
+ label: Mapped[str]
39
+ payload: Mapped[str | None]
40
+ site_id: Mapped[int | None] = mapped_column(
41
+ sqlalchemy.ForeignKey("download_service_site.id")
42
+ )
43
+ site: Mapped[_ExportSite | None] = relationship(back_populates="runs")
44
+ rows: Mapped[list["_ExportRow"]] = relationship(back_populates="run")
45
+
46
+ def __repr__(self):
47
+ return self.label
48
+
49
+
50
+ class _ExportRow(Model):
51
+ __tablename__ = "download_service_row"
52
+ id: Mapped[int] = mapped_column(primary_key=True)
53
+ name: Mapped[str]
54
+ run_id: Mapped[int] = mapped_column(
55
+ sqlalchemy.ForeignKey("download_service_run.id")
56
+ )
57
+ run: Mapped[_ExportRun] = relationship(back_populates="rows")
58
+
59
+
60
+ @pytest.fixture
61
+ def row_service():
62
+ return DownloadService(types.SimpleNamespace(datamodel=SQLAInterface(_ExportRow)))
63
+
64
+
65
+ @pytest.fixture
66
+ def seeded_session():
67
+ engine = sqlalchemy.create_engine(
68
+ "sqlite://",
69
+ connect_args={"check_same_thread": False},
70
+ poolclass=sqlalchemy.pool.StaticPool,
71
+ )
72
+ _ExportSite.__table__.create(engine)
73
+ _ExportRun.__table__.create(engine)
74
+ _ExportRow.__table__.create(engine)
75
+ with sqlalchemy.orm.Session(engine) as session:
76
+ site = _ExportSite(id=1, code="FRA", secret="s" * 1000)
77
+ run = _ExportRun(id=1, label="run 1", payload="x" * 1000, site=site)
78
+ session.add_all([run, _ExportRow(id=1, name="a", run=run)])
79
+ session.commit()
80
+ session.expunge_all()
81
+ yield session
82
+ engine.dispose()
83
+
84
+
85
+ class TestReprProjection:
86
+ def test_bare_relation_with_repr_columns_becomes_pk_plus_repr_columns(
87
+ self, row_service
88
+ ):
89
+ assert row_service._repr_projection(["name", "run"]) == [
90
+ "name",
91
+ "run.id",
92
+ "run.label",
93
+ ]
94
+
95
+ def test_relation_without_repr_columns_is_kept_bare(self, row_service, monkeypatch):
96
+ monkeypatch.setattr(_ExportRun, "__repr_columns__", None)
97
+ assert row_service._repr_projection(["name", "run"]) == ["name", "run"]
98
+
99
+ def test_dotted_and_plain_columns_are_untouched(self, row_service):
100
+ assert row_service._repr_projection(
101
+ ["run.payload", "run.site.code", "name", "id"]
102
+ ) == ["run.payload", "run.site.code", "name", "id"]
103
+
104
+ def test_nested_path_ending_in_a_relation_is_narrowed(self, row_service):
105
+ assert row_service._repr_projection(["run.site"]) == [
106
+ "run.site.id",
107
+ "run.site.code",
108
+ ]
109
+
110
+ def test_unknown_segment_is_kept_as_is(self, row_service):
111
+ assert row_service._repr_projection(["nope.site", "run.nope"]) == [
112
+ "nope.site",
113
+ "run.nope",
114
+ ]
115
+
116
+ async def test_nested_projection_leaves_other_related_columns_unloaded(
117
+ self, row_service, seeded_session
118
+ ):
119
+ columns = row_service._repr_projection(["run.site"])
120
+ rows = await row_service.api.datamodel.get_many(
121
+ seeded_session, {"list_columns": columns}
122
+ )
123
+ site_state = sqlalchemy.inspect(rows[0].run.site)
124
+ assert "secret" in site_state.unloaded
125
+ assert {"id", "code"}.isdisjoint(site_state.unloaded)
126
+ assert str(rows[0].run.site) == "FRA"
127
+
128
+ async def test_projected_query_leaves_other_related_columns_unloaded(
129
+ self, row_service, seeded_session
130
+ ):
131
+ columns = row_service._repr_projection(["name", "run"])
132
+ rows = await row_service.api.datamodel.get_many(
133
+ seeded_session, {"list_columns": columns}
134
+ )
135
+ run_state = sqlalchemy.inspect(rows[0].run)
136
+ assert "payload" in run_state.unloaded
137
+ assert {"id", "label"}.isdisjoint(run_state.unloaded)
138
+ assert str(rows[0].run) == "run 1"
139
+
140
+
141
+ class _RowSchema(pydantic.BaseModel):
142
+ name: str
143
+ description: str
144
+
145
+
146
+ def _rows(count: int, description_length: int = 4):
147
+ return [
148
+ {"name": f"row_{i}", "description": "x" * description_length}
149
+ for i in range(count)
150
+ ]
151
+
152
+
153
+ async def _chunks(rows, chunk_size: int):
154
+ for start in range(0, len(rows), chunk_size):
155
+ yield rows[start : start + chunk_size]
156
+
157
+
158
+ def _expected_csv(rows):
159
+ buffer = io.StringIO()
160
+ writer = csv.writer(buffer)
161
+ writer.writerow([LABEL_COLUMNS[col] for col in LIST_COLUMNS])
162
+ for row in rows:
163
+ writer.writerow([row[col] for col in LIST_COLUMNS])
164
+ return buffer.getvalue()
165
+
166
+
167
+ async def _export(rows, chunk_size: int = 2):
168
+ service = DownloadService(api=None)
169
+ pieces = []
170
+ async for piece in service.export_data(
171
+ _chunks(rows, chunk_size), LIST_COLUMNS, LABEL_COLUMNS, _RowSchema
172
+ ):
173
+ pieces.append(piece)
174
+ return pieces
175
+
176
+
177
+ async def test_header_then_one_chunk_for_a_small_export():
178
+ rows = _rows(5)
179
+ pieces = await _export(rows)
180
+ assert len(pieces) == 2
181
+ assert "".join(pieces) == _expected_csv(rows)
182
+
183
+
184
+ async def test_large_export_is_batched_but_byte_identical(config_setter):
185
+ config_setter("API_DOWNLOAD_RESPONSE_CHUNK_SIZE", 200)
186
+ rows = _rows(50, description_length=30)
187
+ pieces = await _export(rows, chunk_size=7)
188
+ body = pieces[1:]
189
+ assert len(body) > 1
190
+ assert all(len(piece) >= 200 for piece in body[:-1])
191
+ assert "".join(pieces) == _expected_csv(rows)
192
+
193
+
194
+ async def test_negative_chunk_size_sends_one_chunk_per_row(config_setter):
195
+ config_setter("API_DOWNLOAD_RESPONSE_CHUNK_SIZE", -1)
196
+ rows = _rows(5)
197
+ pieces = await _export(rows)
198
+ assert len(pieces) == 1 + len(rows)
199
+ assert "".join(pieces) == _expected_csv(rows)
200
+
201
+
202
+ async def test_empty_export_yields_only_the_header():
203
+ pieces = await _export([])
204
+ assert pieces == [_expected_csv([])]
@@ -379,18 +379,11 @@ class TestCRUD:
379
379
  total = sum(len(c) for c in chunks)
380
380
  assert total == 3
381
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
382
+ async def test_yield_per_does_not_retain_consumed_rows(self, session, iface):
383
+ async for chunk in iface.yield_per(session, {"page_size": 2}):
384
+ assert len(session.identity_map) >= len(chunk)
385
+ del chunk
386
+ assert len(session.identity_map) == 0
394
387
 
395
388
  async def test_yield_per_async_survives_chunk_boundary(self):
396
389
  engine = sqlalchemy.ext.asyncio.create_async_engine("sqlite+aiosqlite://")
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
 
3
3
  import pytest
4
-
5
4
  from fastapi_rtk import const
6
5
 
7
6
 
@@ -60,6 +59,7 @@ class TestDefaults:
60
59
  assert const.DEFAULT_API_MAX_PAGE_SIZE == 25
61
60
  assert const.DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE == 100
62
61
  assert const.DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE == 1000
62
+ assert const.DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE == 8192
63
63
 
64
64
  def test_default_token_url(self):
65
65
  assert const.DEFAULT_TOKEN_URL.startswith("/")
@@ -4,6 +4,7 @@ from fastapi_rtk.const import (
4
4
  DEFAULT_ADMIN_ROLE,
5
5
  DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE,
6
6
  DEFAULT_API_DOWNLOAD_MIN_PAGE_SIZE,
7
+ DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE,
7
8
  DEFAULT_API_MAX_PAGE_SIZE,
8
9
  DEFAULT_API_PAGE_SIZE,
9
10
  DEFAULT_LANG_FOLDER,
@@ -70,6 +71,16 @@ class TestGeneralDefaults:
70
71
  def test_api_download_max_page_size_default(self):
71
72
  assert Setting.API_DOWNLOAD_MAX_PAGE_SIZE == DEFAULT_API_DOWNLOAD_MAX_PAGE_SIZE
72
73
 
74
+ def test_api_download_response_chunk_size(self, config_setter):
75
+ config_setter("API_DOWNLOAD_RESPONSE_CHUNK_SIZE", 8192)
76
+ assert Setting.API_DOWNLOAD_RESPONSE_CHUNK_SIZE == 8192
77
+
78
+ def test_api_download_response_chunk_size_default(self):
79
+ assert (
80
+ Setting.API_DOWNLOAD_RESPONSE_CHUNK_SIZE
81
+ == DEFAULT_API_DOWNLOAD_RESPONSE_CHUNK_SIZE
82
+ )
83
+
73
84
  def test_api_download_page_size_default_is_builtin_resolver(self):
74
85
  from fastapi_rtk.api.model_rest_api.services._download_service import (
75
86
  default_download_page_size,