fastapi-rtk 2.12.0__tar.gz → 2.12.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/PKG-INFO +1 -1
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/__init__.py +3 -2
- fastapi_rtk-2.12.2/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/__init__.py +2 -0
- fastapi_rtk-2.12.2/fastapi_rtk/bases/user_cache.py +41 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/const.py +1 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/globals.py +18 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/apis.py +1 -1
- fastapi_rtk-2.12.2/fastapi_rtk/security/sqla/user_cache.py +238 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/setting.py +24 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_user_cache.py +117 -17
- fastapi_rtk-2.12.0/fastapi_rtk/_version.py +0 -1
- fastapi_rtk-2.12.0/fastapi_rtk/security/sqla/user_cache.py +0 -178
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/.gitignore +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/LICENSE +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/README.md +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_info_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_typed_base.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/apis/info.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/apis/license.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/ldap.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/cache.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/caches/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/caches/cache.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/caches/serializers.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/caches/storages.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/py.typed +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/registry.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/models.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/formatter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/pyproject.toml +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/docker-compose.gis.yml +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/api/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/api/test_model_rest_api.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/apis/test_info_callable_schema.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/apis/test_info_contract.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/auth/test_auth_flow.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/auth/test_oauth.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/file_managers/test_file_routes.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/security/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/integration/test_app_smoke.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/model_rest_api/test_columns.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/model_rest_api/test_init.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/api/test_base_api.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/apis/test_info.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/apis/test_license.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/hashers/test_scrypt.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/password_helpers/test_fab.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/strategies/test_config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/strategies/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/strategies/test_jwt.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/auth/test_auth.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_column.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_exceptions.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/generic/test_session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_audit.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_audit_types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_column.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/backends/sqla/test_session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_filter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_interface.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_model.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/bases/test_session.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/test_export.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/test_security.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/test_security_crypto.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/commands/test_translate.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/test_cli.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/test_decorators.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/test_types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/cli/test_utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/test_file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/test_image_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/lang/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/lang/test_babel_cli.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/lang/test_babel_config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/lang/test_lazy_text.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/sqla/test_apis.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/sqla/test_models.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_caches.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_config.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_const.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_db.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_decorators.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_dependencies.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_exceptions.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_filters.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_globals.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_manager.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_middlewares.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_mixins.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_models.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_registry.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_routers.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_schemas.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_setting.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_types.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/test_version.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/__init__.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_async_task_runner.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_class_factory.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_csv_json_converter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_deep_merge.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_extender_mixin.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_formatter.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_hooks.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_lazy.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_merge_schema.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_pydantic.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_run_utils.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_self_dependencies.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_smartdefaultdict.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_sqla.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_timezone.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_update_signature.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/tests/unit/utils/test_use_default_when_none.py +0 -0
- {fastapi_rtk-2.12.0 → fastapi_rtk-2.12.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 2.12.
|
|
3
|
+
Version: 2.12.2
|
|
4
4
|
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
|
|
@@ -161,6 +161,7 @@ __all__ = [
|
|
|
161
161
|
"MissingGreenletError",
|
|
162
162
|
"BasicModel",
|
|
163
163
|
"AbstractSession",
|
|
164
|
+
"AbstractUserSerializer",
|
|
164
165
|
# .caches
|
|
165
166
|
"Cache",
|
|
166
167
|
"JSONCacheSerializer",
|
|
@@ -246,8 +247,8 @@ __all__ = [
|
|
|
246
247
|
"UsersApi",
|
|
247
248
|
"RolesApi",
|
|
248
249
|
# .security.sqla.user_cache
|
|
249
|
-
"
|
|
250
|
-
"
|
|
250
|
+
"UserSerializer",
|
|
251
|
+
"UserCache",
|
|
251
252
|
"CachedUserDatabase",
|
|
252
253
|
"get_cached_user",
|
|
253
254
|
"set_cached_user",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.12.2"
|
|
@@ -5,6 +5,7 @@ from .filter import *
|
|
|
5
5
|
from .interface import *
|
|
6
6
|
from .model import *
|
|
7
7
|
from .session import *
|
|
8
|
+
from .user_cache import *
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"AbstractCacheSerializer",
|
|
@@ -23,4 +24,5 @@ __all__ = [
|
|
|
23
24
|
"MissingGreenletError",
|
|
24
25
|
"BasicModel",
|
|
25
26
|
"AbstractSession",
|
|
27
|
+
"AbstractUserSerializer",
|
|
26
28
|
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from .cache import AbstractCacheSerializer
|
|
5
|
+
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from ..security.sqla.models import User
|
|
8
|
+
|
|
9
|
+
__all__ = ["AbstractUserSerializer"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AbstractUserSerializer(AbstractCacheSerializer):
|
|
13
|
+
"""
|
|
14
|
+
Converts a user row to and from the form a user cache entry holds.
|
|
15
|
+
|
|
16
|
+
Narrows `AbstractCacheSerializer` to users, since `UserCache` is the only caller and hands it nothing else. Scheduling is unchanged: an `async def` is awaited on the event loop, while a plain `def` is treated as heavy and runs in a thread.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@abc.abstractmethod
|
|
20
|
+
async def dumps(self, value: "User") -> typing.Any:
|
|
21
|
+
"""
|
|
22
|
+
Serialize a user row.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
value (User): The user handed to `UserCache.set_user`.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
typing.Any: The value to store in the cache.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@abc.abstractmethod
|
|
32
|
+
async def loads(self, value: typing.Any) -> "User":
|
|
33
|
+
"""
|
|
34
|
+
Rebuild a detached user from a stored entry.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
value (typing.Any): The value read from the cache.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
User: The user to hand back to the caller.
|
|
41
|
+
"""
|
|
@@ -83,6 +83,7 @@ DEFAULT_PROFILER_FOLDER = DEFAULT_STATIC_FOLDER + "/profiler"
|
|
|
83
83
|
DEFAULT_LANG_FOLDER = DEFAULT_BASEDIR + "/lang"
|
|
84
84
|
DEFAULT_LANGUAGES = "en,de"
|
|
85
85
|
DEFAULT_TRANSLATIONS_KEY = "translations"
|
|
86
|
+
DEFAULT_USER_CACHE_KEY_PREFIX = "fastapi_rtk:user:"
|
|
86
87
|
DEFAULT_SECWEB_PARAMS = {
|
|
87
88
|
"Option": {
|
|
88
89
|
"csp": {
|
|
@@ -59,6 +59,7 @@ if typing.TYPE_CHECKING:
|
|
|
59
59
|
from .caches import Cache
|
|
60
60
|
from .fastapi_react_toolkit import FastAPIReactToolkit
|
|
61
61
|
from .security.sqla.models import User
|
|
62
|
+
from .security.sqla.user_cache import UserCache
|
|
62
63
|
|
|
63
64
|
__all__ = ["g", "current_app", "current_user", "request", "background_tasks"]
|
|
64
65
|
|
|
@@ -116,6 +117,10 @@ class Globals:
|
|
|
116
117
|
"""
|
|
117
118
|
The cache object used by caching features such as `AUTH_USER_CACHE_TTL`. Defaults to `Cache` from `fastapi_rtk.caches.cache`, which keeps entries in an `InMemoryCacheStorage`.
|
|
118
119
|
"""
|
|
120
|
+
user_cache: "UserCache"
|
|
121
|
+
"""
|
|
122
|
+
The object that stores and rebuilds authenticated users in `g.cache`. Defaults to the `AUTH_USER_CACHE` config key, or `UserCache` from `fastapi_rtk.security.sqla.user_cache`.
|
|
123
|
+
"""
|
|
119
124
|
current_app: "FastAPIReactToolkit"
|
|
120
125
|
"""
|
|
121
126
|
The current FastAPI React Toolkit application instance.
|
|
@@ -323,6 +328,19 @@ g.set_default(
|
|
|
323
328
|
)
|
|
324
329
|
),
|
|
325
330
|
)
|
|
331
|
+
g.set_default(
|
|
332
|
+
"user_cache",
|
|
333
|
+
lazy(
|
|
334
|
+
lambda: (
|
|
335
|
+
lazy_import(
|
|
336
|
+
"fastapi_rtk.setting", lambda mod: mod.Setting.AUTH_USER_CACHE
|
|
337
|
+
)()
|
|
338
|
+
or lazy_import(
|
|
339
|
+
"fastapi_rtk.security.sqla.user_cache", lambda mod: mod.UserCache()
|
|
340
|
+
)()
|
|
341
|
+
)
|
|
342
|
+
),
|
|
343
|
+
)
|
|
326
344
|
g.config.add_callback(basic_callback)
|
|
327
345
|
|
|
328
346
|
# Local Proxies
|
|
@@ -188,7 +188,7 @@ class PermissionsApi(ModelRestApi):
|
|
|
188
188
|
|
|
189
189
|
|
|
190
190
|
class PermissionViewApi(ModelRestApi):
|
|
191
|
-
resource_name = "
|
|
191
|
+
resource_name = "permissionviews"
|
|
192
192
|
datamodel = SQLAInterface(PermissionApi)
|
|
193
193
|
description = "Allows the view of which roles have which permissions on which APIs."
|
|
194
194
|
max_page_size = 200
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
import sqlalchemy
|
|
4
|
+
import sqlalchemy.orm
|
|
5
|
+
|
|
6
|
+
from ...bases.user_cache import AbstractUserSerializer
|
|
7
|
+
from ...db import UserDatabase
|
|
8
|
+
from ...globals import g
|
|
9
|
+
from ...setting import Setting
|
|
10
|
+
from ...utils import lazy, smart_run, use_default_when_none
|
|
11
|
+
from .models import Role, User
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"UserSerializer",
|
|
15
|
+
"UserCache",
|
|
16
|
+
"CachedUserDatabase",
|
|
17
|
+
"get_cached_user",
|
|
18
|
+
"set_cached_user",
|
|
19
|
+
"invalidate_user_cache",
|
|
20
|
+
"clear_user_cache",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UserSerializer(AbstractUserSerializer):
|
|
25
|
+
"""
|
|
26
|
+
Convert a user row to and from the form a user cache entry holds.
|
|
27
|
+
|
|
28
|
+
Set an instance of a subclass as the `AUTH_USER_SERIALIZER` config key to change what an entry stores and what a cached user is rebuilt from. It only ever sees users, so it belongs to `UserCache` and not to the `CACHE` it writes through.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
async def dumps(self, value: User) -> dict[str, typing.Any]:
|
|
32
|
+
"""
|
|
33
|
+
Serialize a user row into its mapped columns and the (id, name) pair of each role.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
value (User): The user to serialize, with its roles loaded.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
dict[str, typing.Any]: The serialized user, under the keys `columns` and `roles`.
|
|
40
|
+
"""
|
|
41
|
+
mapper = sqlalchemy.inspect(type(value)).mapper
|
|
42
|
+
return {
|
|
43
|
+
"columns": {
|
|
44
|
+
attr.key: getattr(value, attr.key) for attr in mapper.column_attrs
|
|
45
|
+
},
|
|
46
|
+
"roles": [(role.id, role.name) for role in value.roles],
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async def loads(self, value: dict[str, typing.Any]) -> User:
|
|
50
|
+
"""
|
|
51
|
+
Rebuild a detached user from its serialized form.
|
|
52
|
+
|
|
53
|
+
`make_transient_to_detached` gives the instance the identity of the stored row, so a later `Session.add` updates that row instead of inserting a new one, and `set_committed_value` attaches the roles without marking the collection dirty.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
value (dict[str, typing.Any]): The serialized user, under the keys `columns` and `roles`.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
User: The rebuilt user, detached from any session.
|
|
60
|
+
"""
|
|
61
|
+
user = User(**value["columns"])
|
|
62
|
+
sqlalchemy.orm.make_transient_to_detached(user)
|
|
63
|
+
|
|
64
|
+
roles = []
|
|
65
|
+
for role_id, role_name in value["roles"]:
|
|
66
|
+
role = Role(id=role_id, name=role_name)
|
|
67
|
+
sqlalchemy.orm.make_transient_to_detached(role)
|
|
68
|
+
roles.append(role)
|
|
69
|
+
sqlalchemy.orm.attributes.set_committed_value(user, "roles", roles)
|
|
70
|
+
|
|
71
|
+
return user
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class UserCache:
|
|
75
|
+
"""
|
|
76
|
+
Read and write authenticated users in `g.cache`.
|
|
77
|
+
|
|
78
|
+
Keys, TTL and storage access live here; what an entry holds lives in the serializer. Set an instance of a subclass as the `AUTH_USER_CACHE` config key to change the read and write paths.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
serializer (AbstractUserSerializer): The `AUTH_USER_SERIALIZER` config key, or a `UserSerializer`, read on first use.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
serializer: AbstractUserSerializer = lazy(
|
|
85
|
+
lambda self: use_default_when_none(
|
|
86
|
+
Setting.AUTH_USER_SERIALIZER, UserSerializer()
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def key_prefix(self) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Prefix of every key this cache writes, and the prefix `clear` drops.
|
|
94
|
+
"""
|
|
95
|
+
return Setting.AUTH_USER_CACHE_KEY_PREFIX
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def ttl(self) -> int:
|
|
99
|
+
"""
|
|
100
|
+
Seconds an entry stays cached, `0` or less disabling the cache.
|
|
101
|
+
"""
|
|
102
|
+
return Setting.AUTH_USER_CACHE_TTL
|
|
103
|
+
|
|
104
|
+
async def get_user(self, user_id: typing.Any) -> User | None:
|
|
105
|
+
"""
|
|
106
|
+
Rebuild the cached user with the given id.
|
|
107
|
+
|
|
108
|
+
The returned user is detached and behaves as if freshly loaded, so it can be handed to `Session.add` without inserting a second row.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
user_id (typing.Any): The primary key of the user.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
User | None: The cached user, or `None` when caching is off or the entry is absent.
|
|
115
|
+
"""
|
|
116
|
+
if self.ttl <= 0:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
cached = await g.cache.get(self.cache_key(user_id))
|
|
120
|
+
if cached is None:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
return await smart_run(self.serializer.loads, cached)
|
|
124
|
+
|
|
125
|
+
async def set_user(self, user: User) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Store the given user for `ttl` seconds.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
user (User): The user to store, with its roles loaded.
|
|
131
|
+
"""
|
|
132
|
+
ttl = self.ttl
|
|
133
|
+
if ttl <= 0:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
value = await smart_run(self.serializer.dumps, user)
|
|
137
|
+
await g.cache.set(self.cache_key(user.id), value, ttl)
|
|
138
|
+
|
|
139
|
+
async def invalidate(self, user_id: typing.Any) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Drop the cached user with the given id.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
user_id (typing.Any): The primary key of the user.
|
|
145
|
+
"""
|
|
146
|
+
if self.ttl <= 0:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
await g.cache.delete(self.cache_key(user_id))
|
|
150
|
+
|
|
151
|
+
async def clear(self) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Drop every cached user, leaving other cache entries alone.
|
|
154
|
+
"""
|
|
155
|
+
if self.ttl <= 0:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
await g.cache.clear(self.key_prefix)
|
|
159
|
+
|
|
160
|
+
def cache_key(self, user_id: typing.Any) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Build the cache key holding the user with the given id.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
user_id (typing.Any): The primary key of the user.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
str: The key to read or write in `g.cache`.
|
|
169
|
+
"""
|
|
170
|
+
return f"{self.key_prefix}{user_id}"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class CachedUserDatabase(UserDatabase):
|
|
174
|
+
"""
|
|
175
|
+
User database that answers `get` from the user cache before querying.
|
|
176
|
+
|
|
177
|
+
The session it is built from stays untouched on a cache hit, so the request never checks out a database connection for the lookup.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
async def get(self, id: typing.Any):
|
|
181
|
+
"""
|
|
182
|
+
Read the user with the given id, from the cache when it holds one.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
id (typing.Any): The primary key of the user.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
User | None: The user, or `None` when no row matches.
|
|
189
|
+
"""
|
|
190
|
+
cached = await get_cached_user(id)
|
|
191
|
+
if cached is not None:
|
|
192
|
+
return cached
|
|
193
|
+
|
|
194
|
+
user = await super().get(id)
|
|
195
|
+
if user is not None:
|
|
196
|
+
await set_cached_user(user)
|
|
197
|
+
|
|
198
|
+
return user
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def get_cached_user(user_id: typing.Any) -> User | None:
|
|
202
|
+
"""
|
|
203
|
+
Rebuild the cached user with the given id, through `g.user_cache`.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
user_id (typing.Any): The primary key of the user.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
User | None: The cached user, or `None` when caching is off or the entry is absent.
|
|
210
|
+
"""
|
|
211
|
+
return await g.user_cache.get_user(user_id)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
async def set_cached_user(user: User) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Store the given user through `g.user_cache`.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
user (User): The user to store, with its roles loaded.
|
|
220
|
+
"""
|
|
221
|
+
await g.user_cache.set_user(user)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def invalidate_user_cache(user_id: typing.Any) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Drop the cached user with the given id, through `g.user_cache`.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
user_id (typing.Any): The primary key of the user.
|
|
230
|
+
"""
|
|
231
|
+
await g.user_cache.invalidate(user_id)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def clear_user_cache() -> None:
|
|
235
|
+
"""
|
|
236
|
+
Drop every cached user, leaving other cache entries alone.
|
|
237
|
+
"""
|
|
238
|
+
await g.user_cache.clear()
|
|
@@ -12,6 +12,7 @@ from .const import (
|
|
|
12
12
|
DEFAULT_STATIC_FOLDER,
|
|
13
13
|
DEFAULT_TEMPLATE_FOLDER,
|
|
14
14
|
DEFAULT_TRANSLATIONS_KEY,
|
|
15
|
+
DEFAULT_USER_CACHE_KEY_PREFIX,
|
|
15
16
|
AuthType,
|
|
16
17
|
logger,
|
|
17
18
|
)
|
|
@@ -20,7 +21,9 @@ from .globals import g
|
|
|
20
21
|
|
|
21
22
|
if typing.TYPE_CHECKING:
|
|
22
23
|
from .bases.file_manager import AbstractFileManager, AbstractImageManager
|
|
24
|
+
from .bases.user_cache import AbstractUserSerializer
|
|
23
25
|
from .caches import Cache
|
|
26
|
+
from .security.sqla.user_cache import UserCache
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
__all__ = ["Setting"]
|
|
@@ -299,6 +302,27 @@ class _Setting:
|
|
|
299
302
|
"""
|
|
300
303
|
return g.config.get("AUTH_USER_CACHE_TTL", 0)
|
|
301
304
|
|
|
305
|
+
@property
|
|
306
|
+
def AUTH_USER_CACHE_KEY_PREFIX(self) -> str:
|
|
307
|
+
"""
|
|
308
|
+
Prefix of every key the user cache writes, and the prefix it drops when clearing.
|
|
309
|
+
"""
|
|
310
|
+
return g.config.get("AUTH_USER_CACHE_KEY_PREFIX", DEFAULT_USER_CACHE_KEY_PREFIX)
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def AUTH_USER_CACHE(self) -> "UserCache | None":
|
|
314
|
+
"""
|
|
315
|
+
The user cache honoring `AUTH_USER_CACHE_TTL`. Also readable as `g.user_cache`.
|
|
316
|
+
"""
|
|
317
|
+
return g.config.get("AUTH_USER_CACHE")
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def AUTH_USER_SERIALIZER(self) -> "AbstractUserSerializer | None":
|
|
321
|
+
"""
|
|
322
|
+
The serializer deciding what a cached user entry holds, applied by `g.user_cache` before the value reaches `CACHE`.
|
|
323
|
+
"""
|
|
324
|
+
return g.config.get("AUTH_USER_SERIALIZER")
|
|
325
|
+
|
|
302
326
|
@property
|
|
303
327
|
def AUTH_ROLE_ADMIN(self) -> str | None:
|
|
304
328
|
return g.config.get("AUTH_ROLE_ADMIN", DEFAULT_ADMIN_ROLE)
|
|
@@ -4,14 +4,15 @@ import unittest.mock
|
|
|
4
4
|
import pytest
|
|
5
5
|
import sqlalchemy
|
|
6
6
|
|
|
7
|
+
from fastapi_rtk.bases.user_cache import AbstractUserSerializer
|
|
7
8
|
from fastapi_rtk.caches import Cache, InMemoryCacheStorage, PickleCacheSerializer
|
|
9
|
+
from fastapi_rtk.const import DEFAULT_USER_CACHE_KEY_PREFIX
|
|
8
10
|
from fastapi_rtk.db import UserDatabase
|
|
9
11
|
from fastapi_rtk.security.sqla.models import Role, User
|
|
10
12
|
from fastapi_rtk.security.sqla.user_cache import (
|
|
11
|
-
USER_CACHE_KEY_PREFIX,
|
|
12
13
|
CachedUserDatabase,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
UserCache,
|
|
15
|
+
UserSerializer,
|
|
15
16
|
clear_user_cache,
|
|
16
17
|
get_cached_user,
|
|
17
18
|
invalidate_user_cache,
|
|
@@ -19,6 +20,25 @@ from fastapi_rtk.security.sqla.user_cache import (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
class _PrefixedUserCache(UserCache):
|
|
24
|
+
key_prefix = "custom:user:"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _PasswordlessUserSerializer(UserSerializer):
|
|
28
|
+
async def dumps(self, user):
|
|
29
|
+
dumped = await super().dumps(user)
|
|
30
|
+
dumped["columns"].pop("password", None)
|
|
31
|
+
return dumped
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def _dump_user(user):
|
|
35
|
+
return await UserSerializer().dumps(user)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _rebuild_user(cached):
|
|
39
|
+
return await UserSerializer().loads(cached)
|
|
40
|
+
|
|
41
|
+
|
|
22
42
|
def _make_user(user_id=7, roles=(("3", "Admin"),)):
|
|
23
43
|
user = User(
|
|
24
44
|
id=user_id,
|
|
@@ -39,35 +59,115 @@ def cache(global_setter):
|
|
|
39
59
|
return cache
|
|
40
60
|
|
|
41
61
|
|
|
62
|
+
@pytest.mark.asyncio
|
|
42
63
|
class TestDumpAndRebuild:
|
|
43
|
-
def test_dump_keeps_columns_and_roles(self):
|
|
44
|
-
dumped = _dump_user(_make_user())
|
|
64
|
+
async def test_dump_keeps_columns_and_roles(self):
|
|
65
|
+
dumped = await _dump_user(_make_user())
|
|
45
66
|
assert dumped["columns"]["id"] == 7
|
|
46
67
|
assert dumped["columns"]["username"] == "user7"
|
|
47
68
|
assert dumped["roles"] == [(3, "Admin")]
|
|
48
69
|
|
|
49
|
-
def test_rebuilt_user_is_detached_with_identity(self):
|
|
50
|
-
rebuilt = _rebuild_user(_dump_user(_make_user()))
|
|
70
|
+
async def test_rebuilt_user_is_detached_with_identity(self):
|
|
71
|
+
rebuilt = await _rebuild_user(await _dump_user(_make_user()))
|
|
51
72
|
state = sqlalchemy.inspect(rebuilt)
|
|
52
73
|
assert state.detached is True
|
|
53
74
|
assert state.key == (User, (7,), None)
|
|
54
75
|
|
|
55
|
-
def test_rebuilt_roles_are_loaded_without_pending_changes(self):
|
|
56
|
-
rebuilt = _rebuild_user(_dump_user(_make_user()))
|
|
76
|
+
async def test_rebuilt_roles_are_loaded_without_pending_changes(self):
|
|
77
|
+
rebuilt = await _rebuild_user(await _dump_user(_make_user()))
|
|
57
78
|
assert [(role.id, role.name) for role in rebuilt.roles] == [(3, "Admin")]
|
|
58
79
|
assert sqlalchemy.inspect(rebuilt).attrs.roles.history.has_changes() is False
|
|
59
80
|
|
|
60
|
-
def test_rebuilt_columns_keep_their_types(self):
|
|
61
|
-
rebuilt = _rebuild_user(_dump_user(_make_user()))
|
|
81
|
+
async def test_rebuilt_columns_keep_their_types(self):
|
|
82
|
+
rebuilt = await _rebuild_user(await _dump_user(_make_user()))
|
|
62
83
|
assert rebuilt.last_login == datetime.datetime(2026, 1, 2, 3, 4)
|
|
63
84
|
assert rebuilt.active is True
|
|
64
85
|
|
|
65
86
|
|
|
87
|
+
class TestSettings:
|
|
88
|
+
def test_default_key_prefix(self):
|
|
89
|
+
assert UserCache().key_prefix == DEFAULT_USER_CACHE_KEY_PREFIX
|
|
90
|
+
|
|
91
|
+
def test_key_prefix_reads_the_config(self, config_setter):
|
|
92
|
+
config_setter("AUTH_USER_CACHE_KEY_PREFIX", "myapp:user:")
|
|
93
|
+
assert UserCache().cache_key(7) == "myapp:user:7"
|
|
94
|
+
|
|
95
|
+
def test_ttl_reads_the_config(self, config_setter):
|
|
96
|
+
config_setter("AUTH_USER_CACHE_TTL", 42)
|
|
97
|
+
assert UserCache().ttl == 42
|
|
98
|
+
|
|
99
|
+
def test_serializer_defaults_to_user_serializer(self):
|
|
100
|
+
assert type(UserCache().serializer) is UserSerializer
|
|
101
|
+
|
|
102
|
+
def test_configured_serializer_is_used_as_is(self, config_setter):
|
|
103
|
+
configured = _PasswordlessUserSerializer()
|
|
104
|
+
config_setter("AUTH_USER_SERIALIZER", configured)
|
|
105
|
+
assert UserCache().serializer is configured
|
|
106
|
+
|
|
107
|
+
def test_serializer_implements_the_abstract_contract(self):
|
|
108
|
+
assert isinstance(UserSerializer(), AbstractUserSerializer)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.asyncio
|
|
112
|
+
class TestUserCacheOverride:
|
|
113
|
+
async def test_module_functions_use_the_configured_cache(
|
|
114
|
+
self, cache, config_setter, global_setter
|
|
115
|
+
):
|
|
116
|
+
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
117
|
+
global_setter("user_cache", _PrefixedUserCache())
|
|
118
|
+
|
|
119
|
+
await set_cached_user(_make_user())
|
|
120
|
+
|
|
121
|
+
assert "custom:user:7" in cache.storage._entries
|
|
122
|
+
assert (await get_cached_user(7)).username == "user7"
|
|
123
|
+
|
|
124
|
+
async def test_configured_serializer_decides_what_is_stored(
|
|
125
|
+
self, cache, config_setter, global_setter
|
|
126
|
+
):
|
|
127
|
+
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
128
|
+
config_setter("AUTH_USER_SERIALIZER", _PasswordlessUserSerializer())
|
|
129
|
+
global_setter("user_cache", UserCache())
|
|
130
|
+
|
|
131
|
+
await set_cached_user(_make_user())
|
|
132
|
+
|
|
133
|
+
stored = await cache.get(f"{DEFAULT_USER_CACHE_KEY_PREFIX}7")
|
|
134
|
+
assert "password" not in stored["columns"]
|
|
135
|
+
rebuilt = await get_cached_user(7)
|
|
136
|
+
assert "password" not in sqlalchemy.inspect(rebuilt).dict
|
|
137
|
+
assert rebuilt.username == "user7"
|
|
138
|
+
|
|
139
|
+
async def test_clear_only_drops_the_configured_prefix(
|
|
140
|
+
self, cache, config_setter, global_setter
|
|
141
|
+
):
|
|
142
|
+
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
143
|
+
global_setter("user_cache", _PrefixedUserCache())
|
|
144
|
+
await set_cached_user(_make_user())
|
|
145
|
+
await cache.set(f"{DEFAULT_USER_CACHE_KEY_PREFIX}9", "keep", 60)
|
|
146
|
+
|
|
147
|
+
await clear_user_cache()
|
|
148
|
+
|
|
149
|
+
assert await get_cached_user(7) is None
|
|
150
|
+
assert await cache.get(f"{DEFAULT_USER_CACHE_KEY_PREFIX}9") == "keep"
|
|
151
|
+
|
|
152
|
+
async def test_invalidate_uses_the_configured_key(
|
|
153
|
+
self, cache, config_setter, global_setter
|
|
154
|
+
):
|
|
155
|
+
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
156
|
+
global_setter("user_cache", _PrefixedUserCache())
|
|
157
|
+
await set_cached_user(_make_user())
|
|
158
|
+
|
|
159
|
+
await invalidate_user_cache(7)
|
|
160
|
+
|
|
161
|
+
assert cache.storage._entries == {}
|
|
162
|
+
|
|
163
|
+
|
|
66
164
|
@pytest.mark.asyncio
|
|
67
165
|
class TestUserCacheDisabled:
|
|
68
166
|
async def test_get_returns_none(self, cache, config_setter):
|
|
69
167
|
config_setter("AUTH_USER_CACHE_TTL", 0)
|
|
70
|
-
await cache.set(
|
|
168
|
+
await cache.set(
|
|
169
|
+
f"{DEFAULT_USER_CACHE_KEY_PREFIX}7", await _dump_user(_make_user()), 60
|
|
170
|
+
)
|
|
71
171
|
assert await get_cached_user(7) is None
|
|
72
172
|
|
|
73
173
|
async def test_set_stores_nothing(self, cache, config_setter):
|
|
@@ -77,19 +177,19 @@ class TestUserCacheDisabled:
|
|
|
77
177
|
|
|
78
178
|
async def test_invalidate_leaves_the_cache_alone(self, cache, config_setter):
|
|
79
179
|
config_setter("AUTH_USER_CACHE_TTL", 0)
|
|
80
|
-
await cache.set(f"{
|
|
180
|
+
await cache.set(f"{DEFAULT_USER_CACHE_KEY_PREFIX}7", "raw", 60)
|
|
81
181
|
|
|
82
182
|
await invalidate_user_cache(7)
|
|
83
183
|
|
|
84
|
-
assert await cache.get(f"{
|
|
184
|
+
assert await cache.get(f"{DEFAULT_USER_CACHE_KEY_PREFIX}7") == "raw"
|
|
85
185
|
|
|
86
186
|
async def test_clear_leaves_the_cache_alone(self, cache, config_setter):
|
|
87
187
|
config_setter("AUTH_USER_CACHE_TTL", 0)
|
|
88
|
-
await cache.set(f"{
|
|
188
|
+
await cache.set(f"{DEFAULT_USER_CACHE_KEY_PREFIX}7", "raw", 60)
|
|
89
189
|
|
|
90
190
|
await clear_user_cache()
|
|
91
191
|
|
|
92
|
-
assert await cache.get(f"{
|
|
192
|
+
assert await cache.get(f"{DEFAULT_USER_CACHE_KEY_PREFIX}7") == "raw"
|
|
93
193
|
|
|
94
194
|
|
|
95
195
|
@pytest.mark.asyncio
|
|
@@ -109,7 +209,7 @@ class TestUserCacheEnabled:
|
|
|
109
209
|
async def test_entry_uses_prefixed_key(self, cache, config_setter):
|
|
110
210
|
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
111
211
|
await set_cached_user(_make_user())
|
|
112
|
-
assert f"{
|
|
212
|
+
assert f"{DEFAULT_USER_CACHE_KEY_PREFIX}7" in cache.storage._entries
|
|
113
213
|
|
|
114
214
|
async def test_invalidate_drops_single_user(self, cache, config_setter):
|
|
115
215
|
config_setter("AUTH_USER_CACHE_TTL", 30)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.12.0"
|