fastapi-rtk 2.11.1__tar.gz → 2.11.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 (300) hide show
  1. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/PKG-INFO +1 -1
  2. fastapi_rtk-2.11.2/fastapi_rtk/_version.py +1 -0
  3. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +32 -4
  4. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_info_mixin.py +1 -0
  5. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_typed_base.py +2 -0
  6. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/model_rest_api/test_columns.py +343 -5
  7. fastapi_rtk-2.11.1/fastapi_rtk/_version.py +0 -1
  8. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/.gitignore +0 -0
  9. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/LICENSE +0 -0
  10. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/README.md +0 -0
  11. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/__init__.py +0 -0
  12. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/__init__.py +0 -0
  13. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/base_api.py +0 -0
  14. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
  15. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
  16. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
  17. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
  18. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
  19. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
  20. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
  21. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
  22. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
  23. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
  24. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
  25. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/apis/__init__.py +0 -0
  26. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/apis/info.py +0 -0
  27. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/apis/license.py +0 -0
  28. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/__init__.py +0 -0
  29. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/auth.py +0 -0
  30. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  31. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  32. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  33. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/ldap.py +0 -0
  34. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  35. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  36. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  37. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/strategies/config.py +0 -0
  38. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/strategies/db.py +0 -0
  39. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  40. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/__init__.py +0 -0
  41. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/__init__.py +0 -0
  42. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/column.py +0 -0
  43. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/db.py +0 -0
  44. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  45. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/filters.py +0 -0
  46. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/interface.py +0 -0
  47. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/model.py +0 -0
  48. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/generic/session.py +0 -0
  49. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  50. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/column.py +0 -0
  51. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/db.py +0 -0
  52. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  53. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  54. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  55. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
  56. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
  57. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
  58. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
  59. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  60. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  61. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  62. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  63. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  64. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/filters.py +0 -0
  65. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/interface.py +0 -0
  66. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/model.py +0 -0
  67. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/backends/sqla/session.py +0 -0
  68. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/__init__.py +0 -0
  69. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/db.py +0 -0
  70. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/file_manager.py +0 -0
  71. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/filter.py +0 -0
  72. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/interface.py +0 -0
  73. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/model.py +0 -0
  74. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/bases/session.py +0 -0
  75. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/__init__.py +0 -0
  76. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/cli.py +0 -0
  77. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/__init__.py +0 -0
  78. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
  79. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  80. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  81. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  82. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  83. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  84. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  85. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  86. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  87. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  88. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/export.py +0 -0
  89. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/security.py +0 -0
  90. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/commands/translate.py +0 -0
  91. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/const.py +0 -0
  92. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/decorators.py +0 -0
  93. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/types.py +0 -0
  94. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/cli/utils.py +0 -0
  95. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/config.py +0 -0
  96. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/const.py +0 -0
  97. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/db.py +0 -0
  98. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/decorators.py +0 -0
  99. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/dependencies.py +0 -0
  100. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/exceptions.py +0 -0
  101. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  102. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/file_managers/__init__.py +0 -0
  103. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/file_managers/file_manager.py +0 -0
  104. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/file_managers/image_manager.py +0 -0
  105. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  106. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  107. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/filters.py +0 -0
  108. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/globals.py +0 -0
  109. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/__init__.py +0 -0
  110. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/babel/__init__.py +0 -0
  111. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/babel/cli.py +0 -0
  112. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/babel/config.py +0 -0
  113. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/babel.cfg +0 -0
  114. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/lazy_text.py +0 -0
  115. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/messages.pot +0 -0
  116. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  117. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  118. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  119. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  120. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/manager.py +0 -0
  121. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/middlewares.py +0 -0
  122. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/mixins.py +0 -0
  123. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/models.py +0 -0
  124. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/py.typed +0 -0
  125. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/registry.py +0 -0
  126. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/routers.py +0 -0
  127. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/schemas.py +0 -0
  128. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/__init__.py +0 -0
  129. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/__init__.py +0 -0
  130. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/apis.py +0 -0
  131. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/models.py +0 -0
  132. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
  133. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
  134. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
  135. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
  136. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
  137. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
  138. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
  139. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
  140. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
  141. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/setting.py +0 -0
  142. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/types.py +0 -0
  143. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/__init__.py +0 -0
  144. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/async_task_runner.py +0 -0
  145. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/class_factory.py +0 -0
  146. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/csv_json_converter.py +0 -0
  147. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/deep_merge.py +0 -0
  148. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/extender_mixin.py +0 -0
  149. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  150. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/formatter.py +0 -0
  151. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/hooks.py +0 -0
  152. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/lazy.py +0 -0
  153. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/merge_schema.py +0 -0
  154. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  155. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/pydantic.py +0 -0
  156. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/run_utils.py +0 -0
  157. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/self_dependencies.py +0 -0
  158. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  159. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/sqla.py +0 -0
  160. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/timezone.py +0 -0
  161. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/update_signature.py +0 -0
  162. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  163. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/fastapi_rtk/version.py +0 -0
  164. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/pyproject.toml +0 -0
  165. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/conftest.py +0 -0
  166. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/docker-compose.gis.yml +0 -0
  167. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/__init__.py +0 -0
  168. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/api/__init__.py +0 -0
  169. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/api/test_model_rest_api.py +0 -0
  170. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/apis/__init__.py +0 -0
  171. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/apis/test_info_callable_schema.py +0 -0
  172. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/apis/test_info_contract.py +0 -0
  173. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/auth/__init__.py +0 -0
  174. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/auth/test_auth_flow.py +0 -0
  175. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/auth/test_oauth.py +0 -0
  176. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/conftest.py +0 -0
  177. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/file_managers/__init__.py +0 -0
  178. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/file_managers/test_file_routes.py +0 -0
  179. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/security/__init__.py +0 -0
  180. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/security/sqla/__init__.py +0 -0
  181. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/security/sqla/test_security_manager.py +0 -0
  182. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/integration/test_app_smoke.py +0 -0
  183. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/__init__.py +0 -0
  184. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/__init__.py +0 -0
  185. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/conftest.py +0 -0
  186. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/model_rest_api/__init__.py +0 -0
  187. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
  188. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/model_rest_api/test_init.py +0 -0
  189. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
  190. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/api/test_base_api.py +0 -0
  191. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/apis/__init__.py +0 -0
  192. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/apis/test_info.py +0 -0
  193. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/apis/test_license.py +0 -0
  194. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/__init__.py +0 -0
  195. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/hashers/__init__.py +0 -0
  196. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
  197. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/hashers/test_scrypt.py +0 -0
  198. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/password_helpers/__init__.py +0 -0
  199. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/password_helpers/test_fab.py +0 -0
  200. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/strategies/__init__.py +0 -0
  201. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/strategies/test_config.py +0 -0
  202. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/strategies/test_db.py +0 -0
  203. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/strategies/test_jwt.py +0 -0
  204. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/auth/test_auth.py +0 -0
  205. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/__init__.py +0 -0
  206. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/__init__.py +0 -0
  207. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_column.py +0 -0
  208. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_db.py +0 -0
  209. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_exceptions.py +0 -0
  210. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_filters.py +0 -0
  211. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_interface.py +0 -0
  212. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_model.py +0 -0
  213. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/generic/test_session.py +0 -0
  214. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/__init__.py +0 -0
  215. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/conftest.py +0 -0
  216. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_audit.py +0 -0
  217. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_audit_types.py +0 -0
  218. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_column.py +0 -0
  219. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_db.py +0 -0
  220. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_filters.py +0 -0
  221. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
  222. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_interface.py +0 -0
  223. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_model.py +0 -0
  224. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/backends/sqla/test_session.py +0 -0
  225. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/__init__.py +0 -0
  226. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_db.py +0 -0
  227. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_file_manager.py +0 -0
  228. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_filter.py +0 -0
  229. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_interface.py +0 -0
  230. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_model.py +0 -0
  231. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/bases/test_session.py +0 -0
  232. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/__init__.py +0 -0
  233. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/__init__.py +0 -0
  234. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/conftest.py +0 -0
  235. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/test_db.py +0 -0
  236. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/test_export.py +0 -0
  237. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/test_security.py +0 -0
  238. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/test_security_crypto.py +0 -0
  239. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/commands/test_translate.py +0 -0
  240. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/conftest.py +0 -0
  241. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/test_cli.py +0 -0
  242. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/test_decorators.py +0 -0
  243. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/test_types.py +0 -0
  244. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/cli/test_utils.py +0 -0
  245. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/__init__.py +0 -0
  246. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/conftest.py +0 -0
  247. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/test_file_manager.py +0 -0
  248. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/test_image_manager.py +0 -0
  249. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
  250. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
  251. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/lang/__init__.py +0 -0
  252. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/lang/test_babel_cli.py +0 -0
  253. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/lang/test_babel_config.py +0 -0
  254. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/lang/test_lazy_text.py +0 -0
  255. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/__init__.py +0 -0
  256. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/sqla/__init__.py +0 -0
  257. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/sqla/conftest.py +0 -0
  258. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/sqla/test_apis.py +0 -0
  259. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/sqla/test_models.py +0 -0
  260. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/security/sqla/test_security_manager.py +0 -0
  261. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_config.py +0 -0
  262. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_const.py +0 -0
  263. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_db.py +0 -0
  264. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_decorators.py +0 -0
  265. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_dependencies.py +0 -0
  266. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_exceptions.py +0 -0
  267. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_fastapi_react_toolkit.py +0 -0
  268. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_filters.py +0 -0
  269. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_globals.py +0 -0
  270. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_manager.py +0 -0
  271. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_middlewares.py +0 -0
  272. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_mixins.py +0 -0
  273. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_models.py +0 -0
  274. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_registry.py +0 -0
  275. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_routers.py +0 -0
  276. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_schemas.py +0 -0
  277. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_setting.py +0 -0
  278. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_types.py +0 -0
  279. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/test_version.py +0 -0
  280. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/__init__.py +0 -0
  281. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_async_task_runner.py +0 -0
  282. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_class_factory.py +0 -0
  283. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_csv_json_converter.py +0 -0
  284. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_deep_merge.py +0 -0
  285. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_extender_mixin.py +0 -0
  286. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
  287. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_formatter.py +0 -0
  288. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_hooks.py +0 -0
  289. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_lazy.py +0 -0
  290. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_merge_schema.py +0 -0
  291. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
  292. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_pydantic.py +0 -0
  293. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_run_utils.py +0 -0
  294. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_self_dependencies.py +0 -0
  295. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_smartdefaultdict.py +0 -0
  296. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_sqla.py +0 -0
  297. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_timezone.py +0 -0
  298. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_update_signature.py +0 -0
  299. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/tests/unit/utils/test_use_default_when_none.py +0 -0
  300. {fastapi_rtk-2.11.1 → fastapi_rtk-2.11.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 2.11.1
3
+ Version: 2.11.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.11.2"
@@ -47,6 +47,8 @@ from ...utils import (
47
47
  )
48
48
  from ._typed_base import _TypedModelRestApi
49
49
 
50
+ _UNUSED_SCHEMA_KEYS = ("anyOf", "default", "$ref", "const")
51
+
50
52
 
51
53
  class ColumnsMixin(_TypedModelRestApi):
52
54
  """
@@ -546,6 +548,8 @@ class ColumnsMixin(_TypedModelRestApi):
546
548
  [ColumnInfo | ColumnEnumInfo | ColumnRelationInfo | None], bool
547
549
  ],
548
550
  ] = False,
551
+ *,
552
+ mark_non_nullable_required: bool = False,
549
553
  ):
550
554
  """
551
555
  Generate the JSONForms schema for the API.
@@ -555,6 +559,7 @@ class ColumnsMixin(_TypedModelRestApi):
555
559
  info_columns (list[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo]): The list of columns to include in the JSONForms schema.
556
560
  use_enum (bool | Callable[[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo], bool] | dict[str, bool | Callable[[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo], bool]], optional): A flag or a callable or a dictionary to determine whether to use enum instead of oneOf. Defaults to False.
557
561
  list_as_object (bool | Callable[[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo | None], bool] | dict[str, bool | Callable[[ColumnInfo | ColumnEnumInfo | ColumnRelationInfo | None], bool]], optional): A flag or a callable or a dictionary to determine whether to render a list column as a single `object` control instead of the per-item `array` table. The callable receives the column's `ColumnInfo` when available, else `None`. Defaults to False.
562
+ mark_non_nullable_required (bool, optional): Whether to also mark every non-nullable column present in `properties` as required, in addition to whatever pydantic already marked, excluding `password`-formatted properties, which are never sent back to the client and would force a retype on every edit, and `readOnly` properties, which cannot be filled at all. Defaults to False.
558
563
 
559
564
  Returns:
560
565
  dict[str, typing.Any]: The generated JSONForms schema.
@@ -594,8 +599,15 @@ class ColumnsMixin(_TypedModelRestApi):
594
599
  )
