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.
@@ -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(AUTH_HEADER_AVTOMATIKA)
22
+ token = request.headers.get(AUTH_HEADER_CLIENT)
25
23
  if not token:
26
24
  return web.json_response(
27
- {"error": "Missing X-Avtomatika-Token header"},
25
+ {"error": f"Missing {AUTH_HEADER_CLIENT} header"},
28
26
  status=401,
29
27
  )
30
28
 
@@ -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
- """Get the current number of active (registered) workers.
275
- Used for metrics.
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
  """
@@ -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
- _, task_payload = await wait_for(queue.get(), timeout=timeout)
92
- return task_payload
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
- now = monotonic()
242
- worker_ids = list(self._workers.keys())
243
- return sum(self._worker_ttls.get(worker_id, 0) > now for worker_id in worker_ids)
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:
@@ -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.0b5
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.## Installation
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. Define the main entrypoint to run the server
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,,