aury-boot 0.0.40__tar.gz → 0.0.41__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.
Files changed (225) hide show
  1. {aury_boot-0.0.40 → aury_boot-0.0.41}/PKG-INFO +5 -1
  2. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/_version.py +2 -2
  3. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/app/components.py +12 -1
  4. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/config/settings.py +7 -2
  5. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/redis.py +82 -16
  6. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/channel/__init__.py +2 -1
  7. aury_boot-0.0.41/aury/boot/infrastructure/channel/backends/__init__.py +6 -0
  8. aury_boot-0.0.41/aury/boot/infrastructure/channel/backends/redis_cluster.py +124 -0
  9. aury_boot-0.0.41/aury/boot/infrastructure/channel/backends/redis_cluster_channel.py +139 -0
  10. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/channel/base.py +2 -0
  11. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/channel/manager.py +9 -1
  12. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/redis/manager.py +90 -19
  13. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/notifiers/feishu.py +2 -1
  14. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/profiling/__init__.py +135 -44
  15. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/scheduler/__init__.py +2 -0
  16. aury_boot-0.0.41/aury/boot/infrastructure/scheduler/jobstores/__init__.py +10 -0
  17. aury_boot-0.0.41/aury/boot/infrastructure/scheduler/jobstores/redis_cluster.py +255 -0
  18. {aury_boot-0.0.40 → aury_boot-0.0.41}/pyproject.toml +3 -0
  19. aury_boot-0.0.40/aury/boot/infrastructure/channel/backends/__init__.py +0 -5
  20. {aury_boot-0.0.40 → aury_boot-0.0.41}/.gitignore +0 -0
  21. {aury_boot-0.0.40 → aury_boot-0.0.41}/README.md +0 -0
  22. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/__init__.py +0 -0
  23. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/__init__.py +0 -0
  24. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/__init__.py +0 -0
  25. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/base.py +0 -0
  26. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/config.py +0 -0
  27. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/decorators.py +0 -0
  28. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/exceptions.py +0 -0
  29. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/adapter/http.py +0 -0
  30. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/app/__init__.py +0 -0
  31. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/app/base.py +0 -0
  32. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/app/middlewares.py +0 -0
  33. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/app/startup.py +0 -0
  34. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/config/__init__.py +0 -0
  35. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/config/multi_instance.py +0 -0
  36. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/constants/__init__.py +0 -0
  37. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/constants/components.py +0 -0
  38. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/constants/scheduler.py +0 -0
  39. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/constants/service.py +0 -0
  40. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/__init__.py +0 -0
  41. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/chain.py +0 -0
  42. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/codes.py +0 -0
  43. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/exceptions.py +0 -0
  44. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/handlers.py +0 -0
  45. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/errors/response.py +0 -0
  46. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/interfaces/__init__.py +0 -0
  47. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/interfaces/egress.py +0 -0
  48. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/interfaces/ingress.py +0 -0
  49. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/middleware/__init__.py +0 -0
  50. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/middleware/logging.py +0 -0
  51. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/migrations/__init__.py +0 -0
  52. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/migrations/manager.py +0 -0
  53. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/migrations/setup.py +0 -0
  54. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/rpc/__init__.py +0 -0
  55. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/rpc/base.py +0 -0
  56. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/rpc/client.py +0 -0
  57. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/rpc/discovery.py +0 -0
  58. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/scheduler/__init__.py +0 -0
  59. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/scheduler/runner.py +0 -0
  60. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/application/server/__init__.py +0 -0
  61. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/__init__.py +0 -0
  62. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/add.py +0 -0
  63. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/app.py +0 -0
  64. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/config.py +0 -0
  65. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/docker.py +0 -0
  66. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/docs.py +0 -0
  67. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/generate.py +0 -0
  68. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/init.py +0 -0
  69. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/migrate/__init__.py +0 -0
  70. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/migrate/app.py +0 -0
  71. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/migrate/commands.py +0 -0
  72. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/pkg.py +0 -0
  73. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/scheduler.py +0 -0
  74. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/server/__init__.py +0 -0
  75. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/server/app.py +0 -0
  76. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/generate/api.py.tpl +0 -0
  77. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/generate/model.py.tpl +0 -0
  78. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/generate/repository.py.tpl +0 -0
  79. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/generate/schema.py.tpl +0 -0
  80. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/generate/service.py.tpl +0 -0
  81. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/AGENTS.md.tpl +0 -0
  82. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/README.md.tpl +0 -0
  83. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/admin_console_init.py.tpl +0 -0
  84. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/alert_rules.example.yaml.tpl +0 -0
  85. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +0 -0
  86. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +0 -0
  87. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +0 -0
  88. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +0 -0
  89. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +0 -0
  90. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +0 -0
  91. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +0 -0
  92. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +0 -0
  93. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +0 -0
  94. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +0 -0
  95. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +0 -0
  96. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +0 -0
  97. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +0 -0
  98. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +0 -0
  99. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +0 -0
  100. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +0 -0
  101. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +0 -0
  102. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/17-alerting.md.tpl +0 -0
  103. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/18-monitoring-profiling.md.tpl +0 -0
  104. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +0 -0
  105. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/config.py.tpl +0 -0
  106. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/conftest.py.tpl +0 -0
  107. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/_header.tpl +0 -0
  108. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/admin.tpl +0 -0
  109. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/cache.tpl +0 -0
  110. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/database.tpl +0 -0
  111. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/log.tpl +0 -0
  112. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/messaging.tpl +0 -0
  113. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/monitoring.tpl +0 -0
  114. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/rpc.tpl +0 -0
  115. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/scheduler.tpl +0 -0
  116. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/service.tpl +0 -0
  117. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/storage.tpl +0 -0
  118. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/env_templates/third_party.tpl +0 -0
  119. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/gitignore.tpl +0 -0
  120. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/main.py.tpl +0 -0
  121. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/modules/api.py.tpl +0 -0
  122. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/modules/exceptions.py.tpl +0 -0
  123. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/modules/schedules.py.tpl +0 -0
  124. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/templates/project/modules/tasks.py.tpl +0 -0
  125. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/commands/worker.py +0 -0
  126. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/__init__.py +0 -0
  127. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/exceptions/__init__.py +0 -0
  128. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/i18n/__init__.py +0 -0
  129. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/i18n/translator.py +0 -0
  130. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/logging/__init__.py +0 -0
  131. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/logging/context.py +0 -0
  132. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/logging/decorators.py +0 -0
  133. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/logging/format.py +0 -0
  134. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/common/logging/setup.py +0 -0
  135. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/__init__.py +0 -0
  136. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/admin_console/__init__.py +0 -0
  137. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/admin_console/auth.py +0 -0
  138. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/admin_console/discovery.py +0 -0
  139. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/admin_console/install.py +0 -0
  140. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/contrib/admin_console/utils.py +0 -0
  141. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/__init__.py +0 -0
  142. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/exceptions/__init__.py +0 -0
  143. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/models/__init__.py +0 -0
  144. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/models/base.py +0 -0
  145. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/models/mixins.py +0 -0
  146. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/models/models.py +0 -0
  147. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/pagination/__init__.py +0 -0
  148. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/repository/__init__.py +0 -0
  149. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/repository/impl.py +0 -0
  150. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/repository/interceptors.py +0 -0
  151. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/repository/interface.py +0 -0
  152. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/repository/query_builder.py +0 -0
  153. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/service/__init__.py +0 -0
  154. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/service/base.py +0 -0
  155. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/domain/transaction/__init__.py +0 -0
  156. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/__init__.py +0 -0
  157. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/__init__.py +0 -0
  158. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/backends.py +0 -0
  159. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/base.py +0 -0
  160. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/exceptions.py +0 -0
  161. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/factory.py +0 -0
  162. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/manager.py +0 -0
  163. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/cache/memory.py +0 -0
  164. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/channel/backends/broadcaster.py +0 -0
  165. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/__init__.py +0 -0
  166. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/rabbitmq/__init__.py +0 -0
  167. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/rabbitmq/config.py +0 -0
  168. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/rabbitmq/manager.py +0 -0
  169. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/redis/__init__.py +0 -0
  170. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/clients/redis/config.py +0 -0
  171. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/__init__.py +0 -0
  172. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/config.py +0 -0
  173. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/exceptions.py +0 -0
  174. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/manager.py +0 -0
  175. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/query_tools/__init__.py +0 -0
  176. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/database/strategies/__init__.py +0 -0
  177. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/di/__init__.py +0 -0
  178. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/di/container.py +0 -0
  179. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/__init__.py +0 -0
  180. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/backends/__init__.py +0 -0
  181. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/backends/broadcaster.py +0 -0
  182. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/backends/rabbitmq.py +0 -0
  183. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/base.py +0 -0
  184. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/manager.py +0 -0
  185. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/events/middleware.py +0 -0
  186. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/__init__.py +0 -0
  187. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/__init__.py +0 -0
  188. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/aggregator.py +0 -0
  189. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/events.py +0 -0
  190. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/manager.py +0 -0
  191. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/notifiers/__init__.py +0 -0
  192. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/notifiers/base.py +0 -0
  193. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/notifiers/webhook.py +0 -0
  194. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/alerting/rules.py +0 -0
  195. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/health/__init__.py +0 -0
  196. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/__init__.py +0 -0
  197. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/context.py +0 -0
  198. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/logging.py +0 -0
  199. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/processor.py +0 -0
  200. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/provider.py +0 -0
  201. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/monitoring/tracing/tracing.py +0 -0
  202. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/__init__.py +0 -0
  203. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/backends/__init__.py +0 -0
  204. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/backends/rabbitmq.py +0 -0
  205. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/backends/redis.py +0 -0
  206. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/backends/redis_stream.py +0 -0
  207. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/base.py +0 -0
  208. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/mq/manager.py +0 -0
  209. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/scheduler/exceptions.py +0 -0
  210. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/scheduler/manager.py +0 -0
  211. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/storage/__init__.py +0 -0
  212. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/storage/base.py +0 -0
  213. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/storage/exceptions.py +0 -0
  214. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/storage/factory.py +0 -0
  215. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/tasks/__init__.py +0 -0
  216. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/tasks/config.py +0 -0
  217. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/tasks/constants.py +0 -0
  218. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/tasks/exceptions.py +0 -0
  219. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/infrastructure/tasks/manager.py +0 -0
  220. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/testing/__init__.py +0 -0
  221. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/testing/base.py +0 -0
  222. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/testing/client.py +0 -0
  223. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/testing/factory.py +0 -0
  224. {aury_boot-0.0.40 → aury_boot-0.0.41}/aury/boot/toolkit/__init__.py +0 -0
  225. {aury_boot-0.0.40 → aury_boot-0.0.41}/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.40
