aury-boot 0.0.19__tar.gz → 0.0.29__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.
- {aury_boot-0.0.19 → aury_boot-0.0.29}/PKG-INFO +3 -8
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/_version.py +2 -2
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/app/base.py +6 -2
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/app/components.py +46 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/config/settings.py +32 -2
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/constants/components.py +3 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/handlers.py +40 -16
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/pkg.py +3 -3
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/AGENTS.md.tpl +13 -1
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +31 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +60 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +77 -24
- aury_boot-0.0.29/aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +152 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/messaging.tpl +8 -5
- aury_boot-0.0.29/aury/boot/commands/templates/project/modules/schedules.py.tpl +38 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/repository/impl.py +164 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/transaction/__init__.py +57 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/backends/memory.py +43 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/backends/redis.py +42 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/base.py +15 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/manager.py +41 -11
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/backends/redis.py +9 -2
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/manager.py +9 -5
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/scheduler/manager.py +198 -37
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/tasks/manager.py +37 -17
- {aury_boot-0.0.19 → aury_boot-0.0.29}/pyproject.toml +2 -7
- aury_boot-0.0.19/aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +0 -104
- aury_boot-0.0.19/aury/boot/commands/templates/project/modules/schedules.py.tpl +0 -21
- {aury_boot-0.0.19 → aury_boot-0.0.29}/.gitignore +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/README.md +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/decorators.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/adapter/http.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/app/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/app/middlewares.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/app/startup.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/config/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/config/multi_instance.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/constants/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/constants/scheduler.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/constants/service.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/chain.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/codes.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/errors/response.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/interfaces/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/interfaces/egress.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/interfaces/ingress.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/middleware/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/middleware/logging.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/migrations/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/migrations/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/migrations/setup.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/rpc/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/rpc/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/rpc/client.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/rpc/discovery.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/scheduler/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/scheduler/runner.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/application/server/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/add.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/app.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/docker.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/docs.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/generate.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/init.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/migrate/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/migrate/app.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/migrate/commands.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/scheduler.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/server/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/server/app.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/generate/api.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/generate/model.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/generate/repository.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/generate/schema.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/generate/service.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/README.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/admin_console_init.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/config.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/conftest.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/_header.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/admin.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/cache.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/database.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/log.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/rpc.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/scheduler.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/service.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/storage.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/env_templates/third_party.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/gitignore.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/main.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/modules/api.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/modules/exceptions.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/templates/project/modules/tasks.py.tpl +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/commands/worker.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/exceptions/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/i18n/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/i18n/translator.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/logging/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/logging/context.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/logging/decorators.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/logging/format.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/common/logging/setup.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/admin_console/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/admin_console/auth.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/admin_console/discovery.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/admin_console/install.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/contrib/admin_console/utils.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/exceptions/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/models/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/models/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/models/mixins.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/models/models.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/pagination/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/repository/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/repository/interceptors.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/repository/interface.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/repository/query_builder.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/service/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/domain/service/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/backends.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/factory.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/cache/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/channel/backends/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/rabbitmq/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/rabbitmq/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/rabbitmq/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/redis/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/redis/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/clients/redis/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/query_tools/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/database/strategies/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/di/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/di/container.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/backends/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/backends/memory.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/backends/rabbitmq.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/events/middleware.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/monitoring/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/backends/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/backends/rabbitmq.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/backends/redis.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/mq/manager.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/scheduler/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/scheduler/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/storage/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/storage/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/storage/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/storage/factory.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/tasks/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/tasks/config.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/tasks/constants.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/infrastructure/tasks/exceptions.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/testing/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/testing/base.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/testing/client.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/testing/factory.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/toolkit/__init__.py +0 -0
- {aury_boot-0.0.19 → aury_boot-0.0.29}/aury/boot/toolkit/http/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aury-boot
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.29
|
|
4
4
|
Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: alembic>=1.17.2
|
|
@@ -24,13 +24,11 @@ Requires-Dist: sqladmin>=0.16.0; extra == 'admin'
|
|
|
24
24
|
Provides-Extra: all
|
|
25
25
|
Requires-Dist: aiomysql>=0.3.2; extra == 'all'
|
|
26
26
|
Requires-Dist: aiosqlite>=0.21.0; extra == 'all'
|
|
27
|
-
Requires-Dist: amqp>=5.3.1; extra == 'all'
|
|
28
27
|
Requires-Dist: apscheduler>=3.11.1; extra == 'all'
|
|
29
28
|
Requires-Dist: asyncpg>=0.31.0; extra == 'all'
|
|
30
29
|
Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'all'
|
|
31
|
-
Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'all'
|
|
32
30
|
Requires-Dist: dramatiq>=1.18.0; extra == 'all'
|
|
33
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: pika>=1.3.2; extra == 'all'
|
|
34
32
|
Requires-Dist: redis>=7.1.0; extra == 'all'
|
|
35
33
|
Provides-Extra: dev
|
|
36
34
|
Requires-Dist: mypy>=1.19.0; extra == 'dev'
|
|
@@ -53,9 +51,7 @@ Requires-Dist: aiosqlite>=0.21.0; extra == 'recommended'
|
|
|
53
51
|
Requires-Dist: apscheduler>=3.11.1; extra == 'recommended'
|
|
54
52
|
Requires-Dist: asyncpg>=0.31.0; extra == 'recommended'
|
|
55
53
|
Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'recommended'
|
|
56
|
-
Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'recommended'
|
|
57
54
|
Requires-Dist: dramatiq>=1.18.0; extra == 'recommended'
|
|
58
|
-
Requires-Dist: kombu>=5.6.1; extra == 'recommended'
|
|
59
55
|
Requires-Dist: redis>=7.1.0; extra == 'recommended'
|
|
60
56
|
Provides-Extra: redis
|
|
61
57
|
Requires-Dist: redis>=7.1.0; extra == 'redis'
|
|
@@ -66,9 +62,8 @@ Requires-Dist: apscheduler>=3.11.1; extra == 'scheduler'
|
|
|
66
62
|
Provides-Extra: sqlite
|
|
67
63
|
Requires-Dist: aiosqlite>=0.21.0; extra == 'sqlite'
|
|
68
64
|
Provides-Extra: tasks
|
|
69
|
-
Requires-Dist: dramatiq-kombu-broker>=0.2.2; extra == 'tasks'
|
|
70
65
|
Requires-Dist: dramatiq>=1.18.0; extra == 'tasks'
|
|
71
|
-
Requires-Dist:
|
|
66
|
+
Requires-Dist: redis>=7.1.0; extra == 'tasks'
|
|
72
67
|
Description-Content-Type: text/markdown
|
|
73
68
|
|
|
74
69
|
# Aury Boot
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.29'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 29)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -12,7 +12,9 @@ import sys
|
|
|
12
12
|
from typing import Any, ClassVar
|
|
13
13
|
|
|
14
14
|
from fastapi import FastAPI, Request, status
|
|
15
|
+
from fastapi.exceptions import RequestValidationError
|
|
15
16
|
from fastapi.responses import JSONResponse
|
|
17
|
+
from starlette.exceptions import HTTPException
|
|
16
18
|
from starlette.middleware import Middleware as StarletteMiddleware
|
|
17
19
|
|
|
18
20
|
from aury.boot.application.config import BaseConfig
|
|
@@ -279,8 +281,10 @@ class FoundationApp(FastAPI):
|
|
|
279
281
|
**kwargs,
|
|
280
282
|
)
|
|
281
283
|
|
|
282
|
-
#
|
|
283
|
-
self.add_exception_handler(
|
|
284
|
+
# 异常处理:显式注册以覆盖 FastAPI/Starlette 默认处理器,确保统一响应格式
|
|
285
|
+
self.add_exception_handler(RequestValidationError, global_exception_handler) # 422 参数校验
|
|
286
|
+
self.add_exception_handler(HTTPException, global_exception_handler) # 4xx/5xx HTTP 异常
|
|
287
|
+
self.add_exception_handler(Exception, global_exception_handler) # 其他未处理异常
|
|
284
288
|
|
|
285
289
|
# 设置路由
|
|
286
290
|
self.setup_routes()
|
|
@@ -15,6 +15,7 @@ from aury.boot.application.constants import ComponentName, ServiceType
|
|
|
15
15
|
from aury.boot.application.migrations import MigrationManager
|
|
16
16
|
from aury.boot.common.logging import logger
|
|
17
17
|
from aury.boot.infrastructure.cache import CacheManager
|
|
18
|
+
from aury.boot.infrastructure.channel import ChannelManager
|
|
18
19
|
from aury.boot.infrastructure.database import DatabaseManager
|
|
19
20
|
from aury.boot.infrastructure.events import EventBusManager
|
|
20
21
|
from aury.boot.infrastructure.mq import MQManager
|
|
@@ -505,6 +506,49 @@ class MessageQueueComponent(Component):
|
|
|
505
506
|
logger.warning(f"消息队列 [{name}] 关闭失败: {e}")
|
|
506
507
|
|
|
507
508
|
|
|
509
|
+
class ChannelComponent(Component):
|
|
510
|
+
"""流式通道组件。
|
|
511
|
+
|
|
512
|
+
用于 SSE(Server-Sent Events)和实时通信场景,支持 memory 和 redis 后端。
|
|
513
|
+
"""
|
|
514
|
+
|
|
515
|
+
name = ComponentName.CHANNEL
|
|
516
|
+
enabled = True
|
|
517
|
+
depends_on: ClassVar[list[str]] = []
|
|
518
|
+
|
|
519
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
520
|
+
"""当配置了 Channel 实例时启用。"""
|
|
521
|
+
return self.enabled and bool(config.get_channels())
|
|
522
|
+
|
|
523
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
524
|
+
"""初始化流式通道。"""
|
|
525
|
+
channel_configs = config.get_channels()
|
|
526
|
+
if not channel_configs:
|
|
527
|
+
logger.debug("未配置 Channel 实例,跳过通道初始化")
|
|
528
|
+
return
|
|
529
|
+
|
|
530
|
+
for name, ch_config in channel_configs.items():
|
|
531
|
+
try:
|
|
532
|
+
channel_manager = ChannelManager.get_instance(name)
|
|
533
|
+
if not channel_manager.is_initialized:
|
|
534
|
+
await channel_manager.initialize(
|
|
535
|
+
backend=ch_config.backend,
|
|
536
|
+
url=ch_config.url,
|
|
537
|
+
)
|
|
538
|
+
except Exception as e:
|
|
539
|
+
logger.warning(f"通道 [{name}] 初始化失败(非关键): {e}")
|
|
540
|
+
|
|
541
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
542
|
+
"""关闭所有通道实例。"""
|
|
543
|
+
for name in list(ChannelManager._instances.keys()):
|
|
544
|
+
try:
|
|
545
|
+
channel_manager = ChannelManager.get_instance(name)
|
|
546
|
+
if channel_manager.is_initialized:
|
|
547
|
+
await channel_manager.cleanup()
|
|
548
|
+
except Exception as e:
|
|
549
|
+
logger.warning(f"通道 [{name}] 关闭失败: {e}")
|
|
550
|
+
|
|
551
|
+
|
|
508
552
|
class EventBusComponent(Component):
|
|
509
553
|
"""事件总线组件。
|
|
510
554
|
|
|
@@ -555,6 +599,7 @@ FoundationApp.components = [
|
|
|
555
599
|
StorageComponent,
|
|
556
600
|
TaskComponent,
|
|
557
601
|
MessageQueueComponent,
|
|
602
|
+
ChannelComponent,
|
|
558
603
|
EventBusComponent,
|
|
559
604
|
SchedulerComponent,
|
|
560
605
|
]
|
|
@@ -563,6 +608,7 @@ FoundationApp.components = [
|
|
|
563
608
|
__all__ = [
|
|
564
609
|
"AdminConsoleComponent",
|
|
565
610
|
"CacheComponent",
|
|
611
|
+
"ChannelComponent",
|
|
566
612
|
"DatabaseComponent",
|
|
567
613
|
"EventBusComponent",
|
|
568
614
|
"MessageQueueComponent",
|
|
@@ -246,6 +246,27 @@ class CacheSettings(BaseModel):
|
|
|
246
246
|
)
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
class ChannelSettings(BaseModel):
|
|
250
|
+
"""流式通道配置(单实例)。
|
|
251
|
+
|
|
252
|
+
环境变量格式: CHANNEL__{FIELD}
|
|
253
|
+
示例: CHANNEL__BACKEND, CHANNEL__URL
|
|
254
|
+
|
|
255
|
+
支持的后端类型:
|
|
256
|
+
- memory: 内存后端(默认,单进程)
|
|
257
|
+
- redis: Redis Pub/Sub(多进程/分布式)
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
backend: str = Field(
|
|
261
|
+
default="",
|
|
262
|
+
description="通道后端 (memory/redis),空字符串表示不启用"
|
|
263
|
+
)
|
|
264
|
+
url: str | None = Field(
|
|
265
|
+
default=None,
|
|
266
|
+
description="Redis URL(当 backend=redis 时需要)"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
249
270
|
class StorageSettings(BaseModel):
|
|
250
271
|
"""对象存储组件接入配置(Application 层)。
|
|
251
272
|
|
|
@@ -507,10 +528,9 @@ class MessageQueueSettings(BaseModel):
|
|
|
507
528
|
- Task: 基于 Dramatiq,用于异步任务处理(API + Worker 模式)
|
|
508
529
|
- MQ: 通用消息队列,用于服务间通信、事件驱动架构
|
|
509
530
|
|
|
510
|
-
|
|
531
|
+
支持的后端:
|
|
511
532
|
- Redis: redis://localhost:6379/0
|
|
512
533
|
- RabbitMQ: amqp://guest:guest@localhost:5672//
|
|
513
|
-
- Amazon SQS: sqs://
|
|
514
534
|
"""
|
|
515
535
|
|
|
516
536
|
enabled: bool = Field(
|
|
@@ -780,6 +800,7 @@ class BaseConfig(BaseSettings):
|
|
|
780
800
|
# ========== 数据与缓存 ==========
|
|
781
801
|
database: DatabaseSettings = Field(default_factory=DatabaseSettings)
|
|
782
802
|
cache: CacheSettings = Field(default_factory=CacheSettings)
|
|
803
|
+
channel: ChannelSettings = Field(default_factory=ChannelSettings)
|
|
783
804
|
storage: StorageSettings = Field(default_factory=StorageSettings)
|
|
784
805
|
|
|
785
806
|
# 迁移配置
|
|
@@ -880,10 +901,18 @@ class BaseConfig(BaseSettings):
|
|
|
880
901
|
"""获取所有通道实例配置。
|
|
881
902
|
|
|
882
903
|
从环境变量解析 CHANNEL__{INSTANCE}__{FIELD} 格式的配置。
|
|
904
|
+
如果没有配置多实例,返回从单实例配置转换的 default 实例。
|
|
883
905
|
"""
|
|
884
906
|
if self._channels is None:
|
|
885
907
|
loader = MultiInstanceConfigLoader("CHANNEL", ChannelInstanceConfig)
|
|
886
908
|
self._channels = loader.load()
|
|
909
|
+
if not self._channels and self.channel.backend:
|
|
910
|
+
self._channels = {
|
|
911
|
+
"default": ChannelInstanceConfig(
|
|
912
|
+
backend=self.channel.backend,
|
|
913
|
+
url=self.channel.url,
|
|
914
|
+
)
|
|
915
|
+
}
|
|
887
916
|
return self._channels
|
|
888
917
|
|
|
889
918
|
def get_mqs(self) -> dict[str, MQInstanceConfig]:
|
|
@@ -930,6 +959,7 @@ __all__ = [
|
|
|
930
959
|
"CacheInstanceConfig",
|
|
931
960
|
"CacheSettings",
|
|
932
961
|
"ChannelInstanceConfig",
|
|
962
|
+
"ChannelSettings",
|
|
933
963
|
"DatabaseInstanceConfig",
|
|
934
964
|
"DatabaseSettings",
|
|
935
965
|
"EventInstanceConfig",
|
|
@@ -8,11 +8,12 @@ from __future__ import annotations
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
from fastapi import
|
|
11
|
+
from fastapi import Request, status
|
|
12
12
|
from fastapi.exceptions import RequestValidationError
|
|
13
13
|
from fastapi.responses import JSONResponse
|
|
14
14
|
from pydantic import ValidationError
|
|
15
15
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
|
16
|
+
from starlette.exceptions import HTTPException
|
|
16
17
|
|
|
17
18
|
from aury.boot.common.exceptions import FoundationError
|
|
18
19
|
from aury.boot.common.logging import logger
|
|
@@ -149,23 +150,31 @@ class BaseErrorHandler(ErrorHandler):
|
|
|
149
150
|
|
|
150
151
|
|
|
151
152
|
class HTTPExceptionHandler(ErrorHandler):
|
|
152
|
-
"""
|
|
153
|
+
"""HTTP 异常处理器。
|
|
154
|
+
|
|
155
|
+
处理 FastAPI 和 Starlette 的 HTTPException(包括 404、401、403 等)。
|
|
156
|
+
"""
|
|
153
157
|
|
|
154
158
|
def can_handle(self, exception: Exception) -> bool:
|
|
155
|
-
"""判断是否为HTTP异常。"""
|
|
159
|
+
"""判断是否为 HTTP 异常。"""
|
|
160
|
+
# Starlette HTTPException 是 FastAPI HTTPException 的基类
|
|
156
161
|
return isinstance(exception, HTTPException)
|
|
157
162
|
|
|
158
163
|
async def handle(self, exception: HTTPException, request: Request) -> JSONResponse:
|
|
159
|
-
"""处理HTTP异常。"""
|
|
160
|
-
|
|
164
|
+
"""处理 HTTP 异常。"""
|
|
165
|
+
# 获取错误信息:Starlette 用 detail,FastAPI 也用 detail
|
|
166
|
+
detail = getattr(exception, "detail", str(exception))
|
|
167
|
+
status_code = exception.status_code
|
|
168
|
+
|
|
169
|
+
logger.warning(f"HTTP 异常 [{request.method} {request.url.path}]: {status_code} - {detail}")
|
|
161
170
|
|
|
162
171
|
response = ResponseBuilder.fail(
|
|
163
|
-
message=
|
|
164
|
-
code=
|
|
172
|
+
message=detail if isinstance(detail, str) else str(detail),
|
|
173
|
+
code=status_code,
|
|
165
174
|
)
|
|
166
175
|
|
|
167
176
|
return JSONResponse(
|
|
168
|
-
status_code=
|
|
177
|
+
status_code=status_code,
|
|
169
178
|
content=response.model_dump(mode="json"),
|
|
170
179
|
)
|
|
171
180
|
|
|
@@ -174,6 +183,7 @@ class ValidationErrorHandler(ErrorHandler):
|
|
|
174
183
|
"""验证异常处理器。
|
|
175
184
|
|
|
176
185
|
处理 Pydantic ValidationError 和 FastAPI RequestValidationError。
|
|
186
|
+
返回 422 Unprocessable Entity(符合 FastAPI 规范)。
|
|
177
187
|
"""
|
|
178
188
|
|
|
179
189
|
def can_handle(self, exception: Exception) -> bool:
|
|
@@ -182,24 +192,38 @@ class ValidationErrorHandler(ErrorHandler):
|
|
|
182
192
|
|
|
183
193
|
async def handle(self, exception: Exception, request: Request) -> JSONResponse:
|
|
184
194
|
"""处理验证异常。"""
|
|
185
|
-
logger.warning(f"数据验证失败: {exception}")
|
|
186
|
-
|
|
187
195
|
errors = []
|
|
188
196
|
for error in exception.errors():
|
|
197
|
+
# 构建友好的字段路径:跳过 body 前缀
|
|
198
|
+
loc = error.get("loc", ())
|
|
199
|
+
# FastAPI 会在 loc 前加 'body'/'query'/'path' 等,保留第一个作为来源
|
|
200
|
+
source = str(loc[0]) if loc else ""
|
|
201
|
+
field_path = ".".join(str(part) for part in loc[1:]) if len(loc) > 1 else str(loc[0]) if loc else ""
|
|
202
|
+
|
|
189
203
|
errors.append({
|
|
190
|
-
"field":
|
|
191
|
-
"
|
|
192
|
-
"
|
|
204
|
+
"field": field_path,
|
|
205
|
+
"source": source, # body / query / path / header
|
|
206
|
+
"message": error.get("msg", ""),
|
|
207
|
+
"type": error.get("type", ""),
|
|
208
|
+
"input": error.get("input"), # 实际输入值(便于调试)
|
|
193
209
|
})
|
|
194
210
|
|
|
211
|
+
# 详细日志:方便开发调试
|
|
212
|
+
error_summary = "; ".join(
|
|
213
|
+
f"{e['source']}.{e['field']}({e['type']}): {e['message']}" for e in errors
|
|
214
|
+
)
|
|
215
|
+
logger.warning(
|
|
216
|
+
f"参数校验失败 [{request.method} {request.url.path}]: {error_summary}"
|
|
217
|
+
)
|
|
218
|
+
|
|
195
219
|
response = ResponseBuilder.fail(
|
|
196
|
-
message="
|
|
197
|
-
code=
|
|
220
|
+
message="参数校验失败",
|
|
221
|
+
code=422,
|
|
198
222
|
errors=errors,
|
|
199
223
|
)
|
|
200
224
|
|
|
201
225
|
return JSONResponse(
|
|
202
|
-
status_code=status.
|
|
226
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
203
227
|
content=response.model_dump(mode="json"),
|
|
204
228
|
)
|
|
205
229
|
|
|
@@ -97,16 +97,16 @@ MODULES: dict[str, ModuleInfo] = {
|
|
|
97
97
|
"tasks": ModuleInfo(
|
|
98
98
|
name="tasks",
|
|
99
99
|
desc="Dramatiq 任务队列",
|
|
100
|
-
usage="TaskManager
|
|
100
|
+
usage="TaskManager 异步任务时需要(默认使用 Redis Broker)",
|
|
101
101
|
category=Category.TASK,
|
|
102
|
-
deps=["dramatiq", "
|
|
102
|
+
deps=["dramatiq", "redis"],
|
|
103
103
|
),
|
|
104
104
|
"rabbitmq": ModuleInfo(
|
|
105
105
|
name="rabbitmq",
|
|
106
106
|
desc="RabbitMQ 消息队列后端",
|
|
107
107
|
usage="TaskManager/EventBus 使用 RabbitMQ 时需要(需配合 tasks)",
|
|
108
108
|
category=Category.TASK,
|
|
109
|
-
deps=["
|
|
109
|
+
deps=["pika"],
|
|
110
110
|
),
|
|
111
111
|
# 调度器
|
|
112
112
|
"scheduler": ModuleInfo(
|
|
@@ -75,6 +75,8 @@ mypy {package_name}/
|
|
|
75
75
|
2. **[aury_docs/02-repository.md](./aury_docs/02-repository.md)** - Repository 使用
|
|
76
76
|
- BaseRepository API
|
|
77
77
|
- Filters 语法(__gt, __like 等)
|
|
78
|
+
- Cursor 分页(推荐,性能更优)
|
|
79
|
+
- 流式查询(大数据处理)
|
|
78
80
|
- 自动提交机制
|
|
79
81
|
|
|
80
82
|
3. **[aury_docs/03-service.md](./aury_docs/03-service.md)** - Service 编写与事务
|
|
@@ -162,15 +164,25 @@ class User(Base): ...
|
|
|
162
164
|
- 写操作**必须**使用 `@transactional` 装饰器
|
|
163
165
|
- 只读操作可以不加事务装饰器
|
|
164
166
|
- 跨 Service 调用通过共享 session 实现事务共享
|
|
167
|
+
- **后台任务必须**使用 `@isolated_task` 装饰器
|
|
165
168
|
|
|
166
169
|
```python
|
|
167
|
-
from aury.boot.domain.transaction import transactional
|
|
170
|
+
from aury.boot.domain.transaction import transactional, isolated_task
|
|
168
171
|
|
|
169
172
|
class UserService(BaseService):
|
|
170
173
|
@transactional
|
|
171
174
|
async def create(self, data: UserCreate) -> User:
|
|
172
175
|
# 自动事务管理
|
|
173
176
|
return await self.repo.create(data.model_dump())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# 后台任务必须加 @isolated_task,否则事务不会提交
|
|
180
|
+
@isolated_task
|
|
181
|
+
async def background_upload(space_id: int, url: str):
|
|
182
|
+
async with db.session() as session:
|
|
183
|
+
async with transactional_context(session):
|
|
184
|
+
repo = SpaceRepository(session, Space)
|
|
185
|
+
await repo.update(...)
|
|
174
186
|
```
|
|
175
187
|
|
|
176
188
|
### Manager API 规范
|
|
@@ -72,6 +72,37 @@ result = await repo.paginate(
|
|
|
72
72
|
# - result.has_next: bool # 是否有下一页
|
|
73
73
|
# - result.has_prev: bool # 是否有上一页
|
|
74
74
|
|
|
75
|
+
# === Cursor 分页(推荐,性能更优) ===
|
|
76
|
+
from aury.boot.domain.pagination import CursorPaginationParams
|
|
77
|
+
|
|
78
|
+
# 第一页
|
|
79
|
+
result = await repo.cursor_paginate(
|
|
80
|
+
CursorPaginationParams(limit=20),
|
|
81
|
+
is_active=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# 下一页(带上 cursor)
|
|
85
|
+
result = await repo.cursor_paginate(
|
|
86
|
+
CursorPaginationParams(cursor=result.next_cursor, limit=20),
|
|
87
|
+
is_active=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# CursorPaginationResult 结构:
|
|
91
|
+
# - result.items: list[T] # 数据列表
|
|
92
|
+
# - result.next_cursor: str | None # 下一页游标
|
|
93
|
+
# - result.prev_cursor: str | None # 上一页游标
|
|
94
|
+
# - result.has_next: bool # 是否有下一页
|
|
95
|
+
# - result.has_prev: bool # 是否有上一页
|
|
96
|
+
|
|
97
|
+
# === 流式查询(大数据处理) ===
|
|
98
|
+
# 逐条流式处理,不会一次性加载到内存
|
|
99
|
+
async for user in repo.stream(batch_size=1000, is_active=True):
|
|
100
|
+
await process(user)
|
|
101
|
+
|
|
102
|
+
# 批量流式处理
|
|
103
|
+
async for batch in repo.stream_batches(batch_size=1000):
|
|
104
|
+
await bulk_sync_to_es(batch)
|
|
105
|
+
|
|
75
106
|
# === 创建 ===
|
|
76
107
|
user = await repo.create({{"name": "Alice", "email": "a@b.com"}})
|
|
77
108
|
users = await repo.batch_create([{{"name": "A"}}, {{"name": "B"}}]) # 返回实体
|
|
@@ -396,3 +396,63 @@ DATABASE_ISOLATION_LEVEL=REPEATABLE READ
|
|
|
396
396
|
- 大多数场景:`READ COMMITTED`(平衡性能和一致性)
|
|
397
397
|
- 报表/统计查询:`REPEATABLE READ`(保证读取一致性)
|
|
398
398
|
- 金融交易:`SERIALIZABLE`(最强一致性,性能较低)
|
|
399
|
+
|
|
400
|
+
### 3.12 后台任务事务隔离(重要)
|
|
401
|
+
|
|
402
|
+
在 `@transactional` 装饰的 Service 方法中 spawn 后台任务时,**必须**使用 `@isolated_task` 或 `isolated_context`,否则事务不会提交。
|
|
403
|
+
|
|
404
|
+
**问题背景**:
|
|
405
|
+
`asyncio.create_task()` 会继承父协程的 `contextvars`。如果父协程在 `@transactional` 中,子任务会继承事务深度标记,导致:
|
|
406
|
+
- `auto_commit` 失效
|
|
407
|
+
- `transactional_context` 也不会提交
|
|
408
|
+
- session 关闭时数据被 rollback
|
|
409
|
+
|
|
410
|
+
**解决方案 1:装饰器(推荐)**
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
import asyncio
|
|
414
|
+
from aury.boot.domain.transaction import isolated_task, transactional_context
|
|
415
|
+
from aury.boot.infrastructure.database import DatabaseManager
|
|
416
|
+
|
|
417
|
+
db = DatabaseManager.get_instance()
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@isolated_task
|
|
421
|
+
async def upload_cover(space_id: int, cover_url: str):
|
|
422
|
+
"""后台任务:上传封面。"""
|
|
423
|
+
async with db.session() as session:
|
|
424
|
+
async with transactional_context(session):
|
|
425
|
+
repo = SpaceRepository(session, Space)
|
|
426
|
+
space = await repo.get(space_id)
|
|
427
|
+
if space:
|
|
428
|
+
await repo.update(space, {{"cover": cover_url}})
|
|
429
|
+
# 现在会正常 commit
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class SpaceService(BaseService):
|
|
433
|
+
@transactional
|
|
434
|
+
async def create(self, data: SpaceCreate) -> Space:
|
|
435
|
+
space = await self.repo.create(data.model_dump())
|
|
436
|
+
|
|
437
|
+
# spawn 后台任务
|
|
438
|
+
asyncio.create_task(upload_cover(space.id, data.cover_url))
|
|
439
|
+
|
|
440
|
+
return space
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**解决方案 2:上下文管理器**
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
from aury.boot.domain.transaction import isolated_context
|
|
447
|
+
|
|
448
|
+
async def background_job():
|
|
449
|
+
async with isolated_context():
|
|
450
|
+
async with db.session() as session:
|
|
451
|
+
async with transactional_context(session):
|
|
452
|
+
# 正常的事务处理
|
|
453
|
+
...
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**注意事项**:
|
|
457
|
+
- 后台任务必须新开 session(`db.session()`),不能复用主请求的 `self.session`
|
|
458
|
+
- 后台任务的事务与主请求独立,主请求回滚不影响后台任务
|