fastapi-rtk 2.6.1__tar.gz → 2.7.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.6.1 → fastapi_rtk-2.7.1}/PKG-INFO +1 -1
- fastapi_rtk-2.7.1/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/security.py +1 -1
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/decorators.py +24 -1
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/const.py +1 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/globals.py +5 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/apis.py +46 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_import.py +39 -12
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/test_decorators.py +58 -1
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/sqla/test_apis.py +56 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/sqla/test_security_manager.py +55 -0
- fastapi_rtk-2.6.1/fastapi_rtk/_version.py +0 -1
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/.gitignore +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/LICENSE +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/README.md +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_info_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_typed_base.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/apis/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/apis/info.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/apis/license.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/ldap.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/py.typed +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/registry.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/models.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/formatter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/pyproject.toml +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/docker-compose.gis.yml +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/api/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/api/test_model_rest_api.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/apis/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/apis/test_info_callable_schema.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/apis/test_info_contract.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/auth/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/auth/test_auth_flow.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/auth/test_oauth.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/file_managers/test_file_routes.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/security/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/integration/test_app_smoke.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/model_rest_api/test_columns.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/model_rest_api/test_init.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/api/test_base_api.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/apis/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/apis/test_info.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/apis/test_license.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/hashers/test_scrypt.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/password_helpers/test_fab.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/strategies/test_config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/strategies/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/strategies/test_jwt.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/auth/test_auth.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_column.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_exceptions.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/generic/test_session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_audit.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_audit_types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_column.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/backends/sqla/test_session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_filter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_interface.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_model.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/bases/test_session.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/test_export.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/test_security.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/test_security_crypto.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/commands/test_translate.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/test_cli.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/test_types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/cli/test_utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/test_file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/test_image_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/lang/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/lang/test_babel_cli.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/lang/test_babel_config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/lang/test_lazy_text.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/security/sqla/test_models.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_config.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_const.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_db.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_decorators.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_dependencies.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_exceptions.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_filters.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_globals.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_manager.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_middlewares.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_mixins.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_models.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_registry.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_routers.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_schemas.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_setting.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_types.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/test_version.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/__init__.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_async_task_runner.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_class_factory.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_csv_json_converter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_deep_merge.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_extender_mixin.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_formatter.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_hooks.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_lazy.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_merge_schema.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_pydantic.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_run_utils.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_self_dependencies.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_smartdefaultdict.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_sqla.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_timezone.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_update_signature.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/tests/unit/utils/test_use_default_when_none.py +0 -0
- {fastapi_rtk-2.6.1 → fastapi_rtk-2.7.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.7.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.7.1"
|
|
@@ -129,7 +129,7 @@ async def import_users(
|
|
|
129
129
|
] = False,
|
|
130
130
|
):
|
|
131
131
|
"""
|
|
132
|
-
Import users from a file produced by export-users
|
|
132
|
+
Import users from a file produced by export-users, skipping case-insensitive username/email matches.
|
|
133
133
|
"""
|
|
134
134
|
payload = _load_import_payload(
|
|
135
135
|
file_path,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import inspect
|
|
3
3
|
|
|
4
|
+
import click
|
|
4
5
|
import fastapi_cli.discover
|
|
5
6
|
import fastapi_cli.exceptions
|
|
6
7
|
import typer
|
|
@@ -58,13 +59,35 @@ def ensure_fastapi_rtk_tables_exist(f):
|
|
|
58
59
|
return ensure_app_loaded(wrapper)
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
def _current_command_path() -> str | None:
|
|
63
|
+
"""Build the full CLI command path from the active Click context.
|
|
64
|
+
|
|
65
|
+
Walks from the invoked command up to (but excluding) the program root and
|
|
66
|
+
joins the names with spaces (group then subcommand).
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str | None: The space-joined command names, or `None` when there is no active Click context.
|
|
70
|
+
"""
|
|
71
|
+
ctx = click.get_current_context(silent=True)
|
|
72
|
+
if ctx is None:
|
|
73
|
+
return None
|
|
74
|
+
names = []
|
|
75
|
+
node = ctx
|
|
76
|
+
while node is not None and node.parent is not None:
|
|
77
|
+
names.append(node.info_name)
|
|
78
|
+
node = node.parent
|
|
79
|
+
return " ".join(reversed(names)) or None
|
|
80
|
+
|
|
81
|
+
|
|
61
82
|
def _check_existing_app(f, *, silent=False):
|
|
62
83
|
@functools.wraps(f)
|
|
63
84
|
def wrapper(*args, **kwargs):
|
|
64
85
|
# No app loaded -> CLI invocation: flag cli (before import, so the
|
|
65
|
-
# toolkit skips the lifespan)
|
|
86
|
+
# toolkit skips the lifespan), record the command, then import from
|
|
87
|
+
# path. Else reuse it.
|
|
66
88
|
if g.current_app is None:
|
|
67
89
|
g.is_cli = True
|
|
90
|
+
g.cli_command = _current_command_path()
|
|
68
91
|
try:
|
|
69
92
|
fastapi_cli.discover.get_import_data(path=g.path)
|
|
70
93
|
except fastapi_cli.exceptions.FastAPICLIException as e:
|
|
@@ -148,6 +148,7 @@ BEARER_STRATEGY_CONFIG = {
|
|
|
148
148
|
class ErrorCode(str, enum.Enum):
|
|
149
149
|
GET_USER_MISSING_TOKEN_OR_INACTIVE_USER = "GET_USER_MISSING_TOKEN_OR_INACTIVE_USER"
|
|
150
150
|
GET_USER_NO_ROLES = "GET_USER_NO_ROLES"
|
|
151
|
+
USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS"
|
|
151
152
|
PERMISSION_DENIED = "PERMISSION_DENIED"
|
|
152
153
|
FEATURE_NOT_IMPLEMENTED = "FEATURE_NOT_IMPLEMENTED"
|
|
153
154
|
PAGE_NOT_FOUND = "PAGE_NOT_FOUND"
|
|
@@ -87,6 +87,10 @@ class Globals:
|
|
|
87
87
|
"""
|
|
88
88
|
A boolean value to indicate if the application is loaded by the CLI. `True` when you are running a command from `fastapi-rtk` CLI.
|
|
89
89
|
"""
|
|
90
|
+
cli_command: str | None
|
|
91
|
+
"""
|
|
92
|
+
The full CLI command path currently running (group and subcommand, space-joined), or `None` outside the CLI. Set before the app module is imported.
|
|
93
|
+
"""
|
|
90
94
|
sensitive_data: dict[str, list[str]]
|
|
91
95
|
"""
|
|
92
96
|
A dictionary used to store list of sensitive columns for each model that should not be returned in the list and get endpoints. Default is `{"User": ["password", "hashed_password"]}`.
|
|
@@ -283,6 +287,7 @@ g.set_default(
|
|
|
283
287
|
)
|
|
284
288
|
g.set_default("config", Config())
|
|
285
289
|
g.set_default("is_cli", False)
|
|
290
|
+
g.set_default("cli_command", None)
|
|
286
291
|
g.set_default("sensitive_data", {"User": ["password", "hashed_password"]})
|
|
287
292
|
g.set_default(
|
|
288
293
|
"file_manager",
|
|
@@ -238,6 +238,9 @@ class UsersApi(ModelRestApi):
|
|
|
238
238
|
if col.name == "password":
|
|
239
239
|
col.required = False
|
|
240
240
|
|
|
241
|
+
async def pre_add(self, item, params):
|
|
242
|
+
await self._check_user_unique(params.session, item.email, item.username)
|
|
243
|
+
|
|
241
244
|
async def post_add(self, item, params):
|
|
242
245
|
await g.current_app.sm.update_user(
|
|
243
246
|
user_update={"password": item.password},
|
|
@@ -246,6 +249,14 @@ class UsersApi(ModelRestApi):
|
|
|
246
249
|
)
|
|
247
250
|
await self.datamodel.add(params.session, item)
|
|
248
251
|
|
|
252
|
+
async def pre_update_merge(self, item, data, params):
|
|
253
|
+
await self._check_user_unique(
|
|
254
|
+
params.session,
|
|
255
|
+
data.get("email", item.email),
|
|
256
|
+
data.get("username", item.username),
|
|
257
|
+
current_id=item.id,
|
|
258
|
+
)
|
|
259
|
+
|
|
249
260
|
async def post_update(self, item, params):
|
|
250
261
|
if params.body.password:
|
|
251
262
|
await g.current_app.sm.update_user(
|
|
@@ -255,6 +266,41 @@ class UsersApi(ModelRestApi):
|
|
|
255
266
|
)
|
|
256
267
|
await self.datamodel.add(params.session, item)
|
|
257
268
|
|
|
269
|
+
@classmethod
|
|
270
|
+
async def _check_user_unique(
|
|
271
|
+
cls,
|
|
272
|
+
session: SQLASession,
|
|
273
|
+
email: str,
|
|
274
|
+
username: str,
|
|
275
|
+
current_id: int | None = None,
|
|
276
|
+
):
|
|
277
|
+
"""
|
|
278
|
+
Reject a create/update that collides with an existing user.
|
|
279
|
+
|
|
280
|
+
Matching is case-insensitive (mirrors `get_by_email` /
|
|
281
|
+
`get_by_username`), so `A.b@x.com` and `a.b@x.com` are the same
|
|
282
|
+
user and cannot both exist via the API.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
session (SQLASession): The active database session.
|
|
286
|
+
email (str): The email to check for an existing owner.
|
|
287
|
+
username (str): The username to check for an existing owner.
|
|
288
|
+
current_id (int | None, optional): Id of the row being updated, excluded from the conflict check. Defaults to None.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
fastapi.HTTPException: 400 when another user already owns the email or username.
|
|
292
|
+
"""
|
|
293
|
+
user_db = UserDatabase(session, User)
|
|
294
|
+
for existing in (
|
|
295
|
+
await user_db.get_by_email(email),
|
|
296
|
+
await user_db.get_by_username(username),
|
|
297
|
+
):
|
|
298
|
+
if existing is not None and existing.id != current_id:
|
|
299
|
+
raise fastapi.HTTPException(
|
|
300
|
+
fastapi.status.HTTP_400_BAD_REQUEST,
|
|
301
|
+
ErrorCode.USER_ALREADY_EXISTS,
|
|
302
|
+
)
|
|
303
|
+
|
|
258
304
|
|
|
259
305
|
class RolesApi(ModelRestApi):
|
|
260
306
|
resource_name = "roles"
|
{fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/security/sqla/security_manager/_import.py
RENAMED
|
@@ -17,7 +17,10 @@ inserts rather than N reads plus N inserts.
|
|
|
17
17
|
import collections.abc
|
|
18
18
|
import typing
|
|
19
19
|
|
|
20
|
+
import sqlalchemy
|
|
21
|
+
|
|
20
22
|
from ....backends.sqla.session import SQLASession
|
|
23
|
+
from ....utils import smart_run
|
|
21
24
|
|
|
22
25
|
if typing.TYPE_CHECKING:
|
|
23
26
|
from ..models import Role
|
|
@@ -43,12 +46,14 @@ async def import_users(
|
|
|
43
46
|
|
|
44
47
|
The payload's top-level keys are usernames; each value is the user record
|
|
45
48
|
dict (email, password hash, first/last name, roles list, optional active
|
|
46
|
-
flag).
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
flag). A record is skipped when its username or email case-insensitively
|
|
50
|
+
matches an existing user or an already-imported record, so a case-variant
|
|
51
|
+
of an existing account is never inserted as a second row. Password hashes
|
|
52
|
+
are written verbatim: callers are responsible for ensuring the source dump
|
|
53
|
+
matches the destination's hashing scheme.
|
|
49
54
|
|
|
50
|
-
Bulk lookups: one
|
|
51
|
-
referenced roles, regardless of payload size.
|
|
55
|
+
Bulk lookups: one case-insensitive query for existing usernames/emails and
|
|
56
|
+
one for referenced roles, regardless of payload size.
|
|
52
57
|
|
|
53
58
|
Args:
|
|
54
59
|
sm (SecurityManager): The owning security manager (provides
|
|
@@ -71,17 +76,28 @@ async def import_users(
|
|
|
71
76
|
if not payload:
|
|
72
77
|
return {"imported": [], "skipped": []}
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
user_model = sm.user_model
|
|
80
|
+
lowered_usernames = [username.lower() for username in payload]
|
|
81
|
+
lowered_emails = [
|
|
82
|
+
(record.get("email") or "").lower() for record in payload.values()
|
|
83
|
+
]
|
|
84
|
+
statement = sqlalchemy.select(user_model.username, user_model.email).where(
|
|
85
|
+
sqlalchemy.or_(
|
|
86
|
+
sqlalchemy.func.lower(user_model.username).in_(lowered_usernames),
|
|
87
|
+
sqlalchemy.func.lower(user_model.email).in_(lowered_emails),
|
|
88
|
+
)
|
|
78
89
|
)
|
|
79
|
-
|
|
90
|
+
existing = await smart_run(session.execute, statement)
|
|
91
|
+
existing_usernames_lc: set[str] = set()
|
|
92
|
+
existing_emails_lc: set[str] = set()
|
|
93
|
+
for username, email in existing.all():
|
|
94
|
+
existing_usernames_lc.add(username.lower())
|
|
95
|
+
existing_emails_lc.add((email or "").lower())
|
|
80
96
|
|
|
81
97
|
per_user_role_names: dict[str, list[str]] = {
|
|
82
98
|
username: _normalize_role_names(record.get("roles") or [])
|
|
83
99
|
for username, record in payload.items()
|
|
84
|
-
if username not in
|
|
100
|
+
if username.lower() not in existing_usernames_lc
|
|
85
101
|
}
|
|
86
102
|
referenced_role_names: set[str] = set()
|
|
87
103
|
for names in per_user_role_names.values():
|
|
@@ -96,10 +112,21 @@ async def import_users(
|
|
|
96
112
|
|
|
97
113
|
imported: list[str] = []
|
|
98
114
|
skipped: list[str] = []
|
|
115
|
+
seen_usernames_lc: set[str] = set()
|
|
116
|
+
seen_emails_lc: set[str] = set()
|
|
99
117
|
for username, record in payload.items():
|
|
100
|
-
|
|
118
|
+
username_lc = username.lower()
|
|
119
|
+
email_lc = (record.get("email") or "").lower()
|
|
120
|
+
if (
|
|
121
|
+
username_lc in existing_usernames_lc
|
|
122
|
+
or username_lc in seen_usernames_lc
|
|
123
|
+
or email_lc in existing_emails_lc
|
|
124
|
+
or email_lc in seen_emails_lc
|
|
125
|
+
):
|
|
101
126
|
skipped.append(username)
|
|
102
127
|
continue
|
|
128
|
+
seen_usernames_lc.add(username_lc)
|
|
129
|
+
seen_emails_lc.add(email_lc)
|
|
103
130
|
role_objs = [role_map[name] for name in per_user_role_names[username]]
|
|
104
131
|
user = sm.user_model(
|
|
105
132
|
username=username,
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import unittest.mock
|
|
5
5
|
|
|
6
|
+
import click
|
|
6
7
|
import fastapi_cli.exceptions
|
|
7
8
|
import pytest
|
|
8
9
|
import typer
|
|
9
|
-
|
|
10
10
|
from fastapi_rtk.cli.decorators import (
|
|
11
11
|
_check_existing_app,
|
|
12
12
|
check_existing_app_silent,
|
|
@@ -102,6 +102,63 @@ class TestEnsureAppLoaded:
|
|
|
102
102
|
g.is_cli = False
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
class TestCommandRecording:
|
|
106
|
+
"""`g.cli_command` holds the full CLI path and is set before the app import."""
|
|
107
|
+
|
|
108
|
+
def test_records_full_command_path_before_import(self, global_setter):
|
|
109
|
+
global_setter("is_cli", False)
|
|
110
|
+
|
|
111
|
+
from fastapi_rtk.globals import g
|
|
112
|
+
|
|
113
|
+
captured = {}
|
|
114
|
+
|
|
115
|
+
def _capture(*args, **kwargs):
|
|
116
|
+
# Grab g.cli_command at import time: the guard runs during this import.
|
|
117
|
+
captured["cli_command"] = g.cli_command
|
|
118
|
+
|
|
119
|
+
root = click.Context(click.Command("fastapi-rtk"), info_name="fastapi-rtk")
|
|
120
|
+
export = click.Context(click.Command("export"), parent=root, info_name="export")
|
|
121
|
+
leaf = click.Context(
|
|
122
|
+
click.Command("licenses"), parent=export, info_name="licenses"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
with unittest.mock.patch(
|
|
126
|
+
"fastapi_cli.discover.get_import_data", side_effect=_capture
|
|
127
|
+
):
|
|
128
|
+
|
|
129
|
+
@ensure_app_loaded
|
|
130
|
+
def fn():
|
|
131
|
+
return "done"
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
with leaf:
|
|
135
|
+
assert fn() == "done"
|
|
136
|
+
assert captured["cli_command"] == "export licenses"
|
|
137
|
+
assert g.cli_command == "export licenses"
|
|
138
|
+
finally:
|
|
139
|
+
g.is_cli = False
|
|
140
|
+
g.cli_command = None
|
|
141
|
+
|
|
142
|
+
def test_command_is_none_without_click_context(self, global_setter):
|
|
143
|
+
global_setter("is_cli", False)
|
|
144
|
+
|
|
145
|
+
from fastapi_rtk.globals import g
|
|
146
|
+
|
|
147
|
+
g.cli_command = "stale"
|
|
148
|
+
with unittest.mock.patch("fastapi_cli.discover.get_import_data"):
|
|
149
|
+
|
|
150
|
+
@ensure_app_loaded
|
|
151
|
+
def fn():
|
|
152
|
+
return "done"
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
assert fn() == "done"
|
|
156
|
+
assert g.cli_command is None
|
|
157
|
+
finally:
|
|
158
|
+
g.is_cli = False
|
|
159
|
+
g.cli_command = None
|
|
160
|
+
|
|
161
|
+
|
|
105
162
|
class TestInProcessReusesLoadedApp:
|
|
106
163
|
"""When `g.current_app` is set (in-process, e.g. the app lifespan) the
|
|
107
164
|
decorators reuse the loaded app: no import, no CLI flag, no table-init.
|
|
@@ -11,6 +11,8 @@ can be tested with a constructed UsersApi without going through
|
|
|
11
11
|
|
|
12
12
|
import types
|
|
13
13
|
|
|
14
|
+
import fastapi
|
|
15
|
+
import pytest
|
|
14
16
|
|
|
15
17
|
from fastapi_rtk.security.sqla.apis import (
|
|
16
18
|
AuthApi,
|
|
@@ -161,3 +163,57 @@ class TestAuthApiSchemas:
|
|
|
161
163
|
api = object.__new__(AuthApi)
|
|
162
164
|
schema = api.update_user_schema
|
|
163
165
|
assert schema is not None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.asyncio
|
|
169
|
+
class TestUsersApiUniquenessGuard:
|
|
170
|
+
def _seed(self, session, username, email):
|
|
171
|
+
user = User(username=username, email=email, password="h")
|
|
172
|
+
session.add(user)
|
|
173
|
+
session.commit()
|
|
174
|
+
return user
|
|
175
|
+
|
|
176
|
+
def _api(self):
|
|
177
|
+
return object.__new__(UsersApi)
|
|
178
|
+
|
|
179
|
+
async def test_pre_add_rejects_case_variant_email(self, session):
|
|
180
|
+
self._seed(session, "existing", "Foo@X.com")
|
|
181
|
+
item = User(username="brandnew", email="foo@x.com", password="h")
|
|
182
|
+
with pytest.raises(fastapi.HTTPException) as exc:
|
|
183
|
+
await self._api().pre_add(item, types.SimpleNamespace(session=session))
|
|
184
|
+
assert exc.value.status_code == 400
|
|
185
|
+
|
|
186
|
+
async def test_pre_add_rejects_case_variant_username(self, session):
|
|
187
|
+
self._seed(session, "Admin", "admin@x.com")
|
|
188
|
+
item = User(username="admin", email="fresh@x.com", password="h")
|
|
189
|
+
with pytest.raises(fastapi.HTTPException):
|
|
190
|
+
await self._api().pre_add(item, types.SimpleNamespace(session=session))
|
|
191
|
+
|
|
192
|
+
async def test_pre_add_allows_unique_user(self, session):
|
|
193
|
+
self._seed(session, "existing", "foo@x.com")
|
|
194
|
+
item = User(username="brandnew", email="bar@x.com", password="h")
|
|
195
|
+
assert (
|
|
196
|
+
await self._api().pre_add(item, types.SimpleNamespace(session=session))
|
|
197
|
+
is None
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def test_pre_update_merge_rejects_other_users_email(self, session):
|
|
201
|
+
self._seed(session, "aa", "a@x.com")
|
|
202
|
+
target = self._seed(session, "bb", "b@x.com")
|
|
203
|
+
with pytest.raises(fastapi.HTTPException):
|
|
204
|
+
await self._api().pre_update_merge(
|
|
205
|
+
target,
|
|
206
|
+
{"email": "A@X.com"},
|
|
207
|
+
types.SimpleNamespace(session=session),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def test_pre_update_merge_allows_own_email(self, session):
|
|
211
|
+
target = self._seed(session, "aa", "A@x.com")
|
|
212
|
+
assert (
|
|
213
|
+
await self._api().pre_update_merge(
|
|
214
|
+
target,
|
|
215
|
+
{"email": "a@x.com"},
|
|
216
|
+
types.SimpleNamespace(session=session),
|
|
217
|
+
)
|
|
218
|
+
is None
|
|
219
|
+
)
|
|
@@ -382,6 +382,61 @@ class TestImportData:
|
|
|
382
382
|
result = await sm.import_data("users", payload, session=session)
|
|
383
383
|
assert result["imported"] == ["admin"]
|
|
384
384
|
|
|
385
|
+
async def test_import_users_skips_case_variant_username(self, sm, session):
|
|
386
|
+
base = {
|
|
387
|
+
"password": "$2b$12$x",
|
|
388
|
+
"first_name": "",
|
|
389
|
+
"last_name": "",
|
|
390
|
+
"roles": [],
|
|
391
|
+
}
|
|
392
|
+
await sm.import_data(
|
|
393
|
+
"users",
|
|
394
|
+
{"Admin": {"email": "a@x.com", "username": "Admin", **base}},
|
|
395
|
+
session=session,
|
|
396
|
+
)
|
|
397
|
+
result = await sm.import_data(
|
|
398
|
+
"users",
|
|
399
|
+
{"admin": {"email": "different@x.com", "username": "admin", **base}},
|
|
400
|
+
session=session,
|
|
401
|
+
)
|
|
402
|
+
assert result["imported"] == []
|
|
403
|
+
assert result["skipped"] == ["admin"]
|
|
404
|
+
|
|
405
|
+
async def test_import_users_skips_case_variant_email(self, sm, session):
|
|
406
|
+
base = {
|
|
407
|
+
"password": "$2b$12$x",
|
|
408
|
+
"first_name": "",
|
|
409
|
+
"last_name": "",
|
|
410
|
+
"roles": [],
|
|
411
|
+
}
|
|
412
|
+
await sm.import_data(
|
|
413
|
+
"users",
|
|
414
|
+
{"admin": {"email": "Admin@Example.com", "username": "admin", **base}},
|
|
415
|
+
session=session,
|
|
416
|
+
)
|
|
417
|
+
result = await sm.import_data(
|
|
418
|
+
"users",
|
|
419
|
+
{"other": {"email": "admin@example.com", "username": "other", **base}},
|
|
420
|
+
session=session,
|
|
421
|
+
)
|
|
422
|
+
assert result["imported"] == []
|
|
423
|
+
assert result["skipped"] == ["other"]
|
|
424
|
+
|
|
425
|
+
async def test_import_users_dedups_case_variant_within_payload(self, sm, session):
|
|
426
|
+
base = {
|
|
427
|
+
"password": "$2b$12$x",
|
|
428
|
+
"first_name": "",
|
|
429
|
+
"last_name": "",
|
|
430
|
+
"roles": [],
|
|
431
|
+
}
|
|
432
|
+
payload = {
|
|
433
|
+
"first": {"email": "Dup@x.com", "username": "first", **base},
|
|
434
|
+
"second": {"email": "dup@x.com", "username": "second", **base},
|
|
435
|
+
}
|
|
436
|
+
result = await sm.import_data("users", payload, session=session)
|
|
437
|
+
assert result["imported"] == ["first"]
|
|
438
|
+
assert result["skipped"] == ["second"]
|
|
439
|
+
|
|
385
440
|
|
|
386
441
|
class TestShouldReraise:
|
|
387
442
|
"""Behavior of the `_should_reraise` helper that backs `raise_exception`."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.6.1"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
{fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py
RENAMED
|
File without changes
|
{fastapi_rtk-2.6.1 → fastapi_rtk-2.7.1}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|