fastapi-rtk 2.10.2__tar.gz → 2.11.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/PKG-INFO +1 -1
- fastapi_rtk-2.11.1/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/filters.py +5 -1
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/filters.py +39 -17
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/cli.py +45 -1
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/models.py +9 -3
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_filters.py +12 -1
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_filters.py +60 -0
- fastapi_rtk-2.11.1/tests/unit/cli/test_cli.py +172 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/sqla/test_models.py +7 -0
- fastapi_rtk-2.10.2/fastapi_rtk/_version.py +0 -1
- fastapi_rtk-2.10.2/tests/unit/cli/test_cli.py +0 -117
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/.gitignore +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/LICENSE +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/README.md +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_info_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_typed_base.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/apis/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/apis/info.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/apis/license.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/ldap.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/const.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/globals.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/py.typed +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/registry.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/apis.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/formatter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/pyproject.toml +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/docker-compose.gis.yml +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/api/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/api/test_model_rest_api.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/apis/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/apis/test_info_callable_schema.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/apis/test_info_contract.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/auth/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/auth/test_auth_flow.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/auth/test_oauth.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/file_managers/test_file_routes.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/security/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/integration/test_app_smoke.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/model_rest_api/test_columns.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/model_rest_api/test_init.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/api/test_base_api.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/apis/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/apis/test_info.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/apis/test_license.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/hashers/test_scrypt.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/password_helpers/test_fab.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/strategies/test_config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/strategies/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/strategies/test_jwt.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/auth/test_auth.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_column.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_exceptions.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/generic/test_session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_audit.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_audit_types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_column.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/backends/sqla/test_session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_filter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_interface.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_model.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/bases/test_session.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/test_export.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/test_security.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/test_security_crypto.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/commands/test_translate.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/test_decorators.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/test_types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/cli/test_utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/test_file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/test_image_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/lang/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/lang/test_babel_cli.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/lang/test_babel_config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/lang/test_lazy_text.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/sqla/test_apis.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_config.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_const.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_db.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_decorators.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_dependencies.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_exceptions.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_filters.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_globals.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_manager.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_middlewares.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_mixins.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_models.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_registry.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_routers.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_schemas.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_setting.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_types.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/test_version.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/__init__.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_async_task_runner.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_class_factory.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_csv_json_converter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_deep_merge.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_extender_mixin.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_formatter.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_hooks.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_lazy.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_merge_schema.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_pydantic.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_run_utils.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_self_dependencies.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_smartdefaultdict.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_sqla.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_timezone.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_update_signature.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/tests/unit/utils/test_use_default_when_none.py +0 -0
- {fastapi_rtk-2.10.2 → fastapi_rtk-2.11.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.1
|
|
4
4
|
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.11.1"
|
|
@@ -64,7 +64,11 @@ class GenericBaseFilter(AbstractBaseFilter[GenericSession]):
|
|
|
64
64
|
if self.datamodel.is_float(col):
|
|
65
65
|
return float(value)
|
|
66
66
|
if self.datamodel.is_boolean(col):
|
|
67
|
-
|
|
67
|
+
if isinstance(value, bool):
|
|
68
|
+
return value
|
|
69
|
+
return value == 1 or (
|
|
70
|
+
isinstance(value, str) and value.lower() in ["true", "1"]
|
|
71
|
+
)
|
|
68
72
|
if self.datamodel.is_date(col):
|
|
69
73
|
return datetime.datetime.fromisoformat(value).date()
|
|
70
74
|
if self.datamodel.is_datetime(col):
|
|
@@ -42,6 +42,10 @@ __all__ = [
|
|
|
42
42
|
]
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
# Oracle needs a length on the CAST target: CAST(x AS VARCHAR2) is ORA-00906.
|
|
46
|
+
_TEXT_CAST_TYPE = sa_types.String().with_variant(sa_types.String(4000), "oracle")
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
class BaseFilter(AbstractBaseFilter[sqlalchemy.Select[Model]]):
|
|
46
50
|
def _get_column(self, col_name: str) -> sqlalchemy.Column:
|
|
47
51
|
"""
|
|
@@ -121,7 +125,11 @@ class BaseFilter(AbstractBaseFilter[sqlalchemy.Select[Model]]):
|
|
|
121
125
|
if self.datamodel.is_float(col):
|
|
122
126
|
return float(value)
|
|
123
127
|
if self.datamodel.is_boolean(col):
|
|
124
|
-
|
|
128
|
+
if isinstance(value, bool):
|
|
129
|
+
return value
|
|
130
|
+
return value == 1 or (
|
|
131
|
+
isinstance(value, str) and value.lower() in ["true", "1"]
|
|
132
|
+
)
|
|
125
133
|
if self.datamodel.is_date(
|
|
126
134
|
col
|
|
127
135
|
): # pragma: no cover - exercised via integration filter tests only
|
|
@@ -139,6 +147,28 @@ class BaseFilter(AbstractBaseFilter[sqlalchemy.Select[Model]]):
|
|
|
139
147
|
|
|
140
148
|
|
|
141
149
|
class BaseFilterTextContains(BaseFilter):
|
|
150
|
+
def _build_concat_expr(self, col_objs: list[sqlalchemy.ColumnElement]):
|
|
151
|
+
"""
|
|
152
|
+
Concatenate columns as text, separated by a space.
|
|
153
|
+
|
|
154
|
+
Chains ``||`` instead of ``concat(*args)``: Oracle's CONCAT takes exactly two
|
|
155
|
+
arguments and other databases cap function-call args, which wide tables exceed.
|
|
156
|
+
``coalesce`` keeps concat's NULL-as-empty.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
col_objs (list[sqlalchemy.ColumnElement]): Columns to concatenate.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
sqlalchemy.ColumnElement | None: The concatenation, or None when col_objs is empty.
|
|
163
|
+
"""
|
|
164
|
+
concat_expr = None
|
|
165
|
+
for col_obj in col_objs:
|
|
166
|
+
piece = sqlalchemy.func.coalesce(
|
|
167
|
+
sqlalchemy.cast(col_obj, _TEXT_CAST_TYPE), ""
|
|
168
|
+
)
|
|
169
|
+
concat_expr = piece if concat_expr is None else concat_expr + " " + piece
|
|
170
|
+
return concat_expr
|
|
171
|
+
|
|
142
172
|
def _handle_relation_contains(self, col: str, value: str):
|
|
143
173
|
sub_col = ""
|
|
144
174
|
if "." in col:
|
|
@@ -164,16 +194,14 @@ class BaseFilterTextContains(BaseFilter):
|
|
|
164
194
|
else:
|
|
165
195
|
# If there is no sub-column, we filter all columns in the relation
|
|
166
196
|
rel_cols = rel_interface.list_columns.keys()
|
|
167
|
-
concat = []
|
|
168
197
|
rel_obj = rel_interface.obj
|
|
169
198
|
rel_pks = rel_interface.get_pk_attrs()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
concat.append(" ")
|
|
199
|
+
concat_expr = self._build_concat_expr(
|
|
200
|
+
[getattr(rel_obj, col_name) for col_name in rel_cols]
|
|
201
|
+
)
|
|
174
202
|
rel_statements = [
|
|
175
203
|
sqlalchemy.select(getattr(rel_obj, pk)).filter(
|
|
176
|
-
|
|
204
|
+
concat_expr.ilike("%" + value + "%")
|
|
177
205
|
)
|
|
178
206
|
for pk in rel_pks
|
|
179
207
|
]
|
|
@@ -206,7 +234,7 @@ class BaseFilterTextContains(BaseFilter):
|
|
|
206
234
|
if any(value in keywords for keywords in true_keywords)
|
|
207
235
|
else False
|
|
208
236
|
)
|
|
209
|
-
return sqlalchemy.cast(self._cast_column(col),
|
|
237
|
+
return sqlalchemy.cast(self._cast_column(col), _TEXT_CAST_TYPE).ilike(
|
|
210
238
|
"%" + value + "%"
|
|
211
239
|
)
|
|
212
240
|
|
|
@@ -487,9 +515,7 @@ class FilterGlobal(BaseFilterTextContains):
|
|
|
487
515
|
filters = []
|
|
488
516
|
|
|
489
517
|
for val in value:
|
|
490
|
-
|
|
491
|
-
# which wide tables exceed. coalesce keeps concat's NULL-as-empty.
|
|
492
|
-
concat_expr = None
|
|
518
|
+
concat_cols = []
|
|
493
519
|
rel_filter_arr = []
|
|
494
520
|
for col in cols:
|
|
495
521
|
if (
|
|
@@ -499,13 +525,9 @@ class FilterGlobal(BaseFilterTextContains):
|
|
|
499
525
|
):
|
|
500
526
|
rel_filter_arr.append(self._handle_text_contains(col, val))
|
|
501
527
|
elif col in self.datamodel.list_columns:
|
|
502
|
-
|
|
503
|
-
sqlalchemy.cast(self._cast_column(col), sa_types.String), ""
|
|
504
|
-
)
|
|
505
|
-
concat_expr = (
|
|
506
|
-
piece if concat_expr is None else concat_expr + " " + piece
|
|
507
|
-
)
|
|
528
|
+
concat_cols.append(self._cast_column(col))
|
|
508
529
|
params = [*rel_filter_arr]
|
|
530
|
+
concat_expr = self._build_concat_expr(concat_cols)
|
|
509
531
|
if concat_expr is not None:
|
|
510
532
|
val = val.strip().lower()
|
|
511
533
|
params.append(concat_expr.ilike("%" + val + "%"))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import pathlib
|
|
3
|
+
import re
|
|
3
4
|
import typing
|
|
4
5
|
import zipfile
|
|
5
6
|
|
|
@@ -35,16 +36,26 @@ def callback(
|
|
|
35
36
|
|
|
36
37
|
@app.command()
|
|
37
38
|
def create_app(
|
|
39
|
+
name: typing.Annotated[
|
|
40
|
+
str,
|
|
41
|
+
typer.Option(
|
|
42
|
+
prompt="Project name",
|
|
43
|
+
help="The name of the new application. Fills the project-name placeholders in the generated files.",
|
|
44
|
+
),
|
|
45
|
+
],
|
|
38
46
|
directory: typing.Annotated[
|
|
39
47
|
str,
|
|
40
48
|
typer.Option(
|
|
41
|
-
...,
|
|
42
49
|
help="The directory where the new FastAPI RTK application will be created.",
|
|
43
50
|
),
|
|
44
51
|
] = ".",
|
|
45
52
|
):
|
|
46
53
|
"""
|
|
47
54
|
Create a new FastAPI RTK application with a predefined structure from https://codeberg.org/datatactics/fastapi-rtk-skeleton.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name (str): The project name; normalized and substituted into the scaffolded project's placeholder files.
|
|
58
|
+
directory (str): Target directory for the new application. Defaults to the current directory.
|
|
48
59
|
"""
|
|
49
60
|
with httpx.Client() as client:
|
|
50
61
|
url = "https://codeberg.org/datatactics/fastapi-rtk-skeleton/archive/main.zip"
|
|
@@ -84,8 +95,41 @@ def create_app(
|
|
|
84
95
|
with open(target_file, "wb") as f:
|
|
85
96
|
f.write(source.read())
|
|
86
97
|
|
|
98
|
+
_apply_project_name(pathlib.Path(directory), name)
|
|
99
|
+
|
|
87
100
|
typer.echo(f"FastAPI RTK application skeleton created in {directory}")
|
|
88
101
|
|
|
89
102
|
|
|
103
|
+
def _apply_project_name(directory: pathlib.Path, name: str) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Substitute the skeleton's project-name placeholders with the given name.
|
|
106
|
+
|
|
107
|
+
Each placeholder receives the casing its file expects: a slug for the
|
|
108
|
+
distribution name, upper snake for the config env var, and the raw name for
|
|
109
|
+
the page title. Missing files are skipped so partial scaffolds do not error.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
directory (pathlib.Path): Root directory of the freshly created application.
|
|
113
|
+
name (str): Raw project name entered by the user.
|
|
114
|
+
"""
|
|
115
|
+
raw = name.strip()
|
|
116
|
+
slug = re.sub(r"[-_.\s]+", "-", raw.lower()).strip("-")
|
|
117
|
+
env = slug.upper().replace("-", "_")
|
|
118
|
+
replacements = {
|
|
119
|
+
"pyproject.toml": [("project-name", slug)],
|
|
120
|
+
"requirements.txt": [("project-name", slug)],
|
|
121
|
+
"app/__init__.py": [("PROJECTNAME", env)],
|
|
122
|
+
"webapp/index.html": [("YOUR_APP_NAME_HERE", raw)],
|
|
123
|
+
}
|
|
124
|
+
for relative_path, subs in replacements.items():
|
|
125
|
+
target = directory / relative_path
|
|
126
|
+
if not target.exists():
|
|
127
|
+
continue
|
|
128
|
+
content = target.read_text(encoding="utf-8")
|
|
129
|
+
for placeholder, value in subs:
|
|
130
|
+
content = content.replace(placeholder, value)
|
|
131
|
+
target.write_text(content, encoding="utf-8")
|
|
132
|
+
|
|
133
|
+
|
|
90
134
|
def main():
|
|
91
135
|
app()
|
|
@@ -122,7 +122,9 @@ class Api(Model):
|
|
|
122
122
|
id: Mapped[int] = mapped_column(
|
|
123
123
|
sqlalchemy.Integer, sqlalchemy.Sequence(API_SEQUENCE), primary_key=True
|
|
124
124
|
)
|
|
125
|
-
name: Mapped[str] = mapped_column(
|
|
125
|
+
name: Mapped[str] = mapped_column(
|
|
126
|
+
sqlalchemy.String(256), unique=True, nullable=False
|
|
127
|
+
)
|
|
126
128
|
|
|
127
129
|
permissions: Mapped[list[PermissionApi]] = relationship(
|
|
128
130
|
PermissionApi, back_populates="api", cascade="all, delete-orphan"
|
|
@@ -143,7 +145,9 @@ class Permission(Model):
|
|
|
143
145
|
id: Mapped[int] = mapped_column(
|
|
144
146
|
sqlalchemy.Integer, sqlalchemy.Sequence(PERMISSION_SEQUENCE), primary_key=True
|
|
145
147
|
)
|
|
146
|
-
name: Mapped[str] = mapped_column(
|
|
148
|
+
name: Mapped[str] = mapped_column(
|
|
149
|
+
sqlalchemy.String(256), unique=True, nullable=False
|
|
150
|
+
)
|
|
147
151
|
|
|
148
152
|
apis: Mapped[list[PermissionApi]] = relationship(
|
|
149
153
|
PermissionApi, back_populates="permission", cascade="all, delete-orphan"
|
|
@@ -158,7 +162,9 @@ class Role(Model):
|
|
|
158
162
|
id: Mapped[int] = mapped_column(
|
|
159
163
|
sqlalchemy.Integer, sqlalchemy.Sequence(ROLE_SEQUENCE), primary_key=True
|
|
160
164
|
)
|
|
161
|
-
name: Mapped[str] = mapped_column(
|
|
165
|
+
name: Mapped[str] = mapped_column(
|
|
166
|
+
sqlalchemy.String(256), unique=True, nullable=False
|
|
167
|
+
)
|
|
162
168
|
|
|
163
169
|
users: Mapped[list["User"]] = relationship(
|
|
164
170
|
"User", secondary=assoc_user_role, back_populates="roles"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
|
|
5
4
|
from fastapi_rtk.backends.generic.column import GenericColumn
|
|
6
5
|
from fastapi_rtk.backends.generic.filters import (
|
|
7
6
|
GenericBaseFilter,
|
|
@@ -109,6 +108,18 @@ class TestCastValue:
|
|
|
109
108
|
f = GenericFilterEqual(StubInterface({"flag": bool}))
|
|
110
109
|
assert f._cast_value("flag", 1) is True
|
|
111
110
|
|
|
111
|
+
def test_boolean_cast_native_true(self, caplog):
|
|
112
|
+
f = GenericFilterEqual(StubInterface({"flag": bool}))
|
|
113
|
+
with caplog.at_level("WARNING", logger="DT_FASTAPI"):
|
|
114
|
+
assert f._cast_value("flag", True) is True
|
|
115
|
+
assert not any("Failed to cast" in r.message for r in caplog.records)
|
|
116
|
+
|
|
117
|
+
def test_boolean_cast_native_false(self, caplog):
|
|
118
|
+
f = GenericFilterEqual(StubInterface({"flag": bool}))
|
|
119
|
+
with caplog.at_level("WARNING", logger="DT_FASTAPI"):
|
|
120
|
+
assert f._cast_value("flag", False) is False
|
|
121
|
+
assert not any("Failed to cast" in r.message for r in caplog.records)
|
|
122
|
+
|
|
112
123
|
def test_date_cast(self):
|
|
113
124
|
f = GenericFilterEqual(StubInterface({"d": datetime.date}))
|
|
114
125
|
assert f._cast_value("d", "2024-01-15") == datetime.date(2024, 1, 15)
|
|
@@ -188,6 +188,18 @@ class TestCastValue:
|
|
|
188
188
|
f = FilterEqual(iface)
|
|
189
189
|
assert f._cast_value("active", "false") is False
|
|
190
190
|
|
|
191
|
+
def test_boolean_native_true(self, iface, caplog):
|
|
192
|
+
f = FilterEqual(iface)
|
|
193
|
+
with caplog.at_level("WARNING", logger="DT_FASTAPI"):
|
|
194
|
+
assert f._cast_value("active", True) is True
|
|
195
|
+
assert not caplog.records
|
|
196
|
+
|
|
197
|
+
def test_boolean_native_false(self, iface, caplog):
|
|
198
|
+
f = FilterEqual(iface)
|
|
199
|
+
with caplog.at_level("WARNING", logger="DT_FASTAPI"):
|
|
200
|
+
assert f._cast_value("active", False) is False
|
|
201
|
+
assert not caplog.records
|
|
202
|
+
|
|
191
203
|
|
|
192
204
|
class TestSQLAFilterConverter:
|
|
193
205
|
def test_conversion_table_present(self):
|
|
@@ -555,3 +567,51 @@ class TestSqlaFiltersCastValueExtras:
|
|
|
555
567
|
out = f._cast_value("col", [1, 2, 3])
|
|
556
568
|
# Recursive: list of casted values
|
|
557
569
|
assert out == ["1", "2", "3"]
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class TestOracleDialectCompilation:
|
|
573
|
+
def test_global_filter_casts_with_varchar2_length(self, session, WideRow_cls):
|
|
574
|
+
from sqlalchemy.dialects import oracle
|
|
575
|
+
|
|
576
|
+
from fastapi_rtk.backends.sqla.filters import FilterGlobal
|
|
577
|
+
|
|
578
|
+
iface = SQLAInterface(WideRow_cls)
|
|
579
|
+
f = FilterGlobal(iface)
|
|
580
|
+
stmt = f.apply(sqlalchemy.select(iface.obj), list(iface.list_columns), "needle")
|
|
581
|
+
|
|
582
|
+
compiled = str(stmt.compile(dialect=oracle.dialect()))
|
|
583
|
+
assert "AS VARCHAR2(4000 CHAR)" in compiled
|
|
584
|
+
assert "AS VARCHAR2)" not in compiled
|
|
585
|
+
assert "concat(" not in compiled.lower()
|
|
586
|
+
|
|
587
|
+
def test_text_contains_casts_with_varchar2_length(self, Person_cls):
|
|
588
|
+
from sqlalchemy.dialects import oracle
|
|
589
|
+
|
|
590
|
+
iface = SQLAInterface(Person_cls)
|
|
591
|
+
f = FilterTextContains(iface)
|
|
592
|
+
stmt = f.apply(sqlalchemy.select(iface.obj), "name", "alice")
|
|
593
|
+
|
|
594
|
+
compiled = str(stmt.compile(dialect=oracle.dialect()))
|
|
595
|
+
assert "AS VARCHAR2(4000 CHAR)" in compiled
|
|
596
|
+
assert "AS VARCHAR2)" not in compiled
|
|
597
|
+
|
|
598
|
+
def test_relation_contains_avoids_multi_arg_concat(self, Person_cls):
|
|
599
|
+
from sqlalchemy.dialects import oracle
|
|
600
|
+
|
|
601
|
+
iface = SQLAInterface(Person_cls)
|
|
602
|
+
f = FilterTextContains(iface)
|
|
603
|
+
stmt = f.apply(sqlalchemy.select(iface.obj), "country", "usa")
|
|
604
|
+
|
|
605
|
+
compiled = str(stmt.compile(dialect=oracle.dialect()))
|
|
606
|
+
assert "concat(" not in compiled.lower()
|
|
607
|
+
assert "AS VARCHAR2(4000 CHAR)" in compiled
|
|
608
|
+
|
|
609
|
+
def test_non_oracle_dialect_keeps_length_less_cast(self, Person_cls):
|
|
610
|
+
from sqlalchemy.dialects import postgresql
|
|
611
|
+
|
|
612
|
+
iface = SQLAInterface(Person_cls)
|
|
613
|
+
f = FilterTextContains(iface)
|
|
614
|
+
stmt = f.apply(sqlalchemy.select(iface.obj), "name", "alice")
|
|
615
|
+
|
|
616
|
+
compiled = str(stmt.compile(dialect=postgresql.dialect()))
|
|
617
|
+
assert "AS VARCHAR)" in compiled
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Tier 14 - tests for fastapi_rtk.cli.cli (top-level Typer app)."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import unittest.mock
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
import typer.testing
|
|
8
|
+
from fastapi_rtk.cli.cli import app, main
|
|
9
|
+
from fastapi_rtk.version import __version__
|
|
10
|
+
|
|
11
|
+
runner = typer.testing.CliRunner()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _build_skeleton_zip(
|
|
15
|
+
*, with_naked_root_entry: bool = False, with_placeholders: bool = True
|
|
16
|
+
) -> bytes:
|
|
17
|
+
"""Synthetic skeleton archive that mimics the layout of the upstream zip."""
|
|
18
|
+
buf = io.BytesIO()
|
|
19
|
+
with zipfile.ZipFile(buf, "w") as zf:
|
|
20
|
+
if with_naked_root_entry:
|
|
21
|
+
# Triggers the empty-target_path continue branch
|
|
22
|
+
zf.writestr("skeleton-main", "")
|
|
23
|
+
zf.writestr("skeleton-main/", "")
|
|
24
|
+
zf.writestr("skeleton-main/README.md", "# skeleton")
|
|
25
|
+
if with_placeholders:
|
|
26
|
+
zf.writestr("skeleton-main/pyproject.toml", 'name = "project-name"\n')
|
|
27
|
+
zf.writestr(
|
|
28
|
+
"skeleton-main/requirements.txt",
|
|
29
|
+
"# via project-name (pyproject.toml)\n",
|
|
30
|
+
)
|
|
31
|
+
zf.writestr(
|
|
32
|
+
"skeleton-main/app/__init__.py", 'ENV_VAR = "PROJECTNAME_CONFIG"\n'
|
|
33
|
+
)
|
|
34
|
+
zf.writestr(
|
|
35
|
+
"skeleton-main/webapp/index.html", "<title>YOUR_APP_NAME_HERE</title>\n"
|
|
36
|
+
)
|
|
37
|
+
zf.writestr("skeleton-main/app/", "")
|
|
38
|
+
zf.writestr("skeleton-main/app/main.py", "print('ok')")
|
|
39
|
+
return buf.getvalue()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _fake_client(zip_bytes: bytes):
|
|
43
|
+
"""Build a drop-in httpx.Client replacement serving a fixed zip archive."""
|
|
44
|
+
|
|
45
|
+
class FakeResponse:
|
|
46
|
+
content = zip_bytes
|
|
47
|
+
|
|
48
|
+
def raise_for_status(self):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
class FakeClient:
|
|
52
|
+
def __enter__(self):
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __exit__(self, *a):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def get(self, url):
|
|
59
|
+
return FakeResponse()
|
|
60
|
+
|
|
61
|
+
return FakeClient
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestHelp:
|
|
65
|
+
def test_help_returns_zero(self):
|
|
66
|
+
result = runner.invoke(app, ["--help"])
|
|
67
|
+
assert result.exit_code == 0
|
|
68
|
+
|
|
69
|
+
def test_help_shows_create_app(self):
|
|
70
|
+
result = runner.invoke(app, ["--help"])
|
|
71
|
+
assert "create-app" in result.output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TestVersion:
|
|
75
|
+
def test_version_returns_zero(self):
|
|
76
|
+
result = runner.invoke(app, ["--version"])
|
|
77
|
+
assert result.exit_code == 0
|
|
78
|
+
|
|
79
|
+
def test_version_shows_version_string(self):
|
|
80
|
+
result = runner.invoke(app, ["--version"])
|
|
81
|
+
assert __version__ in result.output
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestCreateAppHelp:
|
|
85
|
+
def test_create_app_help(self):
|
|
86
|
+
result = runner.invoke(app, ["create-app", "--help"])
|
|
87
|
+
assert result.exit_code == 0
|
|
88
|
+
assert "directory" in result.output.lower()
|
|
89
|
+
|
|
90
|
+
def test_create_app_help_shows_name(self):
|
|
91
|
+
result = runner.invoke(app, ["create-app", "--help"])
|
|
92
|
+
assert "name" in result.output.lower()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestCreateAppExtraction:
|
|
96
|
+
def test_create_app_extracts_skeleton(self, tmp_path):
|
|
97
|
+
zip_bytes = _build_skeleton_zip()
|
|
98
|
+
with unittest.mock.patch(
|
|
99
|
+
"fastapi_rtk.cli.cli.httpx.Client", _fake_client(zip_bytes)
|
|
100
|
+
):
|
|
101
|
+
result = runner.invoke(
|
|
102
|
+
app, ["create-app", "--name", "demo", "--directory", str(tmp_path)]
|
|
103
|
+
)
|
|
104
|
+
assert result.exit_code == 0, result.output
|
|
105
|
+
assert (tmp_path / "README.md").exists()
|
|
106
|
+
assert (tmp_path / "app" / "main.py").exists()
|
|
107
|
+
assert "skeleton created" in result.output
|
|
108
|
+
|
|
109
|
+
def test_create_app_handles_naked_root_entry(self, tmp_path):
|
|
110
|
+
zip_bytes = _build_skeleton_zip(with_naked_root_entry=True)
|
|
111
|
+
with unittest.mock.patch(
|
|
112
|
+
"fastapi_rtk.cli.cli.httpx.Client", _fake_client(zip_bytes)
|
|
113
|
+
):
|
|
114
|
+
result = runner.invoke(
|
|
115
|
+
app, ["create-app", "--name", "demo", "--directory", str(tmp_path)]
|
|
116
|
+
)
|
|
117
|
+
assert result.exit_code == 0, result.output
|
|
118
|
+
assert (tmp_path / "README.md").exists()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestCreateAppProjectName:
|
|
122
|
+
def test_create_app_substitutes_all_placeholders(self, tmp_path):
|
|
123
|
+
zip_bytes = _build_skeleton_zip()
|
|
124
|
+
with unittest.mock.patch(
|
|
125
|
+
"fastapi_rtk.cli.cli.httpx.Client", _fake_client(zip_bytes)
|
|
126
|
+
):
|
|
127
|
+
result = runner.invoke(
|
|
128
|
+
app,
|
|
129
|
+
["create-app", "--name", "My Cool App", "--directory", str(tmp_path)],
|
|
130
|
+
)
|
|
131
|
+
assert result.exit_code == 0, result.output
|
|
132
|
+
assert 'name = "my-cool-app"' in (tmp_path / "pyproject.toml").read_text()
|
|
133
|
+
assert "my-cool-app" in (tmp_path / "requirements.txt").read_text()
|
|
134
|
+
assert (
|
|
135
|
+
'ENV_VAR = "MY_COOL_APP_CONFIG"'
|
|
136
|
+
in (tmp_path / "app" / "__init__.py").read_text()
|
|
137
|
+
)
|
|
138
|
+
assert (
|
|
139
|
+
"<title>My Cool App</title>"
|
|
140
|
+
in (tmp_path / "webapp" / "index.html").read_text()
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def test_create_app_prompts_for_name(self, tmp_path):
|
|
144
|
+
zip_bytes = _build_skeleton_zip()
|
|
145
|
+
with unittest.mock.patch(
|
|
146
|
+
"fastapi_rtk.cli.cli.httpx.Client", _fake_client(zip_bytes)
|
|
147
|
+
):
|
|
148
|
+
result = runner.invoke(
|
|
149
|
+
app,
|
|
150
|
+
["create-app", "--directory", str(tmp_path)],
|
|
151
|
+
input="prompted-app\n",
|
|
152
|
+
)
|
|
153
|
+
assert result.exit_code == 0, result.output
|
|
154
|
+
assert 'name = "prompted-app"' in (tmp_path / "pyproject.toml").read_text()
|
|
155
|
+
|
|
156
|
+
def test_create_app_without_placeholders_is_noop(self, tmp_path):
|
|
157
|
+
zip_bytes = _build_skeleton_zip(with_placeholders=False)
|
|
158
|
+
with unittest.mock.patch(
|
|
159
|
+
"fastapi_rtk.cli.cli.httpx.Client", _fake_client(zip_bytes)
|
|
160
|
+
):
|
|
161
|
+
result = runner.invoke(
|
|
162
|
+
app, ["create-app", "--name", "demo", "--directory", str(tmp_path)]
|
|
163
|
+
)
|
|
164
|
+
assert result.exit_code == 0, result.output
|
|
165
|
+
assert not (tmp_path / "pyproject.toml").exists()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestMain:
|
|
169
|
+
def test_main_invokes_app(self):
|
|
170
|
+
with unittest.mock.patch("fastapi_rtk.cli.cli.app") as mock_app:
|
|
171
|
+
main()
|
|
172
|
+
mock_app.assert_called_once()
|
|
@@ -250,3 +250,10 @@ class TestPermissionApiPersist:
|
|
|
250
250
|
assert pa.id is not None
|
|
251
251
|
assert pa.permission.name == "can_edit"
|
|
252
252
|
assert pa.api.name == "WidgetApi"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestColumnLengths:
|
|
256
|
+
def test_name_columns_have_explicit_length(self):
|
|
257
|
+
# Oracle rejects a length-less VARCHAR2 in DDL (ORA-00906).
|
|
258
|
+
for model in (Api, Permission, Role):
|
|
259
|
+
assert model.__table__.c.name.type.length == 256
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.10.2"
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"""Tier 14 - tests for fastapi_rtk.cli.cli (top-level Typer app)."""
|
|
2
|
-
|
|
3
|
-
import io
|
|
4
|
-
import unittest.mock
|
|
5
|
-
import zipfile
|
|
6
|
-
|
|
7
|
-
import typer.testing
|
|
8
|
-
|
|
9
|
-
from fastapi_rtk.cli.cli import app, main
|
|
10
|
-
from fastapi_rtk.version import __version__
|
|
11
|
-
|
|
12
|
-
runner = typer.testing.CliRunner()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _build_skeleton_zip(*, with_naked_root_entry: bool = False) -> bytes:
|
|
16
|
-
"""Synthetic skeleton archive that mimics the layout of the upstream zip."""
|
|
17
|
-
buf = io.BytesIO()
|
|
18
|
-
with zipfile.ZipFile(buf, "w") as zf:
|
|
19
|
-
if with_naked_root_entry:
|
|
20
|
-
# Triggers the empty-target_path continue branch
|
|
21
|
-
zf.writestr("skeleton-main", "")
|
|
22
|
-
zf.writestr("skeleton-main/", "")
|
|
23
|
-
zf.writestr("skeleton-main/README.md", "# skeleton")
|
|
24
|
-
zf.writestr("skeleton-main/app/", "")
|
|
25
|
-
zf.writestr("skeleton-main/app/main.py", "print('ok')")
|
|
26
|
-
return buf.getvalue()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class TestHelp:
|
|
30
|
-
def test_help_returns_zero(self):
|
|
31
|
-
result = runner.invoke(app, ["--help"])
|
|
32
|
-
assert result.exit_code == 0
|
|
33
|
-
|
|
34
|
-
def test_help_shows_create_app(self):
|
|
35
|
-
result = runner.invoke(app, ["--help"])
|
|
36
|
-
assert "create-app" in result.output
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class TestVersion:
|
|
40
|
-
def test_version_returns_zero(self):
|
|
41
|
-
result = runner.invoke(app, ["--version"])
|
|
42
|
-
assert result.exit_code == 0
|
|
43
|
-
|
|
44
|
-
def test_version_shows_version_string(self):
|
|
45
|
-
result = runner.invoke(app, ["--version"])
|
|
46
|
-
assert __version__ in result.output
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class TestCreateAppHelp:
|
|
50
|
-
def test_create_app_help(self):
|
|
51
|
-
result = runner.invoke(app, ["create-app", "--help"])
|
|
52
|
-
assert result.exit_code == 0
|
|
53
|
-
assert "directory" in result.output.lower()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class TestCreateAppExtraction:
|
|
57
|
-
def test_create_app_extracts_skeleton(self, tmp_path):
|
|
58
|
-
# Mock httpx.Client to return a fake zip archive so the command
|
|
59
|
-
# exercises its extraction logic without hitting the network.
|
|
60
|
-
zip_bytes = _build_skeleton_zip()
|
|
61
|
-
|
|
62
|
-
class FakeResponse:
|
|
63
|
-
content = zip_bytes
|
|
64
|
-
|
|
65
|
-
def raise_for_status(self):
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
class FakeClient:
|
|
69
|
-
def __enter__(self):
|
|
70
|
-
return self
|
|
71
|
-
|
|
72
|
-
def __exit__(self, *a):
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
def get(self, url):
|
|
76
|
-
return FakeResponse()
|
|
77
|
-
|
|
78
|
-
with unittest.mock.patch("fastapi_rtk.cli.cli.httpx.Client", FakeClient):
|
|
79
|
-
result = runner.invoke(app, ["create-app", "--directory", str(tmp_path)])
|
|
80
|
-
assert result.exit_code == 0, result.output
|
|
81
|
-
assert (tmp_path / "README.md").exists()
|
|
82
|
-
assert (tmp_path / "app" / "main.py").exists()
|
|
83
|
-
assert "skeleton created" in result.output
|
|
84
|
-
|
|
85
|
-
def test_create_app_handles_naked_root_entry(self, tmp_path):
|
|
86
|
-
# Some zip producers list the top-level dir without a trailing slash;
|
|
87
|
-
# exercise the empty-target_path skip branch.
|
|
88
|
-
zip_bytes = _build_skeleton_zip(with_naked_root_entry=True)
|
|
89
|
-
|
|
90
|
-
class FakeResponse:
|
|
91
|
-
content = zip_bytes
|
|
92
|
-
|
|
93
|
-
def raise_for_status(self):
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
class FakeClient:
|
|
97
|
-
def __enter__(self):
|
|
98
|
-
return self
|
|
99
|
-
|
|
100
|
-
def __exit__(self, *a):
|
|
101
|
-
pass
|
|
102
|
-
|
|
103
|
-
def get(self, url):
|
|
104
|
-
return FakeResponse()
|
|
105
|
-
|
|
106
|
-
with unittest.mock.patch("fastapi_rtk.cli.cli.httpx.Client", FakeClient):
|
|
107
|
-
result = runner.invoke(app, ["create-app", "--directory", str(tmp_path)])
|
|
108
|
-
assert result.exit_code == 0, result.output
|
|
109
|
-
assert (tmp_path / "README.md").exists()
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class TestMain:
|
|
113
|
-
def test_main_invokes_app(self):
|
|
114
|
-
# main() calls app() under typer; verify it's a callable that runs.
|
|
115
|
-
with unittest.mock.patch("fastapi_rtk.cli.cli.app") as mock_app:
|
|
116
|
-
main()
|
|
117
|
-
mock_app.assert_called_once()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|