avtomatika 1.0b1__py3-none-any.whl → 1.0b2__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.html CHANGED
@@ -305,6 +305,20 @@
305
305
  responses: [
306
306
  { code: '200 OK', description: 'Successful response.', body: "{...}" }
307
307
  ]
308
+ },
309
+ {
310
+ id: 'post-reload-worker-configs',
311
+ name: 'Reload Worker Configurations',
312
+ method: 'POST',
313
+ path: '/api/{version}/admin/reload-workers',
314
+ description: 'Triggers a dynamic reload of worker configurations from the TOML file. Requires client authentication.',
315
+ parameters: [
316
+ { name: 'version', type: 'string', description: 'API Version', example: 'v1' }
317
+ ],
318
+ request: { body: null },
319
+ responses: [
320
+ { code: '200 OK', description: 'Successful response.', body: { "status": "worker_configs_reloaded" } }
321
+ ]
308
322
  }
309
323
  ]
310
324
  },
avtomatika/blueprint.py CHANGED
@@ -178,10 +178,13 @@ class StateMachineBlueprint:
178
178
  def render_graph(self, output_filename: Optional[str] = None, output_format: str = "png"):
179
179
  import ast
180
180
  import inspect
181
+ import logging
181
182
  import textwrap
182
183
 
183
184
  from graphviz import Digraph # type: ignore[import]
184
185
 
186
+ logger = logging.getLogger(__name__)
187
+
185
188
  dot = Digraph(comment=f"State Machine for {self.name}")
186
189
  dot.attr("node", shape="box", style="rounded")
187
190
  all_handlers = list(self.handlers.items()) + [(ch.state, ch.func) for ch in self.conditional_handlers]
@@ -222,7 +225,11 @@ class StateMachineBlueprint:
222
225
  value,
223
226
  label=f"on {key}",
224
227
  )
225
- except (TypeError, OSError):
228
+ except (TypeError, OSError) as e:
229
+ logger.warning(
230
+ f"Could not parse handler '{handler_func.__name__}' for state '{handler_state}'. "
231
+ f"Graph may be incomplete. Error: {e}"
232
+ )
226
233
  pass
227
234
  for state in states:
228
235
  dot.node(state, state)
avtomatika/engine.py CHANGED
@@ -584,6 +584,18 @@ class OrchestratorEngine:
584
584
  jobs = await self.storage.get_quarantined_jobs()
585
585
  return web.json_response(jobs)
586
586
 
587
+ async def _reload_worker_configs_handler(self, request: web.Request) -> web.Response:
588
+ """Handles the dynamic reloading of worker configurations."""
589
+ logger.info("Received request to reload worker configurations.")
590
+ if not self.config.WORKERS_CONFIG_PATH:
591
+ return web.json_response(
592
+ {"error": "WORKERS_CONFIG_PATH is not set, cannot reload configs."},
593
+ status=400,
594
+ )
595
+
596
+ await load_worker_configs_to_redis(self.storage, self.config.WORKERS_CONFIG_PATH)
597
+ return web.json_response({"status": "worker_configs_reloaded"})
598
+
587
599
  async def _flush_db_handler(self, request: web.Request) -> web.Response:
588
600
  logger.warning("Received request to flush the database.")
589
601
  await self.storage.flush_all()
@@ -643,6 +655,7 @@ class OrchestratorEngine:
643
655
  app.router.add_get("/workers", self._get_workers_handler)
644
656
  app.router.add_get("/jobs", self._get_jobs_handler)
645
657
  app.router.add_get("/dashboard", self._get_dashboard_handler)
658
+ app.router.add_post("/admin/reload-workers", self._reload_worker_configs_handler)
646
659
 
647
660
  if has_unversioned_routes:
648
661
  self.app.add_subapp("/api/", protected_app)
avtomatika/security.py CHANGED
@@ -1,3 +1,4 @@
1
+ from hashlib import sha256
1
2
  from typing import Any, Awaitable, Callable
2
3
 
3
4
  from aiohttp import web
@@ -89,9 +90,10 @@ def worker_auth_middleware_factory(
89
90
  )
90
91
 
91
92
  # --- Individual Token Check ---
92
- expected_token = await storage.get_worker_token(worker_id)
93
- if expected_token:
94
- if provided_token == expected_token:
93
+ expected_token_hash = await storage.get_worker_token(worker_id)
94
+ if expected_token_hash:
95
+ hashed_provided_token = sha256(provided_token.encode()).hexdigest()
96
+ if hashed_provided_token == expected_token_hash:
95
97
  request["worker_id"] = worker_id # Attach authenticated worker_id
96
98
  return await handler(request)
97
99
  else:
@@ -1,3 +1,4 @@
1
+ from hashlib import sha256
1
2
  from logging import getLogger
2
3
  from os.path import exists
3
4
  from tomllib import load
