codex-platform 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.github/workflows/ci.yml +2 -2
- {codex_platform-0.5.0 → codex_platform-0.5.2}/CHANGELOG.md +17 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/PKG-INFO +4 -2
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/index.md +2 -1
- codex_platform-0.5.2/docs/en/api/messaging/audience.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/campaigns/dto.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/campaigns/index.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/campaigns/protocols.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/channels.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/clients/smtp.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/delivery/arq.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/delivery/base.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/delivery/direct.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/dto.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/index.md +8 -0
- codex_platform-0.5.2/docs/en/api/messaging/orchestrator.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/registry.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/renderer.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/threading.md +5 -0
- codex_platform-0.5.2/docs/en/api/messaging/workers_contract.md +5 -0
- codex_platform-0.5.2/docs/en/api/notifications/index.md +8 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/mkdocs.yml +20 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/pyproject.toml +3 -1
- codex_platform-0.5.2/src/codex_platform/messaging/__init__.py +68 -0
- codex_platform-0.5.2/src/codex_platform/messaging/audience.py +23 -0
- codex_platform-0.5.2/src/codex_platform/messaging/campaigns/__init__.py +6 -0
- codex_platform-0.5.2/src/codex_platform/messaging/campaigns/dto.py +46 -0
- codex_platform-0.5.2/src/codex_platform/messaging/campaigns/protocols.py +18 -0
- {codex_platform-0.5.0/src/codex_platform/notifications → codex_platform-0.5.2/src/codex_platform/messaging}/channels.py +1 -5
- {codex_platform-0.5.0/src/codex_platform/notifications → codex_platform-0.5.2/src/codex_platform/messaging}/clients/__init__.py +2 -0
- {codex_platform-0.5.0/src/codex_platform/notifications → codex_platform-0.5.2/src/codex_platform/messaging}/clients/smtp.py +30 -48
- codex_platform-0.5.2/src/codex_platform/messaging/delivery/__init__.py +11 -0
- codex_platform-0.5.2/src/codex_platform/messaging/delivery/arq.py +38 -0
- codex_platform-0.5.2/src/codex_platform/messaging/delivery/base.py +16 -0
- codex_platform-0.5.2/src/codex_platform/messaging/delivery/direct.py +76 -0
- codex_platform-0.5.2/src/codex_platform/messaging/dto.py +60 -0
- {codex_platform-0.5.0/src/codex_platform/notifications → codex_platform-0.5.2/src/codex_platform/messaging}/interfaces.py +6 -8
- codex_platform-0.5.2/src/codex_platform/messaging/orchestrator.py +101 -0
- codex_platform-0.5.2/src/codex_platform/messaging/registry.py +48 -0
- codex_platform-0.5.2/src/codex_platform/messaging/renderer.py +71 -0
- codex_platform-0.5.2/src/codex_platform/messaging/threading.py +65 -0
- codex_platform-0.5.2/src/codex_platform/messaging/workers_contract.py +14 -0
- codex_platform-0.5.2/src/codex_platform/notifications/__init__.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/channels.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/clients/__init__.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/clients/smtp.py +19 -0
- codex_platform-0.5.2/src/codex_platform/notifications/delivery/__init__.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/delivery/arq.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/delivery/base.py +13 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/notifications/delivery/direct.py +16 -36
- codex_platform-0.5.2/src/codex_platform/notifications/dto.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/interfaces.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/orchestrator.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/registry.py +13 -0
- codex_platform-0.5.2/src/codex_platform/notifications/renderer.py +13 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/consumer.py +9 -3
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/processor.py +7 -2
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/runtime.py +2 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/integration/test_streams.py +2 -0
- codex_platform-0.5.2/tests/unit/test_messaging_api.py +185 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_consumer.py +39 -2
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_processor.py +41 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/uv.lock +59 -55
- codex_platform-0.5.0/docs/en/api/notifications/index.md +0 -5
- codex_platform-0.5.0/src/codex_platform/notifications/__init__.py +0 -48
- codex_platform-0.5.0/src/codex_platform/notifications/delivery/__init__.py +0 -18
- codex_platform-0.5.0/src/codex_platform/notifications/delivery/arq.py +0 -69
- codex_platform-0.5.0/src/codex_platform/notifications/delivery/base.py +0 -39
- codex_platform-0.5.0/src/codex_platform/notifications/dto.py +0 -63
- codex_platform-0.5.0/src/codex_platform/notifications/orchestrator.py +0 -116
- codex_platform-0.5.0/src/codex_platform/notifications/registry.py +0 -71
- codex_platform-0.5.0/src/codex_platform/notifications/renderer.py +0 -112
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.agents/rules/graphify.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.github/workflows/docs.yml +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.github/workflows/publish.yml +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.gitignore +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.pre-commit-config.yaml +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/.python-version +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/AGENTS.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/CLAUDE.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/changelog.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/00_overview.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/01_core_dtos_and_protocols.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/02_channels_and_registry.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/03_renderer_and_templates.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/04_threading_and_headers.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/05_workers_contract.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/dev/messaging/06_migration_from_notifications.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/channels.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/delivery/arq.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/delivery/base.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/delivery/direct.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/dto.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/orchestrator.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/registry.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/notifications/renderer.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/base.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/index.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/keys.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/hash.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/json_module.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/json_string.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/list_.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/pipeline.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/set_.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/string.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/operations/zset.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/redis_service/service.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/consumer.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/dispatcher.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/index.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/processor.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/producer.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/streams/router.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/workers/arq/base.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/workers/arq/config.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/workers/arq/index.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/workers/arq/task_utils.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/api/workers/arq/types.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/notifications/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/notifications/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/redis_service/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/redis_service/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/streams/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/streams/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/workers/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/en/architecture/workers/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/index.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/notifications/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/notifications/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/redis_service/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/redis_service/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/streams/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/streams/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/workers/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/ru/architecture/workers/data_flow.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/docs/stylesheets/extra.css +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/project_structure.txt +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/redis-backends-recovery.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/py.typed +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/base.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/exceptions.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/keys.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/managers/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/managers/base_manager.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/managers/site_settings.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/hash.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/json_module.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/json_string.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/list_.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/pipeline.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/set_.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/string.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/sync_hash.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/sync_string.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/operations/zset.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/redis_service/service.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/codec.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/dispatcher.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/producer.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/streams/router.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/arq/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/arq/base.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/arq/config.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/arq/task_utils.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/src/codex_platform/workers/arq/types.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/integration/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/integration/conftest.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/integration/test_docs_build.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/integration/test_redis_service.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/conftest.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_arq_base.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_arq_config.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_arq_public_api.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_arq_task_utils.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_adapters.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_channels.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_dto.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_orchestrator.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_registry.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_renderer.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_notification_smtp.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_base.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_hash.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_keys.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_list.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_manager.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_pipeline.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_service.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_set.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_string.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_redis_zset.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_site_settings_manager.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_dispatcher.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_producer.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_router.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_stream_runtime.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tests/unit/test_sync_operations.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tools/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tools/dev/README.md +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tools/dev/__init__.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tools/dev/check.py +0 -0
- {codex_platform-0.5.0 → codex_platform-0.5.2}/tools/dev/generate_project_tree.py +0 -0
|
@@ -5,6 +5,23 @@ Grouped by `Added` · `Changed` · `Deprecated` · `Removed` · `Fixed`.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.5.2] - 2026-05-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Added the framework-agnostic `codex_platform.messaging` package with notification delivery, campaign, threading, renderer, and SMTP primitives.
|
|
12
|
+
|
|
13
|
+
### Deprecated
|
|
14
|
+
- Deprecated `codex_platform.notifications` modules in favor of `codex_platform.messaging` compatibility re-exports.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Reduced Redis Streams consumer latency by using configurable blocking `XREADGROUP` reads instead of relying on the processor polling interval for normal wakeups.
|
|
18
|
+
|
|
19
|
+
## [0.5.1] - 2026-04-30
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Fixed GitHub Actions integration environment to install docs dependencies required by the MkDocs build test.
|
|
23
|
+
- Stabilized the multi-consumer-group streams integration test by creating Redis consumer groups before publishing the shared event.
|
|
24
|
+
|
|
8
25
|
## [0.5.0] - 2026-04-30
|
|
9
26
|
|
|
10
27
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-platform
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Modular async infrastructure (Redis, Streams, ARQ, Notifications) built for Codex, designed for independent use.
|
|
5
5
|
Project-URL: Homepage, https://github.com/codexdlc/codex-platform
|
|
6
6
|
Project-URL: Documentation, https://codexdlc.github.io/codex-platform/
|
|
@@ -31,12 +31,14 @@ Requires-Dist: bandit>=1.7; extra == 'dev'
|
|
|
31
31
|
Requires-Dist: detect-secrets>=1.5; extra == 'dev'
|
|
32
32
|
Requires-Dist: fakeredis[lua]>=2.20; extra == 'dev'
|
|
33
33
|
Requires-Dist: jinja2>=3.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: lupa>2.6; extra == 'dev'
|
|
34
35
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
35
36
|
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
37
|
+
Requires-Dist: pip>=26.1; extra == 'dev'
|
|
36
38
|
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
37
39
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
40
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
39
|
-
Requires-Dist: pytest>=
|
|
41
|
+
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
40
42
|
Requires-Dist: redis>=5.0; extra == 'dev'
|
|
41
43
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
42
44
|
Provides-Extra: docs
|
|
@@ -6,7 +6,8 @@ Auto-generated from source docstrings (Google style). If code behavior contradic
|
|
|
6
6
|
|
|
7
7
|
| Module | Description |
|
|
8
8
|
| :--- | :--- |
|
|
9
|
-
| [
|
|
9
|
+
| [messaging](messaging/index.md) | Framework-agnostic messaging engine |
|
|
10
|
+
| [notifications](notifications/index.md) | Deprecated compatibility path for messaging |
|
|
10
11
|
| [redis_service](redis_service/index.md) | Async Redis abstraction with typed operations |
|
|
11
12
|
| [streams](streams/index.md) | Redis Streams producer/consumer event bus |
|
|
12
13
|
| [workers.arq](workers/arq/index.md) | ARQ background worker base infrastructure |
|
|
@@ -100,6 +100,26 @@ nav:
|
|
|
100
100
|
- Поток данных: ru/architecture/workers/data_flow.md
|
|
101
101
|
- API Reference:
|
|
102
102
|
- Overview: en/api/index.md
|
|
103
|
+
- Messaging:
|
|
104
|
+
- Overview: en/api/messaging/index.md
|
|
105
|
+
- DTOs: en/api/messaging/dto.md
|
|
106
|
+
- Channels: en/api/messaging/channels.md
|
|
107
|
+
- Orchestrator: en/api/messaging/orchestrator.md
|
|
108
|
+
- Registry: en/api/messaging/registry.md
|
|
109
|
+
- Renderer: en/api/messaging/renderer.md
|
|
110
|
+
- Threading: en/api/messaging/threading.md
|
|
111
|
+
- Audience: en/api/messaging/audience.md
|
|
112
|
+
- Workers Contract: en/api/messaging/workers_contract.md
|
|
113
|
+
- Clients:
|
|
114
|
+
- SMTP: en/api/messaging/clients/smtp.md
|
|
115
|
+
- Campaigns:
|
|
116
|
+
- Overview: en/api/messaging/campaigns/index.md
|
|
117
|
+
- DTOs: en/api/messaging/campaigns/dto.md
|
|
118
|
+
- Protocols: en/api/messaging/campaigns/protocols.md
|
|
119
|
+
- Delivery:
|
|
120
|
+
- Base Protocol: en/api/messaging/delivery/base.md
|
|
121
|
+
- ARQ: en/api/messaging/delivery/arq.md
|
|
122
|
+
- Direct: en/api/messaging/delivery/direct.md
|
|
103
123
|
- Notifications:
|
|
104
124
|
- Overview: en/api/notifications/index.md
|
|
105
125
|
- DTOs: en/api/notifications/dto.md
|
|
@@ -39,15 +39,17 @@ all = [
|
|
|
39
39
|
]
|
|
40
40
|
dev = [
|
|
41
41
|
"codex-platform[all]",
|
|
42
|
-
"pytest>=
|
|
42
|
+
"pytest>=9.0.3",
|
|
43
43
|
"pytest-asyncio>=0.23",
|
|
44
44
|
"pytest-cov",
|
|
45
45
|
"fakeredis[lua]>=2.20",
|
|
46
|
+
"lupa>2.6",
|
|
46
47
|
"ruff>=0.4",
|
|
47
48
|
"mypy>=1.10",
|
|
48
49
|
"pre-commit>=3.0",
|
|
49
50
|
"bandit>=1.7",
|
|
50
51
|
"pip-audit>=2.7",
|
|
52
|
+
"pip>=26.1",
|
|
51
53
|
"detect-secrets>=1.5",
|
|
52
54
|
]
|
|
53
55
|
docs = [
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Framework-agnostic messaging primitives for codex-platform."""
|
|
2
|
+
|
|
3
|
+
from .audience import AudienceBuilder
|
|
4
|
+
from .campaigns import CampaignBatchDTO, CampaignDispatcher, CampaignRecipientDraft
|
|
5
|
+
from .channels import NotificationChannel
|
|
6
|
+
from .clients.smtp import AsyncEmailClient
|
|
7
|
+
from .delivery import ArqNotificationAdapter, DirectNotificationAdapter, NotificationAdapter
|
|
8
|
+
from .dto import (
|
|
9
|
+
NotificationPayloadDTO,
|
|
10
|
+
NotificationRecipient,
|
|
11
|
+
RenderedNotificationDTO,
|
|
12
|
+
TemplateNotificationDTO,
|
|
13
|
+
ThreadHeadersDTO,
|
|
14
|
+
)
|
|
15
|
+
from .interfaces import ContentCacheAdapter, ContentProvider
|
|
16
|
+
from .orchestrator import BaseDeliveryOrchestrator, DeliveryChannel
|
|
17
|
+
from .registry import ChannelRegistry
|
|
18
|
+
from .renderer import TemplateRenderer, resolve_template_path
|
|
19
|
+
from .threading import (
|
|
20
|
+
CODEX_THREAD_HEADER,
|
|
21
|
+
LEGACY_LILY_THREAD_HEADER,
|
|
22
|
+
build_message_id,
|
|
23
|
+
build_thread_key,
|
|
24
|
+
parse_references,
|
|
25
|
+
render_email_headers,
|
|
26
|
+
serialize_references,
|
|
27
|
+
)
|
|
28
|
+
from .workers_contract import (
|
|
29
|
+
PAYLOAD_SCHEMA_VERSION,
|
|
30
|
+
TASK_SEND_CAMPAIGN,
|
|
31
|
+
TASK_SEND_NOTIFICATION,
|
|
32
|
+
TASK_SEND_RENDERED,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"AudienceBuilder",
|
|
37
|
+
"CampaignBatchDTO",
|
|
38
|
+
"CampaignDispatcher",
|
|
39
|
+
"CampaignRecipientDraft",
|
|
40
|
+
"NotificationChannel",
|
|
41
|
+
"NotificationPayloadDTO",
|
|
42
|
+
"TemplateNotificationDTO",
|
|
43
|
+
"RenderedNotificationDTO",
|
|
44
|
+
"NotificationRecipient",
|
|
45
|
+
"ThreadHeadersDTO",
|
|
46
|
+
"ContentProvider",
|
|
47
|
+
"ContentCacheAdapter",
|
|
48
|
+
"DeliveryChannel",
|
|
49
|
+
"AsyncEmailClient",
|
|
50
|
+
"BaseDeliveryOrchestrator",
|
|
51
|
+
"ChannelRegistry",
|
|
52
|
+
"TemplateRenderer",
|
|
53
|
+
"resolve_template_path",
|
|
54
|
+
"NotificationAdapter",
|
|
55
|
+
"ArqNotificationAdapter",
|
|
56
|
+
"DirectNotificationAdapter",
|
|
57
|
+
"CODEX_THREAD_HEADER",
|
|
58
|
+
"LEGACY_LILY_THREAD_HEADER",
|
|
59
|
+
"build_message_id",
|
|
60
|
+
"build_thread_key",
|
|
61
|
+
"parse_references",
|
|
62
|
+
"render_email_headers",
|
|
63
|
+
"serialize_references",
|
|
64
|
+
"PAYLOAD_SCHEMA_VERSION",
|
|
65
|
+
"TASK_SEND_NOTIFICATION",
|
|
66
|
+
"TASK_SEND_RENDERED",
|
|
67
|
+
"TASK_SEND_CAMPAIGN",
|
|
68
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Audience selection contracts for campaign messaging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from .campaigns.dto import CampaignRecipientDraft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AudienceBuilder(Protocol):
|
|
12
|
+
"""Materializes campaign recipients from a host-defined filter."""
|
|
13
|
+
|
|
14
|
+
def count(self, audience_filter: dict[str, Any]) -> int:
|
|
15
|
+
"""Return the number of recipients matching the filter."""
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def materialize(self, audience_filter: dict[str, Any]) -> Iterable[CampaignRecipientDraft]:
|
|
19
|
+
"""Stream recipient drafts matching the filter."""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ["AudienceBuilder"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Campaign messaging DTOs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from codex_core.core import BaseDTO
|
|
8
|
+
from pydantic import model_validator
|
|
9
|
+
|
|
10
|
+
from codex_platform.messaging.workers_contract import PAYLOAD_SCHEMA_VERSION
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CampaignRecipientDraft(BaseDTO):
|
|
14
|
+
"""Recipient data needed by a campaign batch worker."""
|
|
15
|
+
|
|
16
|
+
recipient_id: str
|
|
17
|
+
email: str
|
|
18
|
+
first_name: str = ""
|
|
19
|
+
last_name: str = ""
|
|
20
|
+
locale: str = "de"
|
|
21
|
+
unsubscribe_token: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CampaignBatchDTO(BaseDTO):
|
|
25
|
+
"""One campaign batch payload for the worker callback contract."""
|
|
26
|
+
|
|
27
|
+
schema_version: int = PAYLOAD_SCHEMA_VERSION
|
|
28
|
+
campaign_id: str
|
|
29
|
+
template_name: str | None = None
|
|
30
|
+
html_content: str | None = None
|
|
31
|
+
subject: str
|
|
32
|
+
recipients: list[CampaignRecipientDraft]
|
|
33
|
+
base_context: dict[str, Any] = {}
|
|
34
|
+
callback_url: str
|
|
35
|
+
callback_token: str
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="after")
|
|
38
|
+
def validate_rendering_mode(self) -> CampaignBatchDTO:
|
|
39
|
+
"""Require exactly one campaign rendering mode."""
|
|
40
|
+
|
|
41
|
+
if bool(self.template_name) == bool(self.html_content):
|
|
42
|
+
raise ValueError("exactly one of template_name or html_content must be set")
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = ["CampaignRecipientDraft", "CampaignBatchDTO"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Campaign messaging protocols."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from .dto import CampaignBatchDTO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CampaignDispatcher(Protocol):
|
|
11
|
+
"""Enqueues campaign batches for worker-side delivery."""
|
|
12
|
+
|
|
13
|
+
def enqueue_batch(self, batch: CampaignBatchDTO) -> str:
|
|
14
|
+
"""Enqueue a batch and return the backend job identifier."""
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["CampaignDispatcher"]
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
"""
|
|
2
|
-
codex_platform.workers.notifications.email
|
|
3
|
-
========================================
|
|
4
|
-
Async SMTP email client — implements ``DeliveryChannel``.
|
|
1
|
+
"""Async SMTP email client implementing the messaging delivery channel."""
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
For multi-tenant: create separate instances with different settings.
|
|
8
|
-
|
|
9
|
-
Requires: pip install codex-tools[notifications]
|
|
10
|
-
"""
|
|
3
|
+
from __future__ import annotations
|
|
11
4
|
|
|
12
5
|
import logging
|
|
13
6
|
from email.message import EmailMessage
|
|
7
|
+
from email.utils import formataddr
|
|
14
8
|
from typing import Any
|
|
15
9
|
|
|
10
|
+
from codex_platform.messaging.dto import ThreadHeadersDTO
|
|
11
|
+
from codex_platform.messaging.threading import render_email_headers
|
|
12
|
+
|
|
16
13
|
log = logging.getLogger(__name__)
|
|
17
14
|
|
|
18
15
|
try:
|
|
@@ -21,28 +18,14 @@ try:
|
|
|
21
18
|
_AIOSMTP_AVAILABLE = True
|
|
22
19
|
except ImportError as e:
|
|
23
20
|
if e.name == "aiosmtplib":
|
|
21
|
+
aiosmtplib = None # type: ignore[assignment]
|
|
24
22
|
_AIOSMTP_AVAILABLE = False
|
|
25
23
|
else:
|
|
26
24
|
raise
|
|
27
25
|
|
|
28
26
|
|
|
29
27
|
class AsyncEmailClient:
|
|
30
|
-
"""
|
|
31
|
-
Async SMTP email sender. Implements ``DeliveryChannel``.
|
|
32
|
-
|
|
33
|
-
Usage:
|
|
34
|
-
client = AsyncEmailClient(
|
|
35
|
-
smtp_host="mail.example.com", smtp_port=465,
|
|
36
|
-
smtp_user="user", smtp_password="pass", # pragma: allowlist secret
|
|
37
|
-
smtp_from_email="noreply@example.com",
|
|
38
|
-
)
|
|
39
|
-
success = await client.send(
|
|
40
|
-
to="user@example.com",
|
|
41
|
-
subject="Hello",
|
|
42
|
-
html_content="<b>Hi</b>",
|
|
43
|
-
text_content="Hi",
|
|
44
|
-
)
|
|
45
|
-
"""
|
|
28
|
+
"""Async SMTP email sender."""
|
|
46
29
|
|
|
47
30
|
def __init__(
|
|
48
31
|
self,
|
|
@@ -52,6 +35,7 @@ class AsyncEmailClient:
|
|
|
52
35
|
smtp_password: str | None = None, # pragma: allowlist secret
|
|
53
36
|
smtp_from_email: str | None = None,
|
|
54
37
|
smtp_use_tls: bool = False,
|
|
38
|
+
smtp_from_name: str = "",
|
|
55
39
|
) -> None:
|
|
56
40
|
self.smtp_host = smtp_host
|
|
57
41
|
self.smtp_port = smtp_port
|
|
@@ -59,9 +43,11 @@ class AsyncEmailClient:
|
|
|
59
43
|
self.smtp_password = smtp_password
|
|
60
44
|
self.smtp_from_email = smtp_from_email
|
|
61
45
|
self.smtp_use_tls = smtp_use_tls
|
|
46
|
+
self.smtp_from_name = smtp_from_name
|
|
62
47
|
|
|
63
48
|
def is_available(self) -> bool:
|
|
64
|
-
"""Returns True if the client is configured
|
|
49
|
+
"""Returns True if the client is configured with a non-localhost host."""
|
|
50
|
+
|
|
65
51
|
return bool(self.smtp_host and self.smtp_host != "localhost")
|
|
66
52
|
|
|
67
53
|
async def send(
|
|
@@ -70,30 +56,14 @@ class AsyncEmailClient:
|
|
|
70
56
|
subject: str,
|
|
71
57
|
html_content: str | None,
|
|
72
58
|
text_content: str | None,
|
|
59
|
+
headers: ThreadHeadersDTO | None = None,
|
|
73
60
|
timeout: int = 15,
|
|
74
61
|
) -> bool:
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
to: Recipient email address.
|
|
80
|
-
subject: Email subject line.
|
|
81
|
-
html_content: HTML body (preferred). Used as HTML part.
|
|
82
|
-
text_content: Plain-text body (fallback for HTML-disabled clients).
|
|
83
|
-
If None and html_content is provided, a generic fallback
|
|
84
|
-
is used.
|
|
85
|
-
timeout: SMTP connection timeout in seconds.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
True on success.
|
|
89
|
-
|
|
90
|
-
Raises:
|
|
91
|
-
RuntimeError: If aiosmtplib is not installed.
|
|
92
|
-
aiosmtplib.SMTPException: On SMTP delivery failure.
|
|
93
|
-
"""
|
|
62
|
+
"""Send email via SMTP."""
|
|
63
|
+
|
|
94
64
|
if not _AIOSMTP_AVAILABLE:
|
|
95
|
-
raise RuntimeError("aiosmtplib not installed. Run: pip install codex-
|
|
96
|
-
await self._send_smtp(to, subject, html_content, text_content, timeout)
|
|
65
|
+
raise RuntimeError("aiosmtplib not installed. Run: pip install codex-platform[notifications]")
|
|
66
|
+
await self._send_smtp(to, subject, html_content, text_content, headers, timeout)
|
|
97
67
|
log.info("AsyncEmailClient | sent to='%s'", to)
|
|
98
68
|
return True
|
|
99
69
|
|
|
@@ -103,13 +73,22 @@ class AsyncEmailClient:
|
|
|
103
73
|
subject: str,
|
|
104
74
|
html_content: str | None,
|
|
105
75
|
text_content: str | None,
|
|
76
|
+
headers: ThreadHeadersDTO | None,
|
|
106
77
|
timeout: int,
|
|
107
78
|
) -> None:
|
|
108
79
|
message = EmailMessage()
|
|
109
|
-
message["From"] =
|
|
80
|
+
message["From"] = (
|
|
81
|
+
formataddr((self.smtp_from_name, self.smtp_from_email))
|
|
82
|
+
if self.smtp_from_name and self.smtp_from_email
|
|
83
|
+
else self.smtp_from_email
|
|
84
|
+
)
|
|
110
85
|
message["To"] = to
|
|
111
86
|
message["Subject"] = subject
|
|
112
87
|
|
|
88
|
+
if headers is not None:
|
|
89
|
+
for name, value in render_email_headers(headers).items():
|
|
90
|
+
message[name] = value
|
|
91
|
+
|
|
113
92
|
plain = text_content or "Please enable HTML to view this email."
|
|
114
93
|
message.set_content(plain)
|
|
115
94
|
|
|
@@ -132,3 +111,6 @@ class AsyncEmailClient:
|
|
|
132
111
|
send_kwargs["password"] = self.smtp_password
|
|
133
112
|
|
|
134
113
|
await aiosmtplib.send(message, **send_kwargs)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
__all__ = ["AsyncEmailClient", "_AIOSMTP_AVAILABLE", "aiosmtplib"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Delivery adapters for messaging payloads."""
|
|
2
|
+
|
|
3
|
+
from .arq import ArqNotificationAdapter
|
|
4
|
+
from .base import NotificationAdapter
|
|
5
|
+
from .direct import DirectNotificationAdapter
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"NotificationAdapter",
|
|
9
|
+
"ArqNotificationAdapter",
|
|
10
|
+
"DirectNotificationAdapter",
|
|
11
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""ARQ delivery adapter for messaging payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from arq.connections import ArqRedis
|
|
9
|
+
|
|
10
|
+
from .base import NotificationAdapter
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArqNotificationAdapter(NotificationAdapter):
|
|
16
|
+
"""Notification adapter that enqueues tasks into an ARQ/Redis queue."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, pool: ArqRedis) -> None:
|
|
19
|
+
self.pool = pool
|
|
20
|
+
|
|
21
|
+
def enqueue(self, task_name: str, payload: dict[str, Any]) -> str | None:
|
|
22
|
+
"""Sync enqueue wrapper. Prefer ``enqueue_async`` in async contexts."""
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
|
|
26
|
+
return asyncio.get_event_loop().run_until_complete(self.enqueue_async(task_name, payload))
|
|
27
|
+
|
|
28
|
+
async def enqueue_async(self, task_name: str, payload: dict[str, Any]) -> str | None:
|
|
29
|
+
"""Enqueue a notification task into the ARQ queue."""
|
|
30
|
+
|
|
31
|
+
log.debug("ArqNotificationAdapter | enqueueing task=%s", task_name)
|
|
32
|
+
job = await self.pool.enqueue_job(task_name, payload_dict=payload)
|
|
33
|
+
job_id = getattr(job, "job_id", None)
|
|
34
|
+
log.info("ArqNotificationAdapter | task=%s job_id=%s", task_name, job_id)
|
|
35
|
+
return job_id
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["ArqNotificationAdapter"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Protocol definition for notification delivery adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotificationAdapter(Protocol):
|
|
9
|
+
"""Contract for notification delivery transport."""
|
|
10
|
+
|
|
11
|
+
def enqueue(self, task_name: str, payload: dict[str, Any]) -> str | None:
|
|
12
|
+
"""Deliver or enqueue a serialized notification payload."""
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["NotificationAdapter"]
|