3
+ Version: 0.0.41
4
4
  Summary: Aury Boot - 基于 FastAPI 生态的企业级 API 开发框架
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: aiohttp>=3.11.0
@@ -28,6 +28,7 @@ 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'
31
32
  Requires-Dist: dramatiq>=1.18.0; extra == 'all'
32
33
  Requires-Dist: pika>=1.3.2; extra == 'all'
33
34
  Requires-Dist: psutil>=7.0.0; extra == 'all'
@@ -35,6 +36,8 @@ Requires-Dist: pyroscope-io>=0.8.7; extra == 'all'
35
36
  Requires-Dist: redis>=7.1.0; extra == 'all'
36
37
  Provides-Extra: broadcaster
37
38
  Requires-Dist: broadcaster[redis]>=0.3.1; extra == 'broadcaster'
39
+ Provides-Extra: channel-cluster
40
+ Requires-Dist: coredis>=5.6.0; extra == 'channel-cluster'
38
41
  Provides-Extra: dev
39
42
  Requires-Dist: httpx>=0.28.1; extra == 'dev'
40
43
  Requires-Dist: mypy>=1.19.0; extra == 'dev'
@@ -68,6 +71,7 @@ Requires-Dist: aiosqlite>=0.21.0; extra == 'recommended'
68
71
  Requires-Dist: apscheduler>=3.11.1; extra == 'recommended'
