avtomatika-worker 1.0b3__py3-none-any.whl → 1.0b5__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.
@@ -1,16 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avtomatika-worker
3
- Version: 1.0b3
3
+ Version: 1.0b5
4
4
  Summary: Worker SDK for the Avtomatika orchestrator.
5
+ Author-email: Dmitrii Gagarin <madgagarin@gmail.com>
5
6
  Project-URL: Homepage, https://github.com/avtomatika-ai/avtomatika-worker
6
7
  Project-URL: Bug Tracker, https://github.com/avtomatika-ai/avtomatika-worker/issues
8
+ Keywords: worker,sdk,orchestrator,distributed,task-queue,rxon,hln
7
9
  Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
8
11
  Classifier: Programming Language :: Python :: 3
9
12
  Classifier: License :: OSI Approved :: MIT License
10
13
  Classifier: Operating System :: OS Independent
14
+ Classifier: Typing :: Typed
11
15
  Requires-Python: >=3.11
12
16
  Description-Content-Type: text/markdown
13
17
  License-File: LICENSE
18
+ Requires-Dist: rxon==1.0b2
14
19
  Requires-Dist: aiohttp~=3.13.2
15
20
  Requires-Dist: python-json-logger~=4.0.0
16
21
  Requires-Dist: obstore>=0.1
@@ -28,7 +33,11 @@ Dynamic: license-file
28
33
 
29
34
  # Avtomatika Worker SDK
30
35
 
