avtomatika 1.0b7__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/watcher.py CHANGED
@@ -26,46 +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
30
  # We set TTL slightly longer than the expected execution time (60s)
33
- if not await self.storage.acquire_lock("global_watcher_lock", self._instance_id, 60):
34
- logger.debug("Watcher lock held by another instance. Skipping check.")
35
- continue
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)
36
35
 
37
- try:
38
- logger.info("Watcher running check for timed out jobs...")
39
- timed_out_job_ids = await self.storage.get_timed_out_jobs()
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)
40
45
 
41
- for job_id in timed_out_job_ids:
42
- logger.warning(f"Job {job_id} timed out. Moving to failed state.")
43
- try:
44
- # Get the latest version to avoid overwriting
45
- job_state = await self.storage.get_job_state(job_id)
46
- if job_state and job_state["status"] == "waiting_for_worker":
47
- job_state["status"] = "failed"
48
- job_state["error_message"] = "Worker task timed out."
49
- await self.storage.save_job_state(job_id, job_state)
46
+ # Increment the metric
47
+ from . import metrics
50
48
 
51
- # Increment the metric
52
- from . import metrics
53
-
54
- metrics.jobs_failed_total.inc(
55
- {
56
- metrics.LABEL_BLUEPRINT: job_state.get(
57
- "blueprint_name",
58
- "unknown",
59
- ),
60
- },
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}",
61
60
  )
62
- except Exception:
63
- logger.exception(
64
- f"Failed to update state for timed out job {job_id}",
65
- )
66
- finally:
67
- # Always release the lock so we (or others) can run next time
68
- 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.")
69
66
 
70
67
  except CancelledError:
71
68
  logger.info("Watcher received cancellation request.")
@@ -73,7 +70,8 @@ class Watcher:
73
70
  except Exception:
74
71
  logger.exception("Error in Watcher main loop.")
75
72
 
76
- logger.info("Watcher stopped.")
73
+ # Sleep at the end of iteration
74
+ await sleep(self.watch_interval_seconds)
77
75
 
78
76
  def stop(self):
79
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.0b7
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
@@ -20,6 +20,9 @@ Requires-Dist: msgpack~=1.1
20
20
  Requires-Dist: orjson~=3.11
21
21
  Provides-Extra: redis
22
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"
23
26
  Provides-Extra: history
24
27
  Requires-Dist: aiosqlite~=0.22; extra == "history"
25
28
  Requires-Dist: asyncpg~=0.30; extra == "history"
@@ -37,10 +40,13 @@ Requires-Dist: pytest-mock~=3.14; extra == "test"
37
40
  Requires-Dist: aioresponses~=0.7; extra == "test"
38
41
  Requires-Dist: backports.zstd~=1.2; extra == "test"
39
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"
40
45
  Provides-Extra: all
41
46
  Requires-Dist: avtomatika[redis]; extra == "all"
42
47
  Requires-Dist: avtomatika[history]; extra == "all"
43
48
  Requires-Dist: avtomatika[telemetry]; extra == "all"
49
+ Requires-Dist: avtomatika[s3]; extra == "all"
44
50
  Dynamic: license-file
45
51
 
46
52
  # Avtomatika Orchestrator
@@ -60,6 +66,7 @@ This document serves as a comprehensive guide for developers looking to build pi
60
66
  - [Parallel Execution and Aggregation (Fan-out/Fan-in)](#parallel-execution-and-aggregation-fan-outfan-in)
61
67
  - [Dependency Injection (DataStore)](#dependency-injection-datastore)
62
68
  - [Native Scheduler](#native-scheduler)
69
+ - [S3 Payload Offloading](#s3-payload-offloading)
63
70
  - [Webhook Notifications](#webhook-notifications)
64
71
  - [Production Configuration](#production-configuration)
65
72
  - [Fault Tolerance](#fault-tolerance)
@@ -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]"
@@ -250,6 +262,19 @@ async def publish_handler_old_style(context):
250
262
  print(f"Job {context.job_id}: Publishing video at {output_path} ({duration}s).")
251
263
  context.actions.transition_to("complete")
252
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
+
253
278
  ## Blueprint Cookbook: Key Features
254
279
 
255
280
  ### 1. Conditional Transitions (`.when()`)
@@ -365,7 +390,30 @@ daily_at = "02:00"
365
390
 
366
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.
367
392
 
368
- * **Usage:** Pass a `webhook_url` in the request body when creating a job.
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
369
417
  * **Events:**
370
418
  * `job_finished`: The job reached a final success state.
371
419
  * `job_failed`: The job failed (e.g., due to an error or invalid input).
@@ -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,45 +0,0 @@
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,,