595
600
  if self.datamodel.is_files(key) or self.datamodel.is_images(key):
596
601
  current_value = value.copy()
602
+ for unused_key in _UNUSED_SCHEMA_KEYS:
603
+ current_value.pop(unused_key, None)
604
+ # JsonForms seeds an inserted row from this default, and a null row must fail so an empty slot cannot be submitted.
605
+ current_value["default"] = None
597
606
  value["type"] = "array"
598
607
  value["items"] = current_value
608
+ # The file keys belong on the leaf; nothing reads them off the array wrapper.
609
+ value.pop("x-file-types", None)
610
+ value.pop("contentMediaType", None)
599
611
  elif self.datamodel.is_boolean(key):
600
612
  value["type"] = "boolean"
601
613
  elif self.datamodel.is_integer(key):
@@ -676,12 +688,15 @@ class ColumnsMixin(_TypedModelRestApi):
676
688
  # Check whether it is nullable
677
689
  if self.datamodel.is_nullable(key):
678
690
  value["type"] = [value["type"], "null"]
691
+ # With setNull on, a cleared field writes a real null, which ajv checks against enum / oneOf regardless of the null type member.
692
+ if "enum" in value:
693
+ value["enum"] = [None, *value["enum"]]
694
+ elif "oneOf" in value:
695
+ value["oneOf"] = [{"const": None, "title": ""}, *value["oneOf"]]
679
696
 
