fastapi-rtk 2.12.2__tar.gz → 2.12.3__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.2 → fastapi_rtk-2.12.3}/PKG-INFO +1 -1
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/__init__.py +4 -0
- fastapi_rtk-2.12.3/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/__init__.py +2 -0
- fastapi_rtk-2.12.3/fastapi_rtk/bases/loop_manager.py +47 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/globals.py +14 -0
- fastapi_rtk-2.12.3/fastapi_rtk/loop_managers/__init__.py +3 -0
- fastapi_rtk-2.12.3/fastapi_rtk/loop_managers/loop_manager.py +232 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/setting.py +8 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/run_utils.py +92 -21
- fastapi_rtk-2.12.3/tests/unit/utils/test_run_utils.py +775 -0
- fastapi_rtk-2.12.2/fastapi_rtk/_version.py +0 -1
- fastapi_rtk-2.12.2/tests/unit/utils/test_run_utils.py +0 -369
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/.gitignore +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/LICENSE +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/README.md +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_add_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_bulk_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_columns_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_delete_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_download_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_edit_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_file_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_info_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_list_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_params.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_show_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_typed_base.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/api/model_rest_api/_upload_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/info.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/apis/license.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/ldap.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/_processing_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/cache.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/bases/user_cache.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/cache.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/serializers.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/caches/storages.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/const.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/py.typed +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/registry.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/apis.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/models.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_association_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_builtin_role_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_cleanup_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_role_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/security_manager/_user_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/security/sqla/user_cache.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/async_task_runner.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/formatter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/pyproject.toml +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/docker-compose.gis.yml +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/api/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/api/test_model_rest_api.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/test_info_callable_schema.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/apis/test_info_contract.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/test_auth_flow.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/auth/test_oauth.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/file_managers/test_file_routes.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/integration/test_app_smoke.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_columns.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_init.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/model_rest_api/test_typed_base_consistency.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/api/test_base_api.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/test_info.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/apis/test_license.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/hashers/test_scrypt.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/password_helpers/test_fab.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/strategies/test_jwt.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/auth/test_auth.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_column.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_exceptions.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/generic/test_session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_audit.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_audit_types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_column.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/backends/sqla/test_session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_filter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_interface.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_model.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/bases/test_session.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_export.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_security.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_security_crypto.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/commands/test_translate.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_cli.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_decorators.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/cli/test_utils.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_image_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_babel_cli.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_babel_config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/lang/test_lazy_text.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_apis.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_models.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_caches.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_config.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_const.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_db.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_decorators.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_dependencies.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_exceptions.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_filters.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_globals.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_manager.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_middlewares.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_mixins.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_models.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_registry.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_routers.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_schemas.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_setting.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_types.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_user_cache.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/test_version.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/__init__.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_async_task_runner.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_class_factory.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_csv_json_converter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_deep_merge.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_extender_mixin.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_formatter.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_hooks.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_lazy.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_merge_schema.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_pydantic.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_self_dependencies.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_smartdefaultdict.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_sqla.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_timezone.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_update_signature.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/tests/unit/utils/test_use_default_when_none.py +0 -0
- {fastapi_rtk-2.12.2 → fastapi_rtk-2.12.3}/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.3
|
|
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
|
|
@@ -17,6 +17,7 @@ from .fastapi_react_toolkit import *
|
|
|
17
17
|
from .file_managers import *
|
|
18
18
|
from .globals import *
|
|
19
19
|
from .lang import *
|
|
20
|
+
from .loop_managers import *
|
|
20
21
|
from .manager import *
|
|
21
22
|
from .mixins import *
|
|
22
23
|
from .routers import *
|
|
@@ -158,6 +159,7 @@ __all__ = [
|
|
|
158
159
|
"AbstractBaseOprFilter",
|
|
159
160
|
"PydanticGenerationSchema",
|
|
160
161
|
"AbstractInterface",
|
|
162
|
+
"AbstractLoopManager",
|
|
161
163
|
"MissingGreenletError",
|
|
162
164
|
"BasicModel",
|
|
163
165
|
"AbstractSession",
|
|
@@ -209,6 +211,8 @@ __all__ = [
|
|
|
209
211
|
"lazy_text",
|
|
210
212
|
"translate",
|
|
211
213
|
"_",
|
|
214
|
+
# .loop_managers
|
|
215
|
+
"LoopManager",
|
|
212
216
|
# .manager
|
|
213
217
|
"UserManager",
|
|
214
218
|
# .mixins
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.12.3"
|
|
@@ -3,6 +3,7 @@ from .db import *
|
|
|
3
3
|
from .file_manager import *
|
|
4
4
|
from .filter import *
|
|
5
5
|
from .interface import *
|
|
6
|
+
from .loop_manager import *
|
|
6
7
|
from .model import *
|
|
7
8
|
from .session import *
|
|
8
9
|
from .user_cache import *
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"AbstractBaseOprFilter",
|
|
22
23
|
"PydanticGenerationSchema",
|
|
23
24
|
"AbstractInterface",
|
|
25
|
+
"AbstractLoopManager",
|
|
24
26
|
"MissingGreenletError",
|
|
25
27
|
"BasicModel",
|
|
26
28
|
"AbstractSession",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
__all__ = ["AbstractLoopManager"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AbstractLoopManager(abc.ABC):
|
|
8
|
+
"""
|
|
9
|
+
Supplies the event loop that synchronous callers submit coroutines to.
|
|
10
|
+
|
|
11
|
+
Implement this to control which loop `smart_run_sync` / `safe_call_sync` reach for when no loop is passed, e.g. to hand them a loop the host application already runs. The loop must be running in another thread, because the calling thread blocks on the result.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def loop(self) -> asyncio.AbstractEventLoop:
|
|
17
|
+
"""
|
|
18
|
+
The loop to submit coroutines to, started if it was not running yet.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
asyncio.AbstractEventLoop: A running event loop owned by another thread.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def shutdown(self) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Stop the loops this manager owns, if it started any.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None: The next `loop` access builds a fresh loop.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def loop_for(
|
|
34
|
+
self, current: asyncio.AbstractEventLoop | None
|
|
35
|
+
) -> asyncio.AbstractEventLoop:
|
|
36
|
+
"""
|
|
37
|
+
The loop to use for a caller already running on ``current``.
|
|
38
|
+
|
|
39
|
+
Blocking a thread for a task on the loop that thread runs would deadlock, since nothing is left to run the task. Override to hand such a caller a different loop; this default ignores ``current`` and is therefore only safe for callers that never run on the returned loop.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
current (asyncio.AbstractEventLoop | None): The loop running in the calling thread, or None outside one.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
asyncio.AbstractEventLoop: The loop to submit to.
|
|
46
|
+
"""
|
|
47
|
+
return self.loop
|
|
@@ -56,6 +56,7 @@ from .utils import lazy, lazy_import
|
|
|
56
56
|
if typing.TYPE_CHECKING:
|
|
57
57
|
from .auth import AuthConfigurator
|
|
58
58
|
from .bases.file_manager import AbstractFileManager, AbstractImageManager
|
|
59
|
+
from .bases.loop_manager import AbstractLoopManager
|
|
59
60
|
from .caches import Cache
|
|
60
61
|
from .fastapi_react_toolkit import FastAPIReactToolkit
|
|
61
62
|
from .security.sqla.models import User
|
|
@@ -117,6 +118,10 @@ class Globals:
|
|
|
117
118
|
"""
|
|
118
119
|
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`.
|
|
119
120
|
"""
|
|
121
|
+
loop_manager: "AbstractLoopManager"
|
|
122
|
+
"""
|
|
123
|
+
The object supplying the event loop `smart_run_sync` / `safe_call_sync` submit to when given no loop. Defaults to the `LOOP_MANAGER` config key, or `LoopManager` from `fastapi_rtk.loop_managers.loop_manager`, which runs one long-lived loop per nesting depth, each on its own daemon thread.
|
|
124
|
+
"""
|
|
120
125
|
user_cache: "UserCache"
|
|
121
126
|
"""
|
|
122
127
|
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`.
|
|
@@ -341,6 +346,15 @@ g.set_default(
|
|
|
341
346
|
)
|
|
342
347
|
),
|
|
343
348
|
)
|
|
349
|
+
g.set_default(
|
|
350
|
+
"loop_manager",
|
|
351
|
+
lazy(
|
|
352
|
+
lambda: (
|
|
353
|
+
lazy_import("fastapi_rtk.setting", lambda mod: mod.Setting.LOOP_MANAGER)()
|
|
354
|
+
or lazy_import("fastapi_rtk.loop_managers", lambda mod: mod.LoopManager())()
|
|
355
|
+
)
|
|
356
|
+
),
|
|
357
|
+
)
|
|
344
358
|
g.config.add_callback(basic_callback)
|
|
345
359
|
|
|
346
360
|
# Local Proxies
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
import typing
|
|
5
|
+
import weakref
|
|
6
|
+
|
|
7
|
+
from ..bases.loop_manager import AbstractLoopManager
|
|
8
|
+
from ..const import logger as _root_logger
|
|
9
|
+
|
|
10
|
+
__all__ = ["LoopManager"]
|
|
11
|
+
|
|
12
|
+
logger = _root_logger.getChild("loop_manager")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LoopManager(AbstractLoopManager):
|
|
16
|
+
"""
|
|
17
|
+
Runs long-lived event loops on daemon threads, one per nesting depth.
|
|
18
|
+
|
|
19
|
+
Reusing loops keeps loop-bound resources (asyncpg pools, redis asyncio clients, `asyncio.Lock`) valid across calls and stops every synchronous call from leaving a closed loop behind. Depth 0 serves ordinary callers; a caller already running on the depth-N loop gets depth N+1, so a synchronous event handler whose session commits inside a coroutine on one loop can still submit work without blocking that loop against itself. Loops are created on first need, restarted when one stops on its own, and forgotten in a forked child (Celery's prefork pool), whose inherited loops have no thread to drain them.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
thread_name (str): Prefix of the thread names, suffixed with the depth.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
thread_name = "fastapi-rtk-loop"
|
|
26
|
+
|
|
27
|
+
_managers: typing.ClassVar["weakref.WeakSet[LoopManager]"] = weakref.WeakSet()
|
|
28
|
+
"""
|
|
29
|
+
Every live manager, so the fork handler can reset each one in the child. Weakly held, so a manager nobody uses is collected instead of being reset on every later fork.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self._loops: list[_Loop] = []
|
|
34
|
+
self._lock = threading.Lock()
|
|
35
|
+
self._managers.add(self)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def loop(self) -> asyncio.AbstractEventLoop:
|
|
39
|
+
"""
|
|
40
|
+
The loop ordinary callers submit to.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
asyncio.AbstractEventLoop: The depth-0 loop, started if it was not running yet.
|
|
44
|
+
"""
|
|
45
|
+
return self._loop_at(0)
|
|
46
|
+
|
|
47
|
+
def loop_for(
|
|
48
|
+
self, current: asyncio.AbstractEventLoop | None
|
|
49
|
+
) -> asyncio.AbstractEventLoop:
|
|
50
|
+
"""
|
|
51
|
+
The loop to use for a caller running on ``current``.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
current (asyncio.AbstractEventLoop | None): The loop running in the calling thread, or None outside one.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
asyncio.AbstractEventLoop: The next depth down when ``current`` is one of this manager's loops, else the depth-0 loop.
|
|
58
|
+
"""
|
|
59
|
+
for depth, entry in enumerate(self._loops):
|
|
60
|
+
if entry.loop is current:
|
|
61
|
+
return self._loop_at(depth + 1)
|
|
62
|
+
return self._loop_at(0)
|
|
63
|
+
|
|
64
|
+
def shutdown(self) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Stop every loop this manager started and let their threads finish.
|
|
67
|
+
|
|
68
|
+
Coroutines still running are cancelled first, so a caller blocked on one raises `concurrent.futures.CancelledError` instead of waiting forever on a loop that stopped under it.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
None: The next `loop` access builds a fresh loop.
|
|
72
|
+
"""
|
|
73
|
+
with self._lock:
|
|
74
|
+
entries, self._loops = self._loops, []
|
|
75
|
+
for entry in entries:
|
|
76
|
+
entry.stopped.set()
|
|
77
|
+
_cancel_and_stop(entry.loop)
|
|
78
|
+
|
|
79
|
+
def _loop_at(self, depth: int) -> asyncio.AbstractEventLoop:
|
|
80
|
+
"""
|
|
81
|
+
Return the loop for one nesting depth, creating loops up to it.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
depth (int): The nesting depth to serve.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
asyncio.AbstractEventLoop: The running loop held at that depth.
|
|
88
|
+
"""
|
|
89
|
+
entries = self._loops
|
|
90
|
+
if depth < len(entries) and entries[depth].serving:
|
|
91
|
+
return entries[depth].loop
|
|
92
|
+
with self._lock:
|
|
93
|
+
while len(self._loops) <= depth:
|
|
94
|
+
self._loops.append(self._start_loop(len(self._loops)))
|
|
95
|
+
if not self._loops[depth].serving:
|
|
96
|
+
self._loops[depth] = self._start_loop(depth)
|
|
97
|
+
return self._loops[depth].loop
|
|
98
|
+
|
|
99
|
+
def _start_loop(self, depth: int) -> "_Loop":
|
|
100
|
+
"""
|
|
101
|
+
Start a loop on its own daemon thread.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
depth (int): The nesting depth the loop serves, used in the thread name.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
_Loop: The loop now running on that thread, with the thread and its stop flag.
|
|
108
|
+
"""
|
|
109
|
+
loop = asyncio.new_event_loop()
|
|
110
|
+
stopped = threading.Event()
|
|
111
|
+
thread = threading.Thread(
|
|
112
|
+
target=_serve,
|
|
113
|
+
args=(loop, stopped),
|
|
114
|
+
name=f"{self.thread_name}-{depth}",
|
|
115
|
+
daemon=True,
|
|
116
|
+
)
|
|
117
|
+
thread.start()
|
|
118
|
+
return _Loop(loop, thread, stopped)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def _reset_all_after_fork(cls) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Reset every live manager in a freshly forked child.
|
|
124
|
+
|
|
125
|
+
Runs in the child's only thread before anything else can reach a manager, so nothing waits on a lock the fork inherited locked, and no child ever submits to a parent loop whose thread it did not inherit.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
None: Each manager starts fresh loops on its next use.
|
|
129
|
+
"""
|
|
130
|
+
for manager in cls._managers:
|
|
131
|
+
manager._reset_after_fork()
|
|
132
|
+
|
|
133
|
+
def _reset_after_fork(self) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Forget the parent's loops, whose threads do not exist in this child.
|
|
136
|
+
|
|
137
|
+
The lock is replaced rather than taken: only the forking thread survives a fork, so a lock another thread held at that moment is inherited locked with nobody left to release it.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
None: The next `loop` access starts fresh loops.
|
|
141
|
+
"""
|
|
142
|
+
self._loops = []
|
|
143
|
+
self._lock = threading.Lock()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class _Loop(typing.NamedTuple):
|
|
147
|
+
"""
|
|
148
|
+
One loop a `LoopManager` holds, with the thread serving it.
|
|
149
|
+
|
|
150
|
+
Attributes:
|
|
151
|
+
loop (asyncio.AbstractEventLoop): The loop coroutines are submitted to.
|
|
152
|
+
thread (threading.Thread): The daemon thread running it.
|
|
153
|
+
stopped (threading.Event): Set by `LoopManager.shutdown` to let that thread finish.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
loop: asyncio.AbstractEventLoop
|
|
157
|
+
thread: threading.Thread
|
|
158
|
+
stopped: threading.Event
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def serving(self) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Whether this loop can still accept coroutines.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
bool: True while its thread is alive and the loop is open.
|
|
167
|
+
"""
|
|
168
|
+
return self.thread.is_alive() and not self.loop.is_closed()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _serve(loop: asyncio.AbstractEventLoop, stopped: threading.Event) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Run one loop until its manager stops it, restarting it if it stops on its own.
|
|
174
|
+
|
|
175
|
+
A `KeyboardInterrupt` or `SystemExit` raised inside a submitted coroutine propagates out of `run_forever`, and outside code can call `loop.stop()`. Either would otherwise end this thread while the loop still looks usable, and every later submission to it would block forever. A loop closed from outside cannot be restarted, so the thread gives it up and the manager builds a new one on next use.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
loop (asyncio.AbstractEventLoop): The loop to run.
|
|
179
|
+
stopped (threading.Event): Set when the loop should be given up rather than restarted.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
None: The loop is closed once this returns.
|
|
183
|
+
"""
|
|
184
|
+
asyncio.set_event_loop(loop)
|
|
185
|
+
try:
|
|
186
|
+
while not stopped.is_set() and not loop.is_closed():
|
|
187
|
+
try:
|
|
188
|
+
loop.run_forever()
|
|
189
|
+
except BaseException:
|
|
190
|
+
logger.exception("Event loop %s crashed, restarting it", loop)
|
|
191
|
+
finally:
|
|
192
|
+
loop.close()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _cancel_and_stop(loop: asyncio.AbstractEventLoop) -> None:
|
|
196
|
+
"""
|
|
197
|
+
Cancel everything running on a loop, then stop it.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
loop (asyncio.AbstractEventLoop): The loop to drain, running in another thread.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
None: The loop stops once the cancelled coroutines have finished.
|
|
204
|
+
"""
|
|
205
|
+
if loop.is_closed():
|
|
206
|
+
return
|
|
207
|
+
try:
|
|
208
|
+
asyncio.run_coroutine_threadsafe(_cancel_all(), loop)
|
|
209
|
+
except RuntimeError:
|
|
210
|
+
logger.debug("Event loop %s was already gone when stopping it", loop)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _cancel_all() -> None:
|
|
214
|
+
"""
|
|
215
|
+
Cancel every other task on this loop, wait for them, then stop the loop.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
None: The loop is stopped when this returns.
|
|
219
|
+
"""
|
|
220
|
+
loop = asyncio.get_running_loop()
|
|
221
|
+
tasks = [
|
|
222
|
+
task for task in asyncio.all_tasks(loop) if task is not asyncio.current_task()
|
|
223
|
+
]
|
|
224
|
+
for task in tasks:
|
|
225
|
+
task.cancel()
|
|
226
|
+
if tasks:
|
|
227
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
228
|
+
loop.stop()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if hasattr(os, "register_at_fork"):
|
|
232
|
+
os.register_at_fork(after_in_child=LoopManager._reset_all_after_fork)
|
|
@@ -21,6 +21,7 @@ from .globals import g
|
|
|
21
21
|
|
|
22
22
|
if typing.TYPE_CHECKING:
|
|
23
23
|
from .bases.file_manager import AbstractFileManager, AbstractImageManager
|
|
24
|
+
from .bases.loop_manager import AbstractLoopManager
|
|
24
25
|
from .bases.user_cache import AbstractUserSerializer
|
|
25
26
|
from .caches import Cache
|
|
26
27
|
from .security.sqla.user_cache import UserCache
|
|
@@ -151,6 +152,13 @@ class _Setting:
|
|
|
151
152
|
"""
|
|
152
153
|
return g.config.get("CACHE")
|
|
153
154
|
|
|
155
|
+
@property
|
|
156
|
+
def LOOP_MANAGER(self) -> "AbstractLoopManager | None":
|
|
157
|
+
"""
|
|
158
|
+
The manager supplying the event loop `smart_run_sync` / `safe_call_sync` submit to when given no loop.
|
|
159
|
+
"""
|
|
160
|
+
return g.config.get("LOOP_MANAGER")
|
|
161
|
+
|
|
154
162
|
@property
|
|
155
163
|
def FILE_ALLOWED_EXTENSIONS(self) -> list[str] | None:
|
|
156
164
|
return g.config.get("FILE_ALLOWED_EXTENSIONS")
|
|
@@ -59,7 +59,7 @@ def smart_run_sync(
|
|
|
59
59
|
Args:
|
|
60
60
|
func (collections.abc.Callable[..., T | collections.abc.Awaitable[T]]): The function to be executed.
|
|
61
61
|
*args: Positional arguments to be passed to the function.
|
|
62
|
-
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit an async ``func`` to. Defaults to None, which
|
|
62
|
+
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit an async ``func`` to. Defaults to None, which uses the loop `g.loop_manager` supplies.
|
|
63
63
|
**kwargs: Keyword arguments to be passed to the function.
|
|
64
64
|
|
|
65
65
|
Returns:
|
|
@@ -98,9 +98,9 @@ def safe_call_sync(
|
|
|
98
98
|
|
|
99
99
|
Args:
|
|
100
100
|
result_or_coro (T | collections.abc.Coroutine[typing.Any, typing.Any, T]): The result or coroutine to be executed.
|
|
101
|
-
max_workers (int, optional):
|
|
102
|
-
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which
|
|
103
|
-
**kwargs: Additional keyword arguments to
|
|
101
|
+
max_workers (int, optional): Unused, kept so existing callers still type-check. Defaults to 1.
|
|
102
|
+
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which uses the loop `g.loop_manager` supplies.
|
|
103
|
+
**kwargs: Additional keyword arguments forwarded to `run_coroutine_in_threadpool`, which ignores them.
|
|
104
104
|
|
|
105
105
|
Returns:
|
|
106
106
|
T: The result of the function call or coroutine.
|
|
@@ -119,31 +119,28 @@ def run_coroutine_in_threadpool(
|
|
|
119
119
|
**kwargs,
|
|
120
120
|
):
|
|
121
121
|
"""
|
|
122
|
-
Run a coroutine
|
|
122
|
+
Run a coroutine on a long-lived event loop from a synchronous caller.
|
|
123
|
+
|
|
124
|
+
``loop`` is submitted to when given; otherwise ``g.loop_manager`` picks a
|
|
125
|
+
long-lived loop, avoiding the one the calling thread already runs. Reusing
|
|
126
|
+
loops keeps loop-bound resources (asyncpg pools, redis asyncio clients,
|
|
127
|
+
``asyncio.Lock``) valid across calls and stops every call from leaving a
|
|
128
|
+
closed loop behind. The caller's context is copied so contextvars set
|
|
129
|
+
before the call are visible inside the coroutine.
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
This keeps loop-bound resources (asyncpg pools, redis asyncio clients,
|
|
127
|
-
``asyncio.Lock``) valid across calls; ``max_workers`` and ``**kwargs`` are
|
|
128
|
-
then unused. The caller owns the loop and must run it in another thread.
|
|
131
|
+
Passing a ``loop`` the calling thread itself runs deadlocks, since blocking
|
|
132
|
+
that thread leaves nothing to run the task.
|
|
129
133
|
|
|
130
134
|
Args:
|
|
131
135
|
coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to run.
|
|
132
|
-
max_workers (int, optional):
|
|
133
|
-
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which
|
|
134
|
-
**kwargs:
|
|
136
|
+
max_workers (int, optional): Unused, kept so existing callers still type-check. Defaults to 1.
|
|
137
|
+
loop (asyncio.AbstractEventLoop | None, optional): A running event loop to submit the coroutine to. Defaults to None, which uses the loop `g.loop_manager` supplies.
|
|
138
|
+
**kwargs: Unused, kept so existing callers still type-check.
|
|
135
139
|
|
|
136
140
|
Returns:
|
|
137
141
|
T: The result of the coroutine.
|
|
138
142
|
"""
|
|
139
|
-
if loop is not None
|
|
140
|
-
return asyncio.run_coroutine_threadsafe(coro, loop).result()
|
|
141
|
-
with concurrent.futures.ThreadPoolExecutor(
|
|
142
|
-
max_workers=max_workers, **kwargs
|
|
143
|
-
) as executor:
|
|
144
|
-
ctx = contextvars.copy_context()
|
|
145
|
-
future = executor.submit(ctx.run, asyncio.run, coro)
|
|
146
|
-
return future.result()
|
|
143
|
+
return _submit_to_loop(coro, loop if loop is not None else _managed_loop())
|
|
147
144
|
|
|
148
145
|
|
|
149
146
|
def run_function_in_threadpool(
|
|
@@ -228,3 +225,77 @@ def parse_from_values_or_func(
|
|
|
228
225
|
if callable(value_or_func):
|
|
229
226
|
return value_or_func(*args, **kwargs)
|
|
230
227
|
return value_or_func
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _managed_loop() -> asyncio.AbstractEventLoop:
|
|
231
|
+
"""
|
|
232
|
+
Return the loop the configured loop manager picks for this caller.
|
|
233
|
+
|
|
234
|
+
The loop already running in this thread is passed along, so the manager can
|
|
235
|
+
hand back a different one: blocking a thread for a task on its own loop
|
|
236
|
+
would deadlock, since nothing is left to run the task. ``g`` is imported
|
|
237
|
+
here rather than at module scope, because ``globals`` imports this package
|
|
238
|
+
while loading.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
asyncio.AbstractEventLoop: The loop `g.loop_manager` supplies for this caller.
|
|
242
|
+
"""
|
|
243
|
+
from ..globals import g
|
|
244
|
+
|
|
245
|
+
return g.loop_manager.loop_for(_running_loop())
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _submit_to_loop(
|
|
249
|
+
coro: collections.abc.Coroutine[typing.Any, typing.Any, T],
|
|
250
|
+
loop: asyncio.AbstractEventLoop,
|
|
251
|
+
) -> T:
|
|
252
|
+
"""
|
|
253
|
+
Run a coroutine on another thread's loop and block for its result.
|
|
254
|
+
|
|
255
|
+
The coroutine runs as a task on that loop, so it starts with the loop
|
|
256
|
+
thread's contextvars rather than the caller's. The caller's values are
|
|
257
|
+
copied in first, inside the task's own context, so nothing leaks between
|
|
258
|
+
concurrent callers.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to run.
|
|
262
|
+
loop (asyncio.AbstractEventLoop): The loop to run it on, running in another thread.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
T: The result of the coroutine.
|
|
266
|
+
"""
|
|
267
|
+
return asyncio.run_coroutine_threadsafe(
|
|
268
|
+
_with_context(contextvars.copy_context(), coro), loop
|
|
269
|
+
).result()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _running_loop() -> asyncio.AbstractEventLoop | None:
|
|
273
|
+
"""
|
|
274
|
+
Return the loop running in this thread, if any.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
asyncio.AbstractEventLoop | None: The running loop, or None outside one.
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
return asyncio.get_running_loop()
|
|
281
|
+
except RuntimeError:
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
async def _with_context(
|
|
286
|
+
context: contextvars.Context,
|
|
287
|
+
coro: collections.abc.Coroutine[typing.Any, typing.Any, T],
|
|
288
|
+
) -> T:
|
|
289
|
+
"""
|
|
290
|
+
Await a coroutine with another context's variables applied.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
context (contextvars.Context): The context whose variables to apply.
|
|
294
|
+
coro (collections.abc.Coroutine[typing.Any, typing.Any, T]): The coroutine to await.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
T: The result of the coroutine.
|
|
298
|
+
"""
|
|
299
|
+
for variable, value in context.items():
|
|
300
|
+
variable.set(value)
|
|
301
|
+
return await coro
|