omkit 0.0.2__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.
omkit/tenant.py ADDED
@@ -0,0 +1,271 @@
1
+ """packages/omur-sdk/omkit/tenant.py — Per-request tenant isolation via contextvars.
2
+
3
+ Middleware sets the tenant on each request. Handlers call require() to access.
4
+ Background tasks use bind() to establish context.
5
+
6
+ exports: require() | current_or_none() | request_id() | _DEFAULT_EXCLUDE | class TenantMiddleware | middleware(exclude_paths) | set_rls(session) | set_rls_conn(conn) | bind(tenant_id, request_id) | async_bind(tenant_id, request_id) | hashed_for_log(tenant_id, key)
7
+ rules: The tenant middleware must be applied before any database operations to ensure RLS policies are properly set, and all tenant context must be bound to the current request scope to maintain isolation between concurrent requests.
8
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
9
+ message:
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import hmac
16
+ import json
17
+ import os
18
+ import uuid
19
+ from contextvars import ContextVar
20
+ from contextlib import asynccontextmanager, contextmanager
21
+ from typing import TYPE_CHECKING, AsyncIterator, Callable
22
+
23
+ import structlog
24
+
25
+ if TYPE_CHECKING:
26
+ import asyncpg
27
+
28
+ log = structlog.get_logger()
29
+
30
+ _tenant_id_var: ContextVar[str | None] = ContextVar("tenant_id", default=None)
31
+ _request_id_var: ContextVar[str | None] = ContextVar("request_id", default=None)
32
+
33
+
34
+ def require() -> str:
35
+ """Return current tenant ID or raise RuntimeError.
36
+
37
+ Rules: Tenant context must be set before calling this function, otherwise a RuntimeError is raised. Used in FastAPI middleware or background tasks via bind().
38
+ """
39
+ tid = _tenant_id_var.get()
40
+ if tid is None:
41
+ raise RuntimeError(
42
+ "No tenant context set. Use tenant.middleware() in FastAPI "
43
+ "or tenant.bind() for background tasks."
44
+ )
45
+ return tid
46
+
47
+
48
+ def current_or_none() -> str | None:
49
+ """Return current tenant ID or None. For shared services where tenant is optional.
50
+
51
+ Rules: Returns None if no tenant context is set; intended for optional tenant scenarios like shared services.
52
+ """
53
+ return _tenant_id_var.get()
54
+
55
+
56
+ def request_id() -> str | None:
57
+ """Return current request ID or None.
58
+
59
+ Rules: Returns None if no request ID is set; used for tracking requests across services.
60
+ """
61
+ return _request_id_var.get()
62
+
63
+
64
+ _DEFAULT_EXCLUDE = frozenset({"/health", "/healthz", "/ready", "/readyz", "/metrics"})
65
+
66
+
67
+ def _get_header(headers: list[tuple[bytes, bytes]], name: bytes) -> str | None:
68
+ """Extract a header value from raw ASGI headers."""
69
+ for key, value in headers:
70
+ if key.lower() == name:
71
+ return value.decode("latin-1")
72
+ return None
73
+
74
+
75
+ def _validate_uuid(value: str) -> bool:
76
+ """Check if value is a valid UUID (any version, case-insensitive)."""
77
+ try:
78
+ uuid.UUID(value)
79
+ return True
80
+ except ValueError:
81
+ return False
82
+
83
+
84
+ class TenantMiddleware:
85
+ """Pure ASGI middleware for tenant extraction.
86
+
87
+ Usage with FastAPI:
88
+ app.add_middleware(TenantMiddleware)
89
+ app.add_middleware(TenantMiddleware, exclude_paths={"/health", "/custom"})
90
+ """
91
+
92
+ def __init__(self, app, exclude_paths: set[str] | None = None) -> None:
93
+ self.app = app
94
+ self.excluded = frozenset(exclude_paths) if exclude_paths is not None else _DEFAULT_EXCLUDE
95
+
96
+ async def __call__(self, scope, receive, send):
97
+ if scope["type"] != "http":
98
+ await self.app(scope, receive, send)
99
+ return
100
+
101
+ path = scope.get("path", "")
102
+ if path in self.excluded:
103
+ await self.app(scope, receive, send)
104
+ return
105
+
106
+ headers = scope.get("headers", [])
107
+ raw_tid = _get_header(headers, b"x-tenant-id")
108
+
109
+ if not raw_tid or not _validate_uuid(raw_tid):
110
+ body = json.dumps({"error": "X-Tenant-ID header required"}).encode()
111
+ await send({
112
+ "type": "http.response.start",
113
+ "status": 401,
114
+ "headers": [
115
+ (b"content-type", b"application/json"),
116
+ (b"content-length", str(len(body)).encode()),
117
+ ],
118
+ })
119
+ await send({"type": "http.response.body", "body": body})
120
+ return
121
+
122
+ raw_rid = _get_header(headers, b"x-request-id") or str(uuid.uuid4())
123
+
124
+ tid_token = _tenant_id_var.set(raw_tid)
125
+ rid_token = _request_id_var.set(raw_rid)
126
+
127
+ async def send_with_request_id(message):
128
+ if message.get("type") == "http.response.start":
129
+ resp_headers = list(message.get("headers", []))
130
+ resp_headers.append((b"x-request-id", raw_rid.encode()))
131
+ message = {**message, "headers": resp_headers}
132
+ await send(message)
133
+
134
+ try:
135
+ await self.app(scope, receive, send_with_request_id)
136
+ finally:
137
+ _tenant_id_var.reset(tid_token)
138
+ _request_id_var.reset(rid_token)
139
+
140
+
141
+ def middleware(exclude_paths: set[str] | None = None) -> Callable:
142
+ """ASGI middleware factory. Prefer TenantMiddleware class with app.add_middleware().
143
+
144
+ This factory form works for raw ASGI wrapping (tests).
145
+ For FastAPI, use: app.add_middleware(TenantMiddleware)
146
+
147
+ Rules: This function returns a factory for ASGI middleware; prefer using TenantMiddleware class directly with app.add_middleware() for FastAPI apps.
148
+ """
149
+ excluded = exclude_paths
150
+
151
+ def asgi_middleware(app):
152
+ mw = TenantMiddleware(app, exclude_paths=excluded)
153
+ return mw
154
+
155
+ return asgi_middleware
156
+
157
+
158
+ async def set_rls(session) -> None:
159
+ """Set PostgreSQL RLS tenant context. Must be called inside an active transaction.
160
+
161
+ Uses transaction-local set_config so the setting resets when the transaction ends.
162
+ Requires sqlalchemy (optional dependency).
163
+
164
+ Rules: Must be called inside an active SQLAlchemy transaction; otherwise raises RuntimeError. Sets PostgreSQL RLS context using set_config within the transaction.
165
+ """
166
+ from sqlalchemy import text
167
+
168
+ if not session.in_transaction():
169
+ raise RuntimeError(
170
+ "set_rls() must be called inside an active transaction. "
171
+ "Use 'async with session.begin():' before calling."
172
+ )
173
+ tid = require()
174
+ await session.execute(
175
+ text("SELECT set_config('app.tenant_id', :tid, true)"),
176
+ {"tid": tid},
177
+ )
178
+
179
+
180
+ async def set_rls_conn(conn: "asyncpg.Connection") -> None:
181
+ """Set PostgreSQL RLS tenant context on an asyncpg connection.
182
+
183
+ Asyncpg counterpart of set_rls(). Reads tenant from ContextVar (require()).
184
+ Must be called inside an active transaction — set_config(..., true) is
185
+ transaction-local; outside a transaction the setting silently leaks across
186
+ pooled checkouts (cross-tenant data leak risk).
187
+
188
+ Rules: Must be called inside an active asyncpg transaction; otherwise raises RuntimeError. Sets PostgreSQL RLS context using set_config within the transaction to prevent cross-tenant data leaks.
189
+ """
190
+ if not conn.is_in_transaction():
191
+ raise RuntimeError(
192
+ "set_rls_conn() must be called inside an active transaction. "
193
+ "Use 'async with conn.transaction():' before calling."
194
+ )
195
+ tid = require()
196
+ await conn.execute("SELECT set_config('app.tenant_id', $1, true)", tid)
197
+
198
+
199
+ @contextmanager
200
+ def bind(tenant_id: str, request_id: str | None = None):
201
+ """Set tenant context for background tasks, scripts, and tests.
202
+
203
+ Resets on exit, even if an exception is raised.
204
+
205
+ Rules: Sets tenant and request ID in ContextVars for background tasks, scripts, or tests; resets values on exit even if an exception occurs.
206
+ """
207
+ tid_token = _tenant_id_var.set(tenant_id)
208
+ rid_token = _request_id_var.set(request_id)
209
+ try:
210
+ yield
211
+ finally:
212
+ _tenant_id_var.reset(tid_token)
213
+ _request_id_var.reset(rid_token)
214
+
215
+
216
+ @asynccontextmanager
217
+ async def async_bind(
218
+ tenant_id: str, request_id: str | None = None
219
+ ) -> AsyncIterator[None]:
220
+ """Async counterpart of bind() for use inside `async with` blocks.
221
+
222
+ ContextVar set/reset itself is sync; this is a convenience wrapper so
223
+ job-queue middleware and other async code can write `async with
224
+ tenant.async_bind(tid):` without a `with` inside `async def`.
225
+
226
+ Rules: Async version of bind(); used in async contexts with `async with tenant.async_bind(tid):` to manage tenant context.
227
+ """
228
+ tid_token = _tenant_id_var.set(tenant_id)
229
+ rid_token = _request_id_var.set(request_id)
230
+ try:
231
+ yield
232
+ finally:
233
+ _tenant_id_var.reset(tid_token)
234
+ _request_id_var.reset(rid_token)
235
+
236
+
237
+ def hashed_for_log(tenant_id: str, key: bytes | None = None) -> str:
238
+ """HMAC-SHA-256 of tenant_id for log/metric correlation without re-id risk.
239
+
240
+ Plain SHA-256 over a finite tenant population is brute-forceable; HMAC with
241
+ a per-deployment secret is not. Reads LOG_HMAC_KEY from env when key
242
+ not supplied. Returns first 16 hex chars (8 bytes) — enough entropy for
243
+ correlation, short enough for log lines.
244
+
245
+ Key encoding contract: LOG_HMAC_KEY must be a hex string (output of
246
+ `openssl rand -hex 32`). hashed_for_log decodes it to raw bytes before
247
+ HMAC, so the full 256 bits of entropy are used. A bare ASCII passphrase
248
+ will silently work but only at ~5 bits/char effective key strength.
249
+
250
+ Rules: Requires LOG_HMAC_KEY environment variable to be set as a hex-encoded 32-byte key; returns first 16 hex chars of HMAC-SHA-256 for log correlation.
251
+ """
252
+ if key is None:
253
+ env = os.environ.get("LOG_HMAC_KEY")
254
+ if not env:
255
+ raise RuntimeError(
256
+ "LOG_HMAC_KEY env var required for tenant log hashing. "
257
+ "Set in BaseServiceSettings or pass key= explicitly."
258
+ )
259
+ try:
260
+ key = bytes.fromhex(env)
261
+ except ValueError as exc:
262
+ raise RuntimeError(
263
+ "LOG_HMAC_KEY must be a hex string (openssl rand -hex 32). "
264
+ f"Got {len(env)} chars, decode error: {exc}"
265
+ ) from exc
266
+ if len(key) < 16:
267
+ raise RuntimeError(
268
+ f"LOG_HMAC_KEY too short ({len(key)} bytes); need >= 16"
269
+ )
270
+ digest = hmac.new(key, tenant_id.encode("utf-8"), hashlib.sha256).hexdigest()
271
+ return digest[:16]
omkit/tracing.py ADDED
@@ -0,0 +1,80 @@
1
+ """packages/omur-sdk/omkit/tracing.py — OpenTelemetry tracing bootstrap for Omur services.
2
+
3
+ Usage:
4
+ from omkit.tracing import init_tracing
5
+ init_tracing("spine") # Call once at startup
6
+
7
+ Tracing is OFF by default since the 2026-04 infra consolidation (Alloy and
8
+ Tempo were removed). Set OTEL_EXPORTER_OTLP_ENDPOINT to a reachable OTLP/HTTP
9
+ collector (e.g. ``http://otel-collector:4318``) to re-enable span export.
10
+
11
+ exports: DEFAULT_ENDPOINT | init_tracing(service_name, endpoint) | instrument_fastapi(app)
12
+ rules: The tracing module must maintain backward compatibility with all existing FastAPI instrumentation patterns and cannot introduce breaking changes to the existing service_name and endpoint parameter signatures. The module requires explicit error handling for endpoint connection failures and must not modify global tracing state outside of the init_tracing and instrument_fastapi functions. All tracing operations must be thread-safe and support concurrent FastAPI application instances.
13
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
14
+ message:
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ import structlog
21
+
22
+ log = structlog.get_logger()
23
+
24
+ DEFAULT_ENDPOINT = ""
25
+
26
+
27
+ def init_tracing(
28
+ service_name: str,
29
+ endpoint: str | None = None,
30
+ ) -> "TracerProvider | None":
31
+ """Initialize OpenTelemetry with OTLP/HTTP export.
32
+
33
+ Returns the TracerProvider, or None if tracing is disabled.
34
+
35
+ Rules: Must ensure OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set when endpoint is None and DEFAULT_ENDPOINT is not provided, otherwise tracing will be silently disabled.
36
+ """
37
+ if endpoint is None:
38
+ endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", DEFAULT_ENDPOINT)
39
+
40
+ if not endpoint:
41
+ log.info("tracing.disabled", service=service_name)
42
+ return None
43
+
44
+ try:
45
+ from opentelemetry import trace
46
+ from opentelemetry.sdk.trace import TracerProvider
47
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
48
+ from opentelemetry.sdk.resources import Resource
49
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
50
+ except ImportError:
51
+ log.info("tracing.not_installed", service=service_name)
52
+ return None
53
+
54
+ resource = Resource.create({"service.name": service_name})
55
+ provider = TracerProvider(resource=resource)
56
+ exporter = OTLPSpanExporter(endpoint=f"{endpoint}/v1/traces")
57
+ provider.add_span_processor(BatchSpanProcessor(exporter))
58
+ trace.set_tracer_provider(provider)
59
+
60
+ log.info("tracing.enabled", service=service_name, endpoint=endpoint)
61
+ return provider
62
+
63
+
64
+ def instrument_fastapi(app) -> None:
65
+ """Wrap a FastAPI app with OpenTelemetry server-side instrumentation.
66
+
67
+ Idempotent: calling twice on the same app is a no-op. Silently no-ops if
68
+ opentelemetry-instrumentation-fastapi is not installed (in-tree optional).
69
+
70
+ Rules: Function is idempotent but requires opentelemetry-instrumentation-fastapi package to be installed, otherwise it will silently no-op without raising an error.
71
+ """
72
+ try:
73
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
74
+ except ImportError:
75
+ return
76
+
77
+ if getattr(app, "_omur_otel_instrumented", False):
78
+ return
79
+ FastAPIInstrumentor.instrument_app(app)
80
+ app._omur_otel_instrumented = True
@@ -0,0 +1,29 @@
1
+ """packages/omur-sdk/omkit/transport/__init__.py — re-exports cross-cutting wire / observability primitives.
2
+
3
+ This is an additive grouping for discoverability. Existing imports from the
4
+ flat modules (``omkit.http``, ``omkit.tracing``, etc.) continue to work
5
+ unchanged; new code is encouraged to import from this facade.
6
+
7
+ exports: none
8
+ rules: The transport module must maintain backward compatibility for all existing API endpoints and response formats, as breaking changes will affect downstream services that depend on stable interfaces. All network communication must go through a centralized connection pooling mechanism to ensure resource efficiency and proper handling of concurrent requests. The module cannot introduce any synchronous blocking operations that would impact the overall performance of applications using the SDK.
9
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
10
+ message:
11
+ """
12
+
13
+ from omkit.health import mount_health_endpoints
14
+ from omkit.http import build_tenant_client
15
+ from omkit.logging import configure_logging
16
+ from omkit.metrics import mount_metrics
17
+ from omkit.resilience import CircuitBreaker, resilient
18
+ from omkit.tracing import init_tracing, instrument_fastapi
19
+
20
+ __all__ = [
21
+ "build_tenant_client",
22
+ "init_tracing",
23
+ "instrument_fastapi",
24
+ "mount_metrics",
25
+ "mount_health_endpoints",
26
+ "configure_logging",
27
+ "CircuitBreaker",
28
+ "resilient",
29
+ ]
omkit/valkey.py ADDED
@@ -0,0 +1,45 @@
1
+ """packages/omur-sdk/omkit/valkey.py — Valkey client factory.
2
+
3
+ Single source of truth for `redis.asyncio.Redis` construction across the SDK.
4
+ Replaces the URL-construction duplication in `eventbus.new_bus()` and other
5
+ call sites. Reads BaseServiceSettings.valkey_url so password handling stays
6
+ consistent.
7
+
8
+ Note: streaq does not use this factory — streaq depends on `coredis`, a
9
+ different async Redis client. Services that use streaq construct the Worker
10
+ directly from `settings.valkey_url`.
11
+
12
+ exports: new_client(settings)
13
+ rules: The module must maintain backward compatibility with existing Redis connection patterns while ensuring all async operations are properly awaited. The client initialization must respect the settings structure defined in the SDK's configuration schema. All Redis operations must be wrapped with appropriate timeout and retry logic to prevent service disruptions.
14
+ agent: ollama/qwen3-coder:latest | ollama | 2026-05-01 | codedna-cli | initial CodeDNA annotation pass
15
+ message:
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ if TYPE_CHECKING:
23
+ import redis.asyncio as aioredis
24
+
25
+ from omkit.config import BaseServiceSettings
26
+
27
+
28
+ def new_client(
29
+ settings: "BaseServiceSettings", **kwargs: Any
30
+ ) -> "aioredis.Redis":
31
+ """Build a redis.asyncio.Redis client from BaseServiceSettings.
32
+
33
+ Reuses settings.valkey_url to keep password handling and host/port logic
34
+ in one place. Empty password falls back to no-auth URL — fail-fast on
35
+ empty password is enforced at compose-startup via the
36
+ `${VALKEY_PASSWORD:?VALKEY_PASSWORD required}` interpolation, not here.
37
+
38
+ Extra kwargs pass through to redis.asyncio.from_url (decode_responses,
39
+ socket_timeout, etc.).
40
+
41
+ Rules: The function relies on settings.valkey_url being properly configured with a valid Redis connection string. It assumes that password handling and host/port logic are correctly implemented in the BaseServiceSettings, and that the VALKEY_PASSWORD environment variable is enforced at startup to prevent empty passwords.
42
+ """
43
+ import redis.asyncio as aioredis
44
+
45
+ return aioredis.from_url(settings.valkey_url, **kwargs)
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: omkit
3
+ Version: 0.0.2
4
+ Summary: Multi-tenant SaaS scaffolding for Python services.
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: pydantic>=2.13
7
+ Requires-Dist: pydantic-settings>=2.13
8
+ Requires-Dist: asyncpg>=0.31
9
+ Requires-Dist: redis>=7.4
10
+ Requires-Dist: structlog>=25.5
11
+ Requires-Dist: cryptography>=47.0
12
+ Requires-Dist: tenacity>=9.1
13
+ Requires-Dist: prometheus_client>=0.25
14
+ Requires-Dist: httpx>=0.28.1
15
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.49
16
+ Provides-Extra: dev
17
+ Requires-Dist: fastapi>=0.136.0; extra == "dev"
18
+ Requires-Dist: pytest>=9.0; extra == "dev"
19
+ Requires-Dist: pytest-asyncio>=1.3; extra == "dev"
20
+ Requires-Dist: redis>=7.4; extra == "dev"
21
+ Requires-Dist: respx>=0.21; extra == "dev"
22
+ Provides-Extra: tracing
23
+ Requires-Dist: opentelemetry-api>=1.41.0; extra == "tracing"
24
+ Requires-Dist: opentelemetry-sdk>=1.41.0; extra == "tracing"
25
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41.0; extra == "tracing"
26
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.62b0; extra == "tracing"
27
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.62b0; extra == "tracing"
28
+ Provides-Extra: metrics
29
+ Requires-Dist: prometheus-fastapi-instrumentator>=7.1; extra == "metrics"
@@ -0,0 +1,40 @@
1
+ omkit/__init__.py,sha256=9wLFyshKmCV8EM4wtNs_Hp_yUlTec4Yq2BxrwnhUz10,518
2
+ omkit/cleanup.py,sha256=7Pss_w6dZx9VRGrHPfLD8fILdVsOYzUJrs953rih8NE,2415
3
+ omkit/config.py,sha256=TD554an8GC7WdKkuhDFbnErDvLZkEWjFXEFIq9ReCfo,1846
4
+ omkit/cost.py,sha256=b4EoJekhj-UdqJDM3ETyuFT7Ea9ccko7osr-nX1-3F8,2752
5
+ omkit/dbpool.py,sha256=vI3HNbolu1OmWnJbEz1eD_X2QZNFrZohHzsPVyHXVEo,6182
6
+ omkit/encryption.py,sha256=kM_iOXk80zJn4NhIYFFmonho-uTdcXG14IaYeI6umms,2546
7
+ omkit/eventbus.py,sha256=-A6ALK3dNk8xjkuNOOkL_i8SnN2WREhb3bzjwh7ZuNI,16296
8
+ omkit/events.py,sha256=Qg9SEnsLX-KmvpRzOBDqEcJlBbx5l7c0X4aJV4PZvuo,1117
9
+ omkit/health.py,sha256=PP7fa2JkWtKf6lLZsa6GjxndSL0UD7iVuA5wGhX5RyU,3236
10
+ omkit/http.py,sha256=mEpxp2LZ25ZBEu0lVbsq_lqGIfIW8b3npQ7mKKzdYGM,3423
11
+ omkit/logging.py,sha256=LtH8uCix9U1Y-osvY2YIgz9aakYgtZ-eRjSUPydwXpY,3290
12
+ omkit/metrics.py,sha256=LftIRzSRDFDj1e1QGv5a8CEUkWSuI-vpYZAUqicMJss,1432
13
+ omkit/model_lifecycle.py,sha256=VB20gfP4GeVDr0d77sKkDHkmU_jk7JKglTrHWaIiaMs,6163
14
+ omkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ omkit/quota.py,sha256=R2rxRmn7yBiU-5DmUVvGL-j0rpNZCPvS22Nhx1tIZmA,6682
16
+ omkit/resilience.py,sha256=P7IdHjRop80wIuWIHPKxO4629OgHsVGr1TmzRmQCxzo,5238
17
+ omkit/sanitize.py,sha256=hE4RrRcRnqT4Wk5F1fTXUtbxpTo5Dh4UXdkYybRn81M,4646
18
+ omkit/sessions.py,sha256=vv2lF6e8-reGNQr_Nba3C4Jg7veCixi1sPwi4XYArac,15489
19
+ omkit/settings.py,sha256=wEBemRcALeaA2CAXikcQ1ktfLARgzXFzF4RK3HjC3Uk,14792
20
+ omkit/sync_notifier.py,sha256=Zr-mVzJDClC82aQO80944b8oFrWJhY2RnkVEcuF0sX4,5228
21
+ omkit/tenant.py,sha256=jpV-tXsmudFScQGmN3jj-VDWpiRbT1QOQHbzikH9C3o,10305
22
+ omkit/tracing.py,sha256=J2atOwD13wdnMpx68AUCEDo81AP0MQBK-JjkOB4fo5w,3415
23
+ omkit/valkey.py,sha256=KbRWbNEcjlYEhApfIz6LKjiodSxkql2Z7sTmDB1gL-Y,2183
24
+ omkit/data/__init__.py,sha256=jAX729bco_Jv4m-l1mhnUvOGuUSbuOjrCeFGObYin50,1106
25
+ omkit/internal/__init__.py,sha256=JCAYaVasGvY1GJ5SCXHV7Q1uQSEy1BYJICelXSPjIqM,258
26
+ omkit/internal/crypto.py,sha256=ddPwA4JQgy8t116OxCXUFplsrorrADW3wanHMXdlBxg,904
27
+ omkit/jobqueue/__init__.py,sha256=XHYU229TG73AVM8g1Dhn636MArvzPrdMp2PGTKTzGPQ,1174
28
+ omkit/jobqueue/envelope.py,sha256=-8FVv2ZhaC9KtVzYkU8qOaRNiECx0h1KrjPnDi5M3s4,4608
29
+ omkit/jobqueue/streaq.py,sha256=g_t8DK-1K1-SYuiTHHUjVktiaqAi9HCFLzfj8TLMTO0,11104
30
+ omkit/platform/__init__.py,sha256=BN6cqsUyGWMgVRb_hQKiSDVyo51-BSOQ708cv4N5EBI,490
31
+ omkit/providers/__init__.py,sha256=1JMezeFpDRzIF15f1LNBe-lIIWUf_Bfaqtjx2rSc-OM,419
32
+ omkit/providers/base.py,sha256=YG01V18FYHQHxUNV9-DGTdtjeu-BuqjT0py5lhelATE,2787
33
+ omkit/providers/registry.py,sha256=peM134Z9Zib-9krjyImlqLZGeBL8uzw0zFCd-cIQib4,11257
34
+ omkit/security/__init__.py,sha256=pmE98gw2DO87fMMKcZjcOkg2_L8wV919Ig5afI_sKkA,1231
35
+ omkit/security/events.py,sha256=ur0oRFuoaj-E5KhrxPoZd0mphkGNHsGWH9A4UGf-V5s,2687
36
+ omkit/transport/__init__.py,sha256=YF5U0wCaJzEn2aMBhgMkky6AyT4JUyCycoRqNbMjD4U,1451
37
+ omkit-0.0.2.dist-info/METADATA,sha256=vz3GMY3pUiFjUzkTaJ8ZjVqdNQt95toW7Y9XyrnJUVs,1195
38
+ omkit-0.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
39
+ omkit-0.0.2.dist-info/top_level.txt,sha256=sCEPnxCXPMDdG0h3BXj0COELTJHdOn5q_LycbET5zrg,6
40
+ omkit-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ omkit