@@ -36,8 +37,10 @@ async def load_worker_configs_to_redis(storage: StorageBackend, config_path: str
36
37
  continue
37
38
 
38
39
  try:
40
+ # Hash the token before storing it
41
+ hashed_token = sha256(token.encode()).hexdigest()
39
42
  # Store the token in a way that's easily retrievable by worker_id
40
- await storage.set_worker_token(worker_id, token)
43
+ await storage.set_worker_token(worker_id, hashed_token)
41
44
  logger.info(f"Loaded token for worker_id '{worker_id}'.")
42
45
  except Exception as e:
43
46
  logger.error(f"Failed to store token for worker_id '{worker_id}' in Redis: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avtomatika
3
- Version: 1.0b1
3
+ Version: 1.0b2
4
4
  Summary: A state-machine based orchestrator for long-running 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
@@ -251,6 +251,11 @@ async def handle_normal(actions):
251
251
  actions.transition_to("normal_processing")
252
252
  ```
253
253
 
254
+ > **Note on Limitations:** The current version of `.when()` uses a simple parser with the following limitations:
255
+ > * **No Nested Attributes:** You can only access direct fields of `context.initial_data` or `context.state_history` (e.g., `context.initial_data.field`). Nested objects (e.g., `context.initial_data.area.field`) are not supported.
256
+ > * **Simple Comparisons Only:** Only the following operators are supported: `==`, `!=`, `>`, `<`, `>=`, `<=`. Complex logical expressions with `AND`, `OR`, or `NOT` are not allowed.
257
+ > * **Limited Value Types:** The parser only recognizes strings (in quotes), integers, and floats. Boolean values (`True`, `False`) and `None` are not correctly parsed and will be treated as strings.
258
+
254
259
  ### 2. Delegating Tasks to Workers (`dispatch_task`)
255
260
 
256
261
  This is the primary function for delegating work. The orchestrator will queue the task and wait for a worker to pick it up and return a result.
@@ -368,7 +373,9 @@ The orchestrator uses tokens to authenticate API requests.
368
373
  * **Client Authentication**: All API clients must provide a token in the `X-Avtomatika-Token` header. The orchestrator validates this token against client configurations.
369
374
  * **Worker Authentication**: Workers must provide a token in the `X-Worker-Token` header.
370
375
  * `GLOBAL_WORKER_TOKEN`: You can set a global token for all workers using this environment variable. For development and testing, it defaults to `"secure-worker-token"`.
371
- * **Individual Tokens**: For production, it is recommended to define individual tokens for each worker in a separate configuration file and provide its path via the `WORKERS_CONFIG_PATH` environment variable.
376
+ * **Individual Tokens**: For production, it is recommended to define individual tokens for each worker in a separate configuration file and provide its path via the `WORKERS_CONFIG_PATH` environment variable. Tokens from this file are stored in a hashed format for security.
377
+
378
+ > **Note on Dynamic Reloading:** The worker configuration file can be reloaded without restarting the orchestrator by sending an authenticated `POST` request to the `/api/v1/admin/reload-workers` endpoint. This allows for dynamic updates of worker tokens.
372
379
 
373
380
  ### Observability
374
381
 
@@ -1,6 +1,6 @@
1
1
  avtomatika/__init__.py,sha256=BtYVoi9xy3yuMDMmzvLTO3AfbL2xeEjVz2bP9GhQeHU,675
2
- avtomatika/api.html,sha256=BrsaJDctd6c2gGKGO7-ZyvjyXW7tLdxD0lVhwYR0AvA,32796
3
- avtomatika/blueprint.py,sha256=zJySegRNnIA8IFR6mDKqNgcAYDERhw6N9bVUvggSUuc,9159
2
+ avtomatika/api.html,sha256=Z-Ikqrle7YPXagx2D-C5ylVZicLQFSsIzPsHCQgqMHM,33628
3
+ avtomatika/blueprint.py,sha256=RvDidn2xkziZOOnJlkV30SMp59GuscASz11GolxK7fE,9445
4
4
  avtomatika/client_config_loader.py,sha256=N41juqx9CF2XZ3-WRXspkLG2eoGhj9LkWdaMCCCk33E,1962
5
5
  avtomatika/compression.py,sha256=bhA1kw4YrCR3I3kdquZSY0fAzCrRrjtz55uepzLUDKI,2498
6
6
  avtomatika/config.py,sha256=0vlMfVMjxwVUC8m_NglGocC_EoklzAc0qmt3UJbxm10,2087
@@ -8,7 +8,7 @@ avtomatika/context.py,sha256=rnF09jqQGkaKlax8P5ku9USwijSm6dommDGZbeVrzLk,4295
8
8
  avtomatika/data_types.py,sha256=g-g5hPnCpzeATgOn5v7EvDm5ps314owFJD5iWJ6IPR0,1425
9
9
  avtomatika/datastore.py,sha256=ERMyiFYQpAhVYijxzTrrdm6jtIPFf4dngWIa0qod3Wc,551
10
10
  avtomatika/dispatcher.py,sha256=2YW-A0QXD4T6RF3KiNHflGs8LeLNrMlo5NScE4T49-o,10015
11
- avtomatika/engine.py,sha256=cHkUrNU-Oi6wyhyeZ56Z7XFL8-hb8PUXIXkxMuvJnug,36778
11
+ avtomatika/engine.py,sha256=IeBy7pnWjERZwTIAp02wcaKggRMi5Jof-02kB2LfAKc,37482
12
12
  avtomatika/executor.py,sha256=-gbLfluZfwtLnAYI8GdK1xswA_DYp_-yoLnP4AWlcyQ,21157
13
13
  avtomatika/health_checker.py,sha256=WXwvRJ-3cZC2Udc_ogsyIQp7VzcvJjq_IaqzkTdE0TE,1265
14
14
  avtomatika/logging_config.py,sha256=e0-eEEGHw1zz9ZshzXaxfavV0uZfamRNdcAeHnrgBYQ,1370
@@ -17,10 +17,10 @@ avtomatika/py.typed,sha256=CT_L7gw2MLcQY-X0vs-xB5Vr0wzvGo7GuQYPI_qwJE8,65
17
17
  avtomatika/quota.py,sha256=DNcaL6k0J1REeP8sVqbY9FprY_3BSr2SxM2Vf4mEqdw,1612
18
18
  avtomatika/ratelimit.py,sha256=bZSmdCHviSGMVDNOKTBSswDl6P9Dc63BKLqKU50twpg,1579
19
19
  avtomatika/reputation.py,sha256=XFEqDW4TEqjrWgGB2KwnBBJF1RpwLlZPgFwxjPl2z8w,3221
20
- avtomatika/security.py,sha256=XFHlurSxsqyTJPIAp6R1RmE3htQ5Pq6wemsyyO8K-vw,4312
20
+ avtomatika/security.py,sha256=afj28O3xB20EmA75DAQCQm_QKzx_tX2Qv9zE9TlcFvM,4441
21
21
  avtomatika/telemetry.py,sha256=FIbbYjNX0JhpfT7UHjx6EdcFUGF1DpQDrbicIp0ZGvA,2353
22
22
  avtomatika/watcher.py,sha256=djqwgK8wu73LL2Rtxa6mV-EXimFO7T-iRQV5U_26dc8,2571
23
- avtomatika/worker_config_loader.py,sha256=QNjC1_y4ZvykKUDW3rmMNnNPZD2dNvomRZtiuPs8DdQ,1645
23
+ avtomatika/worker_config_loader.py,sha256=2Fu2gU3O5H9VJ62D_Yc4gT19vV_be8DJZWzrTTovq8E,1788
24
24
  avtomatika/ws_manager.py,sha256=X420oV9_vnujV78VWuOVNqiMZAMQzfw6_0Ep694LJjQ,3069
25
25
  avtomatika/history/base.py,sha256=p0zItsdxFzd889LujV8py6GwK4CUfqAt8QL915mrT4k,1680
26
26
  avtomatika/history/noop.py,sha256=Hk5yJsS4S5G5A7NRRMEafIV_IFI9hddSwEvRg2Reh0M,982
@@ -30,8 +30,8 @@ avtomatika/storage/__init__.py,sha256=TFb3_Ab6K1tlGaBihMqhzwdwCarMG2uRpATDW372YN
30
30
  avtomatika/storage/base.py,sha256=rcHbTB_0s4ZhkxIACnp9f6Ow7Jvr9pHx96t3VUf4lIc,9581
31
31
  avtomatika/storage/memory.py,sha256=Wqi879hw1cZBIhMDk_bKPaWhnH33QEl4aUIxu9g3M0M,10282
32
32
  avtomatika/storage/redis.py,sha256=eT65_tvRufy-txiKfSSs5UmKBeEio8aWG0UTBfj9DdY,17088
33
- avtomatika-1.0b1.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
34
- avtomatika-1.0b1.dist-info/METADATA,sha256=YQWPU2WEdZ9XpWYInig-5FJNYZ7mW_2EgM8GzKguyn8,17026
35
- avtomatika-1.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- avtomatika-1.0b1.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
37
- avtomatika-1.0b1.dist-info/RECORD,,
33
+ avtomatika-1.0b2.dist-info/licenses/LICENSE,sha256=tqCjw9Y1vbU-hLcWi__7wQstLbt2T1XWPdbQYqCxuWY,1072
34
+ avtomatika-1.0b2.dist-info/METADATA,sha256=IofXNi3-o-YDIfLKrBIT0mV5osHPvRyYX95b_9WI8dY,18083
35
+ avtomatika-1.0b2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ avtomatika-1.0b2.dist-info/top_level.txt,sha256=gLDWhA_wxHj0I6fG5X8vw9fE0HSN4hTE2dEJzeVS2x8,11
37
+ avtomatika-1.0b2.dist-info/RECORD,,