codex-platform 0.1.0__py3-none-any.whl
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/__init__.py +1 -0
- codex_platform/notifications/__init__.py +48 -0
- codex_platform/notifications/channels.py +24 -0
- codex_platform/notifications/clients/__init__.py +3 -0
- codex_platform/notifications/clients/smtp.py +134 -0
- codex_platform/notifications/delivery/__init__.py +18 -0
- codex_platform/notifications/delivery/arq.py +69 -0
- codex_platform/notifications/delivery/base.py +39 -0
- codex_platform/notifications/delivery/direct.py +89 -0
- codex_platform/notifications/dto.py +63 -0
- codex_platform/notifications/interfaces.py +30 -0
- codex_platform/notifications/orchestrator.py +116 -0
- codex_platform/notifications/registry.py +71 -0
- codex_platform/notifications/renderer.py +111 -0
- codex_platform/redis_service/__init__.py +59 -0
- codex_platform/redis_service/base.py +69 -0
- codex_platform/redis_service/exceptions.py +20 -0
- codex_platform/redis_service/keys.py +110 -0
- codex_platform/redis_service/managers/__init__.py +18 -0
- codex_platform/redis_service/managers/base_manager.py +31 -0
- codex_platform/redis_service/operations/__init__.py +19 -0
- codex_platform/redis_service/operations/hash.py +302 -0
- codex_platform/redis_service/operations/json_module.py +129 -0
- codex_platform/redis_service/operations/json_string.py +113 -0
- codex_platform/redis_service/operations/list_.py +219 -0
- codex_platform/redis_service/operations/pipeline.py +197 -0
- codex_platform/redis_service/operations/set_.py +212 -0
- codex_platform/redis_service/operations/string.py +318 -0
- codex_platform/redis_service/operations/zset.py +277 -0
- codex_platform/redis_service/service.py +80 -0
- codex_platform/streams/__init__.py +59 -0
- codex_platform/streams/consumer.py +106 -0
- codex_platform/streams/dispatcher.py +175 -0
- codex_platform/streams/processor.py +217 -0
- codex_platform/streams/producer.py +60 -0
- codex_platform/streams/router.py +78 -0
- codex_platform/workers/__init__.py +1 -0
- codex_platform/workers/arq/__init__.py +27 -0
- codex_platform/workers/arq/base.py +170 -0
- codex_platform/workers/arq/config.py +67 -0
- codex_platform/workers/arq/task_utils.py +72 -0
- codex_platform/workers/arq/types.py +11 -0
- codex_platform-0.1.0.dist-info/METADATA +120 -0
- codex_platform-0.1.0.dist-info/RECORD +45 -0
- codex_platform-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Worker infrastructure for async background job processing."""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""ARQ workers infrastructure — base classes, config, task utilities."""
|
|
2
|
+
|
|
3
|
+
from .base import (
|
|
4
|
+
CORE_FUNCTIONS,
|
|
5
|
+
HEALTH_FILE,
|
|
6
|
+
BaseArqService,
|
|
7
|
+
BaseArqWorkerSettings,
|
|
8
|
+
base_shutdown,
|
|
9
|
+
base_startup,
|
|
10
|
+
requeue_to_stream,
|
|
11
|
+
)
|
|
12
|
+
from .config import BaseWorkerConfig
|
|
13
|
+
from .task_utils import arq_task
|
|
14
|
+
from .types import DependencyFunction
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"BaseArqService",
|
|
18
|
+
"BaseArqWorkerSettings",
|
|
19
|
+
"BaseWorkerConfig",
|
|
20
|
+
"CORE_FUNCTIONS",
|
|
21
|
+
"DependencyFunction",
|
|
22
|
+
"HEALTH_FILE",
|
|
23
|
+
"arq_task",
|
|
24
|
+
"base_startup",
|
|
25
|
+
"base_shutdown",
|
|
26
|
+
"requeue_to_stream",
|
|
27
|
+
]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
codex_platform.workers.arq.base
|
|
3
|
+
============================
|
|
4
|
+
Base infrastructure for ARQ background workers.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from codex_platform.workers.arq import BaseArqWorkerSettings, base_startup, base_shutdown
|
|
8
|
+
|
|
9
|
+
class MyWorkerSettings(BaseArqWorkerSettings):
|
|
10
|
+
redis_settings = RedisSettings(...)
|
|
11
|
+
functions = MY_FUNCTIONS
|
|
12
|
+
on_startup = my_startup
|
|
13
|
+
on_shutdown = my_shutdown
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import pathlib
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from arq.connections import ArqRedis, RedisSettings, create_pool
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseArqService:
|
|
27
|
+
"""Async ARQ client for use inside worker tasks.
|
|
28
|
+
|
|
29
|
+
For the Django producer-side client see
|
|
30
|
+
``codex_platform.adapters.arq.client.BaseArqClient``.
|
|
31
|
+
|
|
32
|
+
Example::
|
|
33
|
+
|
|
34
|
+
service = BaseArqService(redis_settings)
|
|
35
|
+
await service.init()
|
|
36
|
+
await service.enqueue_job("my_task", arg1, arg2)
|
|
37
|
+
await service.close()
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, redis_settings: RedisSettings) -> None:
|
|
41
|
+
"""Initialize the service with Redis connection settings.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
redis_settings: ARQ ``RedisSettings`` instance.
|
|
45
|
+
"""
|
|
46
|
+
self.pool: ArqRedis | None = None
|
|
47
|
+
self.redis_settings = redis_settings
|
|
48
|
+
|
|
49
|
+
async def init(self) -> None:
|
|
50
|
+
"""Open the ARQ connection pool. No-op if already initialized.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
Exception: If the Redis connection cannot be established.
|
|
54
|
+
"""
|
|
55
|
+
if not self.pool:
|
|
56
|
+
try:
|
|
57
|
+
self.pool = await create_pool(self.redis_settings)
|
|
58
|
+
log.debug("BaseArqService | init | status=success")
|
|
59
|
+
except Exception:
|
|
60
|
+
log.exception("BaseArqService | init | status=failed")
|
|
61
|
+
raise
|
|
62
|
+
|
|
63
|
+
async def close(self) -> None:
|
|
64
|
+
"""Close the ARQ connection pool gracefully."""
|
|
65
|
+
if self.pool:
|
|
66
|
+
try:
|
|
67
|
+
await self.pool.close()
|
|
68
|
+
log.debug("BaseArqService | close | status=success")
|
|
69
|
+
except Exception:
|
|
70
|
+
log.exception("BaseArqService | close | status=failed")
|
|
71
|
+
|
|
72
|
+
async def enqueue_job(self, function: str, *args: Any, **kwargs: Any) -> Any | None:
|
|
73
|
+
"""Enqueue a background job. Initializes the pool on first call.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
function: ARQ worker function name.
|
|
77
|
+
*args: Positional arguments forwarded to the function.
|
|
78
|
+
**kwargs: Keyword arguments forwarded to the function.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
ARQ ``Job`` handle, or ``None`` if enqueueing failed.
|
|
82
|
+
"""
|
|
83
|
+
if not self.pool:
|
|
84
|
+
await self.init()
|
|
85
|
+
if self.pool:
|
|
86
|
+
try:
|
|
87
|
+
job = await self.pool.enqueue_job(function, *args, **kwargs)
|
|
88
|
+
log.debug(
|
|
89
|
+
"BaseArqService | enqueue_job | function=%s | job_id=%s",
|
|
90
|
+
function,
|
|
91
|
+
job.job_id if job else "None",
|
|
92
|
+
)
|
|
93
|
+
return job
|
|
94
|
+
except Exception:
|
|
95
|
+
log.exception("BaseArqService | enqueue_job | function=%s | status=failed", function)
|
|
96
|
+
return None
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# --- Health Check ---
|
|
101
|
+
# Marker file for container health probes (Docker HEALTHCHECK, K8s liveness)
|
|
102
|
+
HEALTH_FILE = pathlib.Path("/tmp/arq_worker_healthy") # nosec B108
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def base_startup(ctx: dict[str, Any]) -> None:
|
|
106
|
+
"""Base startup hook. Creates health check file. Extend in your workers."""
|
|
107
|
+
HEALTH_FILE.touch()
|
|
108
|
+
log.info("ArqWorker | startup | health_file=%s", HEALTH_FILE)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def base_shutdown(ctx: dict[str, Any]) -> None:
|
|
112
|
+
"""Base shutdown hook. Removes health check file. Extend in your workers."""
|
|
113
|
+
HEALTH_FILE.unlink(missing_ok=True)
|
|
114
|
+
log.info("ArqWorker | shutdown")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class BaseArqWorkerSettings:
|
|
118
|
+
"""Base ARQ ``WorkerSettings``. Subclass and override fields in your workers module.
|
|
119
|
+
|
|
120
|
+
Example::
|
|
121
|
+
|
|
122
|
+
class MyWorkerSettings(BaseArqWorkerSettings):
|
|
123
|
+
redis_settings = RedisSettings(host="localhost", port=6379)
|
|
124
|
+
functions = [my_task1, my_task2] + CORE_FUNCTIONS
|
|
125
|
+
on_startup = my_startup
|
|
126
|
+
on_shutdown = my_shutdown
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
max_jobs: int = 20
|
|
130
|
+
job_timeout: int = 60
|
|
131
|
+
keep_result: int = 60
|
|
132
|
+
max_retries: int = 5
|
|
133
|
+
retry_delay: int = 10
|
|
134
|
+
|
|
135
|
+
on_startup = base_startup
|
|
136
|
+
on_shutdown = base_shutdown
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# --- CORE_FUNCTIONS ---
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def requeue_to_stream(ctx: dict[str, Any], stream_name: str, payload: dict[str, Any]) -> None:
|
|
143
|
+
"""Re-enqueue a failed message back into a Redis Stream for retry processing.
|
|
144
|
+
|
|
145
|
+
After 5 retries the message is moved to a Dead Letter Queue (DLQ) instead of
|
|
146
|
+
being dropped. DLQ key: ``{stream_name}:dlq``.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
ctx: ARQ worker context dict. Must contain a ``"stream_manager"`` key.
|
|
150
|
+
stream_name: Target Redis Stream name.
|
|
151
|
+
payload: Original message payload. ``_retries`` counter is incremented in-place.
|
|
152
|
+
"""
|
|
153
|
+
sm = ctx.get("stream_manager")
|
|
154
|
+
if not sm:
|
|
155
|
+
log.error("requeue_to_stream | stream_manager not in context")
|
|
156
|
+
return
|
|
157
|
+
retries = int(payload.get("_retries", 0)) + 1
|
|
158
|
+
if retries > 5:
|
|
159
|
+
dlq_name = f"{stream_name}:dlq"
|
|
160
|
+
payload["_failed_at"] = datetime.now(timezone.utc).isoformat()
|
|
161
|
+
payload["_original_stream"] = stream_name
|
|
162
|
+
await sm.add_event(dlq_name, payload)
|
|
163
|
+
log.error("requeue_to_stream | max retries reached | moved to DLQ '%s'", dlq_name)
|
|
164
|
+
return
|
|
165
|
+
payload["_retries"] = str(retries)
|
|
166
|
+
await sm.add_event(stream_name, payload)
|
|
167
|
+
log.info("requeue_to_stream | requeued to '%s' retry=%d", stream_name, retries)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
CORE_FUNCTIONS = [requeue_to_stream]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
codex_platform.workers.arq.config
|
|
3
|
+
================================
|
|
4
|
+
Base workers configuration with SMTP and ARQ fields.
|
|
5
|
+
|
|
6
|
+
Extends BaseCommonSettings from codex_platform.settings.
|
|
7
|
+
Maps standard Django .env names to workers-friendly fields.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
class MyWorkerConfig(BaseWorkerConfig):
|
|
11
|
+
SEVEN_IO_API_KEY: str | None = None # project-specific
|
|
12
|
+
TEMPLATES_DIR: str = "src/workers/templates"
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
env_file = ".env"
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
|
|
22
|
+
from codex_platform.settings import BaseCommonSettings
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseWorkerConfig(BaseCommonSettings):
|
|
26
|
+
"""Base configuration for ARQ workers — provides SMTP and ARQ settings.
|
|
27
|
+
|
|
28
|
+
Maps standard Django ``.env`` variable names (``EMAIL_HOST``, etc.) to
|
|
29
|
+
internal ``SMTP_``-prefixed fields for clarity.
|
|
30
|
+
|
|
31
|
+
Extend this class in your project to add vendor-specific fields
|
|
32
|
+
(Twilio, Seven.io, SendGrid, etc.).
|
|
33
|
+
|
|
34
|
+
Example::
|
|
35
|
+
|
|
36
|
+
class MyWorkerConfig(BaseWorkerConfig):
|
|
37
|
+
SEVEN_IO_API_KEY: str | None = None
|
|
38
|
+
TEMPLATES_DIR: str = "src/workers/templates"
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
env_file = ".env"
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# --- Email (SMTP) — маппинг Django env names ---
|
|
45
|
+
SMTP_HOST: str = Field(default="localhost", alias="EMAIL_HOST")
|
|
46
|
+
SMTP_PORT: int = Field(default=465, alias="EMAIL_PORT")
|
|
47
|
+
SMTP_USER: str | None = Field(default=None, alias="EMAIL_HOST_USER")
|
|
48
|
+
SMTP_PASSWORD: str | None = Field(default=None, alias="EMAIL_HOST_PASSWORD")
|
|
49
|
+
SMTP_FROM_EMAIL: str | None = Field(default=None, alias="DEFAULT_FROM_EMAIL")
|
|
50
|
+
SMTP_USE_TLS: bool = Field(default=False, alias="EMAIL_USE_TLS")
|
|
51
|
+
SMTP_USE_SSL: bool = Field(default=True, alias="EMAIL_USE_SSL")
|
|
52
|
+
|
|
53
|
+
# --- ARQ Configuration ---
|
|
54
|
+
arq_max_jobs: int = 10
|
|
55
|
+
arq_job_timeout: int = 60
|
|
56
|
+
arq_keep_result: int = 60
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def arq_redis_settings(self) -> Any:
|
|
60
|
+
"""Build and return an ARQ ``RedisSettings`` object from the base Redis config."""
|
|
61
|
+
from arq.connections import RedisSettings
|
|
62
|
+
|
|
63
|
+
return RedisSettings(
|
|
64
|
+
host=self.effective_redis_host,
|
|
65
|
+
port=self.redis_port,
|
|
66
|
+
password=self.redis_password,
|
|
67
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
codex_platform.workers.arq.task_utils
|
|
3
|
+
====================================
|
|
4
|
+
Utilities for writing ARQ tasks with less boilerplate.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from codex_platform.workers.arq import arq_task
|
|
8
|
+
|
|
9
|
+
@arq_task(retry_backoff=60, max_retries=3)
|
|
10
|
+
async def send_booking_notification(ctx, appointment_id: int):
|
|
11
|
+
service = ctx["notification_service"]
|
|
12
|
+
await service.send(...)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from functools import wraps
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from arq import Retry
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def arq_task(
|
|
25
|
+
*,
|
|
26
|
+
retry_backoff: int = 30,
|
|
27
|
+
max_retries: int = 5,
|
|
28
|
+
log_args: bool = False,
|
|
29
|
+
) -> Any:
|
|
30
|
+
"""
|
|
31
|
+
Decorator that adds standard error handling, logging, and retry to ARQ tasks.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
retry_backoff: Base delay in seconds. Actual delay = job_try * retry_backoff.
|
|
35
|
+
max_retries: Maximum number of retries before giving up.
|
|
36
|
+
log_args: If True, log task arguments (be careful with PII).
|
|
37
|
+
|
|
38
|
+
Wrapped function receives ctx as first arg (standard ARQ convention).
|
|
39
|
+
On exception:
|
|
40
|
+
- If job_try < max_retries → raises Retry(defer=job_try * retry_backoff)
|
|
41
|
+
- If job_try >= max_retries → logs error and returns None (task dropped)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def decorator(fn: Any) -> Any:
|
|
45
|
+
@wraps(fn)
|
|
46
|
+
async def wrapper(ctx: dict[str, Any], *args: Any, **kwargs: Any) -> Any:
|
|
47
|
+
task_name = fn.__name__
|
|
48
|
+
job_try = ctx.get("job_try", 1)
|
|
49
|
+
|
|
50
|
+
if log_args:
|
|
51
|
+
log.info("%s | start | try=%d | args=%s kwargs=%s", task_name, job_try, args, kwargs)
|
|
52
|
+
else:
|
|
53
|
+
log.info("%s | start | try=%d", task_name, job_try)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
result = await fn(ctx, *args, **kwargs)
|
|
57
|
+
log.info("%s | success | try=%d", task_name, job_try)
|
|
58
|
+
return result
|
|
59
|
+
except Retry:
|
|
60
|
+
raise
|
|
61
|
+
except Exception as e:
|
|
62
|
+
if job_try < max_retries:
|
|
63
|
+
defer = job_try * retry_backoff
|
|
64
|
+
log.warning("%s | retry in %ds | try=%d | error=%s", task_name, defer, job_try, e)
|
|
65
|
+
raise Retry(defer=defer) from e
|
|
66
|
+
log.exception("%s | max retries (%d) reached | giving up", task_name, max_retries)
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
wrapper.__qualname__ = fn.__qualname__
|
|
70
|
+
return wrapper
|
|
71
|
+
|
|
72
|
+
return decorator
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
codex_platform.workers.arq.types
|
|
3
|
+
================================
|
|
4
|
+
Common type definitions for workers dependency injection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Type for dependency init/close functions used in workers startup/shutdown lists
|
|
11
|
+
DependencyFunction = Callable[[dict[str, Any], Any], Awaitable[None]]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-platform
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Infrastructure, background tasks (ARQ), Redis service and framework adapters for Codex
|
|
5
|
+
Project-URL: Homepage, https://github.com/codexdlc/codex-platform
|
|
6
|
+
Project-URL: Documentation, https://codexdlc.github.io/codex-platform/
|
|
7
|
+
Project-URL: Repository, https://github.com/codexdlc/codex-platform
|
|
8
|
+
Project-URL: Issues, https://github.com/codexdlc/codex-platform/issues
|
|
9
|
+
Author: CodexDLC
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: arq,django,notifications,redis,waas,worker
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: codex-core
|
|
21
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'all'
|
|
24
|
+
Requires-Dist: arq>=0.25; extra == 'all'
|
|
25
|
+
Requires-Dist: redis>=5.0; extra == 'all'
|
|
26
|
+
Provides-Extra: arq
|
|
27
|
+
Requires-Dist: arq>=0.25; extra == 'arq'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: arq>=0.25; extra == 'dev'
|
|
31
|
+
Requires-Dist: bandit>=1.7; extra == 'dev'
|
|
32
|
+
Requires-Dist: detect-secrets>=1.5; extra == 'dev'
|
|
33
|
+
Requires-Dist: fakeredis[lua]>=2.20; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
35
|
+
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
36
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: redis>=5.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
42
|
+
Provides-Extra: docs
|
|
43
|
+
Requires-Dist: mike>=2.0; extra == 'docs'
|
|
44
|
+
Requires-Dist: mkdocs-include-markdown-plugin; extra == 'docs'
|
|
45
|
+
Requires-Dist: mkdocs-material>=9.0; extra == 'docs'
|
|
46
|
+
Requires-Dist: mkdocs-static-i18n; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocs<2.0,>=1.5; extra == 'docs'
|
|
48
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
49
|
+
Provides-Extra: notifications
|
|
50
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'notifications'
|
|
51
|
+
Provides-Extra: redis
|
|
52
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
53
|
+
Provides-Extra: streams
|
|
54
|
+
Requires-Dist: redis>=5.0; extra == 'streams'
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
|
|
57
|
+
# codex-platform
|
|
58
|
+
|
|
59
|
+
[](https://pypi.org/project/codex-platform/)
|
|
60
|
+
[](https://pypi.org/project/codex-platform/)
|
|
61
|
+
[](https://github.com/codexdlc/codex-platform/actions/workflows/ci.yml)
|
|
62
|
+
[](LICENSE)
|
|
63
|
+
|
|
64
|
+
Infrastructure library for the **Codex WaaS toolkit**. Provides async Redis abstraction, Redis Streams event bus, ARQ background workers, and a multi-channel notification engine — each as an independently installable extra.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Core only
|
|
72
|
+
pip install codex-platform
|
|
73
|
+
|
|
74
|
+
# With Redis support
|
|
75
|
+
pip install "codex-platform[redis]"
|
|
76
|
+
|
|
77
|
+
# With ARQ background workers
|
|
78
|
+
pip install "codex-platform[arq]"
|
|
79
|
+
|
|
80
|
+
# With async SMTP notifications
|
|
81
|
+
pip install "codex-platform[notifications]"
|
|
82
|
+
|
|
83
|
+
# Redis Streams
|
|
84
|
+
pip install "codex-platform[streams]"
|
|
85
|
+
|
|
86
|
+
# Everything
|
|
87
|
+
pip install "codex-platform[all]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from redis.asyncio import Redis
|
|
94
|
+
from codex_platform.redis_service import RedisService
|
|
95
|
+
|
|
96
|
+
redis = Redis(host="localhost", port=6379)
|
|
97
|
+
service = RedisService(redis)
|
|
98
|
+
|
|
99
|
+
await service.hash.set_json("user:42", "profile", {"name": "Alex"})
|
|
100
|
+
data = await service.hash.get_json("user:42", "profile")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Modules
|
|
104
|
+
|
|
105
|
+
| Module | Extra | Description |
|
|
106
|
+
| :--- | :--- | :--- |
|
|
107
|
+
| `redis_service` | `[redis]` | Typed async Redis abstraction — Hash, String, List, Set, ZSet, JSON, Pipeline |
|
|
108
|
+
| `streams` | `[streams]` | Redis Streams producer/consumer with consumer groups, retry, and DLQ |
|
|
109
|
+
| `workers.arq` | `[arq]` | ARQ worker base infrastructure — health probes, DLQ retry, CORE_FUNCTIONS |
|
|
110
|
+
| `notifications` | `[notifications]` | Multi-channel notification engine — SMTP, ARQ/Direct delivery, Jinja2 renderer |
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
Full docs with architecture, API reference, and data flow diagrams:
|
|
115
|
+
|
|
116
|
+
**[codexdlc.github.io/codex-platform](https://codexdlc.github.io/codex-platform/)**
|
|
117
|
+
|
|
118
|
+
## Part of the Codex ecosystem
|
|
119
|
+
|
|
120
|
+
[codex-core](https://github.com/codexdlc/codex-core) · **codex-platform** · [codexdlc](https://github.com/codexdlc)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
codex_platform/__init__.py,sha256=efyDuGd0noDsmCFnZ2fQnHDJ8L1XBEujKpLSSSvkcwk,86
|
|
2
|
+
codex_platform/notifications/__init__.py,sha256=4UdFzQ5-C8NTu0qvMNdSYJkdCLjokFSj7VMLxIEKhwQ,1745
|
|
3
|
+
codex_platform/notifications/channels.py,sha256=8mvSzRUSkwE5iSMZSwCqVDAgXFIBMF-pRCtBDXKt1zU,502
|
|
4
|
+
codex_platform/notifications/dto.py,sha256=-fg_Z-YLra6MwzkPPpCkfDsRb2_IR-HKHIaSgrpxhzY,1801
|
|
5
|
+
codex_platform/notifications/interfaces.py,sha256=rTLVz4odNBMAupBa-hzHXpuqbLouNo06a5W7KyqeimY,874
|
|
6
|
+
codex_platform/notifications/orchestrator.py,sha256=cJw4L9nJ1Lq3iZ55I6UXv4d6ZEALkUUTH5hyL5Tg4v4,3724
|
|
7
|
+
codex_platform/notifications/registry.py,sha256=ZmPxDywCDgwkIGAHSHr_WsHqgfqOcx3rXy_AU77q-AY,2617
|
|
8
|
+
codex_platform/notifications/renderer.py,sha256=2m1RzyNxaax5kcsoxdYKD_xq5eekGOETaieVtNzh40U,3466
|
|
9
|
+
codex_platform/notifications/clients/__init__.py,sha256=DZt0cwla0KlMHpapIUgI1A1_40GU06MrIshT6Hi-bMc,67
|
|
10
|
+
codex_platform/notifications/clients/smtp.py,sha256=GklQktt2-_ZHRDy3sc2w6ySMomjwyOACyettFukwvdw,4090
|
|
11
|
+
codex_platform/notifications/delivery/__init__.py,sha256=2o22-uBJ6Pazp3Z4Z9xXHSLRNU_MQIW-wBXjbIvtA6A,539
|
|
12
|
+
codex_platform/notifications/delivery/arq.py,sha256=j41_m4YSkkMg1nZzAS4SpMEo2pjG88CTRcTTino2lYk,2289
|
|
13
|
+
codex_platform/notifications/delivery/base.py,sha256=PRU80-5DV3zUO8FZ4RBqJXzknJdMlbkHd_WNVchmDlU,1279
|
|
14
|
+
codex_platform/notifications/delivery/direct.py,sha256=Lxp-P6G9J-jo-kwS7itHhZhyPrmxMgMQg9uSLdcKlh8,3241
|
|
15
|
+
codex_platform/redis_service/__init__.py,sha256=SN0e5CdQlqL9QVS984NO15RDH0_mL6eYSY6YjrrEkrY,1359
|
|
16
|
+
codex_platform/redis_service/base.py,sha256=xpIibHar0afaz2uwV8dPZQ7fWCPcP6SkRhxT3_6hSJw,2385
|
|
17
|
+
codex_platform/redis_service/exceptions.py,sha256=7WVBoMae0IczP_000p43MWhKYxYb9W-UgKCedLYyeo4,759
|
|
18
|
+
codex_platform/redis_service/keys.py,sha256=JXlT7NM9E9RqJ4Oyo2tBwaQ0PAz3wvErJ2jkZQnB-4w,3085
|
|
19
|
+
codex_platform/redis_service/service.py,sha256=usgiua76smJA8q6RdByH0Ro7JPrWWDgYCXmxXumUA3I,2783
|
|
20
|
+
codex_platform/redis_service/managers/__init__.py,sha256=aJUv1oL3LAy2Md6EHuBuu1cU6Fys2Qx8Z9n17u8cUIE,560
|
|
21
|
+
codex_platform/redis_service/managers/base_manager.py,sha256=GlYqA-94PcJI-oG_SxAG_n04pb1VDD7y6USAazsg94k,987
|
|
22
|
+
codex_platform/redis_service/operations/__init__.py,sha256=3X_5PEP_-g_QMGLrHnjdgOBwND1nLl49sM9LRcf_RDo,510
|
|
23
|
+
codex_platform/redis_service/operations/hash.py,sha256=9UWWnmVbJgnqkNkC66DZkqN6r8APdkKeGNqAr7glkK8,11120
|
|
24
|
+
codex_platform/redis_service/operations/json_module.py,sha256=LXgndy1XTO1Pokho9hxQXSDS-hd9FjeGzCeSpsWxAnM,4552
|
|
25
|
+
codex_platform/redis_service/operations/json_string.py,sha256=Dh75R_QnL8YHAIIw62qvlh4toBi5MMrLnY5w0rFj0mk,3780
|
|
26
|
+
codex_platform/redis_service/operations/list_.py,sha256=6MHlgDmkw4E7jiDtmLD5AxLoGNI6IrRjhFuX9O8Uy7c,7874
|
|
27
|
+
codex_platform/redis_service/operations/pipeline.py,sha256=lbwoDd97NwoOtKMZP6uJk2qr_UuHT70mBevyK3CYRXY,6796
|
|
28
|
+
codex_platform/redis_service/operations/set_.py,sha256=lFRykixCbW-0iz3BVEPAeyZtQeSIVKOT0IfX_sveEJQ,7356
|
|
29
|
+
codex_platform/redis_service/operations/string.py,sha256=xiXxFoeVg3WwfpOdkPv6VnTz_eiJz14bG3j6lpSyRGg,11041
|
|
30
|
+
codex_platform/redis_service/operations/zset.py,sha256=EGUYWLADUJthtSzWkQUcrIFLzjS-tX06NTkeI8DQUq8,10368
|
|
31
|
+
codex_platform/streams/__init__.py,sha256=qG-VJcpCRJlX62aTYavOttjumjWMf0Jlg5PhTciAjd0,1805
|
|
32
|
+
codex_platform/streams/consumer.py,sha256=4xO_c0NvtQeu6DRbwhXeJdsU43RU-kChvOIZ5UKV9bU,3997
|
|
33
|
+
codex_platform/streams/dispatcher.py,sha256=weePz1_oKTGoyCja1WUU6hBetnqrytdT3HfERE8isq8,5979
|
|
34
|
+
codex_platform/streams/processor.py,sha256=ORFyg8Ado6lgHWBZJroixxo9hnbb_N2actzCxEhUIUI,8042
|
|
35
|
+
codex_platform/streams/producer.py,sha256=AkdRvthL_DtfNMqS0XZgzxNDvU0HKkYxXSciyJAjPgg,2227
|
|
36
|
+
codex_platform/streams/router.py,sha256=T2Oe14G-HP40CDcZ-YwqOy6rSfZavp1TkWSOXmP_ijs,2281
|
|
37
|
+
codex_platform/workers/__init__.py,sha256=lLCwutgqNCeq6CI9y78EcyikRqhyNN_sMgxl0-O2Zt8,65
|
|
38
|
+
codex_platform/workers/arq/__init__.py,sha256=AJMkIuEF722Y9AVRNizlG2u-6PJfBNaPxGINDuMoA6o,589
|
|
39
|
+
codex_platform/workers/arq/base.py,sha256=_rnqQQryr28HrvAaeKEeJ1yFOnSTA4kGEmEkurFqLeg,5692
|
|
40
|
+
codex_platform/workers/arq/config.py,sha256=cYyP6aelo88DgKWeny90i94jMNd_tMTrtcZVkbPrnD8,2218
|
|
41
|
+
codex_platform/workers/arq/task_utils.py,sha256=2hKh-6Lxr9CJePXRdCYIV16OSXlFBP3hJe-9EV5Lvyw,2377
|
|
42
|
+
codex_platform/workers/arq/types.py,sha256=yiiQ-5hnGTEzVs_Ebv3bapvRc9gGBNUHzz7Hi0PV3_o,357
|
|
43
|
+
codex_platform-0.1.0.dist-info/METADATA,sha256=I6zKU__3ypIRettYDiixoQ4PgXh63Cpqa-FO761a8dI,4610
|
|
44
|
+
codex_platform-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
45
|
+
codex_platform-0.1.0.dist-info/RECORD,,
|