avtomatika 1.0b6__py3-none-any.whl → 1.0b8__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.
- avtomatika/api/handlers.py +549 -0
- avtomatika/api/routes.py +118 -0
- avtomatika/app_keys.py +33 -0
- avtomatika/blueprint.py +125 -54
- avtomatika/config.py +10 -0
- avtomatika/context.py +2 -2
- avtomatika/data_types.py +4 -2
- avtomatika/dispatcher.py +9 -27
- avtomatika/engine.py +70 -601
- avtomatika/executor.py +55 -22
- avtomatika/health_checker.py +23 -5
- avtomatika/history/base.py +60 -6
- avtomatika/history/noop.py +18 -7
- avtomatika/history/postgres.py +8 -6
- avtomatika/history/sqlite.py +7 -5
- avtomatika/metrics.py +1 -1
- avtomatika/reputation.py +46 -40
- avtomatika/s3.py +323 -0
- avtomatika/scheduler.py +8 -8
- avtomatika/storage/base.py +45 -4
- avtomatika/storage/memory.py +56 -13
- avtomatika/storage/redis.py +185 -252
- avtomatika/utils/__init__.py +0 -0
- avtomatika/utils/webhook_sender.py +96 -0
- avtomatika/watcher.py +34 -38
- avtomatika/ws_manager.py +7 -6
- {avtomatika-1.0b6.dist-info → avtomatika-1.0b8.dist-info}/METADATA +91 -3
- avtomatika-1.0b8.dist-info/RECORD +46 -0
- avtomatika-1.0b6.dist-info/RECORD +0 -40
- {avtomatika-1.0b6.dist-info → avtomatika-1.0b8.dist-info}/WHEEL +0 -0
- {avtomatika-1.0b6.dist-info → avtomatika-1.0b8.dist-info}/licenses/LICENSE +0 -0
- {avtomatika-1.0b6.dist-info → avtomatika-1.0b8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from asyncio import CancelledError, Queue, QueueFull, create_task, sleep
|
|
2
|
+
from contextlib import suppress
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aiohttp import ClientSession, ClientTimeout
|
|
8
|
+
|
|
9
|
+
logger = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class WebhookPayload:
|
|
14
|
+
event: str # "job_finished", "job_failed", "job_quarantined"
|
|
15
|
+
job_id: str
|
|
16
|
+
status: str
|
|
17
|
+
result: dict[str, Any] | None = None
|
|
18
|
+
error: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WebhookSender:
|
|
22
|
+
def __init__(self, session: ClientSession):
|
|
23
|
+
self.session = session
|
|
24
|
+
self.timeout = ClientTimeout(total=10)
|
|
25
|
+
self.max_retries = 3
|
|
26
|
+
self._queue: Queue[tuple[str, WebhookPayload]] = Queue(maxsize=1000)
|
|
27
|
+
self._worker_task = None
|
|
28
|
+
|
|
29
|
+
def start(self) -> None:
|
|
30
|
+
if not self._worker_task:
|
|
31
|
+
self._worker_task = create_task(self._worker())
|
|
32
|
+
logger.info("WebhookSender background worker started.")
|
|
33
|
+
|
|
34
|
+
async def stop(self) -> None:
|
|
35
|
+
if self._worker_task:
|
|
36
|
+
self._worker_task.cancel()
|
|
37
|
+
with suppress(CancelledError):
|
|
38
|
+
await self._worker_task
|
|
39
|
+
self._worker_task = None
|
|
40
|
+
logger.info("WebhookSender background worker stopped.")
|
|
41
|
+
|
|
42
|
+
async def send(self, url: str, payload: WebhookPayload) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Queues a webhook to be sent. Non-blocking.
|
|
45
|
+
Drops the message if the queue is full to prevent backpressure.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
self._queue.put_nowait((url, payload))
|
|
49
|
+
except QueueFull:
|
|
50
|
+
logger.error(
|
|
51
|
+
f"Webhook queue is full! Dropping webhook for job {payload.job_id} to {url}. "
|
|
52
|
+
"Consider increasing queue size or checking external service latency."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async def _worker(self) -> None:
|
|
56
|
+
while True:
|
|
57
|
+
try:
|
|
58
|
+
url, payload = await self._queue.get()
|
|
59
|
+
try:
|
|
60
|
+
await self._send_single(url, payload)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.exception(f"Unexpected error in webhook worker: {e}")
|
|
63
|
+
finally:
|
|
64
|
+
self._queue.task_done()
|
|
65
|
+
except CancelledError:
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
async def _send_single(self, url: str, payload: WebhookPayload) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Sends a webhook payload to the specified URL with retries.
|
|
71
|
+
Returns True if successful, False otherwise.
|
|
72
|
+
"""
|
|
73
|
+
data = asdict(payload)
|
|
74
|
+
for attempt in range(1, self.max_retries + 1):
|
|
75
|
+
try:
|
|
76
|
+
async with self.session.post(url, json=data, timeout=self.timeout) as response:
|
|
77
|
+
if 200 <= response.status < 300:
|
|
78
|
+
logger.info(f"Webhook sent successfully to {url} for job {payload.job_id}")
|
|
79
|
+
return True
|
|
80
|
+
else:
|
|
81
|
+
logger.warning(
|
|
82
|
+
f"Webhook failed for job {payload.job_id} to {url}. "
|
|
83
|
+
f"Status: {response.status}. Attempt {attempt}/{self.max_retries}"
|
|
84
|
+
)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.warning(
|
|
87
|
+
f"Error sending webhook for job {payload.job_id} to {url}: {e}. "
|
|
88
|
+
f"Attempt {attempt}/{self.max_retries}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Exponential backoff
|
|
92
|
+
if attempt < self.max_retries:
|
|
93
|
+
await sleep(2**attempt)
|
|
94
|
+
|
|
95
|
+
logger.error(f"Failed to send webhook for job {payload.job_id} to {url} after {self.max_retries} attempts.")
|
|
96
|
+
return False
|
avtomatika/watcher.py
CHANGED
|
@@ -26,48 +26,43 @@ class Watcher:
|
|
|
26
26
|
self._running = True
|
|
27
27
|
while self._running:
|
|
28
28
|
try:
|
|
29
|
-
await sleep(self.watch_interval_seconds)
|
|
30
|
-
|
|
31
29
|
# Attempt to acquire distributed lock
|
|
32
|
-
# We set TTL slightly longer than the expected execution time
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
continue
|
|
30
|
+
# We set TTL slightly longer than the expected execution time (60s)
|
|
31
|
+
if await self.storage.acquire_lock("global_watcher_lock", self._instance_id, 60):
|
|
32
|
+
try:
|
|
33
|
+
logger.debug("Watcher running check for timed out jobs...")
|
|
34
|
+
timed_out_job_ids = await self.storage.get_timed_out_jobs(limit=100)
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
for job_id in timed_out_job_ids:
|
|
37
|
+
logger.warning(f"Job {job_id} timed out. Moving to failed state.")
|
|
38
|
+
try:
|
|
39
|
+
# Get the latest version to avoid overwriting
|
|
40
|
+
job_state = await self.storage.get_job_state(job_id)
|
|
41
|
+
if job_state and job_state["status"] == "waiting_for_worker":
|
|
42
|
+
job_state["status"] = "failed"
|
|
43
|
+
job_state["error_message"] = "Worker task timed out."
|
|
44
|
+
await self.storage.save_job_state(job_id, job_state)
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
# Get the latest version to avoid overwriting
|
|
47
|
-
job_state = await self.storage.get_job_state(job_id)
|
|
48
|
-
if job_state and job_state["status"] == "waiting_for_worker":
|
|
49
|
-
job_state["status"] = "failed"
|
|
50
|
-
job_state["error_message"] = "Worker task timed out."
|
|
51
|
-
await self.storage.save_job_state(job_id, job_state)
|
|
46
|
+
# Increment the metric
|
|
47
|
+
from . import metrics
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
metrics.jobs_failed_total.inc(
|
|
50
|
+
{
|
|
51
|
+
metrics.LABEL_BLUEPRINT: job_state.get(
|
|
52
|
+
"blueprint_name",
|
|
53
|
+
"unknown",
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
except Exception:
|
|
58
|
+
logger.exception(
|
|
59
|
+
f"Failed to update state for timed out job {job_id}",
|
|
63
60
|
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Always release the lock so we (or others) can run next time
|
|
70
|
-
await self.storage.release_lock("global_watcher_lock", self._instance_id)
|
|
61
|
+
finally:
|
|
62
|
+
# Always release the lock so we (or others) can run next time
|
|
63
|
+
await self.storage.release_lock("global_watcher_lock", self._instance_id)
|
|
64
|
+
else:
|
|
65
|
+
logger.debug("Watcher lock held by another instance. Skipping check.")
|
|
71
66
|
|
|
72
67
|
except CancelledError:
|
|
73
68
|
logger.info("Watcher received cancellation request.")
|
|
@@ -75,7 +70,8 @@ class Watcher:
|
|
|
75
70
|
except Exception:
|
|
76
71
|
logger.exception("Error in Watcher main loop.")
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
# Sleep at the end of iteration
|
|
74
|
+
await sleep(self.watch_interval_seconds)
|
|
79
75
|
|
|
80
76
|
def stop(self):
|
|
81
77
|
"""Stops the watcher."""
|
avtomatika/ws_manager.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from asyncio import Lock
|
|
2
2
|
from logging import getLogger
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
from aiohttp import web
|
|
5
6
|
|
|
@@ -9,11 +10,11 @@ logger = getLogger(__name__)
|
|
|
9
10
|
class WebSocketManager:
|
|
10
11
|
"""Manages active WebSocket connections from workers."""
|
|
11
12
|
|
|
12
|
-
def __init__(self):
|
|
13
|
+
def __init__(self) -> None:
|
|
13
14
|
self._connections: dict[str, web.WebSocketResponse] = {}
|
|
14
15
|
self._lock = Lock()
|
|
15
16
|
|
|
16
|
-
async def register(self, worker_id: str, ws: web.WebSocketResponse):
|
|
17
|
+
async def register(self, worker_id: str, ws: web.WebSocketResponse) -> None:
|
|
17
18
|
"""Registers a new WebSocket connection for a worker."""
|
|
18
19
|
async with self._lock:
|
|
19
20
|
if worker_id in self._connections:
|
|
@@ -22,14 +23,14 @@ class WebSocketManager:
|
|
|
22
23
|
self._connections[worker_id] = ws
|
|
23
24
|
logger.info(f"WebSocket connection registered for worker {worker_id}.")
|
|
24
25
|
|
|
25
|
-
async def unregister(self, worker_id: str):
|
|
26
|
+
async def unregister(self, worker_id: str) -> None:
|
|
26
27
|
"""Unregisters a WebSocket connection."""
|
|
27
28
|
async with self._lock:
|
|
28
29
|
if worker_id in self._connections:
|
|
29
30
|
del self._connections[worker_id]
|
|
30
31
|
logger.info(f"WebSocket connection for worker {worker_id} unregistered.")
|
|
31
32
|
|
|
32
|
-
async def send_command(self, worker_id: str, command: dict):
|
|
33
|
+
async def send_command(self, worker_id: str, command: dict[str, Any]) -> bool:
|
|
33
34
|
"""Sends a JSON command to a specific worker."""
|
|
34
35
|
async with self._lock:
|
|
35
36
|
connection = self._connections.get(worker_id)
|
|
@@ -46,7 +47,7 @@ class WebSocketManager:
|
|
|
46
47
|
return False
|
|
47
48
|
|
|
48
49
|
@staticmethod
|
|
49
|
-
async def handle_message(worker_id: str, message: dict):
|
|
50
|
+
async def handle_message(worker_id: str, message: dict[str, Any]) -> None:
|
|
50
51
|
"""Handles an incoming message from a worker."""
|
|
51
52
|
event_type = message.get("event")
|
|
52
53
|
if event_type == "progress_update":
|
|
@@ -59,7 +60,7 @@ class WebSocketManager:
|
|
|
59
60
|
else:
|
|
60
61
|
logger.debug(f"Received unhandled event from worker {worker_id}: {event_type}")
|
|
61
62
|
|
|
62
|
-
async def close_all(self):
|
|
63
|
+
async def close_all(self) -> None:
|
|
63
64
|
"""Closes all active WebSocket connections."""
|
|
64
65
|
async with self._lock:
|
|
65
66
|
logger.info(f"Closing {len(self._connections)} active WebSocket connections...")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avtomatika
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.0b8
|
|
4
4
|
Summary: A state-machine based orchestrator for long-running AI and other jobs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/avtomatika-ai/avtomatika
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/avtomatika-ai/avtomatika/issues
|
|
@@ -12,7 +12,6 @@ Requires-Python: >=3.11
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: aiohttp~=3.12
|
|
15
|
-
Requires-Dist: aiocache~=0.12
|
|
16
15
|
Requires-Dist: python-json-logger~=4.0
|
|
17
16
|
Requires-Dist: graphviz~=0.21
|
|
18
17
|
Requires-Dist: zstandard~=0.24
|
|
@@ -21,6 +20,9 @@ Requires-Dist: msgpack~=1.1
|
|
|
21
20
|
Requires-Dist: orjson~=3.11
|
|
22
21
|
Provides-Extra: redis
|
|
23
22
|
Requires-Dist: redis~=7.1; extra == "redis"
|
|
23
|
+
Provides-Extra: s3
|
|
24
|
+
Requires-Dist: obstore>=0.2; extra == "s3"
|
|
25
|
+
Requires-Dist: aiofiles~=23.2; extra == "s3"
|
|
24
26
|
Provides-Extra: history
|
|
25
27
|
Requires-Dist: aiosqlite~=0.22; extra == "history"
|
|
26
28
|
Requires-Dist: asyncpg~=0.30; extra == "history"
|
|
@@ -38,10 +40,13 @@ Requires-Dist: pytest-mock~=3.14; extra == "test"
|
|
|
38
40
|
Requires-Dist: aioresponses~=0.7; extra == "test"
|
|
39
41
|
Requires-Dist: backports.zstd~=1.2; extra == "test"
|
|
40
42
|
Requires-Dist: opentelemetry-instrumentation-aiohttp-client; extra == "test"
|
|
43
|
+
Requires-Dist: obstore>=0.2; extra == "test"
|
|
44
|
+
Requires-Dist: aiofiles~=23.2; extra == "test"
|
|
41
45
|
Provides-Extra: all
|
|
42
46
|
Requires-Dist: avtomatika[redis]; extra == "all"
|
|
43
47
|
Requires-Dist: avtomatika[history]; extra == "all"
|
|
44
48
|
Requires-Dist: avtomatika[telemetry]; extra == "all"
|
|
49
|
+
Requires-Dist: avtomatika[s3]; extra == "all"
|
|
45
50
|
Dynamic: license-file
|
|
46
51
|
|
|
47
52
|
# Avtomatika Orchestrator
|
|
@@ -61,6 +66,8 @@ This document serves as a comprehensive guide for developers looking to build pi
|
|
|
61
66
|
- [Parallel Execution and Aggregation (Fan-out/Fan-in)](#parallel-execution-and-aggregation-fan-outfan-in)
|
|
62
67
|
- [Dependency Injection (DataStore)](#dependency-injection-datastore)
|
|
63
68
|
- [Native Scheduler](#native-scheduler)
|
|
69
|
+
- [S3 Payload Offloading](#s3-payload-offloading)
|
|
70
|
+
- [Webhook Notifications](#webhook-notifications)
|
|
64
71
|
- [Production Configuration](#production-configuration)
|
|
65
72
|
- [Fault Tolerance](#fault-tolerance)
|
|
66
73
|
- [Storage Backend](#storage-backend)
|
|
@@ -107,6 +114,11 @@ Avtomatika is part of a larger ecosystem:
|
|
|
107
114
|
pip install "avtomatika[telemetry]"
|
|
108
115
|
```
|
|
109
116
|
|
|
117
|
+
* **Install with S3 support (Payload Offloading):**
|
|
118
|
+
```bash
|
|
119
|
+
pip install "avtomatika[s3]"
|
|
120
|
+
```
|
|
121
|
+
|
|
110
122
|
* **Install all dependencies, including for testing:**
|
|
111
123
|
```bash
|
|
112
124
|
pip install "avtomatika[all,test]"
|
|
@@ -157,7 +169,13 @@ async def end_handler(context):
|
|
|
157
169
|
engine = OrchestratorEngine(storage, config)
|
|
158
170
|
engine.register_blueprint(my_blueprint)
|
|
159
171
|
|
|
160
|
-
# 4.
|
|
172
|
+
# 4. Accessing Components (Optional)
|
|
173
|
+
# You can access the internal aiohttp app and core components using AppKeys
|
|
174
|
+
# from avtomatika.app_keys import ENGINE_KEY, DISPATCHER_KEY
|
|
175
|
+
# app = engine.app
|
|
176
|
+
# dispatcher = app[DISPATCHER_KEY]
|
|
177
|
+
|
|
178
|
+
# 5. Define the main entrypoint to run the server
|
|
161
179
|
async def main():
|
|
162
180
|
await engine.start()
|
|
163
181
|
|
|
@@ -244,6 +262,19 @@ async def publish_handler_old_style(context):
|
|
|
244
262
|
print(f"Job {context.job_id}: Publishing video at {output_path} ({duration}s).")
|
|
245
263
|
context.actions.transition_to("complete")
|
|
246
264
|
```
|
|
265
|
+
## Key Concepts: JobContext and Actions
|
|
266
|
+
|
|
267
|
+
### High Performance Architecture
|
|
268
|
+
|
|
269
|
+
Avtomatika is engineered for high-load environments with thousands of concurrent workers.
|
|
270
|
+
|
|
271
|
+
* **O(1) Dispatcher**: Uses advanced Redis Set intersections to find suitable workers instantly, regardless of the cluster size. No O(N) scanning.
|
|
272
|
+
* **Non-Blocking I/O**:
|
|
273
|
+
* **Webhooks**: Sent via a bounded background queue to prevent backpressure.
|
|
274
|
+
* **History Logging**: Writes to SQL databases are buffered and asynchronous, ensuring the main execution loop never blocks.
|
|
275
|
+
* **Redis Streams**: Uses blocking reads to eliminate busy-waiting and reduce CPU usage.
|
|
276
|
+
* **Memory Safety**: S3 file transfers use streaming to handle multi-gigabyte files with constant, low RAM usage.
|
|
277
|
+
|
|
247
278
|
## Blueprint Cookbook: Key Features
|
|
248
279
|
|
|
249
280
|
### 1. Conditional Transitions (`.when()`)
|
|
@@ -355,6 +386,63 @@ blueprint = "backup_flow"
|
|
|
355
386
|
daily_at = "02:00"
|
|
356
387
|
```
|
|
357
388
|
|
|
389
|
+
### 6. Webhook Notifications
|
|
390
|
+
|
|
391
|
+
The orchestrator can send asynchronous notifications to an external system when a job completes, fails, or is quarantined. This eliminates the need for clients to constantly poll the API for status updates.
|
|
392
|
+
|
|
393
|
+
### 7. S3 Payload Offloading
|
|
394
|
+
|
|
395
|
+
Orchestrator provides first-class support for handling large files via S3-compatible storage, powered by the high-performance `obstore` library (Rust bindings).
|
|
396
|
+
|
|
397
|
+
* **Memory Safe (Streaming)**: Uses streaming for uploads and downloads, allowing processing of files larger than available RAM without OOM errors.
|
|
398
|
+
* **Managed Mode**: The Orchestrator manages file lifecycle (automatic cleanup of S3 objects and local temporary files on job completion).
|
|
399
|
+
* **Dependency Injection**: Use the `task_files` argument in your handlers to easily read/write data.
|
|
400
|
+
* **Directory Support**: Supports recursive download and upload of entire directories.
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
@bp.handler_for("process_data")
|
|
404
|
+
async def process_data(task_files, actions):
|
|
405
|
+
# Streaming download of a large file
|
|
406
|
+
local_path = await task_files.download("large_dataset.csv")
|
|
407
|
+
|
|
408
|
+
# ... process data ...
|
|
409
|
+
|
|
410
|
+
# Upload results
|
|
411
|
+
await task_files.write_json("results.json", {"status": "done"})
|
|
412
|
+
|
|
413
|
+
actions.transition_to("finished")
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Production Configuration
|
|
417
|
+
* **Events:**
|
|
418
|
+
* `job_finished`: The job reached a final success state.
|
|
419
|
+
* `job_failed`: The job failed (e.g., due to an error or invalid input).
|
|
420
|
+
* `job_quarantined`: The job was moved to quarantine after repeated failures.
|
|
421
|
+
|
|
422
|
+
**Example Request:**
|
|
423
|
+
```json
|
|
424
|
+
POST /api/v1/jobs/my_flow
|
|
425
|
+
{
|
|
426
|
+
"initial_data": {
|
|
427
|
+
"video_url": "..."
|
|
428
|
+
},
|
|
429
|
+
"webhook_url": "https://my-app.com/webhooks/avtomatika"
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Example Webhook Payload:**
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"event": "job_finished",
|
|
437
|
+
"job_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
438
|
+
"status": "finished",
|
|
439
|
+
"result": {
|
|
440
|
+
"output_path": "/videos/result.mp4"
|
|
441
|
+
},
|
|
442
|
+
"error": null
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
358
446
|
## Production Configuration
|
|
359
447
|
|
|
360
448
|
The orchestrator's behavior can be configured through environment variables. Additionally, any configuration parameter loaded from environment variables can be programmatically overridden in your application code after the `Config` object has been initialized. This provides flexibility for different deployment and testing scenarios.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
avtomatika/__init__.py,sha256=D5r3L-H06uxsY_wgfh7u9YR29QvZMer1BlvzjW9Umfo,701
|
|
2
|
+
avtomatika/api.html,sha256=RLx-D1uFCSAXIf_2WgFlSTWrWPcmonNYM-9oNanKXBg,32835
|
|
3
|
+
avtomatika/app_keys.py,sha256=XL9Q1Ef5JYa_JG8LM3jzb3cOQjnKaM6ypwqXWnUKeDU,1212
|
|
4
|
+
avtomatika/blueprint.py,sha256=AJ8hZkzvqkdu9aPmlJeFzuSj57L6Xm41KS9kBMGB4Vg,11827
|
|
5
|
+
avtomatika/client_config_loader.py,sha256=zVVHZlxSqZUaNpZ4zoU0T1CFYXdxy-3vKSmPcaFuHSY,2772
|
|
6
|
+
avtomatika/compression.py,sha256=bhA1kw4YrCR3I3kdquZSY0fAzCrRrjtz55uepzLUDKI,2498
|
|
7
|
+
avtomatika/config.py,sha256=zqaZrt44Zd_ppvWPDkSth1nJydhAsK0jQLDo0QIW77A,3103
|
|
8
|
+
avtomatika/constants.py,sha256=WL58Nh-EY6baM9Ur_tR9merwPRGb41_klUG2V-yUUaA,963
|
|
9
|
+
avtomatika/context.py,sha256=T6Ux4Fb1DwWRGTpMNeukM51MQDQbGk2HS6Cwpc0dc1s,4248
|
|
10
|
+
avtomatika/data_types.py,sha256=nm9pLhLLx8fngjvAE8d3nQZkD8Y3Q28RHch9kjYAG2Y,1447
|
|
11
|
+
avtomatika/datastore.py,sha256=gJjhZ5kxjF8pmbbPQb_qu3HPUpfy2c6T75KZ-smb_zg,545
|
|
12
|
+
avtomatika/dispatcher.py,sha256=CKqniknJZe7gjY7QmGlODJc5X8B6qWjtr2UxRivUbuY,8603
|
|
13
|
+
avtomatika/engine.py,sha256=VUw5s65Q403a1JgkRtF1KeSiVx1W6MFmukxZpYN-f00,16175
|
|
14
|
+
avtomatika/executor.py,sha256=K7RGGxWz_4n6q0m8tW0oNtUJm90b-4O2U9lTB_yLX-k,24461
|
|
15
|
+
avtomatika/health_checker.py,sha256=jXYSH4BPeZ4LCxSZV4uXM4BZhGJYgpoAOWQXE8yojLo,2078
|
|
16
|
+
avtomatika/logging_config.py,sha256=Zb6f9Nri9WVWhlpuBg6Lpi5SWRLGIUmS8Dc3xD1Gg0g,2993
|
|
17
|
+
avtomatika/metrics.py,sha256=tiksK1fFSOMlz8zFu6GT19JTduvxMTNlLu0QFrTHoQI,1866
|
|
18
|
+
avtomatika/py.typed,sha256=CT_L7gw2MLcQY-X0vs-xB5Vr0wzvGo7GuQYPI_qwJE8,65
|
|
19
|
+
avtomatika/quota.py,sha256=DNcaL6k0J1REeP8sVqbY9FprY_3BSr2SxM2Vf4mEqdw,1612
|
|
20
|
+
avtomatika/ratelimit.py,sha256=hFGW5oN9G6_W_jnHmopXW8bRjjzlvanY19MLghsNLE8,1306
|
|
21
|
+
avtomatika/reputation.py,sha256=pK-x9FrPN2Oc2gtPa1AZJHlhvkd7xlRe4orxM2auJJc,3979
|
|
22
|
+
avtomatika/s3.py,sha256=d-zyqdS0dV6K_92BbOH4Lo0Um0AvrMxfSFoD5QFPBSo,11941
|
|
23
|
+
avtomatika/scheduler.py,sha256=F5Kv5Rx34nDd0mE5jxjwpjRg8duDZBEr91N5Y6CNR24,4231
|
|
24
|
+
avtomatika/scheduler_config_loader.py,sha256=F6mLM8yPRgG4bMHV_WnXX7UOrXD8fCXJT30bbEuQ2mk,1311
|
|
25
|
+
avtomatika/security.py,sha256=kkU68YmLWq1ClMUdEW98pS9WsEwHinHoZcdMoPm63Uk,4417
|
|
26
|
+
avtomatika/telemetry.py,sha256=ZBt1_xJ36PzDSz-zdCXeNp58NiezUgbqvMctTG25PT0,2352
|
|
27
|
+
avtomatika/watcher.py,sha256=-oqyUKq4WYtFYe0PjfDkAjjTUxpyN79JTnOzwlg-Z84,3467
|
|
28
|
+
avtomatika/worker_config_loader.py,sha256=n0j8gfuJDacWONr8744RsHTCWpc_1ZTRMC-rJZh6P6A,2249
|
|
29
|
+
avtomatika/ws_manager.py,sha256=fjIGWZF_L5j5QEpTdNT2Qz4N5ut9pjKlgEKI0sY2GOc,3148
|
|
30
|
+
avtomatika/api/handlers.py,sha256=MO6QkbT001jj4latUXGT0hGOmQf_6TkRkmwx19OcXeQ,22176
|
|
31
|
+
avtomatika/api/routes.py,sha256=vSwj2jJlmftZrjrctt-mNYLF23CfCtlUfaMoZzNOqCk,4895
|
|
32
|
+
avtomatika/history/base.py,sha256=RsCvCkHK1teHjXSk9ZHVEtpQlIjz8kWsfKYHVnapf6c,3848
|
|
33
|
+
avtomatika/history/noop.py,sha256=hLzt0RblsrKUtoyQNauOni6jCi-IYCWEPsiR0vh7tho,1226
|
|
34
|
+
avtomatika/history/postgres.py,sha256=T0XpDurnh48pPI-2JhB285GdNIexNkCSu8ExhLJzcxc,9538
|
|
35
|
+
avtomatika/history/sqlite.py,sha256=txWax9RVzBQzIZuU-SjHnEXEzBmGzIjqzoVsK2oyiAQ,9252
|
|
36
|
+
avtomatika/storage/__init__.py,sha256=mGRj_40dWZ7R7uYbqC6gCsUWCKHAbZz4ZVIhYg5dT_E,262
|
|
37
|
+
avtomatika/storage/base.py,sha256=l6ppbvbIshb5cD0yByDAeCSePBWp05iuaovbi4gaWww,12939
|
|
38
|
+
avtomatika/storage/memory.py,sha256=cZRVHqXRn3Gm06jJirBOk2IgB-ocqaJQSmzUEsCmK-M,14009
|
|
39
|
+
avtomatika/storage/redis.py,sha256=WrUbDq1wC9fpFjMM0IqkbYAvmwm9-s3DmEFo8sO18FA,18664
|
|
40
|
+
avtomatika/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
avtomatika/utils/webhook_sender.py,sha256=4Ud2Tz18yV4kzfGQyDO2by8s2EIEwHkv1vtIUis9kVQ,3606
|
|
42
|
+
avtomatika-1.0b8.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
|
|
43
|
+
avtomatika-1.0b8.dist-info/METADATA,sha256=AV7TG3pNVxvHyj7eyS5OTo7BXrBigb4mFCfJvtZqzTM,26382
|
|
44
|
+
avtomatika-1.0b8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
45
|
+
avtomatika-1.0b8.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
|
|
46
|
+
avtomatika-1.0b8.dist-info/RECORD,,
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
avtomatika/__init__.py,sha256=D5r3L-H06uxsY_wgfh7u9YR29QvZMer1BlvzjW9Umfo,701
|
|
2
|
-
avtomatika/api.html,sha256=RLx-D1uFCSAXIf_2WgFlSTWrWPcmonNYM-9oNanKXBg,32835
|
|
3
|
-
avtomatika/blueprint.py,sha256=jU1Un9yiIUbZ9I7k20XyAhnMbXyrnkC0AakMX3GbK6k,9207
|
|
4
|
-
avtomatika/client_config_loader.py,sha256=zVVHZlxSqZUaNpZ4zoU0T1CFYXdxy-3vKSmPcaFuHSY,2772
|
|
5
|
-
avtomatika/compression.py,sha256=bhA1kw4YrCR3I3kdquZSY0fAzCrRrjtz55uepzLUDKI,2498
|
|
6
|
-
avtomatika/config.py,sha256=Tc-vpaQS11i_JTa1pQjGuQD3R5Kj9sIf6gGJGjItBBo,2487
|
|
7
|
-
avtomatika/constants.py,sha256=WL58Nh-EY6baM9Ur_tR9merwPRGb41_klUG2V-yUUaA,963
|
|
8
|
-
avtomatika/context.py,sha256=MT_RMMxSCAVEhlo5HUQdQ1uaR86D9d4s_3nrd6O5xAg,4241
|
|
9
|
-
avtomatika/data_types.py,sha256=VWWbTcJaQt43JaD09qZL1spwNdzCR9vPXVthlM133gM,1362
|
|
10
|
-
avtomatika/datastore.py,sha256=gJjhZ5kxjF8pmbbPQb_qu3HPUpfy2c6T75KZ-smb_zg,545
|
|
11
|
-
avtomatika/dispatcher.py,sha256=Tsye9zOcLN2c7O0AalbX9Il5l3XdT64UDV7iQKiLIJE,9627
|
|
12
|
-
avtomatika/engine.py,sha256=fSCalpR3-j-ATmDE5XMYjz1PSubVcrbeMFWGX_NRI8g,41883
|
|
13
|
-
avtomatika/executor.py,sha256=-SKqLs-DVtMw5P7bZInK57f0M4zRFoVFr1fU5MUrtiY,22543
|
|
14
|
-
avtomatika/health_checker.py,sha256=WXwvRJ-3cZC2Udc_ogsyIQp7VzcvJjq_IaqzkTdE0TE,1265
|
|
15
|
-
avtomatika/logging_config.py,sha256=Zb6f9Nri9WVWhlpuBg6Lpi5SWRLGIUmS8Dc3xD1Gg0g,2993
|
|
16
|
-
avtomatika/metrics.py,sha256=7XDhr_xMJ9JpElpZmBG7R0ml7AMdAp9UYp_W-i7tyLg,1858
|
|
17
|
-
avtomatika/py.typed,sha256=CT_L7gw2MLcQY-X0vs-xB5Vr0wzvGo7GuQYPI_qwJE8,65
|
|
18
|
-
avtomatika/quota.py,sha256=DNcaL6k0J1REeP8sVqbY9FprY_3BSr2SxM2Vf4mEqdw,1612
|
|
19
|
-
avtomatika/ratelimit.py,sha256=hFGW5oN9G6_W_jnHmopXW8bRjjzlvanY19MLghsNLE8,1306
|
|
20
|
-
avtomatika/reputation.py,sha256=IHcaIAILWZftPPmXj5En28OSDNK7U8ivQ-w30zIF8fk,3748
|
|
21
|
-
avtomatika/scheduler.py,sha256=D-qpoGSA_Jqq1R7GlJNma9tssCujNwkOnWcRbbCHTnk,4138
|
|
22
|
-
avtomatika/scheduler_config_loader.py,sha256=F6mLM8yPRgG4bMHV_WnXX7UOrXD8fCXJT30bbEuQ2mk,1311
|
|
23
|
-
avtomatika/security.py,sha256=kkU68YmLWq1ClMUdEW98pS9WsEwHinHoZcdMoPm63Uk,4417
|
|
24
|
-
avtomatika/telemetry.py,sha256=ZBt1_xJ36PzDSz-zdCXeNp58NiezUgbqvMctTG25PT0,2352
|
|
25
|
-
avtomatika/watcher.py,sha256=IHaqSqp3XSGXjRY-LEeTG9BJpq2nqJSnmjY_Vdvk3jo,3493
|
|
26
|
-
avtomatika/worker_config_loader.py,sha256=n0j8gfuJDacWONr8744RsHTCWpc_1ZTRMC-rJZh6P6A,2249
|
|
27
|
-
avtomatika/ws_manager.py,sha256=pi5xe0ivsCjRZw08ri5N-gAChMH2I2YPLpl3E2tP89k,3057
|
|
28
|
-
avtomatika/history/base.py,sha256=Gfw0Gb4Mt9wQrMlYLugZwey_6-cDej5OUctiMTCWg7Q,1668
|
|
29
|
-
avtomatika/history/noop.py,sha256=ETVtPiTfkaMpzhGD8c0_4Iu6pWD89dnPrrRrSIjmc8s,970
|
|
30
|
-
avtomatika/history/postgres.py,sha256=vtW4LMW7Vli5MjcGYY3ez667-C8Cq3I7kIHrcEgSYps,9409
|
|
31
|
-
avtomatika/history/sqlite.py,sha256=Blc9ckvzoDaMRStXyfJOzMAdU_t2JcwtQtVdPgnr6s0,9131
|
|
32
|
-
avtomatika/storage/__init__.py,sha256=mGRj_40dWZ7R7uYbqC6gCsUWCKHAbZz4ZVIhYg5dT_E,262
|
|
33
|
-
avtomatika/storage/base.py,sha256=hW7XFhe6CQDP69q5NPSkUzEInIFxDR1-AyRPZNPEDEc,11424
|
|
34
|
-
avtomatika/storage/memory.py,sha256=Px_ISqZz5Nhl5wLvGpMqY21-eKaLfkzgof7jquCmHaM,12234
|
|
35
|
-
avtomatika/storage/redis.py,sha256=opOhqBL_uCsNXcMD_W_tJU-8wzDUSjBJWEsXrwP2_YM,21035
|
|
36
|
-
avtomatika-1.0b6.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
|
|
37
|
-
avtomatika-1.0b6.dist-info/METADATA,sha256=kBfLgOgvd1e06JZ_l6TJwsyAHi81jLFnqZ03QoZbISA,22986
|
|
38
|
-
avtomatika-1.0b6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
-
avtomatika-1.0b6.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
|
|
40
|
-
avtomatika-1.0b6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|