69
72
  Requires-Dist: asyncpg>=0.31.0; extra == 'recommended'
70
73
  Requires-Dist: aury-sdk-storage[aws]>=0.0.1; extra == 'recommended'
74
+ Requires-Dist: coredis>=5.6.0; extra == 'recommended'
71
75
  Requires-Dist: dramatiq>=1.18.0; extra == 'recommended'
72
76
  Requires-Dist: opentelemetry-api>=1.25.0; extra == 'recommended'
73
77
  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.40'
32
- __version_tuple__ = version_tuple = (0, 0, 40)
31
+ __version__ = version = '0.0.41'
32
+ __version_tuple__ = version_tuple = (0, 0, 41)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -318,7 +318,18 @@ class SchedulerComponent(Component):
318
318
  # jobstores: 根据 URL 自动选择存储后端
319
319
  if scheduler_config.jobstore_url:
320
320
  url = scheduler_config.jobstore_url
321
- if url.startswith("redis://"):
321
+ if url.startswith("redis-cluster://"):
322
+ # Redis Cluster 模式
323
+ try:
324
+ from aury.boot.infrastructure.scheduler.jobstores import RedisClusterJobStore
325
+
326
+ scheduler_kwargs["jobstores"] = {
327
+ "default": RedisClusterJobStore(url=url)
328
+ }
329
+ logger.info(f"调度器使用 Redis Cluster 存储: {url.split('@')[-1].split('/')[0]}")
330
+ except ImportError:
331
+ logger.warning("Redis Cluster jobstore 需要安装 redis[cluster]: pip install 'redis[cluster]'")
332
+ elif url.startswith("redis://"):
322
333
  try:
