avtomatika 1.0b5__py3-none-any.whl → 1.0b7__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 +32 -0
- avtomatika/blueprint.py +125 -54
- avtomatika/config.py +4 -0
- avtomatika/constants.py +30 -0
- avtomatika/context.py +2 -2
- avtomatika/data_types.py +3 -2
- avtomatika/dispatcher.py +1 -1
- avtomatika/engine.py +103 -577
- avtomatika/executor.py +21 -16
- avtomatika/history/postgres.py +56 -13
- avtomatika/history/sqlite.py +54 -34
- avtomatika/logging_config.py +58 -7
- avtomatika/scheduler.py +119 -0
- avtomatika/scheduler_config_loader.py +41 -0
- avtomatika/security.py +3 -5
- avtomatika/storage/base.py +17 -3
- avtomatika/storage/memory.py +50 -8
- avtomatika/storage/redis.py +17 -0
- avtomatika/utils/__init__.py +0 -0
- avtomatika/utils/webhook_sender.py +54 -0
- avtomatika/watcher.py +1 -3
- {avtomatika-1.0b5.dist-info → avtomatika-1.0b7.dist-info}/METADATA +77 -4
- avtomatika-1.0b7.dist-info/RECORD +45 -0
- avtomatika-1.0b5.dist-info/RECORD +0 -37
- {avtomatika-1.0b5.dist-info → avtomatika-1.0b7.dist-info}/WHEEL +0 -0
- {avtomatika-1.0b5.dist-info → avtomatika-1.0b7.dist-info}/licenses/LICENSE +0 -0
- {avtomatika-1.0b5.dist-info → avtomatika-1.0b7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from tomllib import load
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ScheduledJobConfig:
|
|
8
|
+
name: str
|
|
9
|
+
blueprint: str
|
|
10
|
+
input_data: dict[str, Any]
|
|
11
|
+
interval_seconds: int | None = None
|
|
12
|
+
daily_at: str | None = None
|
|
13
|
+
weekly_days: list[str] | None = None
|
|
14
|
+
monthly_dates: list[int] | None = None
|
|
15
|
+
time: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_schedules_from_file(file_path: str) -> list[ScheduledJobConfig]:
|
|
19
|
+
"""Loads scheduled job configurations from a TOML file."""
|
|
20
|
+
with open(file_path, "rb") as f:
|
|
21
|
+
data = load(f)
|
|
22
|
+
|
|
23
|
+
schedules = []
|
|
24
|
+
for name, config in data.items():
|
|
25
|
+
# Skip sections that might be metadata (though TOML structure usually implies all top-level keys are jobs)
|
|
26
|
+
if not isinstance(config, dict):
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
schedules.append(
|
|
30
|
+
ScheduledJobConfig(
|
|
31
|
+
name=name,
|
|
32
|
+
blueprint=config.get("blueprint"),
|
|
33
|
+
input_data=config.get("input_data", {}),
|
|
34
|
+
interval_seconds=config.get("interval_seconds"),
|
|
35
|
+
daily_at=config.get("daily_at"),
|
|
36
|
+
weekly_days=config.get("weekly_days"),
|
|
37
|
+
monthly_dates=config.get("monthly_dates"),
|
|
38
|
+
time=config.get("time"),
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
return schedules
|
avtomatika/security.py
CHANGED
|
@@ -4,11 +4,9 @@ from typing import Any, Awaitable, Callable
|
|
|
4
4
|
from aiohttp import web
|
|
5
5
|
|
|
6
6
|
from .config import Config
|
|
7
|
+
from .constants import AUTH_HEADER_CLIENT, AUTH_HEADER_WORKER
|
|
7
8
|
from .storage.base import StorageBackend
|
|
8
9
|
|
|
9
|
-
AUTH_HEADER_AVTOMATIKA = "X-Avtomatika-Token"
|
|
10
|
-
AUTH_HEADER_WORKER = "X-Worker-Token"
|
|
11
|
-
|
|
12
10
|
Handler = Callable[[web.Request], Awaitable[web.Response]]
|
|
13
11
|
|
|
14
12
|
|
|
@@ -21,10 +19,10 @@ def client_auth_middleware_factory(
|
|
|
21
19
|
|
|
22
20
|
@web.middleware
|
|
23
21
|
async def middleware(request: web.Request, handler: Handler) -> web.Response:
|
|
24
|
-
token = request.headers.get(
|
|
22
|
+
token = request.headers.get(AUTH_HEADER_CLIENT)
|
|
25
23
|
if not token:
|
|
26
24
|
return web.json_response(
|
|
27
|
-
{"error": "Missing
|
|
25
|
+
{"error": f"Missing {AUTH_HEADER_CLIENT} header"},
|
|
28
26
|
status=401,
|
|
29
27
|
)
|
|
30
28
|
|
avtomatika/storage/base.py
CHANGED
|
@@ -269,13 +269,27 @@ class StorageBackend(ABC):
|
|
|
269
269
|
"""Completely clears the storage. Used mainly for tests."""
|
|
270
270
|
raise NotImplementedError
|
|
271
271
|
|
|
272
|
-
@abstractmethod
|
|
273
272
|
async def get_active_worker_count(self) -> int:
|
|
274
|
-
"""
|
|
275
|
-
|
|
273
|
+
"""Returns the number of currently active workers."""
|
|
274
|
+
raise NotImplementedError
|
|
275
|
+
|
|
276
|
+
async def set_nx_ttl(self, key: str, value: str, ttl: int) -> bool:
|
|
277
|
+
"""
|
|
278
|
+
Atomically sets key to value if it does not exist.
|
|
279
|
+
Sets a TTL (in seconds) on the key.
|
|
280
|
+
Returns True if set, False if already exists.
|
|
281
|
+
Critical for distributed locking.
|
|
276
282
|
"""
|
|
277
283
|
raise NotImplementedError
|
|
278
284
|
|
|
285
|
+
async def get_str(self, key: str) -> str | None:
|
|
286
|
+
"""Gets a simple string value from storage."""
|
|
287
|
+
raise NotImplementedError
|
|
288
|
+
|
|
289
|
+
async def set_str(self, key: str, value: str, ttl: int | None = None) -> None:
|
|
290
|
+
"""Sets a simple string value in storage with optional TTL."""
|
|
291
|
+
raise NotImplementedError
|
|
292
|
+
|
|
279
293
|
@abstractmethod
|
|
280
294
|
async def acquire_lock(self, key: str, holder_id: str, ttl: int) -> bool:
|
|
281
295
|
"""
|
avtomatika/storage/memory.py
CHANGED
|
@@ -33,6 +33,20 @@ class MemoryStorage(StorageBackend):
|
|
|
33
33
|
async with self._lock:
|
|
34
34
|
return self._jobs.get(job_id)
|
|
35
35
|
|
|
36
|
+
async def _clean_expired(self) -> None:
|
|
37
|
+
"""Helper to remove expired keys."""
|
|
38
|
+
now = monotonic()
|
|
39
|
+
|
|
40
|
+
expired_generic = [k for k, t in self._generic_key_ttls.items() if t < now]
|
|
41
|
+
for k in expired_generic:
|
|
42
|
+
self._generic_key_ttls.pop(k, None)
|
|
43
|
+
self._generic_keys.pop(k, None)
|
|
44
|
+
|
|
45
|
+
expired_workers = [k for k, t in self._worker_ttls.items() if t < now]
|
|
46
|
+
for k in expired_workers:
|
|
47
|
+
self._worker_ttls.pop(k, None)
|
|
48
|
+
self._workers.pop(k, None)
|
|
49
|
+
|
|
36
50
|
async def save_job_state(self, job_id: str, state: dict[str, Any]) -> None:
|
|
37
51
|
async with self._lock:
|
|
38
52
|
self._jobs[job_id] = state
|
|
@@ -88,8 +102,13 @@ class MemoryStorage(StorageBackend):
|
|
|
88
102
|
queue = self._worker_task_queues[worker_id]
|
|
89
103
|
|
|
90
104
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
# Type ignore because PriorityQueue.get() return type is generic
|
|
106
|
+
item = await wait_for(queue.get(), timeout=timeout) # type: ignore
|
|
107
|
+
_, task_payload = item
|
|
108
|
+
# Explicit cast for mypy
|
|
109
|
+
if isinstance(task_payload, dict):
|
|
110
|
+
return task_payload
|
|
111
|
+
return None # Should not happen if data integrity is kept
|
|
93
112
|
except AsyncTimeoutError:
|
|
94
113
|
return None
|
|
95
114
|
|
|
@@ -127,7 +146,7 @@ class MemoryStorage(StorageBackend):
|
|
|
127
146
|
async def get_available_workers(self) -> list[dict[str, Any]]:
|
|
128
147
|
async with self._lock:
|
|
129
148
|
now = monotonic()
|
|
130
|
-
active_workers = []
|
|
149
|
+
active_workers: list[dict[str, Any]] = []
|
|
131
150
|
active_workers.extend(
|
|
132
151
|
worker_info
|
|
133
152
|
for worker_id, worker_info in self._workers.items()
|
|
@@ -188,7 +207,7 @@ class MemoryStorage(StorageBackend):
|
|
|
188
207
|
|
|
189
208
|
self._generic_keys[key] += 1
|
|
190
209
|
self._generic_key_ttls[key] = now + ttl
|
|
191
|
-
return self._generic_keys[key]
|
|
210
|
+
return int(self._generic_keys[key])
|
|
192
211
|
|
|
193
212
|
async def save_client_config(self, token: str, config: dict[str, Any]) -> None:
|
|
194
213
|
async with self._lock:
|
|
@@ -209,7 +228,7 @@ class MemoryStorage(StorageBackend):
|
|
|
209
228
|
return True
|
|
210
229
|
return False
|
|
211
230
|
|
|
212
|
-
async def flush_all(self):
|
|
231
|
+
async def flush_all(self) -> None:
|
|
213
232
|
"""
|
|
214
233
|
Resets all in-memory storage containers to their initial empty state.
|
|
215
234
|
This is a destructive operation intended for use in tests to ensure
|
|
@@ -238,9 +257,32 @@ class MemoryStorage(StorageBackend):
|
|
|
238
257
|
|
|
239
258
|
async def get_active_worker_count(self) -> int:
|
|
240
259
|
async with self._lock:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
260
|
+
await self._clean_expired()
|
|
261
|
+
return len(self._workers)
|
|
262
|
+
|
|
263
|
+
async def set_nx_ttl(self, key: str, value: str, ttl: int) -> bool:
|
|
264
|
+
async with self._lock:
|
|
265
|
+
await self._clean_expired()
|
|
266
|
+
if key in self._generic_keys:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
self._generic_keys[key] = value
|
|
270
|
+
self._generic_key_ttls[key] = monotonic() + ttl
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
async def get_str(self, key: str) -> str | None:
|
|
274
|
+
async with self._lock:
|
|
275
|
+
await self._clean_expired()
|
|
276
|
+
val = self._generic_keys.get(key)
|
|
277
|
+
return str(val) if val is not None else None
|
|
278
|
+
|
|
279
|
+
async def set_str(self, key: str, value: str, ttl: int | None = None) -> None:
|
|
280
|
+
async with self._lock:
|
|
281
|
+
self._generic_keys[key] = value
|
|
282
|
+
if ttl:
|
|
283
|
+
self._generic_key_ttls[key] = monotonic() + ttl
|
|
284
|
+
else:
|
|
285
|
+
self._generic_key_ttls.pop(key, None)
|
|
244
286
|
|
|
245
287
|
async def get_worker_info(self, worker_id: str) -> dict[str, Any] | None:
|
|
246
288
|
async with self._lock:
|
avtomatika/storage/redis.py
CHANGED
|
@@ -446,6 +446,23 @@ class RedisStorage(StorageBackend):
|
|
|
446
446
|
count += 1
|
|
447
447
|
return count
|
|
448
448
|
|
|
449
|
+
async def set_nx_ttl(self, key: str, value: str, ttl: int) -> bool:
|
|
450
|
+
"""
|
|
451
|
+
Uses Redis SET command with NX (Not Exists) and EX (Expire) options.
|
|
452
|
+
"""
|
|
453
|
+
# redis.set returns True if set, None if not set (when nx=True)
|
|
454
|
+
result = await self._redis.set(key, value, nx=True, ex=ttl)
|
|
455
|
+
return bool(result)
|
|
456
|
+
|
|
457
|
+
async def get_str(self, key: str) -> str | None:
|
|
458
|
+
val = await self._redis.get(key)
|
|
459
|
+
if val is None:
|
|
460
|
+
return None
|
|
461
|
+
return val.decode("utf-8") if isinstance(val, bytes) else str(val)
|
|
462
|
+
|
|
463
|
+
async def set_str(self, key: str, value: str, ttl: int | None = None) -> None:
|
|
464
|
+
await self._redis.set(key, value, ex=ttl)
|
|
465
|
+
|
|
449
466
|
async def set_worker_token(self, worker_id: str, token: str):
|
|
450
467
|
"""Stores the individual token for a specific worker."""
|
|
451
468
|
key = f"orchestrator:worker:token:{worker_id}"
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from asyncio import sleep
|
|
2
|
+
from dataclasses import asdict, dataclass
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from aiohttp import ClientSession, ClientTimeout
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class WebhookPayload:
|
|
13
|
+
event: str # "job_finished", "job_failed", "job_quarantined"
|
|
14
|
+
job_id: str
|
|
15
|
+
status: str
|
|
16
|
+
result: dict[str, Any] | None = None
|
|
17
|
+
error: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WebhookSender:
|
|
21
|
+
def __init__(self, session: ClientSession):
|
|
22
|
+
self.session = session
|
|
23
|
+
self.timeout = ClientTimeout(total=10)
|
|
24
|
+
self.max_retries = 3
|
|
25
|
+
|
|
26
|
+
async def send(self, url: str, payload: WebhookPayload) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Sends a webhook payload to the specified URL with retries.
|
|
29
|
+
Returns True if successful, False otherwise.
|
|
30
|
+
"""
|
|
31
|
+
data = asdict(payload)
|
|
32
|
+
for attempt in range(1, self.max_retries + 1):
|
|
33
|
+
try:
|
|
34
|
+
async with self.session.post(url, json=data, timeout=self.timeout) as response:
|
|
35
|
+
if 200 <= response.status < 300:
|
|
36
|
+
logger.info(f"Webhook sent successfully to {url} for job {payload.job_id}")
|
|
37
|
+
return True
|
|
38
|
+
else:
|
|
39
|
+
logger.warning(
|
|
40
|
+
f"Webhook failed for job {payload.job_id} to {url}. "
|
|
41
|
+
f"Status: {response.status}. Attempt {attempt}/{self.max_retries}"
|
|
42
|
+
)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(
|
|
45
|
+
f"Error sending webhook for job {payload.job_id} to {url}: {e}. "
|
|
46
|
+
f"Attempt {attempt}/{self.max_retries}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Exponential backoff
|
|
50
|
+
if attempt < self.max_retries:
|
|
51
|
+
await sleep(2**attempt)
|
|
52
|
+
|
|
53
|
+
logger.error(f"Failed to send webhook for job {payload.job_id} to {url} after {self.max_retries} attempts.")
|
|
54
|
+
return False
|
avtomatika/watcher.py
CHANGED
|
@@ -29,9 +29,7 @@ class Watcher:
|
|
|
29
29
|
await sleep(self.watch_interval_seconds)
|
|
30
30
|
|
|
31
31
|
# Attempt to acquire distributed lock
|
|
32
|
-
# We set TTL slightly longer than the expected execution time
|
|
33
|
-
# but shorter than the interval if possible.
|
|
34
|
-
# Actually, a fixed TTL like 60s is fine as long as we release it.
|
|
32
|
+
# We set TTL slightly longer than the expected execution time (60s)
|
|
35
33
|
if not await self.storage.acquire_lock("global_watcher_lock", self._instance_id, 60):
|
|
36
34
|
logger.debug("Watcher lock held by another instance. Skipping check.")
|
|
37
35
|
continue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avtomatika
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.0b7
|
|
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
|
|
@@ -60,6 +59,8 @@ This document serves as a comprehensive guide for developers looking to build pi
|
|
|
60
59
|
- [Delegating Tasks to Workers (dispatch_task)](#delegating-tasks-to-workers-dispatch_task)
|
|
61
60
|
- [Parallel Execution and Aggregation (Fan-out/Fan-in)](#parallel-execution-and-aggregation-fan-outfan-in)
|
|
62
61
|
- [Dependency Injection (DataStore)](#dependency-injection-datastore)
|
|
62
|
+
- [Native Scheduler](#native-scheduler)
|
|
63
|
+
- [Webhook Notifications](#webhook-notifications)
|
|
63
64
|
- [Production Configuration](#production-configuration)
|
|
64
65
|
- [Fault Tolerance](#fault-tolerance)
|
|
65
66
|
- [Storage Backend](#storage-backend)
|
|
@@ -74,7 +75,17 @@ The project is based on a simple yet powerful architectural pattern that separat
|
|
|
74
75
|
|
|
75
76
|
* **Orchestrator (OrchestratorEngine)** — The Director. It manages the entire process from start to finish, tracks state, handles errors, and decides what should happen next. It does not perform business tasks itself.
|
|
76
77
|
* **Blueprints (Blueprint)** — The Script. Each blueprint is a detailed plan (a state machine) for a specific business process. It describes the steps (states) and the rules for transitioning between them.
|
|
77
|
-
* **Workers (Worker)** — The Team of Specialists. These are independent, specialized executors. Each worker knows how to perform a specific set of tasks (e.g., "process video," "send email") and reports back to the Orchestrator
|
|
78
|
+
* **Workers (Worker)** — The Team of Specialists. These are independent, specialized executors. Each worker knows how to perform a specific set of tasks (e.g., "process video," "send email") and reports back to the Orchestrator.
|
|
79
|
+
|
|
80
|
+
## Ecosystem
|
|
81
|
+
|
|
82
|
+
Avtomatika is part of a larger ecosystem:
|
|
83
|
+
|
|
84
|
+
* **[Avtomatika Worker SDK](https://github.com/avtomatika-ai/avtomatika-worker)**: The official Python SDK for building workers that connect to this engine.
|
|
85
|
+
* **[RCA Protocol](https://github.com/avtomatika-ai/rca)**: The architectural specification and manifesto behind the system.
|
|
86
|
+
* **[Full Example](https://github.com/avtomatika-ai/avtomatika-full-example)**: A complete reference project demonstrating the engine and workers in action.
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
78
89
|
|
|
79
90
|
* **Install the core engine only:**
|
|
80
91
|
```bash
|
|
@@ -146,7 +157,13 @@ async def end_handler(context):
|
|
|
146
157
|
engine = OrchestratorEngine(storage, config)
|
|
147
158
|
engine.register_blueprint(my_blueprint)
|
|
148
159
|
|
|
149
|
-
# 4.
|
|
160
|
+
# 4. Accessing Components (Optional)
|
|
161
|
+
# You can access the internal aiohttp app and core components using AppKeys
|
|
162
|
+
# from avtomatika.app_keys import ENGINE_KEY, DISPATCHER_KEY
|
|
163
|
+
# app = engine.app
|
|
164
|
+
# dispatcher = app[DISPATCHER_KEY]
|
|
165
|
+
|
|
166
|
+
# 5. Define the main entrypoint to run the server
|
|
150
167
|
async def main():
|
|
151
168
|
await engine.start()
|
|
152
169
|
|
|
@@ -328,6 +345,56 @@ async def cache_handler(data_stores):
|
|
|
328
345
|
user_data = await data_stores.cache.get("user:123")
|
|
329
346
|
print(f"User from cache: {user_data}")
|
|
330
347
|
```
|
|
348
|
+
|
|
349
|
+
### 5. Native Scheduler
|
|
350
|
+
|
|
351
|
+
Avtomatika includes a built-in distributed scheduler. It allows you to trigger blueprints periodically (interval, daily, weekly, monthly) without external tools like cron.
|
|
352
|
+
|
|
353
|
+
* **Configuration:** Defined in `schedules.toml`.
|
|
354
|
+
* **Timezone Aware:** Supports global timezone configuration (e.g., `TZ="Europe/Moscow"`).
|
|
355
|
+
* **Distributed Locking:** Safe to run with multiple orchestrator instances; jobs are guaranteed to run only once per interval using distributed locks (Redis/Memory).
|
|
356
|
+
|
|
357
|
+
```toml
|
|
358
|
+
# schedules.toml example
|
|
359
|
+
[nightly_backup]
|
|
360
|
+
blueprint = "backup_flow"
|
|
361
|
+
daily_at = "02:00"
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 6. Webhook Notifications
|
|
365
|
+
|
|
366
|
+
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.
|
|
367
|
+
|
|
368
|
+
* **Usage:** Pass a `webhook_url` in the request body when creating a job.
|
|
369
|
+
* **Events:**
|
|
370
|
+
* `job_finished`: The job reached a final success state.
|
|
371
|
+
* `job_failed`: The job failed (e.g., due to an error or invalid input).
|
|
372
|
+
* `job_quarantined`: The job was moved to quarantine after repeated failures.
|
|
373
|
+
|
|
374
|
+
**Example Request:**
|
|
375
|
+
```json
|
|
376
|
+
POST /api/v1/jobs/my_flow
|
|
377
|
+
{
|
|
378
|
+
"initial_data": {
|
|
379
|
+
"video_url": "..."
|
|
380
|
+
},
|
|
381
|
+
"webhook_url": "https://my-app.com/webhooks/avtomatika"
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Example Webhook Payload:**
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"event": "job_finished",
|
|
389
|
+
"job_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
390
|
+
"status": "finished",
|
|
391
|
+
"result": {
|
|
392
|
+
"output_path": "/videos/result.mp4"
|
|
393
|
+
},
|
|
394
|
+
"error": null
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
331
398
|
## Production Configuration
|
|
332
399
|
|
|
333
400
|
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.
|
|
@@ -349,6 +416,12 @@ To manage access and worker settings securely, Avtomatika uses TOML configuratio
|
|
|
349
416
|
[gpu-worker-01]
|
|
350
417
|
token = "worker-secret-456"
|
|
351
418
|
```
|
|
419
|
+
- **`schedules.toml`**: Defines periodic tasks (CRON-like) for the native scheduler.
|
|
420
|
+
```toml
|
|
421
|
+
[nightly_backup]
|
|
422
|
+
blueprint = "backup_flow"
|
|
423
|
+
daily_at = "02:00"
|
|
424
|
+
```
|
|
352
425
|
|
|
353
426
|
For detailed specifications and examples, please refer to the [**Configuration Guide**](docs/configuration.md).
|
|
354
427
|
|
|
@@ -0,0 +1,45 @@
|
|
|
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=hwg5IBYUsCe-tCru61z3a4-lTw98JL41SXWCmIM_YHc,1161
|
|
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=Tc-vpaQS11i_JTa1pQjGuQD3R5Kj9sIf6gGJGjItBBo,2487
|
|
8
|
+
avtomatika/constants.py,sha256=WL58Nh-EY6baM9Ur_tR9merwPRGb41_klUG2V-yUUaA,963
|
|
9
|
+
avtomatika/context.py,sha256=T6Ux4Fb1DwWRGTpMNeukM51MQDQbGk2HS6Cwpc0dc1s,4248
|
|
10
|
+
avtomatika/data_types.py,sha256=odbkraTYhOo1j3ETIX8NJOy8yIwTdQ_i3juATsjroas,1424
|
|
11
|
+
avtomatika/datastore.py,sha256=gJjhZ5kxjF8pmbbPQb_qu3HPUpfy2c6T75KZ-smb_zg,545
|
|
12
|
+
avtomatika/dispatcher.py,sha256=RrUPvDnCEwAdqxrGpUUcvQZOxG8Ir1E3m9yJjkQeEig,9635
|
|
13
|
+
avtomatika/engine.py,sha256=LZBN4Z50cEITHcrzoYqPS-Rjw4WdPSKRuq-iaBipEbE,15475
|
|
14
|
+
avtomatika/executor.py,sha256=iR9NqS9IffoW692Cq5PKM0P5lmIikmQ65vx8b-D9IZQ,23098
|
|
15
|
+
avtomatika/health_checker.py,sha256=WXwvRJ-3cZC2Udc_ogsyIQp7VzcvJjq_IaqzkTdE0TE,1265
|
|
16
|
+
avtomatika/logging_config.py,sha256=Zb6f9Nri9WVWhlpuBg6Lpi5SWRLGIUmS8Dc3xD1Gg0g,2993
|
|
17
|
+
avtomatika/metrics.py,sha256=7XDhr_xMJ9JpElpZmBG7R0ml7AMdAp9UYp_W-i7tyLg,1858
|
|
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=IHcaIAILWZftPPmXj5En28OSDNK7U8ivQ-w30zIF8fk,3748
|
|
22
|
+
avtomatika/scheduler.py,sha256=F5Kv5Rx34nDd0mE5jxjwpjRg8duDZBEr91N5Y6CNR24,4231
|
|
23
|
+
avtomatika/scheduler_config_loader.py,sha256=F6mLM8yPRgG4bMHV_WnXX7UOrXD8fCXJT30bbEuQ2mk,1311
|
|
24
|
+
avtomatika/security.py,sha256=kkU68YmLWq1ClMUdEW98pS9WsEwHinHoZcdMoPm63Uk,4417
|
|
25
|
+
avtomatika/telemetry.py,sha256=ZBt1_xJ36PzDSz-zdCXeNp58NiezUgbqvMctTG25PT0,2352
|
|
26
|
+
avtomatika/watcher.py,sha256=WAYTjhVmXyqWZfWfQm-iFDVZFBYpNRfwgGDxa_LCnAI,3354
|
|
27
|
+
avtomatika/worker_config_loader.py,sha256=n0j8gfuJDacWONr8744RsHTCWpc_1ZTRMC-rJZh6P6A,2249
|
|
28
|
+
avtomatika/ws_manager.py,sha256=pi5xe0ivsCjRZw08ri5N-gAChMH2I2YPLpl3E2tP89k,3057
|
|
29
|
+
avtomatika/api/handlers.py,sha256=MO6QkbT001jj4latUXGT0hGOmQf_6TkRkmwx19OcXeQ,22176
|
|
30
|
+
avtomatika/api/routes.py,sha256=vSwj2jJlmftZrjrctt-mNYLF23CfCtlUfaMoZzNOqCk,4895
|
|
31
|
+
avtomatika/history/base.py,sha256=Gfw0Gb4Mt9wQrMlYLugZwey_6-cDej5OUctiMTCWg7Q,1668
|
|
32
|
+
avtomatika/history/noop.py,sha256=ETVtPiTfkaMpzhGD8c0_4Iu6pWD89dnPrrRrSIjmc8s,970
|
|
33
|
+
avtomatika/history/postgres.py,sha256=vtW4LMW7Vli5MjcGYY3ez667-C8Cq3I7kIHrcEgSYps,9409
|
|
34
|
+
avtomatika/history/sqlite.py,sha256=Blc9ckvzoDaMRStXyfJOzMAdU_t2JcwtQtVdPgnr6s0,9131
|
|
35
|
+
avtomatika/storage/__init__.py,sha256=mGRj_40dWZ7R7uYbqC6gCsUWCKHAbZz4ZVIhYg5dT_E,262
|
|
36
|
+
avtomatika/storage/base.py,sha256=hW7XFhe6CQDP69q5NPSkUzEInIFxDR1-AyRPZNPEDEc,11424
|
|
37
|
+
avtomatika/storage/memory.py,sha256=BYox_3v1qRqfJyyjkjxXj5OmyGBu0EZqz5BWKZy6YsU,12561
|
|
38
|
+
avtomatika/storage/redis.py,sha256=opOhqBL_uCsNXcMD_W_tJU-8wzDUSjBJWEsXrwP2_YM,21035
|
|
39
|
+
avtomatika/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
avtomatika/utils/webhook_sender.py,sha256=sbX6HEED_TvH3MBxFlVrAqP-n68y0q5quYty5b8Z4D8,1941
|
|
41
|
+
avtomatika-1.0b7.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
|
|
42
|
+
avtomatika-1.0b7.dist-info/METADATA,sha256=FVUEaG5_NWobxU5_FDG2l5RmdhvVN5t1quZXQ0GsSpw,24215
|
|
43
|
+
avtomatika-1.0b7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
avtomatika-1.0b7.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
|
|
45
|
+
avtomatika-1.0b7.dist-info/RECORD,,
|
|
@@ -1,37 +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=_XMLdg-iIuXxbmnns17DC8xe8DeLH4xTOCddqUeYxV4,2337
|
|
7
|
-
avtomatika/context.py,sha256=MT_RMMxSCAVEhlo5HUQdQ1uaR86D9d4s_3nrd6O5xAg,4241
|
|
8
|
-
avtomatika/data_types.py,sha256=VWWbTcJaQt43JaD09qZL1spwNdzCR9vPXVthlM133gM,1362
|
|
9
|
-
avtomatika/datastore.py,sha256=gJjhZ5kxjF8pmbbPQb_qu3HPUpfy2c6T75KZ-smb_zg,545
|
|
10
|
-
avtomatika/dispatcher.py,sha256=Tsye9zOcLN2c7O0AalbX9Il5l3XdT64UDV7iQKiLIJE,9627
|
|
11
|
-
avtomatika/engine.py,sha256=tHYmOCb74gBN-YhxBNVmsE-e5i88wmudLYfK4LWX8Bk,39041
|
|
12
|
-
avtomatika/executor.py,sha256=-SKqLs-DVtMw5P7bZInK57f0M4zRFoVFr1fU5MUrtiY,22543
|
|
13
|
-
avtomatika/health_checker.py,sha256=WXwvRJ-3cZC2Udc_ogsyIQp7VzcvJjq_IaqzkTdE0TE,1265
|
|
14
|
-
avtomatika/logging_config.py,sha256=7RkcdFyhyiCz8MIipDO689mTQVofUJEv-k59QmtqYgc,1368
|
|
15
|
-
avtomatika/metrics.py,sha256=7XDhr_xMJ9JpElpZmBG7R0ml7AMdAp9UYp_W-i7tyLg,1858
|
|
16
|
-
avtomatika/py.typed,sha256=CT_L7gw2MLcQY-X0vs-xB5Vr0wzvGo7GuQYPI_qwJE8,65
|
|
17
|
-
avtomatika/quota.py,sha256=DNcaL6k0J1REeP8sVqbY9FprY_3BSr2SxM2Vf4mEqdw,1612
|
|
18
|
-
avtomatika/ratelimit.py,sha256=hFGW5oN9G6_W_jnHmopXW8bRjjzlvanY19MLghsNLE8,1306
|
|
19
|
-
avtomatika/reputation.py,sha256=IHcaIAILWZftPPmXj5En28OSDNK7U8ivQ-w30zIF8fk,3748
|
|
20
|
-
avtomatika/security.py,sha256=afj28O3xB20EmA75DAQCQm_QKzx_tX2Qv9zE9TlcFvM,4441
|
|
21
|
-
avtomatika/telemetry.py,sha256=ZBt1_xJ36PzDSz-zdCXeNp58NiezUgbqvMctTG25PT0,2352
|
|
22
|
-
avtomatika/watcher.py,sha256=IHaqSqp3XSGXjRY-LEeTG9BJpq2nqJSnmjY_Vdvk3jo,3493
|
|
23
|
-
avtomatika/worker_config_loader.py,sha256=n0j8gfuJDacWONr8744RsHTCWpc_1ZTRMC-rJZh6P6A,2249
|
|
24
|
-
avtomatika/ws_manager.py,sha256=pi5xe0ivsCjRZw08ri5N-gAChMH2I2YPLpl3E2tP89k,3057
|
|
25
|
-
avtomatika/history/base.py,sha256=Gfw0Gb4Mt9wQrMlYLugZwey_6-cDej5OUctiMTCWg7Q,1668
|
|
26
|
-
avtomatika/history/noop.py,sha256=ETVtPiTfkaMpzhGD8c0_4Iu6pWD89dnPrrRrSIjmc8s,970
|
|
27
|
-
avtomatika/history/postgres.py,sha256=CtwupdKGV_gmxcUlbv2xSFadaF8OX2Gul_FYz0K05SE,7551
|
|
28
|
-
avtomatika/history/sqlite.py,sha256=NuuSW9HhzHJcz7_S2otKkudGF4nRVUU2m28ZgVgqMro,8675
|
|
29
|
-
avtomatika/storage/__init__.py,sha256=mGRj_40dWZ7R7uYbqC6gCsUWCKHAbZz4ZVIhYg5dT_E,262
|
|
30
|
-
avtomatika/storage/base.py,sha256=NGQNLPL5z1AX7TzZkQTHAq3gOmLLhhIr8pO-u0VLrBg,10824
|
|
31
|
-
avtomatika/storage/memory.py,sha256=x1FI33KfY6wpGVMY5dGetZR-GAyAajQTg1J2T8slO3U,10938
|
|
32
|
-
avtomatika/storage/redis.py,sha256=of4K5qC_jKMMSj-oyP6rYsAPvTuGLdsLCl4pqfrlhGA,20341
|
|
33
|
-
avtomatika-1.0b5.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
|
|
34
|
-
avtomatika-1.0b5.dist-info/METADATA,sha256=t1mcWWtHdh-SnsTymYpxYNztsQ50mvSz3QxZA4FBWrI,21644
|
|
35
|
-
avtomatika-1.0b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
36
|
-
avtomatika-1.0b5.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
|
|
37
|
-
avtomatika-1.0b5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|