680
697
  # Remove unused vars
681
- value.pop("anyOf", None)
682
- value.pop("default", None)
683
- value.pop("$ref", None)
684
- value.pop("const", None)
698
+ for unused_key in _UNUSED_SCHEMA_KEYS:
699
+ value.pop(unused_key, None)
685
700
 
686
701
  # Check whether the value should be a `secret`
687
702
  if key in g.sensitive_data.get(self.datamodel.obj.__name__, []):
@@ -689,4 +704,17 @@ class ColumnsMixin(_TypedModelRestApi):
689
704
 
690
705
  result["properties"][key] = value
691
706
 
707
+ if mark_non_nullable_required:
708
+ pydantic_required = result.get("required", [])
709
+ added_required = [
710
+ key
711
+ for key, value in result["properties"].items()
712
+ # A password is never sent back to the client and a readOnly field cannot be filled, so requiring either would fail the moment the form opens.
713
+ if key not in pydantic_required
714
+ and not self.datamodel.is_nullable(key)
715
+ and value.get("format") != "password"
716
+ and not value.get("readOnly")
717
+ ]
718
+ result["required"] = [*pydantic_required, *added_required]
719
+
692
720
  return result
@@ -194,6 +194,7 @@ class InfoMixin(_TypedModelRestApi):
194
194
  schema.edit_columns,
