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