31
- This is the official SDK for creating workers compatible with the **[Avtomatika Orchestrator](https://github.com/avtomatika-ai/avtomatika)**. It implements the **[RCA Protocol](https://github.com/avtomatika-ai/rca)**, handling all communication complexity (polling, heartbeats, S3 offloading) so you can focus on writing your business logic.
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
37
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/release/python-3110/)
38
+ [![Code Style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
39
+
40
+ This is the official SDK for creating workers compatible with the **[Avtomatika Orchestrator](https://github.com/avtomatika-ai/avtomatika)**. It is built upon the **[Avtomatika Protocol](https://github.com/avtomatika-ai/rxon)** and implements the **[HLN Protocol](https://github.com/avtomatika-ai/hln)**, handling all communication complexity (polling, heartbeats, S3 offloading) so you can focus on writing your business logic.
32
41
 
33
42
  ## Installation
34
43
 
@@ -286,13 +295,26 @@ async def image_resizer(params: ResizeParams, **kwargs):
286
295
 
287
296
  ### 1. Task Handlers
288
297
 
289
- Each handler is an asynchronous function that accepts two arguments:
298
+ Each handler is a function (either `async def` or `def`) that accepts two arguments:
290
299
 
291
300
  - `params` (`dict`, `dataclass`, or `pydantic.BaseModel`): The parameters for the task, automatically validated and instantiated based on your type hint.
292
301
  - `**kwargs`: Additional metadata about the task, including:
293
302
  - `task_id` (`str`): The unique ID of the task itself.
294
303
  - `job_id` (`str`): The ID of the parent `Job` to which the task belongs.
295
304
  - `priority` (`int`): The execution priority of the task.
305
+ - `send_progress` (`callable`): An async function `await send_progress(progress_float, message_string)` to report task execution progress (0.0 to 1.0) to the orchestrator.
306
+
307
+ **Synchronous Handlers:**
308
+ If you define your handler as a standard synchronous function (`def handler(...)`), the SDK will automatically execute it in a separate thread using `asyncio.to_thread`. This ensures that CPU-intensive operations (like model inference) do not block the worker's main event loop, allowing heartbeats and other background tasks to continue running smoothly.
309
+
310
+ ```python
311
+ @worker.task("cpu_heavy_task")
312
+ def heavy_computation(params: dict, **kwargs):
313
+ # This will run in a thread, not blocking the loop
314
+ import time
315
+ time.sleep(10)
316
+ return {"status": "success"}
317
+ ```
296
318
 
297
319
  ### 2. Concurrency Limiting
298
320
 
@@ -383,7 +405,7 @@ return {
383
405
 
384
406
  #### Error Handling
385
407
 
386
- To control the orchestrator's fault tolerance mechanism, you can return standardized error types.
408
+ To control the orchestrator's fault tolerance mechanism, you can return standardized error types. All error constants can be imported from `avtomatika_worker.typing`.
387
409
 
388
410
  - **Transient Error (`TRANSIENT_ERROR`)**: For issues that might be resolved on a retry (e.g., a network failure).
389
411
  ```python
@@ -396,17 +418,10 @@ To control the orchestrator's fault tolerance mechanism, you can return standard
396
418
  }
397
419
  }
398
420
  ```
399
- - **Permanent Error (`PERMANENT_ERROR`)**: For unresolvable problems (e.g., an invalid file format).
400
- ```python
401
- from avtomatika_worker.typing import PERMANENT_ERROR
402
- return {
403
- "status": "failure",
404
- "error": {
405
- "code": PERMANENT_ERROR,
406
- "message": "Corrupted input file"
407
- }
408
- }
409
- ```
421
+ - **Permanent Error (`PERMANENT_ERROR`)**: For unresolvable problems (e.g., an invalid file format). Causes immediate quarantine.
422
+ - **Security Error (`SECURITY_ERROR`)**: For security violations. Causes immediate quarantine.
423
+ - **Dependency Error (`DEPENDENCY_ERROR`)**: For missing models or tools. Causes immediate quarantine.
424
+ - **Resource Exhausted (`RESOURCE_EXHAUSTED_ERROR`)**: When resources are temporarily unavailable. Treated as transient (retried).
410
425
 
411
426
  ### 4. Failover and Load Balancing
412
427
 
@@ -521,6 +536,48 @@ This only requires configuring environment variables for S3 access (see Full Con
521
536
 
522
537
  ### 7. WebSocket Support
523
538
 
539
+ For real-time communication (e.g., immediate task cancellation), the worker supports WebSocket connections. This is enabled by setting `WORKER_ENABLE_WEBSOCKETS=true`. When connected, the orchestrator can push commands like `cancel_task` directly to the worker.
540
+
541
+ ### 8. Middleware
542
+
543
+ The worker supports a middleware system, allowing you to wrap task executions with custom logic. This is particularly useful for resource management (e.g., acquiring GPU locks), logging, error handling, or **Dependency Injection**.
544
+
545
+ Middleware functions wrap the execution of the task handler (and any subsequent middlewares). They receive a context dictionary and the next handler in the chain.
546
+
547
+ The `context` dictionary contains:
548
+ - `task_id`, `job_id`, `task_name`: Metadata.
549
+ - `params`: The validated parameters object.
550
+ - `handler_kwargs`: A dictionary of arguments that will be passed to the handler. **Middleware can modify this dictionary to inject dependencies.**
551
+
552
+ **Example: GPU Resource Manager & Dependency Injection**
553
+
554
+ ```python
555
+ async def gpu_lock_middleware(context: dict, next_handler: callable):
556
+ # Pre-processing: Acquire resource
557
+ print(f"Acquiring GPU for task {context['task_id']}...")
558
+ model_path = await resource_manager.allocate()
559
+
560
+ # Inject the model path into the handler's arguments
561
+ context["handler_kwargs"]["model_path"] = model_path
562
+
563
+ try:
564
+ # Execute the next handler in the chain
565
+ result = await next_handler()
566
+ return result
567
+ finally:
568
+ # Post-processing: Release resource
569
+ print(f"Releasing GPU for task {context['task_id']}...")
570
+ resource_manager.release()
571
+
572
+ # Register the middleware
573
+ worker.add_middleware(gpu_lock_middleware)
574
+
575
+ # Handler now receives 'model_path' automatically
576
+ @worker.task("generate")
577
+ def generate(params, model_path, **kwargs):
578
+ print(f"Using model at: {model_path}")
579
+ ```
580
+
524
581
  ## Advanced Features
525
582
 
526
583
  ### Reporting Skill & Model Dependencies
@@ -577,8 +634,11 @@ The worker is fully configured via environment variables.
577
634
  | `WORKER_TYPE` | A string identifying the type of the worker. | `generic-cpu-worker` |
578
635
  | `WORKER_PORT` | The port for the worker's health check server. | `8083` |
579
636
  | `WORKER_TOKEN` | A common authentication token used to connect to orchestrators. | `your-secret-worker-token` |
580
- | `WORKER_INDIVIDUAL_TOKEN` | An individual token for this worker, which overrides `WORKER_TOKEN` if set. | - |
581
- | `ORCHESTRATOR_URL` | The URL of a single orchestrator (used if `ORCHESTRATORS_CONFIG` is not set). | `http://localhost:8080` |
637
+ - **`WORKER_INDIVIDUAL_TOKEN`**: An individual token for this worker, which overrides `WORKER_TOKEN` if set.
638
+ - **`TLS_CA_PATH`**: Path to the CA certificate to verify the orchestrator.
639
+ - **`TLS_CERT_PATH`**: Path to the client certificate for mTLS.
640
+ - **`TLS_KEY_PATH`**: Path to the client private key for mTLS.
641
+ - **`ORCHESTRATOR_URL`**: The address of the Avtomatika orchestrator.
582
642
  | `ORCHESTRATORS_CONFIG` | A JSON string with a list of orchestrators for multi-orchestrator modes. | `[]` |
583
643
  | `MULTI_ORCHESTRATOR_MODE` | The mode for handling multiple orchestrators. Possible values: `FAILOVER`, `ROUND_ROBIN`. | `FAILOVER` |
584
644
  | `MAX_CONCURRENT_TASKS` | The maximum number of tasks the worker can execute simultaneously. | `10` |
@@ -605,8 +665,9 @@ The worker is fully configured via environment variables.
605
665
 
606
666
  ## Development
607
667
 
608
- To install the necessary dependencies for running tests, use the following command:
668
+ To install the necessary dependencies for running tests (assuming you are in the package root):
609
669
 
610
- ```bash
611
- pip install .[test]
612
- ```
670
+ 1. Install the worker in editable mode with test dependencies:
671
+ ```bash
672
+ pip install -e .[test]
673
+ ```
@@ -0,0 +1,12 @@
1
+ avtomatika_worker/__init__.py,sha256=YYxuVc3EUabivZe6ZXNyFDUZH97qTKIQwxKZxV4klDc,342
2
+ avtomatika_worker/config.py,sha256=wXmtQTABhHEl0vwxQdWgSfmCFsKNEH-lF5CF70VV2SU,6114
3
+ avtomatika_worker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ avtomatika_worker/s3.py,sha256=v_xSMVsPRgjkhQZWJreGjMSYCIfH_n7BHo5GMqmPw5k,10371
5
+ avtomatika_worker/task_files.py,sha256=9WmvVt4iZP65CA3lTttOmVnW8tlvfQMwutfTla4yJyY,5858
6
+ avtomatika_worker/types.py,sha256=9lFjRIhBBtztsPcnnbQ0ifnikBUQFqf8EeieuOG3MYI,1094
7
+ avtomatika_worker/worker.py,sha256=oMuMiPGKUYKwCq18T11zQeT36OBSGfxPNd5unVEEWsk,28250
8
+ avtomatika_worker-1.0b5.dist-info/licenses/LICENSE,sha256=J19fUi8XywciRuLLWHvcPQGc0Uo-9K1a0dKaIbzw_Yg,1092
9
+ avtomatika_worker-1.0b5.dist-info/METADATA,sha256=qz5Dl8qIubhAPtGSxv0LY7t6pVDDqiLD8wOhSg7I3dY,33240
10
+ avtomatika_worker-1.0b5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ avtomatika_worker-1.0b5.dist-info/top_level.txt,sha256=d3b5BUeUrHM1Cn-cbStz-hpucikEBlPOvtcmQ_j3qAs,18
12
+ avtomatika_worker-1.0b5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Dmitrii Gagarin
3
+ Copyright (c) 2025-2026 Dmitrii Gagarin aka madgagarin
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,93 +0,0 @@
1
- from asyncio import sleep
2
- from logging import getLogger
3
- from typing import Any
4
-
5
- from aiohttp import ClientError, ClientSession, ClientTimeout, ClientWebSocketResponse
6
-
7
- from .constants import AUTH_HEADER_WORKER
8
-
9
- logger = getLogger(__name__)
10
-
11
-
12
- class OrchestratorClient:
13
- """
14
- Dedicated client for communicating with a single Avtomatika Orchestrator instance.
15
- Handles HTTP requests, retries, and authentication.
16
- """
17
-
18
- def __init__(self, session: ClientSession, base_url: str, worker_id: str, token: str):
19
- self.session = session
20
- self.base_url = base_url.rstrip("/")
21
- self.worker_id = worker_id
22
- self.token = token
23
- self._headers = {AUTH_HEADER_WORKER: self.token}
24
-
25
- async def register(self, payload: dict[str, Any]) -> bool:
26
- """Registers the worker with the orchestrator."""
27
- url = f"{self.base_url}/_worker/workers/register"
28
- try:
29
- async with self.session.post(url, json=payload, headers=self._headers) as resp:
30
- if resp.status >= 400:
31
- logger.error(f"Error registering with {self.base_url}: {resp.status}")
32
- return False
33
- return True
34
- except ClientError as e:
35
- logger.error(f"Error registering with orchestrator {self.base_url}: {e}")
36
- return False
37
-
38
- async def poll_task(self, timeout: float) -> dict[str, Any] | None:
39
- """Polls for the next available task."""
40
- url = f"{self.base_url}/_worker/workers/{self.worker_id}/tasks/next"
41
- client_timeout = ClientTimeout(total=timeout + 5)
42
- try:
43
- async with self.session.get(url, headers=self._headers, timeout=client_timeout) as resp:
44
- if resp.status == 200:
45
- return await resp.json()
46
- elif resp.status != 204:
47
- logger.warning(f"Unexpected status from {self.base_url} during poll: {resp.status}")
48
- except ClientError as e:
49
- logger.error(f"Error polling for tasks from {self.base_url}: {e}")
50
- except Exception as e:
51
- logger.exception(f"Unexpected error polling from {self.base_url}: {e}")
52
- return None
53
-
54
- async def send_heartbeat(self, payload: dict[str, Any]) -> bool:
55
- """Sends a heartbeat message to update worker state."""
56
- url = f"{self.base_url}/_worker/workers/{self.worker_id}"
57
- try:
58
- async with self.session.patch(url, json=payload, headers=self._headers) as resp:
59
- if resp.status >= 400:
60
- logger.warning(f"Heartbeat to {self.base_url} failed with status: {resp.status}")
61
- return False
62
- return True
63
- except ClientError as e:
64
- logger.error(f"Error sending heartbeat to orchestrator {self.base_url}: {e}")
65
- return False
66
-
67
- async def send_result(self, payload: dict[str, Any], max_retries: int, initial_delay: float) -> bool:
68
- """Sends task result with retries and exponential backoff."""
69
- url = f"{self.base_url}/_worker/tasks/result"
70
- delay = initial_delay
71
- for i in range(max_retries):
72
- try:
73
- async with self.session.post(url, json=payload, headers=self._headers) as resp:
74
- if resp.status == 200:
75
- return True
76
- logger.error(f"Error sending result to {self.base_url}: {resp.status}")
77
- except ClientError as e:
78
- logger.error(f"Error sending result to {self.base_url}: {e}")
79
-
80
- if i < max_retries - 1:
81
- await sleep(delay * (2**i))
82
- return False
83
-
84
- async def connect_websocket(self) -> ClientWebSocketResponse | None:
85
- """Establishes a WebSocket connection for real-time commands."""
86
- ws_url = self.base_url.replace("http", "ws", 1) + "/_worker/ws"
87
- try:
88
- ws = await self.session.ws_connect(ws_url, headers=self._headers)
89
- logger.info(f"WebSocket connection established to {ws_url}")
90
- return ws
91
- except Exception as e:
92
- logger.warning(f"WebSocket connection to {ws_url} failed: {e}")
93
- return None
@@ -1,22 +0,0 @@
1
- """
2
- Centralized constants for the Avtomatika protocol (Worker SDK).
3
- These should match the constants in the core `avtomatika` package.
4
- """
5
-
6
- # --- Auth Headers ---
7
- AUTH_HEADER_CLIENT = "X-Avtomatika-Token"
8
- AUTH_HEADER_WORKER = "X-Worker-Token"
9
-
10
- # --- Error Codes ---
11
- ERROR_CODE_TRANSIENT = "TRANSIENT_ERROR"
12
- ERROR_CODE_PERMANENT = "PERMANENT_ERROR"
13
- ERROR_CODE_INVALID_INPUT = "INVALID_INPUT_ERROR"
14
-
15
- # --- Task Statuses ---
16
- TASK_STATUS_SUCCESS = "success"
17
- TASK_STATUS_FAILURE = "failure"
18
- TASK_STATUS_CANCELLED = "cancelled"
19
- TASK_STATUS_NEEDS_REVIEW = "needs_review" # Example of a common custom status
20
-
21
- # --- Commands (WebSocket) ---
22
- COMMAND_CANCEL_TASK = "cancel_task"
@@ -1,14 +0,0 @@
1
- avtomatika_worker/__init__.py,sha256=y_s5KlsgFu7guemZfjLVQ3Jzq7DyLG168-maVGwWRC4,334
2
- avtomatika_worker/client.py,sha256=mkvwrMY8tAaZN_lwMSxWHmAoWsDemD-WiKSeH5fM6GI,4173
3
- avtomatika_worker/config.py,sha256=NaAhufpwyG6CsHW-cXmqR3MfGp_5SdDZ_vEhmmV8G3g,5819
4
- avtomatika_worker/constants.py,sha256=DfGR_YkW9rbioCorKpNGfZ0i_0iGgMq2swyJhVl9nNA,669
5
- avtomatika_worker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- avtomatika_worker/s3.py,sha256=sAuXBp__XTVhyNwK9gsxy1Jm_udJM6ypssGTZ00pa6U,8847
7
- avtomatika_worker/task_files.py,sha256=ucjBuI78UmtMvfucTzDTNJ1g0KJaRIwyshRNTipIZSU,3351
8
- avtomatika_worker/types.py,sha256=dSNsHgqV6hZhOt4eUK2PDWB6lrrwCA5_T_iIBI_wTZ0,442
9
- avtomatika_worker/worker.py,sha256=XSRfLO-W0J6WG128Iu-rL_w3-PqmsWQMUElVLi3Z1gk,21904
10
- avtomatika_worker-1.0b3.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
11
- avtomatika_worker-1.0b3.dist-info/METADATA,sha256=yiEtJuMv5WHYHfScna7cF5QAvAUhMCJdUDENHvrMRFY,29601
12
- avtomatika_worker-1.0b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- avtomatika_worker-1.0b3.dist-info/top_level.txt,sha256=d3b5BUeUrHM1Cn-cbStz-hpucikEBlPOvtcmQ_j3qAs,18
14
- avtomatika_worker-1.0b3.dist-info/RECORD,,