323
334
  from urllib.parse import urlparse
324
335
  from apscheduler.jobstores.redis import RedisJobStore
@@ -263,15 +263,19 @@ class ChannelSettings(BaseModel):
263
263
  支持的后端类型:
264
264
  - memory: 内存后端(默认,单进程)
265
265
  - redis: Redis Pub/Sub(多进程/分布式)
266
+ - redis_cluster: Redis Cluster Sharded Pub/Sub(Redis 7.0+)
267
+
268
+ 注意:URL 的 scheme 会自动决定后端类型:
269
+ - redis-cluster://... 自动使用 redis_cluster 后端
266
270
  """
267
271
 
268
272
  backend: str = Field(
269
273
  default="",
270
- description="通道后端 (memory/redis),空字符串表示不启用"
274
+ description="通道后端 (memory/redis/redis_cluster),空字符串表示不启用"
271
275
  )
272
276
  url: str | None = Field(
273
277
  default=None,
274
- description="Redis URL(当 backend=redis 时需要)"
278
+ description="连接 URL(redis://... redis-cluster://...)"
275
279
  )
276
280
 
277
281
 
@@ -472,6 +476,7 @@ class SchedulerSettings(BaseModel):
472
476
  default=None,
473
477
  description=(
474
478
  "任务存储 URL。支持:\n"
479
+ "- redis-cluster://password@host:port(Redis Cluster 存储)\n"
475
480
  "- redis://localhost:6379/0(Redis 存储)\n"
476
481
  "- sqlite:///jobs.db(SQLite 存储)\n"
477
482
  "- postgresql://user:pass@host/db(PostgreSQL 存储)\n"
@@ -1,4 +1,9 @@
1
- """Redis 缓存后端实现。"""
1
+ """Redis 缓存后端实现。
2
+
3
+ 支持普通 Redis 和 Redis Cluster:
4
+ - redis://... - 普通 Redis
5
+ - redis-cluster://... - Redis Cluster
6
+ """
2
7
 
3
8
  from __future__ import annotations
4
9
 
@@ -9,6 +14,7 @@ import time
9
14
  from collections.abc import Callable
10
15
  from datetime import timedelta
11
16
  from typing import TYPE_CHECKING, Any
17
+ from urllib.parse import urlparse
12
18
 
13
19
  from redis.asyncio import Redis
14
20
 
@@ -17,6 +23,7 @@ from aury.boot.common.logging import logger
17
23
  from .base import ICache
18
24
 
19
25
  if TYPE_CHECKING:
26
+ from redis.asyncio.cluster import RedisCluster
20
27
  from aury.boot.infrastructure.clients.redis import RedisClient
21
28
 
22
29
 
@@ -24,7 +31,7 @@ class RedisCache(ICache):
24
31
  """Redis缓存实现。
25
32
 
26
33
  支持两种初始化方式:
