aury-boot 0.0.42__tar.gz → 0.0.44__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.42 → aury_boot-0.0.44}/PKG-INFO +1 -5
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/_version.py +2 -2
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/redis.py +21 -33
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/channel/__init__.py +2 -2
- aury_boot-0.0.44/aury/boot/infrastructure/channel/backends/__init__.py +5 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/channel/base.py +1 -3
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/channel/manager.py +8 -8
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/redis/manager.py +17 -33
- {aury_boot-0.0.42 → aury_boot-0.0.44}/pyproject.toml +0 -3
- aury_boot-0.0.42/aury/boot/infrastructure/channel/backends/__init__.py +0 -6
- aury_boot-0.0.42/aury/boot/infrastructure/channel/backends/redis_cluster.py +0 -124
- aury_boot-0.0.42/aury/boot/infrastructure/channel/backends/redis_cluster_channel.py +0 -139
- {aury_boot-0.0.42 → aury_boot-0.0.44}/.gitignore +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/README.md +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/decorators.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/adapter/http.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/app/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/app/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/app/components.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/app/middlewares.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/app/startup.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/config/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/config/multi_instance.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/config/settings.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/constants/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/constants/components.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/constants/scheduler.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/constants/service.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/chain.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/codes.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/handlers.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/errors/response.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/interfaces/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/interfaces/egress.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/interfaces/ingress.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/middleware/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/middleware/logging.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/migrations/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/migrations/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/migrations/setup.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/rpc/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/rpc/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/rpc/client.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/rpc/discovery.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/scheduler/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/scheduler/runner.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/application/server/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/add.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/app.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/docker.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/docs.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/generate.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/init.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/migrate/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/migrate/app.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/migrate/commands.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/pkg.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/scheduler.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/server/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/server/app.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/generate/api.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/generate/model.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/generate/repository.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/generate/schema.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/generate/service.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/AGENTS.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/README.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/admin_console_init.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/alert_rules.example.yaml.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/17-alerting.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/18-monitoring-profiling.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/config.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/conftest.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/_header.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/admin.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/cache.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/database.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/log.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/messaging.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/monitoring.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/rpc.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/scheduler.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/service.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/storage.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/env_templates/third_party.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/gitignore.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/main.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/modules/api.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/modules/exceptions.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/modules/schedules.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/templates/project/modules/tasks.py.tpl +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/commands/worker.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/exceptions/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/i18n/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/i18n/translator.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/logging/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/logging/context.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/logging/decorators.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/logging/format.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/common/logging/setup.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/admin_console/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/admin_console/auth.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/admin_console/discovery.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/admin_console/install.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/contrib/admin_console/utils.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/exceptions/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/models/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/models/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/models/mixins.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/models/models.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/pagination/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/repository/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/repository/impl.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/repository/interceptors.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/repository/interface.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/repository/query_builder.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/service/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/service/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/domain/transaction/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/backends.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/factory.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/cache/memory.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/channel/backends/broadcaster.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/rabbitmq/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/rabbitmq/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/rabbitmq/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/redis/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/clients/redis/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/query_tools/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/database/strategies/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/di/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/di/container.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/backends/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/backends/broadcaster.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/backends/rabbitmq.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/events/middleware.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/aggregator.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/events.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/notifiers/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/notifiers/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/notifiers/feishu.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/notifiers/webhook.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/alerting/rules.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/health/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/profiling/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/context.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/logging.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/processor.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/provider.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/monitoring/tracing/tracing.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/backends/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/backends/rabbitmq.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/backends/redis.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/backends/redis_stream.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/mq/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/scheduler/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/scheduler/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/scheduler/jobstores/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/scheduler/jobstores/redis_cluster.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/scheduler/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/storage/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/storage/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/storage/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/storage/factory.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/tasks/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/tasks/config.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/tasks/constants.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/tasks/exceptions.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/infrastructure/tasks/manager.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/testing/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/testing/base.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/testing/client.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/testing/factory.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/aury/boot/toolkit/__init__.py +0 -0
- {aury_boot-0.0.42 → aury_boot-0.0.44}/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.44
|
|
4
4
|
Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: aiohttp>=3.11.0
|
|
@@ -28,7 +28,6 @@ Requires-Dist: aiosqlite>=0.21.0; extra == 'all'
|
|
|
28
28
|
Requires-Dist: apscheduler>=3.11.1; extra == 'all'
|
|
29
29
|
Requires-Dist: asyncpg>=0.31.0; extra == 'all'
|
|
30
30
|
Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'all'
|
|
31
|
-
Requires-Dist: coredis>=5.6.0; extra == 'all'
|
|
32
31
|
Requires-Dist: dramatiq>=1.18.0; extra == 'all'
|
|
33
32
|
Requires-Dist: pika>=1.3.2; extra == 'all'
|
|
34
33
|
Requires-Dist: psutil>=7.0.0; extra == 'all'
|
|
@@ -36,8 +35,6 @@ Requires-Dist: pyroscope-io>=0.8.7; extra == 'all'
|
|
|
36
35
|
Requires-Dist: redis>=7.1.0; extra == 'all'
|
|
37
36
|
Provides-Extra: broadcaster
|
|
38
37
|
Requires-Dist: broadcaster[redis]>=0.3.1; extra == 'broadcaster'
|
|
39
|
-
Provides-Extra: channel-cluster
|
|
40
|
-
Requires-Dist: coredis>=5.6.0; extra == 'channel-cluster'
|
|
41
38
|
Provides-Extra: dev
|
|
42
39
|
Requires-Dist: httpx>=0.28.1; extra == 'dev'
|
|
43
40
|
Requires-Dist: mypy>=1.19.0; extra == 'dev'
|
|
@@ -71,7 +68,6 @@ Requires-Dist: aiosqlite>=0.21.0; extra == 'recommended'
|
|
|
71
68
|
Requires-Dist: apscheduler>=3.11.1; extra == 'recommended'
|
|
72
69
|
Requires-Dist: asyncpg>=0.31.0; extra == 'recommended'
|
|
73
70
|
Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'recommended'
|
|
74
|
-
Requires-Dist: coredis>=5.6.0; extra == 'recommended'
|
|
75
71
|
Requires-Dist: dramatiq>=1.18.0; extra == 'recommended'
|
|
76
72
|
Requires-Dist: opentelemetry-api>=1.25.0; extra == 'recommended'
|
|
77
73
|
Requires-Dist: opentelemetry-instrumentation-aiohttp-client>=0.46b0; extra == 'recommended'
|
|
@@ -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.44'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 44)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -96,46 +96,31 @@ class RedisCache(ICache):
|
|
|
96
96
|
self._is_cluster = False
|
|
97
97
|
|
|
98
98
|
async def _init_cluster(self) -> None:
|
|
99
|
-
"""初始化 Redis Cluster(使用
|
|
99
|
+
"""初始化 Redis Cluster(使用 redis-py)。
|
|
100
100
|
|
|
101
101
|
支持 URL 格式:
|
|
102
102
|
- redis-cluster://password@host:port (密码在用户名位置)
|
|
103
103
|
- redis-cluster://:password@host:port (标准格式)
|
|
104
104
|
- redis-cluster://username:password@host:port (ACL 模式)
|
|
105
105
|
"""
|
|
106
|
-
|
|
107
|
-
from coredis import RedisCluster
|
|
108
|
-
except ImportError as exc:
|
|
109
|
-
raise ImportError(
|
|
110
|
-
"Redis Cluster 需要安装 coredis: pip install coredis"
|
|
111
|
-
) from exc
|
|
112
|
-
|
|
113
|
-
# 解析 URL
|
|
114
|
-
parsed_url = self._url.replace("redis-cluster://", "redis://")
|
|
115
|
-
parsed = urlparse(parsed_url)
|
|
116
|
-
|
|
117
|
-
# 提取认证信息
|
|
118
|
-
username = parsed.username
|
|
119
|
-
password = parsed.password
|
|
120
|
-
|
|
121
|
-
# 处理 password@host 格式
|
|
122
|
-
if username and not password:
|
|
123
|
-
password = username
|
|
124
|
-
username = None
|
|
106
|
+
from redis.asyncio.cluster import RedisCluster
|
|
125
107
|
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
"host": parsed.hostname or "localhost",
|
|
129
|
-
"port": parsed.port or 6379,
|
|
130
|
-
"decode_responses": False,
|
|
131
|
-
}
|
|
108
|
+
# 转换 URL scheme
|
|
109
|
+
redis_url = self._url.replace("redis-cluster://", "redis://")
|
|
132
110
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if password:
|
|
136
|
-
|
|
111
|
+
# 处理 password@host 格式(转换为标准 :password@host 格式)
|
|
112
|
+
parsed = urlparse(redis_url)
|
|
113
|
+
if parsed.username and not parsed.password:
|
|
114
|
+
# redis://password@host -> redis://:password@host
|
|
115
|
+
redis_url = redis_url.replace(
|
|
116
|
+
f"redis://{parsed.username}@",
|
|
117
|
+
f"redis://:{parsed.username}@"
|
|
118
|
+
)
|
|
137
119
|
|
|
138
|
-
self._redis = RedisCluster(
|
|
120
|
+
self._redis = RedisCluster.from_url(
|
|
121
|
+
redis_url,
|
|
122
|
+
decode_responses=False,
|
|
123
|
+
)
|
|
139
124
|
self._is_cluster = True
|
|
140
125
|
|
|
141
126
|
async def get(self, key: str, default: Any = None) -> Any:
|
|
@@ -254,8 +239,11 @@ class RedisCache(ICache):
|
|
|
254
239
|
async def close(self) -> None:
|
|
255
240
|
"""关闭连接(仅当自己拥有连接时)。"""
|
|
256
241
|
if self._redis and self._owns_connection:
|
|
257
|
-
|
|
258
|
-
|
|
242
|
+
if self._is_cluster:
|
|
243
|
+
# redis-py cluster 使用 aclose()
|
|
244
|
+
await self._redis.aclose()
|
|
245
|
+
else:
|
|
246
|
+
await self._redis.close()
|
|
259
247
|
logger.info("Redis连接已关闭")
|
|
260
248
|
self._redis = None
|
|
261
249
|
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
支持的后端(通过 Broadcaster 库):
|
|
6
6
|
- memory:// - 内存通道(单进程,开发/测试用)
|
|
7
7
|
- redis:// - Redis Pub/Sub(多进程/分布式)
|
|
8
|
+
- redis-cluster:// - Redis Cluster(自动转换为普通 Pub/Sub)
|
|
8
9
|
- kafka:// - Apache Kafka
|
|
9
10
|
- postgres:// - PostgreSQL LISTEN/NOTIFY
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
|
-
from .backends import BroadcasterChannel
|
|
13
|
+
from .backends import BroadcasterChannel
|
|
13
14
|
from .base import ChannelBackend, ChannelMessage, IChannel
|
|
14
15
|
from .manager import ChannelManager
|
|
15
16
|
|
|
@@ -22,5 +23,4 @@ __all__ = [
|
|
|
22
23
|
"ChannelManager",
|
|
23
24
|
# 后端实现
|
|
24
25
|
"BroadcasterChannel",
|
|
25
|
-
"RedisClusterChannel",
|
|
26
26
|
]
|
|
@@ -16,10 +16,8 @@ from typing import Any
|
|
|
16
16
|
class ChannelBackend(Enum):
|
|
17
17
|
"""通道后端类型。"""
|
|
18
18
|
|
|
19
|
-
# Broadcaster 统一后端(支持 memory/redis/kafka/postgres,通过 URL scheme 区分)
|
|
19
|
+
# Broadcaster 统一后端(支持 memory/redis/redis-cluster/kafka/postgres,通过 URL scheme 区分)
|
|
20
20
|
BROADCASTER = "broadcaster"
|
|
21
|
-
# Redis Cluster + Sharded Pub/Sub (Redis 7.0+),使用 coredis 库
|
|
22
|
-
REDIS_CLUSTER = "redis_cluster"
|
|
23
21
|
# 未来扩展
|
|
24
22
|
RABBITMQ = "rabbitmq"
|
|
25
23
|
ROCKETMQ = "rocketmq"
|
|
@@ -10,7 +10,6 @@ from collections.abc import AsyncIterator
|
|
|
10
10
|
from aury.boot.common.logging import logger
|
|
11
11
|
|
|
12
12
|
from .backends.broadcaster import BroadcasterChannel
|
|
13
|
-
from .backends.redis_cluster_channel import RedisClusterChannel
|
|
14
13
|
from .base import ChannelBackend, ChannelMessage, IChannel
|
|
15
14
|
|
|
16
15
|
|
|
@@ -94,7 +93,7 @@ class ChannelManager:
|
|
|
94
93
|
url: 连接 URL,支持:
|
|
95
94
|
- memory:// - 内存后端(单进程,默认)
|
|
96
95
|
- redis://host:port/db - Redis Pub/Sub
|
|
97
|
-
- redis-cluster://[password@]host:port - Redis Cluster (
|
|
96
|
+
- redis-cluster://[password@]host:port - Redis Cluster (普通 Pub/Sub)
|
|
98
97
|
- kafka://host:port - Apache Kafka
|
|
99
98
|
- postgres://user:pass@host/db - PostgreSQL
|
|
100
99
|
|
|
@@ -109,17 +108,18 @@ class ChannelManager:
|
|
|
109
108
|
if isinstance(backend, str):
|
|
110
109
|
backend = ChannelBackend(backend.lower())
|
|
111
110
|
|
|
112
|
-
#
|
|
111
|
+
# redis-cluster:// 转换为 redis://,使用 broadcaster 的普通 Pub/Sub
|
|
112
|
+
# 普通 Pub/Sub 在 Redis Cluster 中会自动广播到所有节点
|
|
113
|
+
broadcast_url = url
|
|
113
114
|
if url.startswith("redis-cluster://"):
|
|
114
|
-
|
|
115
|
+
broadcast_url = url.replace("redis-cluster://", "redis://")
|
|
116
|
+
logger.info(f"通道管理器 [{self.name}] Redis Cluster 使用普通 Pub/Sub 模式")
|
|
115
117
|
|
|
116
118
|
self._backend_type = backend
|
|
117
119
|
self._url = url
|
|
118
120
|
|
|
119
|
-
if backend == ChannelBackend.BROADCASTER:
|
|
120
|
-
self._backend = BroadcasterChannel(
|
|
121
|
-
elif backend == ChannelBackend.REDIS_CLUSTER:
|
|
122
|
-
self._backend = RedisClusterChannel(url)
|
|
121
|
+
if backend == ChannelBackend.BROADCASTER or url.startswith("redis-cluster://"):
|
|
122
|
+
self._backend = BroadcasterChannel(broadcast_url)
|
|
123
123
|
elif backend in (ChannelBackend.RABBITMQ, ChannelBackend.ROCKETMQ):
|
|
124
124
|
raise NotImplementedError(f"{backend.value} 后端暂未实现")
|
|
125
125
|
else:
|
|
@@ -202,46 +202,30 @@ class RedisClient:
|
|
|
202
202
|
self._is_cluster = False
|
|
203
203
|
|
|
204
204
|
async def _initialize_cluster(self, url: str) -> None:
|
|
205
|
-
"""初始化 Redis Cluster 连接(使用
|
|
205
|
+
"""初始化 Redis Cluster 连接(使用 redis-py)。
|
|
206
206
|
|
|
207
207
|
支持 URL 格式:
|
|
208
208
|
- redis-cluster://password@host:port (密码在用户名位置)
|
|
209
209
|
- redis-cluster://:password@host:port (标准格式)
|
|
210
210
|
- redis-cluster://username:password@host:port (ACL 模式)
|
|
211
211
|
"""
|
|
212
|
-
|
|
213
|
-
from coredis import RedisCluster
|
|
214
|
-
except ImportError as exc:
|
|
215
|
-
raise ImportError(
|
|
216
|
-
"Redis Cluster 需要安装 coredis: pip install coredis"
|
|
217
|
-
) from exc
|
|
218
|
-
|
|
219
|
-
# 解析 URL
|
|
220
|
-
parsed_url = url.replace("redis-cluster://", "redis://")
|
|
221
|
-
parsed = urlparse(parsed_url)
|
|
222
|
-
|
|
223
|
-
# 提取认证信息
|
|
224
|
-
username = parsed.username
|
|
225
|
-
password = parsed.password
|
|
226
|
-
|
|
227
|
-
# 处理 password@host 格式
|
|
228
|
-
if username and not password:
|
|
229
|
-
password = username
|
|
230
|
-
username = None
|
|
212
|
+
from redis.asyncio.cluster import RedisCluster
|
|
231
213
|
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
"host": parsed.hostname or "localhost",
|
|
235
|
-
"port": parsed.port or 6379,
|
|
236
|
-
"decode_responses": self._config.decode_responses,
|
|
237
|
-
}
|
|
214
|
+
# 转换 URL scheme
|
|
215
|
+
redis_url = url.replace("redis-cluster://", "redis://")
|
|
238
216
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if password:
|
|
242
|
-
|
|
217
|
+
# 处理 password@host 格式(转换为标准 :password@host 格式)
|
|
218
|
+
parsed = urlparse(redis_url)
|
|
219
|
+
if parsed.username and not parsed.password:
|
|
220
|
+
redis_url = redis_url.replace(
|
|
221
|
+
f"redis://{parsed.username}@",
|
|
222
|
+
f"redis://:{parsed.username}@"
|
|
223
|
+
)
|
|
243
224
|
|
|
244
|
-
self._redis = RedisCluster(
|
|
225
|
+
self._redis = RedisCluster.from_url(
|
|
226
|
+
redis_url,
|
|
227
|
+
decode_responses=self._config.decode_responses,
|
|
228
|
+
)
|
|
245
229
|
self._is_cluster = True
|
|
246
230
|
|
|
247
231
|
def _mask_url(self, url: str) -> str:
|
|
@@ -316,8 +300,8 @@ class RedisClient:
|
|
|
316
300
|
"""清理资源,关闭连接。"""
|
|
317
301
|
if self._redis:
|
|
318
302
|
if self._is_cluster:
|
|
319
|
-
#
|
|
320
|
-
await self._redis.
|
|
303
|
+
# redis-py cluster 使用 aclose()
|
|
304
|
+
await self._redis.aclose()
|
|
321
305
|
else:
|
|
322
306
|
await self._redis.close()
|
|
323
307
|
logger.info(f"Redis 客户端 [{self.name}] 已关闭")
|
|
@@ -51,7 +51,6 @@ redis = ["redis>=7.1.0"]
|
|
|
51
51
|
|
|
52
52
|
# ============ 实时通信 (Pub/Sub) ============
|
|
53
53
|
broadcaster = ["broadcaster[redis]>=0.3.1"]
|
|
54
|
-
channel-cluster = ["coredis>=5.6.0"] # Redis Cluster Sharded Pub/Sub
|
|
55
54
|
|
|
56
55
|
# ============ 对象存储 ============
|
|
57
56
|
s3 = ["aury-sdk-storage[aws]>=0.0.1"]
|
|
@@ -97,7 +96,6 @@ admin = [
|
|
|
97
96
|
recommended = [
|
|
98
97
|
"asyncpg>=0.31.0",
|
|
99
98
|
"redis>=7.1.0",
|
|
100
|
-
"coredis>=5.6.0", # Redis Cluster Channel
|
|
101
99
|
"dramatiq>=1.18.0",
|
|
102
100
|
"apscheduler>=3.11.1",
|
|
103
101
|
"aury-sdk-storage[aws]>=0.0.1",
|
|
@@ -115,7 +113,6 @@ all = [
|
|
|
115
113
|
"aiomysql>=0.3.2",
|
|
116
114
|
"aiosqlite>=0.21.0",
|
|
117
115
|
"redis>=7.1.0",
|
|
118
|
-
"coredis>=5.6.0", # Redis Cluster Channel
|
|
119
116
|
"aury-sdk-storage[aws]>=0.0.1",
|
|
120
117
|
"dramatiq>=1.18.0",
|
|
121
118
|
"pika>=1.3.2", # RabbitMQ
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"""Redis Cluster 通道后端。
|
|
2
|
-
|
|
3
|
-
使用 coredis 库支持异步 Redis Cluster Sharded Pub/Sub (Redis 7.0+)。
|
|
4
|
-
|
|
5
|
-
URL 格式:
|
|
6
|
-
redis-cluster://password@host:port
|
|
7
|
-
redis-cluster://host:port
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import asyncio
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
from typing import TYPE_CHECKING, Any
|
|
15
|
-
from urllib.parse import urlparse
|
|
16
|
-
|
|
17
|
-
from aury.boot.common.logging import logger
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
from coredis import RedisCluster
|
|
21
|
-
except ImportError as exc:
|
|
22
|
-
raise ImportError(
|
|
23
|
-
"Redis Cluster Channel 需要安装 coredis: pip install coredis"
|
|
24
|
-
) from exc
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class Event:
|
|
29
|
-
"""与 broadcaster._base.Event 兼容的事件类。"""
|
|
30
|
-
channel: str
|
|
31
|
-
message: str
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class RedisClusterBackend:
|
|
35
|
-
"""Redis Cluster Pub/Sub 后端。
|
|
36
|
-
|
|
37
|
-
使用 coredis 库的 sharded_pubsub() 支持 Sharded Pub/Sub。
|
|
38
|
-
与 broadcaster.RedisBackend 接口兼容。
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def __init__(self, url: str) -> None:
|
|
42
|
-
host, port, password = self._parse_url(url)
|
|
43
|
-
|
|
44
|
-
self._client: RedisCluster = RedisCluster(
|
|
45
|
-
host=host,
|
|
46
|
-
port=port,
|
|
47
|
-
password=password,
|
|
48
|
-
decode_responses=True,
|
|
49
|
-
)
|
|
50
|
-
self._pubsub: Any = None
|
|
51
|
-
self._queue: asyncio.Queue[Event] = asyncio.Queue()
|
|
52
|
-
self._listener_task: asyncio.Task | None = None
|
|
53
|
-
self._subscribed_channels: set[str] = set()
|
|
54
|
-
|
|
55
|
-
def _parse_url(self, url: str) -> tuple[str, int, str | None]:
|
|
56
|
-
"""解析 URL。"""
|
|
57
|
-
url = url.replace("redis-cluster://", "redis://")
|
|
58
|
-
parsed = urlparse(url)
|
|
59
|
-
password = parsed.password
|
|
60
|
-
# 支持 password@host 格式
|
|
61
|
-
if not password and parsed.username:
|
|
62
|
-
password = parsed.username
|
|
63
|
-
return parsed.hostname or "localhost", parsed.port or 6379, password
|
|
64
|
-
|
|
65
|
-
async def connect(self) -> None:
|
|
66
|
-
"""连接并初始化 pubsub。"""
|
|
67
|
-
# coredis 的 sharded_pubsub() 支持 Redis 7.0+ Sharded Pub/Sub
|
|
68
|
-
self._pubsub = self._client.sharded_pubsub()
|
|
69
|
-
self._listener_task = asyncio.create_task(self._listen_loop())
|
|
70
|
-
logger.debug("Redis Cluster Channel 已连接")
|
|
71
|
-
|
|
72
|
-
async def disconnect(self) -> None:
|
|
73
|
-
"""断开连接。"""
|
|
74
|
-
if self._listener_task:
|
|
75
|
-
self._listener_task.cancel()
|
|
76
|
-
try:
|
|
77
|
-
await self._listener_task
|
|
78
|
-
except asyncio.CancelledError:
|
|
79
|
-
pass
|
|
80
|
-
if self._pubsub:
|
|
81
|
-
await self._pubsub.sunsubscribe()
|
|
82
|
-
await self._client.close()
|
|
83
|
-
logger.debug("Redis Cluster Channel 已断开")
|
|
84
|
-
|
|
85
|
-
async def subscribe(self, channel: str) -> None:
|
|
86
|
-
"""订阅频道。"""
|
|
87
|
-
if self._pubsub:
|
|
88
|
-
await self._pubsub.ssubscribe(channel)
|
|
89
|
-
self._subscribed_channels.add(channel)
|
|
90
|
-
|
|
91
|
-
async def unsubscribe(self, channel: str) -> None:
|
|
92
|
-
"""取消订阅。"""
|
|
93
|
-
if self._pubsub and channel in self._subscribed_channels:
|
|
94
|
-
await self._pubsub.sunsubscribe(channel)
|
|
95
|
-
self._subscribed_channels.discard(channel)
|
|
96
|
-
|
|
97
|
-
async def publish(self, channel: str, message: str) -> None:
|
|
98
|
-
"""发布消息(使用 SPUBLISH)。"""
|
|
99
|
-
await self._client.spublish(channel, message)
|
|
100
|
-
|
|
101
|
-
async def next_published(self) -> Event:
|
|
102
|
-
"""获取下一条消息。"""
|
|
103
|
-
return await self._queue.get()
|
|
104
|
-
|
|
105
|
-
async def _listen_loop(self) -> None:
|
|
106
|
-
"""监听消息循环。"""
|
|
107
|
-
while True:
|
|
108
|
-
try:
|
|
109
|
-
if self._pubsub:
|
|
110
|
-
async for message in self._pubsub:
|
|
111
|
-
if message and message.get("type") in ("message", "smessage"):
|
|
112
|
-
event = Event(
|
|
113
|
-
channel=message.get("channel", ""),
|
|
114
|
-
message=message.get("data", ""),
|
|
115
|
-
)
|
|
116
|
-
await self._queue.put(event)
|
|
117
|
-
except asyncio.CancelledError:
|
|
118
|
-
break
|
|
119
|
-
except Exception as e:
|
|
120
|
-
logger.warning(f"Redis Cluster pubsub error: {e}")
|
|
121
|
-
await asyncio.sleep(1)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
__all__ = ["RedisClusterBackend"]
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"""Redis Cluster 通道封装。
|
|
2
|
-
|
|
3
|
-
使用 RedisClusterBackend,提供与 BroadcasterChannel 一致的接口。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
from collections.abc import AsyncIterator
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
|
|
12
|
-
from aury.boot.common.logging import logger
|
|
13
|
-
|
|
14
|
-
from ..base import ChannelMessage, IChannel
|
|
15
|
-
from .redis_cluster import RedisClusterBackend
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class RedisClusterChannel(IChannel):
|
|
19
|
-
"""Redis Cluster 通道实现。
|
|
20
|
-
|
|
21
|
-
使用 broadcaster 架构:共享连接 + Queue 分发。
|
|
22
|
-
支持 Sharded Pub/Sub (Redis 7.0+)。
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, url: str) -> None:
|
|
26
|
-
"""初始化 Redis Cluster 通道。
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
url: redis-cluster://[password@]host:port
|
|
30
|
-
"""
|
|
31
|
-
self._url = url
|
|
32
|
-
self._backend = RedisClusterBackend(url)
|
|
33
|
-
self._connected = False
|
|
34
|
-
# 订阅者管理(与 broadcaster 相同的模式)
|
|
35
|
-
self._subscribers: dict[str, set] = {}
|
|
36
|
-
self._listener_task = None
|
|
37
|
-
|
|
38
|
-
async def _ensure_connected(self) -> None:
|
|
39
|
-
if not self._connected:
|
|
40
|
-
await self._backend.connect()
|
|
41
|
-
self._listener_task = __import__("asyncio").create_task(self._listener())
|
|
42
|
-
self._connected = True
|
|
43
|
-
logger.debug(f"Redis Cluster 通道已连接: {self._mask_url(self._url)}")
|
|
44
|
-
|
|
45
|
-
def _mask_url(self, url: str) -> str:
|
|
46
|
-
if "@" in url:
|
|
47
|
-
parts = url.split("@")
|
|
48
|
-
prefix = parts[0]
|
|
49
|
-
suffix = parts[1]
|
|
50
|
-
if "://" in prefix:
|
|
51
|
-
scheme = prefix.split("://")[0]
|
|
52
|
-
return f"{scheme}://***@{suffix}"
|
|
53
|
-
return url
|
|
54
|
-
|
|
55
|
-
async def _listener(self) -> None:
|
|
56
|
-
"""监听后端消息,分发到订阅者。"""
|
|
57
|
-
import asyncio
|
|
58
|
-
while True:
|
|
59
|
-
try:
|
|
60
|
-
event = await self._backend.next_published()
|
|
61
|
-
channel = event.channel
|
|
62
|
-
if channel in self._subscribers:
|
|
63
|
-
for queue in list(self._subscribers[channel]):
|
|
64
|
-
await queue.put(event)
|
|
65
|
-
except asyncio.CancelledError:
|
|
66
|
-
break
|
|
67
|
-
except Exception as e:
|
|
68
|
-
logger.warning(f"Redis Cluster listener error: {e}")
|
|
69
|
-
|
|
70
|
-
async def publish(self, channel: str, message: ChannelMessage) -> None:
|
|
71
|
-
await self._ensure_connected()
|
|
72
|
-
message.channel = channel
|
|
73
|
-
data = {
|
|
74
|
-
"data": message.data,
|
|
75
|
-
"event": message.event,
|
|
76
|
-
"id": message.id,
|
|
77
|
-
"channel": message.channel,
|
|
78
|
-
"timestamp": message.timestamp.isoformat(),
|
|
79
|
-
}
|
|
80
|
-
await self._backend.publish(channel, json.dumps(data))
|
|
81
|
-
|
|
82
|
-
async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
|
|
83
|
-
import asyncio
|
|
84
|
-
await self._ensure_connected()
|
|
85
|
-
|
|
86
|
-
queue: asyncio.Queue = asyncio.Queue()
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
# 首个订阅者时订阅 Redis
|
|
90
|
-
if channel not in self._subscribers:
|
|
91
|
-
await self._backend.subscribe(channel)
|
|
92
|
-
self._subscribers[channel] = set()
|
|
93
|
-
self._subscribers[channel].add(queue)
|
|
94
|
-
|
|
95
|
-
while True:
|
|
96
|
-
event = await queue.get()
|
|
97
|
-
try:
|
|
98
|
-
data = json.loads(event.message)
|
|
99
|
-
yield ChannelMessage(
|
|
100
|
-
data=data.get("data"),
|
|
101
|
-
event=data.get("event"),
|
|
102
|
-
id=data.get("id"),
|
|
103
|
-
channel=data.get("channel") or channel,
|
|
104
|
-
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
105
|
-
if data.get("timestamp")
|
|
106
|
-
else datetime.now(),
|
|
107
|
-
)
|
|
108
|
-
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
109
|
-
logger.warning(f"解析通道消息失败: {e}")
|
|
110
|
-
finally:
|
|
111
|
-
if channel in self._subscribers:
|
|
112
|
-
self._subscribers[channel].discard(queue)
|
|
113
|
-
if not self._subscribers[channel]:
|
|
114
|
-
del self._subscribers[channel]
|
|
115
|
-
try:
|
|
116
|
-
await self._backend.unsubscribe(channel)
|
|
117
|
-
except Exception:
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
|
|
121
|
-
raise NotImplementedError(
|
|
122
|
-
"Redis Cluster Sharded Pub/Sub 不支持模式订阅。"
|
|
123
|
-
"请使用具体的 channel 名称。"
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
async def unsubscribe(self, channel: str) -> None:
|
|
127
|
-
pass # subscribe() 的 finally 块自动处理
|
|
128
|
-
|
|
129
|
-
async def close(self) -> None:
|
|
130
|
-
if self._connected:
|
|
131
|
-
if self._listener_task:
|
|
132
|
-
self._listener_task.cancel()
|
|
133
|
-
await self._backend.disconnect()
|
|
134
|
-
self._connected = False
|
|
135
|
-
self._subscribers.clear()
|
|
136
|
-
logger.debug("Redis Cluster 通道已关闭")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
__all__ = ["RedisClusterChannel"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|