195
195
  self.edit_jsonforms_use_enum,
196
196
  self.edit_jsonforms_list_as_object,
197
+ mark_non_nullable_required=True,
197
198
  )
198
199
  )
199
200
  schema.edit_uischema = self.edit_jsonforms_uischema
@@ -204,6 +204,8 @@ class _TypedModelRestApi:
204
204
  [ColumnInfo | ColumnEnumInfo | ColumnRelationInfo | None], bool
205
205
  ],
206
206
  ] = False,
207
+ *,
208
+ mark_non_nullable_required: bool = False,
207
209
  ) -> dict[str, typing.Any]: ...
208
210
 
209
211
  def _get_datamodel_and_column(self, col: str) -> tuple[SQLAInterface, str]: ...
@@ -6,11 +6,11 @@ Covers the prettify statics, label-column auto-generation,
6
6
  ``_get_datamodel_and_column``, and the ``_jsonforms_*`` schema helpers.
7
7
  """
8
8
 
9
+ import enum
9
10
  import typing
10
11
 
11
12
  import pydantic
12
13
  import pytest
13
-
14
14
  from fastapi_rtk.api.model_rest_api import ModelRestApi
15
15
  from fastapi_rtk.backends.sqla.interface import SQLAInterface
16
16
  from fastapi_rtk.schemas import ColumnRelationInfo, RelInfo
@@ -121,7 +121,6 @@ class TestGetDatamodelAndColumn:
121
121
  @pytest.fixture
122
122
  def Country_cls():
123
123
  import sqlalchemy
124
-
125
124
  from fastapi_rtk.backends.sqla.model import Model
126
125
 
127
126
  if "tier12_country" in Model.metadata.tables:
@@ -140,7 +139,6 @@ def Country_cls():
140
139
  @pytest.fixture
141
140
  def WidgetWithCountry_cls(Country_cls):
142
141
  import sqlalchemy.orm
143
-
144
142
  from fastapi_rtk.backends.sqla.model import Model
145
143
 
146
144
  if "tier12_widget_country" in Model.metadata.tables:
@@ -289,7 +287,6 @@ class TestGenerateJsonformsSchemaRelationReadOnly:
289
287
  def m2m_api(self):
290
288
  import sqlalchemy
291
289
  import sqlalchemy.orm
292
-
293
290
  from fastapi_rtk.backends.sqla.model import Model
294
291
 
295
292
  assoc = sqlalchemy.Table(
@@ -362,7 +359,6 @@ class TestGenerateJsonformsSchemaListBranch:
362
359
  @pytest.fixture(scope="class")
363
360
  def list_api(self):
364
361
  import sqlalchemy
365
-
366
362
  from fastapi_rtk import (
367
363
  JSONBListColumn,
368
364
  ListColumn,
@@ -457,3 +453,345 @@ class TestGenerateJsonformsSchemaListBranch:
457
453
  result = list_api._generate_jsonforms_schema(schema, [], False)
458
454
  assert result["properties"]["tags"]["type"] == ["array", "null"]
459
455
  assert result["properties"]["tags"]["items"] == {"type": "string"}
456
+
457
+
458
+ class TestGenerateJsonformsSchemaFileBranch:
459
+ """A multi-file / multi-image column's `items` sub-schema must be the
460
+ cleaned leaf schema (no `anyOf` leftover from the pydantic union), not a
461
+ copy taken before that cleanup runs. It also carries a deliberate
462
+ `default: null` so a freshly inserted row fails validation until a file
463
+ is picked."""
464
+
465
+ @pytest.fixture
466
+ def file_api(self, tmp_path, config_setter):
467
+ import sqlalchemy
468
+ from fastapi_rtk.backends.sqla.model import Model
469
+ from fastapi_rtk.types import FileColumn, ImageColumns
470
+
471
+ upload_folder = tmp_path / "uploads"
472
+ img_folder = tmp_path / "img_uploads"
473
+ upload_folder.mkdir()
474
+ img_folder.mkdir()
475
+ config_setter("UPLOAD_FOLDER", str(upload_folder))
476
+ config_setter("IMG_UPLOAD_FOLDER", str(img_folder))
477
+
478
+ class _TierFileModel(Model):
479
+ __tablename__ = "tier12_file_model"
480
+ __table_args__ = {"extend_existing": True}
481
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
482
+ document = sqlalchemy.Column(FileColumn(allowed_extensions=["txt", "pdf"]))
483
+ gallery = sqlalchemy.Column(ImageColumns(allowed_extensions=["png", "jpg"]))
484
+
485
+ class TierFileApi(ModelRestApi):
486
+ datamodel = SQLAInterface(_TierFileModel)
487
+
488
+ return TierFileApi()
489
+
490
+ @pytest.fixture
491
+ def file_schema_result(self, file_api):
492
+ schema = file_api.datamodel.generate_schema(
493
+ columns=["document", "gallery"],
494
+ with_id=False,
495
+ with_name=False,
496
+ with_property=False,
497
+ optional=True,
498
+ hide_sensitive_columns=False,
499
+ is_form_data=True,
500
+ name="TierFileFormData",
501
+ )
502
+ return file_api._generate_jsonforms_schema(schema, [])
503
+
504
+ def test_multi_image_items_is_the_cleaned_leaf_with_null_default(
505
+ self, file_api, file_schema_result
506
+ ):
507
+ from fastapi_rtk.setting import Setting
508
+
509
+ gallery = file_schema_result["properties"]["gallery"]
510
+ assert "anyOf" not in gallery["items"]
511
+ assert gallery["items"] == {
512
+ "title": file_api.label_columns["gallery"],
513
+ "type": "string",
514
+ "default": None,
515
+ "x-file-types": [".png", ".jpg"],
516
+ "contentMediaType": ",".join(Setting.IMG_MIME_TYPES),
517
+ }
518
+
519
+ def test_multi_image_does_not_duplicate_file_keys_on_the_array(
520
+ self, file_schema_result
521
+ ):
522
+ gallery = file_schema_result["properties"]["gallery"]
523
+ assert "x-file-types" not in gallery
524
+ assert "contentMediaType" not in gallery
525
+ assert "x-file-types" in gallery["items"]
526
+ assert "contentMediaType" in gallery["items"]
527
+
528
+ def test_multi_image_own_type_is_array_or_null(self, file_schema_result):
529
+ gallery = file_schema_result["properties"]["gallery"]
530
+ assert gallery["type"] == ["array", "null"]
531
+ assert "anyOf" not in gallery
532
+
533
+ def test_single_file_has_no_items_or_anyof(self, file_schema_result):
534
+ document = file_schema_result["properties"]["document"]
535
+ assert "anyOf" not in document
536
+ assert "items" not in document
537
+ assert document["type"] == ["string", "null"]
538
+
539
+
540
+ class TestGenerateJsonformsSchemaNullableConstrainedValues:
541
+ """A nullable field with a constrained value set (enum / oneOf) must gain an
542
+ empty member alongside the ``null`` type, or AJV reports an option-mismatch
543
+ error the moment the frontend clears the field.
544
+ """
545
+
546
+ @pytest.fixture(scope="class")
547
+ def enum_api(self):
548
+ import sqlalchemy
549
+ from fastapi_rtk.backends.sqla.model import Model
550
+
551
+ class _TierStatus(enum.Enum):
552
+ ACTIVE = "active"
553
+ INACTIVE = "inactive"
554
+
555
+ class _TierEnumModel(Model):
556
+ __tablename__ = "tier12_enum_model"
557
+ __table_args__ = {"extend_existing": True}
558
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
559
+ status = sqlalchemy.Column(sqlalchemy.Enum(_TierStatus), nullable=True)
560
+ status_required = sqlalchemy.Column(
561
+ sqlalchemy.Enum(_TierStatus), nullable=False
562
+ )
563
+
564
+ class TierEnumApi(ModelRestApi):
565
+ datamodel = SQLAInterface(_TierEnumModel)
566
+
567
+ return TierEnumApi()
568
+
569
+ def test_nullable_enum_gets_null_type_and_member(self, enum_api):
570
+ schema = enum_api.add_obj_schema
571
+ result = enum_api._generate_jsonforms_schema(schema, [], False)
572
+ status = result["properties"]["status"]
573
+ assert status["type"] == ["string", "null"]
574
+ assert status["enum"][0] is None
575
+ assert set(status["enum"][1:]) == {"active", "inactive"}
576
+
577
+ def test_non_nullable_enum_has_no_null_member(self, enum_api):
578
+ schema = enum_api.add_obj_schema
579
+ result = enum_api._generate_jsonforms_schema(schema, [], False)
580
+ status_required = result["properties"]["status_required"]
581
+ assert None not in status_required["enum"]
582
+
583
+ def test_repeated_calls_do_not_grow_the_enum(self, enum_api):
584
+ """Regression: ``get_enum_value`` hands back the SQLAlchemy column's own
585
+ ``enums`` list, so mutating it in place would corrupt it for every later
586
+ request instead of just the current schema dict.
587
+ """
588
+ schema = enum_api.add_obj_schema
589
+ enum_api._generate_jsonforms_schema(schema, [], False)
590
+ result = enum_api._generate_jsonforms_schema(schema, [], False)
591
+ status = result["properties"]["status"]
592
+ assert status["enum"].count(None) == 1
593
+
594
+ @pytest.fixture
595
+ def relation_api(self, WidgetWithCountry_cls):
596
+ class TierRelationApi(ModelRestApi):
597
+ datamodel = SQLAInterface(WidgetWithCountry_cls)
598
+
599
+ return TierRelationApi()
600
+
601
+ def test_nullable_relation_with_use_enum_gets_null_member(self, relation_api):
602
+ schema = relation_api.add_obj_schema
603
+ info = [
604
+ ColumnRelationInfo(name="country", values=[RelInfo(id=1, value="Germany")])
605
+ ]
606
+ result = relation_api._generate_jsonforms_schema(schema, info, use_enum=True)
607
+ assert result["properties"]["country"]["enum"][0] is None
608
+
609
+ def test_nullable_relation_without_use_enum_gets_null_oneof_member(
610
+ self, relation_api
611
+ ):
612
+ schema = relation_api.add_obj_schema
613
+ info = [
614
+ ColumnRelationInfo(name="country", values=[RelInfo(id=1, value="Germany")])
615
+ ]
616
+ result = relation_api._generate_jsonforms_schema(schema, info, False)
617
+ assert result["properties"]["country"]["oneOf"][0] == {
618
+ "const": None,
619
+ "title": "",
620
+ }
621
+
622
+ @pytest.fixture(scope="class")
623
+ def m2m_relation_api(self):
624
+ import sqlalchemy
625
+ import sqlalchemy.orm
626
+ from fastapi_rtk.backends.sqla.model import Model
627
+
628
+ assoc = sqlalchemy.Table(
629
+ "tier12_nullable_m2m_assoc",
630
+ Model.metadata,
631
+ sqlalchemy.Column(
632
+ "widget_id",
633
+ sqlalchemy.ForeignKey("tier12_nullable_m2m_widget.id"),
634
+ primary_key=True,
635
+ ),
636
+ sqlalchemy.Column(
637
+ "tag_id",
638
+ sqlalchemy.ForeignKey("tier12_nullable_m2m_tag.id"),
639
+ primary_key=True,
640
+ ),
641
+ extend_existing=True,
642
+ )
643
+
644
+ class _NullableM2MTag(Model):
645
+ __tablename__ = "tier12_nullable_m2m_tag"
646
+ __table_args__ = {"extend_existing": True}
647
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
648
+ name = sqlalchemy.Column(sqlalchemy.String(50))
649
+
650
+ class _NullableM2MWidget(Model):
651
+ __tablename__ = "tier12_nullable_m2m_widget"
652
+ __table_args__ = {"extend_existing": True}
653
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
654
+ name = sqlalchemy.Column(sqlalchemy.String(50))
655
+ tags = sqlalchemy.orm.relationship(_NullableM2MTag, secondary=assoc)
656
+
657
+ class TierNullableM2MApi(ModelRestApi):
658
+ datamodel = SQLAInterface(_NullableM2MWidget)
659
+
660
+ return TierNullableM2MApi()
661
+
662
+ def test_nullable_to_many_relation_items_have_no_null_member(
663
+ self, m2m_relation_api
664
+ ):
665
+ schema = m2m_relation_api.add_obj_schema
666
+ info = [ColumnRelationInfo(name="tags", values=[RelInfo(id=1, value="a")])]
667
+ result = m2m_relation_api._generate_jsonforms_schema(schema, info, False)
668
+ tags = result["properties"]["tags"]
669
+ assert tags["type"] == ["array", "null"]
670
+ assert None not in [item.get("const") for item in tags["items"]["oneOf"]]
671
+
672
+
673
+ class TestGenerateJsonformsSchemaMarkNonNullableRequired:
674
+ """``mark_non_nullable_required`` lets the EDIT schema report a missing
675
+ non-nullable value itself, instead of the frontend inventing that error.
676
+ """
677
+
678
+ @pytest.fixture(scope="class")
679
+ def enum_api(self):
680
+ import sqlalchemy
681
+ from fastapi_rtk.backends.sqla.model import Model
682
+
683
+ class _TierRequiredStatus(enum.Enum):
684
+ ACTIVE = "active"
685
+ INACTIVE = "inactive"
686
+
687
+ class _TierRequiredModel(Model):
688
+ __tablename__ = "tier12_required_model"
689
+ __table_args__ = {"extend_existing": True}
690
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
691
+ status = sqlalchemy.Column(
692
+ sqlalchemy.Enum(_TierRequiredStatus), nullable=True
693
+ )
694
+ status_required = sqlalchemy.Column(
695
+ sqlalchemy.Enum(_TierRequiredStatus), nullable=False
696
+ )
697
+
698
+ class TierRequiredApi(ModelRestApi):
699
+ datamodel = SQLAInterface(_TierRequiredModel)
700
+
701
+ return TierRequiredApi()
702
+
703
+ @pytest.fixture
704
+ def excluded_required_api(self, Widget_cls):
705
+ class TierExcludedRequiredApi(ModelRestApi):
706
+ datamodel = SQLAInterface(Widget_cls)
707
+ edit_columns = ["description", "quantity"]
708
+
709
+ return TierExcludedRequiredApi()
710
+
711
+ def test_default_leaves_required_as_pydantic_emitted(self, enum_api):
712
+ schema = enum_api.edit_obj_schema
713
+ result = enum_api._generate_jsonforms_schema(schema, [])
714
+ assert result.get("required", []) == schema.model_json_schema().get(
715
+ "required", []
716
+ )
717
+
718
+ def test_flag_marks_every_non_nullable_column_present_as_required(self, enum_api):
719
+ schema = enum_api.edit_obj_schema
720
+ result = enum_api._generate_jsonforms_schema(
721
+ schema, [], mark_non_nullable_required=True
722
+ )
723
+ assert "status_required" in result["required"]
724
+ assert "status" not in result["required"]
725
+
726
+ def test_flag_does_not_duplicate_an_already_required_column(self, enum_api):
727
+ schema = enum_api.add_obj_schema
728
+ result = enum_api._generate_jsonforms_schema(
729
+ schema, [], mark_non_nullable_required=True
730
+ )
731
+ assert result["required"].count("status_required") == 1
732
+
733
+ def test_flag_skips_non_nullable_column_absent_from_properties(
734
+ self, excluded_required_api
735
+ ):
736
+ schema = excluded_required_api.edit_obj_schema
737
+ result = excluded_required_api._generate_jsonforms_schema(
738
+ schema, [], mark_non_nullable_required=True
739
+ )
740
+ assert "name" not in result["properties"]
741
+ assert "name" not in result["required"]
742
+
743
+ def test_repeated_calls_with_flag_yield_identical_required(self, enum_api):
744
+ schema = enum_api.edit_obj_schema
745
+ first = enum_api._generate_jsonforms_schema(
746
+ schema, [], mark_non_nullable_required=True
747
+ )
748
+ second = enum_api._generate_jsonforms_schema(
749
+ schema, [], mark_non_nullable_required=True
750
+ )
751
+ assert first["required"] == second["required"]
752
+
753
+ def test_flag_skips_a_non_nullable_password_column(self, enum_api):
754
+ from fastapi_rtk.globals import g
755
+
756
+ model_name = enum_api.datamodel.obj.__name__
757
+ original = g.sensitive_data
758
+ g.sensitive_data = {**original, model_name: ["status_required"]}
759
+ try:
760
+ schema = enum_api.edit_obj_schema
761
+ result = enum_api._generate_jsonforms_schema(
762
+ schema, [], mark_non_nullable_required=True
763
+ )
764
+ assert result["properties"]["status_required"]["format"] == "password"
765
+ assert "status_required" not in result["required"]
766
+ finally:
767
+ g.sensitive_data = original
768
+
769
+ def test_flag_skips_a_non_nullable_readonly_relation(self, Country_cls):
770
+ import sqlalchemy
771
+ import sqlalchemy.orm
772
+ from fastapi_rtk.backends.sqla.model import Model
773
+
774
+ class TierWidgetRequiredCountry(Model):
775
+ __tablename__ = "tier12_widget_required_country"
776
+ __table_args__ = {"extend_existing": True}
777
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
778
+ name = sqlalchemy.Column(sqlalchemy.String(50))
779
+ country_id = sqlalchemy.Column(
780
+ sqlalchemy.Integer,
781
+ sqlalchemy.ForeignKey("tier12_country.id"),
782
+ nullable=False,
783
+ )
784
+ country = sqlalchemy.orm.relationship(Country_cls)
785
+
786
+ class TierRequiredRelationApi(ModelRestApi):
787
+ datamodel = SQLAInterface(TierWidgetRequiredCountry)
788
+ edit_columns = ["name", "country"]
789
+
790
+ api = TierRequiredRelationApi()
791
+ schema = api.edit_obj_schema
792
+ info = [ColumnRelationInfo(name="country", values=[])]
793
+ result = api._generate_jsonforms_schema(
794
+ schema, info, mark_non_nullable_required=True
795
+ )
796
+ assert result["properties"]["country"]["readOnly"] is True
797
+ assert "country" not in result["required"]
@@ -1 +0,0 @@
1
- __version__ = "2.11.1"
File without changes
File without changes
File without changes