27
- 1. 传入 URL 自行创建连接
34
+ 1. 传入 URL 自行创建连接(支持 redis:// 和 redis-cluster://)
28
35
  2. 传入 RedisClient 实例(推荐)
29
36
  """
30
37
 
@@ -38,44 +45,95 @@ class RedisCache(ICache):
38
45
  """初始化Redis缓存。
39
46
 
40
47
  Args:
41
- url: Redis连接URL
48
+ url: Redis连接URL(支持 redis:// 和 redis-cluster://)
42
49
  redis_client: RedisClient 实例(推荐)
43
50
  serializer: 序列化方式(json/pickle)
44
51
  """
45
52
  self._url = url
46
53
  self._redis_client = redis_client
47
54
  self._serializer = serializer
48
- self._redis: Redis | None = None
49
- self._owns_connection = False # 是否自己拥有连接(需要自己关闭)
55
+ self._redis: Redis | RedisCluster | None = None
56
+ self._owns_connection = False
57
+ self._is_cluster = False
50
58
 
51
59
  async def initialize(self) -> None:
52
60
  """初始化连接。"""
53
61
  # 优先使用 RedisClient
54
62
  if self._redis_client is not None:
55
63
  self._redis = self._redis_client.connection
64
+ self._is_cluster = getattr(self._redis_client, "is_cluster", False)
56
65
  self._owns_connection = False
57
- logger.info("Redis缓存初始化成功(使用 RedisClient)")
66
+ mode = "Cluster" if self._is_cluster else "Standalone"
67
+ logger.info(f"Redis缓存初始化成功(使用 RedisClient, {mode})")
58
68
  return
59
69
 
60
70
  # 使用 URL 创建连接
61
71
  if self._url:
62
72
  try:
63
- self._redis = Redis.from_url(
64
- self._url,
65
- encoding="utf-8",
66
- decode_responses=False,
67
- socket_connect_timeout=5,
68
- socket_timeout=5,
69
- )
73
+ if self._url.startswith("redis-cluster://"):
74
+ await self._init_cluster()
75
+ else:
76
+ await self._init_standalone()
70
77
  await self._redis.ping()
71
78
  self._owns_connection = True
72
- logger.info("Redis缓存初始化成功")
79
+ mode = "Cluster" if self._is_cluster else "Standalone"
80
+ logger.info(f"Redis缓存初始化成功 ({mode})")
73
81
  except Exception as exc:
74
82
  logger.error(f"Redis连接失败: {exc}")
75
83
  raise
76
84
  else:
77
85
  raise ValueError("Redis缓存需要提供 url 或 redis_client 参数")
78
86
 
87
+ async def _init_standalone(self) -> None:
88
+ """初始化普通 Redis。"""
89
+ self._redis = Redis.from_url(
90
+ self._url,
91
+ encoding="utf-8",
92
+ decode_responses=False,
93
+ socket_connect_timeout=5,
94
+ socket_timeout=5,
95
+ )
96
+ self._is_cluster = False
97
+
98
+ async def _init_cluster(self) -> None:
99
+ """初始化 Redis Cluster。
100
+
101
+ 支持 URL 格式:
102
+ - redis-cluster://password@host:port (密码在用户名位置)
103
+ - redis-cluster://:password@host:port (标准格式)
104
+ - redis-cluster://username:password@host:port (ACL 模式)
105
+ """
106
+ from redis.asyncio.cluster import RedisCluster
107
+
108
+ parsed_url = self._url.replace("redis-cluster://", "redis://")
109
+ parsed = urlparse(parsed_url)
110
+
111
+ # 提取认证信息
112
+ username = parsed.username
113
+ password = parsed.password
114
+
115
+ # 处理 password@host 格式(密码在用户名位置)
116
+ if username and not password:
117
+ password = username
118
+ username = None
119
+
120
+ # 构建连接参数
121
+ cluster_kwargs: dict = {
122
+ "host": parsed.hostname or "localhost",
123
+ "port": parsed.port or 6379,
124
+ "decode_responses": False,
125
+ "socket_connect_timeout": 5,
126
+ "socket_timeout": 5,
127
+ }
128
+
129
+ if username:
130
+ cluster_kwargs["username"] = username
131
+ if password:
132
+ cluster_kwargs["password"] = password
133
+
134
+ self._redis = RedisCluster(**cluster_kwargs)
135
+ self._is_cluster = True
136
+
79
137
  async def get(self, key: str, default: Any = None) -> Any:
80
138
  """获取缓存。"""
81
139
  if not self._redis:
@@ -192,15 +250,23 @@ class RedisCache(ICache):
192
250
  async def close(self) -> None:
193
251
  """关闭连接(仅当自己拥有连接时)。"""
194
252
  if self._redis and self._owns_connection:
195
- await self._redis.close()
253
+ if self._is_cluster:
254
+ await self._redis.aclose()
255
+ else:
256
+ await self._redis.close()
196
257
  logger.info("Redis连接已关闭")
197
258
  self._redis = None
198
259
 
199
260
  @property
200
- def redis(self) -> Redis | None:
261
+ def redis(self) -> Redis | RedisCluster | None:
201
262
  """获取Redis客户端。"""
202
263
  return self._redis
203
264
 
265
+ @property
266
+ def is_cluster(self) -> bool:
267
+ """检查是否为集群模式。"""
268
+ return self._is_cluster
269
+
204
270
  # ==================== 分布式锁 ====================
205
271
  # TODO: 后续优化考虑:
206
272
  # - 看门狗(Watchdog)机制:自动续期,防止业务执行超过锁超时导致提前释放
@@ -9,7 +9,7 @@
9
9
  - postgres:// - PostgreSQL LISTEN/NOTIFY
10
10
  """
11
11
 
12
- from .backends import BroadcasterChannel
12
+ from .backends import BroadcasterChannel, RedisClusterChannel
13
13
  from .base import ChannelBackend, ChannelMessage, IChannel
14
14
  from .manager import ChannelManager
15
15
 
@@ -22,4 +22,5 @@ __all__ = [
22
22
  "ChannelManager",
23
23
  # 后端实现
24
24
  "BroadcasterChannel",
25
+ "RedisClusterChannel",
25
26
  ]
@@ -0,0 +1,6 @@
1
+ """通道后端实现。"""
2
+
3
+ from .broadcaster import BroadcasterChannel
4
+ from .redis_cluster_channel import RedisClusterChannel
5
+
6
+ __all__ = ["BroadcasterChannel", "RedisClusterChannel"]
@@ -0,0 +1,124 @@
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"]
@@ -0,0 +1,139 @@
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"]
@@ -18,6 +18,8 @@ class ChannelBackend(Enum):
18
18
 
19
19
  # Broadcaster 统一后端(支持 memory/redis/kafka/postgres,通过 URL scheme 区分)
20
20
  BROADCASTER = "broadcaster"
21
+ # Redis Cluster + Sharded Pub/Sub (Redis 7.0+),使用 coredis 库
22
+ REDIS_CLUSTER = "redis_cluster"
21
23
  # 未来扩展
22
24
  RABBITMQ = "rabbitmq"
23
25
  ROCKETMQ = "rocketmq"
@@ -10,6 +10,7 @@ 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
13
14
  from .base import ChannelBackend, ChannelMessage, IChannel
14
15
 
15
16
 
@@ -93,6 +94,7 @@ class ChannelManager:
93
94
  url: 连接 URL,支持:
94
95
  - memory:// - 内存后端(单进程,默认)
95
96
  - redis://host:port/db - Redis Pub/Sub
97
+ - redis-cluster://[password@]host:port - Redis Cluster (Sharded Pub/Sub)
96
98
  - kafka://host:port - Apache Kafka
97
99
  - postgres://user:pass@host/db - PostgreSQL
98
100
 
@@ -103,15 +105,21 @@ class ChannelManager:
103
105
  logger.warning(f"通道管理器 [{self.name}] 已初始化,跳过")
104
106
  return self
105
107
 
106
- # 处理字符串类型的 backend
108
+ # 根据 URL scheme 自动选择后端
107
109
  if isinstance(backend, str):
108
110
  backend = ChannelBackend(backend.lower())
111
+
112
+ # 自动检测 redis-cluster:// scheme
113
+ if url.startswith("redis-cluster://"):
114
+ backend = ChannelBackend.REDIS_CLUSTER
109
115
 
110
116
  self._backend_type = backend
111
117
  self._url = url
112
118
 
113
119
  if backend == ChannelBackend.BROADCASTER:
114
120
  self._backend = BroadcasterChannel(url)
121
+ elif backend == ChannelBackend.REDIS_CLUSTER:
122
+ self._backend = RedisClusterChannel(url)
115
123
  elif backend in (ChannelBackend.RABBITMQ, ChannelBackend.ROCKETMQ):
116
124
  raise NotImplementedError(f"{backend.value} 后端暂未实现")
117
125
  else: