dotflow 0.16.0.dev2__py3-none-any.whl → 0.16.0.dev4__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.
dotflow/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Dotflow __init__ module."""
2
2
 
3
- __version__ = "0.16.0.dev2"
3
+ __version__ = "0.16.0.dev4"
4
4
  __description__ = "🎲 Dotflow turns an idea into flow!"
5
5
 
6
6
  from .core.action import Action as action
@@ -1,23 +1,32 @@
1
1
  """Command start module"""
2
2
 
3
- import os
4
3
  from os import system
5
4
 
6
5
  from dotflow import Config, DotFlow
7
6
  from dotflow.cli.command import Command
7
+ from dotflow.core.exception import InvalidWorkflowFactory, WorkflowFlagConflict
8
+ from dotflow.core.module import Module
8
9
  from dotflow.core.types.execution import TypeExecution
9
10
  from dotflow.providers import (
10
- ServerAPI,
11
- ServerDefault,
12
11
  StorageDefault,
13
12
  StorageFile,
14
13
  StorageGCS,
15
14
  StorageS3,
16
15
  )
16
+ from dotflow.utils.basic_functions import basic_callback
17
17
 
18
18
 
19
19
  class StartCommand(Command):
20
20
  def setup(self):
21
+ if getattr(self.params, "workflow", None):
22
+ self._start_from_factory()
23
+ else:
24
+ self._start_from_step()
25
+
26
+ if self.params.mode == TypeExecution.BACKGROUND:
27
+ system("/bin/bash")
28
+
29
+ def _start_from_step(self):
21
30
  workflow = self._new_workflow()
22
31
 
23
32
  workflow.task.add(
@@ -28,36 +37,32 @@ class StartCommand(Command):
28
37
 
29
38
  workflow.start(mode=self.params.mode)
30
39
 
31
- if self.params.mode == TypeExecution.BACKGROUND:
32
- system("/bin/bash")
33
-
34
- def _new_workflow(self):
35
- config = self._build_config()
36
- workflow_id = os.getenv("WORKFLOW_ID")
40
+ def _start_from_factory(self):
41
+ step_only_flags = {
42
+ "--callback": self.params.callback is not basic_callback,
43
+ "--initial-context": self.params.initial_context is not None,
44
+ }
45
+ for flag, provided in step_only_flags.items():
46
+ if provided:
47
+ raise WorkflowFlagConflict(flag=flag)
37
48
 
38
- if config is None:
39
- return DotFlow(workflow_id=workflow_id)
40
- return DotFlow(config=config, workflow_id=workflow_id)
49
+ factory = Module(self.params.workflow)
41
50
 
42
- def _build_config(self):
43
- storage = self._build_storage()
44
- server = self._build_server()
51
+ if not callable(factory):
52
+ raise InvalidWorkflowFactory(factory=self.params.workflow)
45
53
 
46
- if storage is None and server is None:
47
- return None
54
+ result = factory()
48
55
 
49
- return Config(
50
- storage=storage or StorageDefault(),
51
- server=server or ServerDefault(),
52
- )
56
+ if not isinstance(result, DotFlow):
57
+ raise InvalidWorkflowFactory(factory=self.params.workflow)
53
58
 
54
- def _build_server(self):
55
- base_url = os.getenv("SERVER_BASE_URL")
56
- user_token = os.getenv("SERVER_USER_TOKEN")
59
+ result.start(mode=self.params.mode)
57
60
 
58
- if not base_url or not user_token:
59
- return None
60
- return ServerAPI(base_url=base_url, user_token=user_token)
61
+ def _new_workflow(self):
62
+ storage = self._build_storage()
63
+ if storage is None:
64
+ return DotFlow()
65
+ return DotFlow(config=Config(storage=storage))
61
66
 
62
67
  def _build_storage(self):
63
68
  if not self.params.storage:
dotflow/cli/setup.py CHANGED
@@ -16,7 +16,9 @@ from dotflow.core.exception import (
16
16
  MESSAGE_UNKNOWN_ERROR,
17
17
  ExecutionModeNotExist,
18
18
  ImportModuleError,
19
+ InvalidWorkflowFactory,
19
20
  MissingActionDecorator,
21
+ WorkflowFlagConflict,
20
22
  )
21
23
  from dotflow.core.types import TypeExecution, TypeOverlap, TypeStorage
22
24
  from dotflow.logging import logger
@@ -56,12 +58,16 @@ class Command:
56
58
  self.cmd_init.set_defaults(exec=InitCommand)
57
59
 
58
60
  def setup_start(self):
59
- self.cmd_start = self.subparsers.add_parser("start", help="Start")
60
- self.cmd_start = self.cmd_start.add_argument_group(
61
- "Usage: dotflow start [OPTIONS]"
61
+ cmd_start_parser = self.subparsers.add_parser("start", help="Start")
62
+ entry_group = cmd_start_parser.add_mutually_exclusive_group(
63
+ required=True
62
64
  )
65
+ entry_group.add_argument("-s", "--step")
66
+ entry_group.add_argument("-w", "--workflow")
63
67
 
64
- self.cmd_start.add_argument("-s", "--step", required=True)
68
+ self.cmd_start = cmd_start_parser.add_argument_group(
69
+ "Usage: dotflow start [OPTIONS]"
70
+ )
65
71
  self.cmd_start.add_argument("-c", "--callback", default=basic_callback)
66
72
  self.cmd_start.add_argument("-i", "--initial-context")
67
73
  self.cmd_start.add_argument(
@@ -88,7 +94,7 @@ class Command:
88
94
  ],
89
95
  )
90
96
 
91
- self.cmd_start.set_defaults(exec=StartCommand)
97
+ cmd_start_parser.set_defaults(exec=StartCommand)
92
98
 
93
99
  def setup_schedule(self):
94
100
  self.cmd_schedule = self.subparsers.add_parser(
@@ -233,6 +239,12 @@ class Command:
233
239
  except ImportModuleError as err:
234
240
  print(settings.WARNING_ALERT, err)
235
241
 
242
+ except InvalidWorkflowFactory as err:
243
+ print(settings.WARNING_ALERT, err)
244
+
245
+ except WorkflowFlagConflict as err:
246
+ print(settings.WARNING_ALERT, err)
247
+
236
248
  except KeyboardInterrupt:
237
249
  print("\n", settings.INFO_ALERT, "Aborted.")
238
250
 
dotflow/core/engine.py CHANGED
@@ -257,13 +257,14 @@ class TaskEngine:
257
257
 
258
258
  for new in ordered_list:
259
259
  new_object = getattr(class_instance, new[1])
260
+ task_ulid = str(ULID())
260
261
  try:
261
262
  subcontext = new_object(
262
263
  initial_context=self.task.initial_context,
263
264
  previous_context=previous_context,
264
265
  task=self.task,
265
266
  )
266
- subcontext.task_id = str(ULID())
267
+ subcontext.task_id = task_ulid
267
268
  new_context.storage.append(subcontext)
268
269
  previous_context = subcontext
269
270
 
@@ -277,7 +278,7 @@ class TaskEngine:
277
278
  previous_context=previous_context,
278
279
  task=self.task,
279
280
  )
280
- subcontext.task_id = str(ULID())
281
+ subcontext.task_id = task_ulid
281
282
  new_context.storage.append(subcontext)
282
283
  previous_context = subcontext
283
284
 
dotflow/core/exception.py CHANGED
@@ -15,6 +15,12 @@ MESSAGE_PROBLEM_ORDERING = (
15
15
  MESSAGE_MODULE_NOT_FOUND = (
16
16
  "Module '{module}' not found. Please install with 'pip install {library}'"
17
17
  )
18
+ MESSAGE_INVALID_WORKFLOW_FACTORY = (
19
+ "'{factory}' must be a callable that returns a DotFlow instance."
20
+ )
21
+ MESSAGE_WORKFLOW_FLAG_CONFLICT = (
22
+ "{flag} is only valid with --step and cannot be used with --workflow."
23
+ )
18
24
 
19
25
 
20
26
  class MissingActionDecorator(Exception):
@@ -49,6 +55,18 @@ class ModuleNotFound(Exception):
49
55
  )
50
56
 
51
57
 
58
+ class InvalidWorkflowFactory(Exception):
59
+ def __init__(self, factory: str):
60
+ super().__init__(
61
+ MESSAGE_INVALID_WORKFLOW_FACTORY.format(factory=factory)
62
+ )
63
+
64
+
65
+ class WorkflowFlagConflict(Exception):
66
+ def __init__(self, flag: str):
67
+ super().__init__(MESSAGE_WORKFLOW_FLAG_CONFLICT.format(flag=flag))
68
+
69
+
52
70
  class ExecutionWithClassError(Exception):
53
71
  def __init__(self):
54
72
  super().__init__("Unknown")
@@ -6,7 +6,6 @@ from dotflow.providers.notify_default import NotifyDefault
6
6
  from dotflow.providers.notify_discord import NotifyDiscord
7
7
  from dotflow.providers.notify_telegram import NotifyTelegram
8
8
  from dotflow.providers.scheduler_default import SchedulerDefault
9
- from dotflow.providers.server_api import ServerAPI
10
9
  from dotflow.providers.server_default import ServerDefault
11
10
  from dotflow.providers.storage_default import StorageDefault
12
11
  from dotflow.providers.storage_file import StorageFile
@@ -21,7 +20,6 @@ __all__ = [
21
20
  "NotifyTelegram",
22
21
  "SchedulerCron",
23
22
  "SchedulerDefault",
24
- "ServerAPI",
25
23
  "ServerDefault",
26
24
  "StorageDefault",
27
25
  "StorageFile",
@@ -1,23 +1,116 @@
1
- """ServerDefault — no-op server provider."""
1
+ """ServerDefault — no-op or managed HTTP server provider."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
6
+ from functools import wraps
5
7
  from typing import Any
6
8
 
9
+ from requests import patch, post
10
+ from requests.exceptions import RequestException
11
+
7
12
  from dotflow.abc.server import Server
13
+ from dotflow.logging import logger
14
+
15
+
16
+ def managed(method):
17
+ @wraps(method)
18
+ def wrapper(self, *args, **kwargs):
19
+ if not self._managed:
20
+ return
21
+ return method(self, *args, **kwargs)
22
+
23
+ return wrapper
8
24
 
9
25
 
10
26
  class ServerDefault(Server):
11
- """Default Server implementation (no-op)."""
27
+ """Default Server provider with auto-detected managed mode.
28
+
29
+ When ``SERVER_BASE_URL`` and ``SERVER_USER_TOKEN`` env vars are set,
30
+ sends workflow and task events to the remote API. Otherwise, all
31
+ methods are no-ops.
32
+ """
33
+
34
+ MAX_RESULT_SIZE = 5_000_000
35
+ TIMEOUT = 15.0
36
+
37
+ ENDPOINT_WORKFLOWS = "/workflows"
38
+ ENDPOINT_WORKFLOW = "/workflows/{workflow_id}"
39
+ ENDPOINT_TASKS = "/workflows/{workflow_id}/tasks"
40
+ ENDPOINT_TASK = "/workflows/{workflow_id}/tasks/{task_id}"
41
+
42
+ def __init__(self) -> None:
43
+ base_url = os.getenv("SERVER_BASE_URL")
44
+ user_token = os.getenv("SERVER_USER_TOKEN")
45
+ self._managed = bool(base_url and user_token)
46
+
47
+ self._base_url = base_url.rstrip("/") if base_url else None
48
+ self._user_token = user_token
49
+
50
+ @property
51
+ def _headers(self) -> dict:
52
+ return {
53
+ "Authorization": f"Bearer {self._user_token}",
54
+ "Content-Type": "application/json",
55
+ }
56
+
57
+ def _post(self, url: str, json: dict) -> None:
58
+ try:
59
+ response = post(
60
+ url,
61
+ json=json,
62
+ headers=self._headers,
63
+ timeout=self.TIMEOUT,
64
+ )
65
+ logger.info("POST %s [%s]", url, response.status_code)
66
+ except RequestException as error:
67
+ logger.error("POST %s failed: %s", url, error)
68
+
69
+ def _patch(self, url: str, json: dict) -> None:
70
+ try:
71
+ response = patch(
72
+ url,
73
+ json=json,
74
+ headers=self._headers,
75
+ timeout=self.TIMEOUT,
76
+ )
77
+ logger.info("PATCH %s [%s]", url, response.status_code)
78
+ except RequestException as error:
79
+ logger.error("PATCH %s failed: %s", url, error)
12
80
 
81
+ @managed
13
82
  def create_workflow(self, workflow: Any) -> None:
14
- pass
83
+ self._post(
84
+ self._base_url + self.ENDPOINT_WORKFLOWS,
85
+ json={"id": str(workflow)},
86
+ )
15
87
 
88
+ @managed
16
89
  def update_workflow(self, workflow: Any, status: str = "") -> None:
17
- pass
90
+ self._patch(
91
+ self._base_url
92
+ + self.ENDPOINT_WORKFLOW.format(workflow_id=workflow),
93
+ json={"status": status},
94
+ )
18
95
 
96
+ @managed
19
97
  def create_task(self, task: Any) -> None:
20
- pass
98
+ data = task.result(max=self.MAX_RESULT_SIZE)
99
+ data["id"] = data.pop("task_id", task.task_id)
100
+ self._post(
101
+ self._base_url
102
+ + self.ENDPOINT_TASKS.format(workflow_id=task.workflow_id),
103
+ json=data,
104
+ )
21
105
 
106
+ @managed
22
107
  def update_task(self, task: Any) -> None:
23
- pass
108
+ data = task.result(max=self.MAX_RESULT_SIZE)
109
+ self._patch(
110
+ self._base_url
111
+ + self.ENDPOINT_TASK.format(
112
+ workflow_id=task.workflow_id,
113
+ task_id=task.task_id,
114
+ ),
115
+ json=data,
116
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotflow
3
- Version: 0.16.0.dev2
3
+ Version: 0.16.0.dev4
4
4
  Summary: 🎲 Dotflow turns an idea into flow! Lightweight Python library for execution pipelines with retry, parallel, cron and async support.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -1,4 +1,4 @@
1
- dotflow/__init__.py,sha256=ZTvhDm0gGdFeA5QstasHnD-4keZBIdwnwvFNYUzDk7c,351
1
+ dotflow/__init__.py,sha256=wJ-Qvt1kMdT5G76fioVTPUDK8qzEP8Grr19h3kdQEAA,351
2
2
  dotflow/abc/__init__.py,sha256=UIk84jCJYoHAk4In_eVXAaFAnNE63lI3dVP5qunyBJs,32
3
3
  dotflow/abc/file.py,sha256=QEDIS-AXaW3GK5q5ckhNdoC5A3p8oOFE7VyNGSvx540,296
4
4
  dotflow/abc/flow.py,sha256=j6m9u13UXgBCIhgrUwzvBBYHTDul_DfERD4DWA1BQBw,1436
@@ -19,9 +19,9 @@ dotflow/cli/commands/deploy.py,sha256=qUeZx-C00OfOEMURNZ4LMRI4qYOwwDS_ajOGtLa_Wb
19
19
  dotflow/cli/commands/init.py,sha256=VySDg1gNx_8jH0vfhc15BhBJR3Yckw91v26ijyGz5Pw,387
20
20
  dotflow/cli/commands/log.py,sha256=AJmDQM1ulbQg7_wZRsx3xac_7_6WlP8eA4pBWhpNBTo,590
21
21
  dotflow/cli/commands/schedule.py,sha256=C9wvgSowV4xgQqrx6pjNZk5-709uNDfsA7xGBjjbZYA,1442
22
- dotflow/cli/commands/start.py,sha256=kvnJIme_NJdK80GUXKZhxteEqpQS9V_WB-v_eFyR3q0,2075
22
+ dotflow/cli/commands/start.py,sha256=FBtuSY5KKuNWbqxppKVG-QrSc5fLbqtwrRELgGk7c9g,2425
23
23
  dotflow/cli/commands/storage.py,sha256=b3CMBSFGEqYxaI9gwiO90MIirnW82CcM3GUf4XJGVYs,1549
24
- dotflow/cli/setup.py,sha256=baCsFM8jHoWYsQtYnq0t8NkLg8ScvV_ihmSEofuPfHo,7708
24
+ dotflow/cli/setup.py,sha256=MDt_yNOdyDIhQHQfrWjXCvvnO5i0Ul-fwa7Dag2F6h0,8094
25
25
  dotflow/cli/validators/__init__.py,sha256=JKD-C4igDghgLjU2eBbpHkThUapoJ2mJRtC_YkeEvHg,121
26
26
  dotflow/cli/validators/start.py,sha256=ziQQWpLds6K_XnkiZt3m8bSs3KPdDSQzIuEiea5slks,466
27
27
  dotflow/cloud/__init__.py,sha256=N8XuL0M9P2vbP3WssHI85J8TSofVHo5llOI8EXp9RU0,21
@@ -68,8 +68,8 @@ dotflow/core/context.py,sha256=X72lG-ISBHIhS03X_FalG0THdgcLpiWrJEsyH2u-g3Q,2893
68
68
  dotflow/core/decorators/__init__.py,sha256=xPQMuZWV8TnIAH8uTF-Be9NoG46K7OmfJx_7GSWO_bc,101
69
69
  dotflow/core/decorators/time.py,sha256=46mI6Q8EZYkxU5DRnPJ5B8KgDBdR1bKivEm0u4pqwAg,300
70
70
  dotflow/core/dotflow.py,sha256=1dj854Q9GwIyznkU6P5nmNInPCFA7F_lcHswKScEj-E,2609
71
- dotflow/core/engine.py,sha256=4J8ZmM8vymhc0q6m1e65PPM4t23eDEGXLFgnQDHhxv4,9218
72
- dotflow/core/exception.py,sha256=wxNLcDE3n2HjRsSioNo86Fd9V4K7oZZDL-FD0mcZxDg,2242
71
+ dotflow/core/engine.py,sha256=lSb_txhk8PwswtaE2kCCoI8qw8rS1Nvpw5QOkYGvo-c,9250
72
+ dotflow/core/exception.py,sha256=e_IrsKNrRI3hBDgHgrnx7R3jVwsFP7wXUT8QRFxjbew,2800
73
73
  dotflow/core/module.py,sha256=5-19cKMa-BW3TVD1XVxiB_mPLTaeTIHfvVAvkkK3lmU,760
74
74
  dotflow/core/serializers/__init__.py,sha256=2b2vyCuXmvS8N_K-ePuCrWC9yvMqLPBMeORjAWS5U70,35
75
75
  dotflow/core/serializers/task.py,sha256=zpiXyQGzKsdA3dGUysFFlNdmGBsyqk-w0C5Uj4ubex0,4668
@@ -85,7 +85,7 @@ dotflow/core/types/workflow.py,sha256=Obc3SpXCFW-ZDxPAirdYVK7W21G_j8WYY6e0nwYIk6
85
85
  dotflow/core/workflow.py,sha256=d16JEi33I8kw9cju5Tt7v7-GIfu2HKNkhTFfY-c9tMI,14813
86
86
  dotflow/logging.py,sha256=xjzHyAHIMLCtt1Kto_gZaFCzr_fyc51z1C9je2sm3ds,292
87
87
  dotflow/main.py,sha256=ZxGTH_gX6CnKO9wb1vfjHpLQwaD5dLIBWM82K_puZEI,252
88
- dotflow/providers/__init__.py,sha256=1U_tgpbCglgbq0sv7ft56vZXjlCA-n0XX67SnMNEV0o,2273
88
+ dotflow/providers/__init__.py,sha256=aQigvJuf1vq5X-5-nWp3ET6uDp-Ek0l34nflGcZbjX8,2205
89
89
  dotflow/providers/log_default.py,sha256=1X9jYqylwTMy1AJt5552SgmVWMR_B_JFcMe8QE8XHgQ,1796
90
90
  dotflow/providers/log_opentelemetry.py,sha256=6A1LEVH3qNxAnqt6Zil23Tb7lZqL920SfstZ_TYX988,3094
91
91
  dotflow/providers/log_sentry.py,sha256=ttVHkiFc1SRbt04xNtKJGtRlpfaN4JBVg5ZJnlD0-04,2695
@@ -96,8 +96,7 @@ dotflow/providers/notify_discord.py,sha256=9cOt3YwJzuZzpNQlMLdc3sfJ5hfFRBBAmpNoU
96
96
  dotflow/providers/notify_telegram.py,sha256=xiyeqVSo_xQg3FR3_Q6Dnx2Qk8Sih2J7tkIVxkFSefk,3230
97
97
  dotflow/providers/scheduler_cron.py,sha256=wRXI5g0fDrYkBgD5zAMa5T8xxjU9v1SjDNpmsQD8nwA,8245
98
98
  dotflow/providers/scheduler_default.py,sha256=mkgMz5D6mROTDDY5juGXV4IgEDRPM0yW8_NJWDVfRTQ,258
99
- dotflow/providers/server_api.py,sha256=VNFfWly8EVNo7PSoVBF1hQJIwb1cPtULv34v3KHeu0U,2515
100
- dotflow/providers/server_default.py,sha256=RRJMwN2oyyS_gHPif2fPJRGNsmTw2e3BmXVuQMYSR2M,500
99
+ dotflow/providers/server_default.py,sha256=T-Ex3BHkXWiF5pWFu65fKtaoJUdjOLnxaRA40H44qjk,3418
101
100
  dotflow/providers/storage_default.py,sha256=ijE6cN0TlmTn8J5TiNvq_xakoK3JmciqI7qJWxesEBM,568
102
101
  dotflow/providers/storage_file.py,sha256=N4BPiDWWDOUbh5suzWvYzX6ZUtjhrfdZgSqwBPDW__I,2154
103
102
  dotflow/providers/storage_gcs.py,sha256=LgkS0zjlPElbvPhk7-uVKbNTnVn2OTtk8h5XltWIOH0,2484
@@ -111,8 +110,8 @@ dotflow/utils/__init__.py,sha256=AXiUm5p-FVG9SBWPxdG6jV0ft49EK6c40ME4aznPWQc,366
111
110
  dotflow/utils/basic_functions.py,sha256=S-maghYauG5QkMtHdm7qNcxBkKkSoE4akHSiI178Bwg,131
112
111
  dotflow/utils/error_handler.py,sha256=I8DY2AKR-WxKl8k7aUJPExS6wlzKVPy_u42ZJ9gYyts,272
113
112
  dotflow/utils/tools.py,sha256=RtC0D1SnaNCB02cT-l0Dw8Wl6uDSQSABoZH2kdD1PJQ,787
114
- dotflow-0.16.0.dev2.dist-info/METADATA,sha256=i6enVoDSnIaKacPBZnBEIgPatqWtSTysXE9Nal97FOM,40731
115
- dotflow-0.16.0.dev2.dist-info/WHEEL,sha256=Vz2fHgx6HFtSwhs8KvkHLqH5Ea4w1_rner5uNVGCeIE,88
116
- dotflow-0.16.0.dev2.dist-info/entry_points.txt,sha256=3AW0pYKZpaMW6WZmN3t71X0FuwxQdivmQwe47_6yBp4,45
117
- dotflow-0.16.0.dev2.dist-info/licenses/LICENSE,sha256=0h5VY_8dcAiTIGt0weGPV6cjxdPGB37Pjusxm_WAiPU,10766
118
- dotflow-0.16.0.dev2.dist-info/RECORD,,
113
+ dotflow-0.16.0.dev4.dist-info/METADATA,sha256=ODUQfHLoSp1YdXb3EylXMAbv-DX0j9IObYt3ucLwvTQ,40731
114
+ dotflow-0.16.0.dev4.dist-info/WHEEL,sha256=Vz2fHgx6HFtSwhs8KvkHLqH5Ea4w1_rner5uNVGCeIE,88
115
+ dotflow-0.16.0.dev4.dist-info/entry_points.txt,sha256=3AW0pYKZpaMW6WZmN3t71X0FuwxQdivmQwe47_6yBp4,45
116
+ dotflow-0.16.0.dev4.dist-info/licenses/LICENSE,sha256=0h5VY_8dcAiTIGt0weGPV6cjxdPGB37Pjusxm_WAiPU,10766
117
+ dotflow-0.16.0.dev4.dist-info/RECORD,,
@@ -1,88 +0,0 @@
1
- """ServerAPI — sends workflow/task events to a remote HTTP endpoint."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import Any
7
-
8
- from requests import patch, post
9
- from requests.exceptions import RequestException
10
-
11
- from dotflow.abc.server import Server
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class ServerAPI(Server):
17
- """HTTP implementation of the Server ABC."""
18
-
19
- def __init__(
20
- self,
21
- base_url: str,
22
- user_token: str,
23
- timeout: float = 15.0,
24
- ):
25
- self._base_url = base_url.rstrip("/")
26
- self._user_token = user_token
27
- self._timeout = timeout
28
-
29
- @property
30
- def _headers(self) -> dict:
31
- return {
32
- "Authorization": f"Bearer {self._user_token}",
33
- "Content-Type": "application/json",
34
- }
35
-
36
- def _post(self, url: str, json: dict) -> None:
37
- try:
38
- response = post(
39
- url,
40
- json=json,
41
- headers=self._headers,
42
- timeout=self._timeout,
43
- )
44
- logger.info("POST %s [%s]", url, response.status_code)
45
- except RequestException as error:
46
- logger.error("POST %s failed: %s", url, error)
47
-
48
- def _patch(self, url: str, json: dict) -> None:
49
- try:
50
- response = patch(
51
- url,
52
- json=json,
53
- headers=self._headers,
54
- timeout=self._timeout,
55
- )
56
- logger.info("PATCH %s [%s]", url, response.status_code)
57
- except RequestException as error:
58
- logger.error("PATCH %s failed: %s", url, error)
59
-
60
- def create_workflow(self, workflow: Any) -> None:
61
- self._post(
62
- f"{self._base_url}/workflows",
63
- json={"id": str(workflow)},
64
- )
65
-
66
- def update_workflow(self, workflow: Any, status: str = "") -> None:
67
- self._patch(
68
- f"{self._base_url}/workflows/{workflow}",
69
- json={"status": status},
70
- )
71
-
72
- def create_task(self, task: Any) -> None:
73
- data = task.result(max=5_000_000)
74
- data["id"] = data.pop("task_id", task.task_id)
75
- self._post(
76
- f"{self._base_url}/workflows/{task.workflow_id}/tasks",
77
- json=data,
78
- )
79
-
80
- def update_task(self, task: Any) -> None:
81
- data = task.result(max=5_000_000)
82
- self._patch(
83
- (
84
- f"{self._base_url}/workflows/{task.workflow_id}"
85
- f"/tasks/{task.task_id}"
86
- ),
87
- json=data,
88
- )