ddeutil-workflow 0.0.15__py3-none-any.whl → 0.0.16__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.
ddeutil/workflow/utils.py CHANGED
@@ -7,7 +7,6 @@ from __future__ import annotations
7
7
 
8
8
  import inspect
9
9
  import logging
10
- import os
11
10
  import stat
12
11
  import time
13
12
  from abc import ABC, abstractmethod
@@ -15,7 +14,7 @@ from ast import Call, Constant, Expr, Module, Name, parse
15
14
  from collections.abc import Iterator
16
15
  from dataclasses import field
17
16
  from datetime import date, datetime
18
- from functools import cached_property, wraps
17
+ from functools import wraps
19
18
  from hashlib import md5
20
19
  from importlib import import_module
21
20
  from inspect import isfunction
@@ -30,17 +29,15 @@ try:
30
29
  except ImportError:
31
30
  from typing_extensions import ParamSpec
32
31
 
33
- from ddeutil.core import getdot, hasdot, hash_str, import_string, lazy, str2bool
34
- from ddeutil.io import PathData, PathSearch, YamlFlResolve, search_env_replace
35
- from ddeutil.io.models.lineage import dt_now
36
- from pydantic import BaseModel, ConfigDict, Field
32
+ from ddeutil.core import getdot, hasdot, hash_str, import_string, lazy
33
+ from ddeutil.io import search_env_replace
34
+ from pydantic import BaseModel, Field
37
35
  from pydantic.dataclasses import dataclass
38
- from pydantic.functional_serializers import field_serializer
39
36
  from pydantic.functional_validators import model_validator
40
37
  from typing_extensions import Self
41
38
 
42
39
  from .__types import DictData, Matrix, Re
43
- from .conf import config
40
+ from .conf import config, load_config
44
41
  from .exceptions import ParamValueException, UtilException
45
42
 
46
43
  P = ParamSpec("P")
@@ -50,16 +47,30 @@ AnyModelType = type[AnyModel]
50
47
  logger = logging.getLogger("ddeutil.workflow")
51
48
 
52
49
 
53
- def get_diff_sec(dt: datetime, tz: ZoneInfo | None = None) -> int:
50
+ def get_dt_now(tz: ZoneInfo | None = None) -> datetime: # pragma: no cov
51
+ """Return the current datetime object.
52
+
53
+ :param tz:
54
+ :return: The current datetime object that use an input timezone or UTC.
55
+ """
56
+ return datetime.now(tz=(tz or ZoneInfo("UTC")))
57
+
58
+
59
+ def get_diff_sec(
60
+ dt: datetime, tz: ZoneInfo | None = None
61
+ ) -> int: # pragma: no cov
54
62
  """Return second value that come from diff of an input datetime and the
55
63
  current datetime with specific timezone.
64
+
65
+ :param dt:
66
+ :param tz:
56
67
  """
57
68
  return round(
58
69
  (dt - datetime.now(tz=(tz or ZoneInfo("UTC")))).total_seconds()
59
70
  )
60
71
 
61
72
 
62
- def delay(second: float = 0) -> None:
73
+ def delay(second: float = 0) -> None: # pragma: no cov
63
74
  """Delay time that use time.sleep with random second value between
64
75
  0.00 - 0.99 seconds.
65
76
 
@@ -68,203 +79,6 @@ def delay(second: float = 0) -> None:
68
79
  time.sleep(second + randrange(0, 99, step=10) / 100)
69
80
 
70
81
 
71
- class Engine(BaseModel):
72
- """Engine Model"""
73
-
74
- paths: PathData = Field(default_factory=PathData)
75
- registry: list[str] = Field(
76
- default_factory=lambda: ["ddeutil.workflow"], # pragma: no cover
77
- )
78
- registry_filter: list[str] = Field(
79
- default_factory=lambda: ["ddeutil.workflow.utils"], # pragma: no cover
80
- )
81
-
82
- @model_validator(mode="before")
83
- def __prepare_registry(cls, values: DictData) -> DictData:
84
- """Prepare registry value that passing with string type. It convert the
85
- string type to list of string.
86
- """
87
- if (_regis := values.get("registry")) and isinstance(_regis, str):
88
- values["registry"] = [_regis]
89
- if (_regis_filter := values.get("registry_filter")) and isinstance(
90
- _regis, str
91
- ):
92
- values["registry_filter"] = [_regis_filter]
93
- return values
94
-
95
-
96
- class CoreConf(BaseModel):
97
- """Core Config Model"""
98
-
99
- model_config = ConfigDict(arbitrary_types_allowed=True)
100
-
101
- tz: ZoneInfo = Field(default_factory=lambda: ZoneInfo("UTC"))
102
-
103
-
104
- class ConfParams(BaseModel):
105
- """Params Model"""
106
-
107
- engine: Engine = Field(
108
- default_factory=Engine,
109
- description="A engine mapping values.",
110
- )
111
- core: CoreConf = Field(
112
- default_factory=CoreConf,
113
- description="A core config value",
114
- )
115
-
116
-
117
- def load_config() -> ConfParams:
118
- """Load Config data from ``workflows-conf.yaml`` file.
119
-
120
- Configuration Docs:
121
- ---
122
- :var engine.registry:
123
- :var engine.registry_filter:
124
- :var paths.root:
125
- :var paths.conf:
126
- """
127
- root_path: str = os.getenv("WORKFLOW_ROOT_PATH", ".")
128
-
129
- regis: list[str] = ["ddeutil.workflow"]
130
- if regis_env := os.getenv("WORKFLOW_CORE_REGISTRY"):
131
- regis = [r.strip() for r in regis_env.split(",")]
132
-
133
- regis_filter: list[str] = ["ddeutil.workflow.utils"]
134
- if regis_filter_env := os.getenv("WORKFLOW_CORE_REGISTRY_FILTER"):
135
- regis_filter = [r.strip() for r in regis_filter_env.split(",")]
136
-
137
- conf_path: str = (
138
- f"{root_path}/{conf_env}"
139
- if (conf_env := os.getenv("WORKFLOW_CORE_PATH_CONF"))
140
- else None
141
- )
142
- return ConfParams.model_validate(
143
- obj={
144
- "engine": {
145
- "registry": regis,
146
- "registry_filter": regis_filter,
147
- "paths": {
148
- "root": root_path,
149
- "conf": conf_path,
150
- },
151
- },
152
- }
153
- )
154
-
155
-
156
- class SimLoad:
157
- """Simple Load Object that will search config data by given some identity
158
- value like name of workflow or on.
159
-
160
- :param name: A name of config data that will read by Yaml Loader object.
161
- :param params: A Params model object.
162
- :param externals: An external parameters
163
-
164
- Noted:
165
-
166
- The config data should have ``type`` key for modeling validation that
167
- make this loader know what is config should to do pass to.
168
-
169
- ... <identity-key>:
170
- ... type: <importable-object>
171
- ... <key-data>: <value-data>
172
- ... ...
173
-
174
- """
175
-
176
- def __init__(
177
- self,
178
- name: str,
179
- params: ConfParams,
180
- externals: DictData | None = None,
181
- ) -> None:
182
- self.data: DictData = {}
183
- for file in PathSearch(params.engine.paths.conf).files:
184
- if any(file.suffix.endswith(s) for s in (".yml", ".yaml")) and (
185
- data := YamlFlResolve(file).read().get(name, {})
186
- ):
187
- self.data = data
188
-
189
- # VALIDATE: check the data that reading should not empty.
190
- if not self.data:
191
- raise ValueError(f"Config {name!r} does not found on conf path")
192
-
193
- self.conf_params: ConfParams = params
194
- self.externals: DictData = externals or {}
195
- self.data.update(self.externals)
196
-
197
- @classmethod
198
- def finds(
199
- cls,
200
- obj: object,
201
- params: ConfParams,
202
- *,
203
- include: list[str] | None = None,
204
- exclude: list[str] | None = None,
205
- ) -> Iterator[tuple[str, DictData]]:
206
- """Find all data that match with object type in config path. This class
207
- method can use include and exclude list of identity name for filter and
208
- adds-on.
209
-
210
- :param obj:
211
- :param params:
212
- :param include:
213
- :param exclude:
214
- :rtype: Iterator[tuple[str, DictData]]
215
- """
216
- exclude: list[str] = exclude or []
217
- for file in PathSearch(params.engine.paths.conf).files:
218
- if any(file.suffix.endswith(s) for s in (".yml", ".yaml")) and (
219
- values := YamlFlResolve(file).read()
220
- ):
221
- for key, data in values.items():
222
- if key in exclude:
223
- continue
224
- if issubclass(get_type(data["type"], params), obj) and (
225
- include is None or all(i in data for i in include)
226
- ):
227
- yield key, data
228
-
229
- @cached_property
230
- def type(self) -> AnyModelType:
231
- """Return object of string type which implement on any registry. The
232
- object type.
233
-
234
- :rtype: AnyModelType
235
- """
236
- if not (_typ := self.data.get("type")):
237
- raise ValueError(
238
- f"the 'type' value: {_typ} does not exists in config data."
239
- )
240
- return get_type(_typ, self.conf_params)
241
-
242
-
243
- class Loader(SimLoad):
244
- """Loader Object that get the config `yaml` file from current path.
245
-
246
- :param name: A name of config data that will read by Yaml Loader object.
247
- :param externals: An external parameters
248
- """
249
-
250
- @classmethod
251
- def finds(
252
- cls,
253
- obj: object,
254
- *,
255
- include: list[str] | None = None,
256
- exclude: list[str] | None = None,
257
- **kwargs,
258
- ) -> DictData:
259
- """Override the find class method from the Simple Loader object."""
260
- return super().finds(
261
- obj=obj, params=load_config(), include=include, exclude=exclude
262
- )
263
-
264
- def __init__(self, name: str, externals: DictData) -> None:
265
- super().__init__(name, load_config(), externals)
266
-
267
-
268
82
  def gen_id(
269
83
  value: Any,
270
84
  *,
@@ -272,9 +86,9 @@ def gen_id(
272
86
  unique: bool = False,
273
87
  ) -> str:
274
88
  """Generate running ID for able to tracking. This generate process use `md5`
275
- algorithm function if ``WORKFLOW_CORE_PIPELINE_ID_SIMPLE`` set to false.
276
- But it will cut this hashing value length to 10 it the setting value set to
277
- true.
89
+ algorithm function if ``WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE`` set to
90
+ false. But it will cut this hashing value length to 10 it the setting value
91
+ set to true.
278
92
 
279
93
  :param value: A value that want to add to prefix before hashing with md5.
280
94
  :param sensitive: A flag that convert the value to lower case before hashing
@@ -285,7 +99,7 @@ def gen_id(
285
99
  if not isinstance(value, str):
286
100
  value: str = str(value)
287
101
 
288
- if str2bool(os.getenv("WORKFLOW_CORE_PIPELINE_ID_SIMPLE", "true")):
102
+ if config.workflow_id_simple_mode:
289
103
  return hash_str(f"{(value if sensitive else value.lower())}", n=10) + (
290
104
  f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}" if unique else ""
291
105
  )
@@ -297,40 +111,22 @@ def gen_id(
297
111
  ).hexdigest()
298
112
 
299
113
 
300
- def get_type(t: str, params: ConfParams) -> AnyModelType:
301
- """Return import type from string importable value in the type key.
302
-
303
- :param t: A importable type string.
304
- :param params: A config parameters that use registry to search this
305
- type.
306
- :rtype: AnyModelType
307
- """
308
- try:
309
- # NOTE: Auto adding module prefix if it does not set
310
- return import_string(f"ddeutil.workflow.{t}")
311
- except ModuleNotFoundError:
312
- for registry in params.engine.registry:
313
- try:
314
- return import_string(f"{registry}.{t}")
315
- except ModuleNotFoundError:
316
- continue
317
- return import_string(f"{t}")
318
-
319
-
320
114
  class TagFunc(Protocol):
321
115
  """Tag Function Protocol"""
322
116
 
323
117
  name: str
324
118
  tag: str
325
119
 
326
- def __call__(self, *args, **kwargs): ... # pragma: no cove
120
+ def __call__(self, *args, **kwargs): ... # pragma: no cov
327
121
 
328
122
 
329
123
  ReturnTagFunc = Callable[P, TagFunc]
330
124
  DecoratorTagFunc = Callable[[Callable[[...], Any]], ReturnTagFunc]
331
125
 
332
126
 
333
- def tag(name: str, alias: str | None = None) -> DecoratorTagFunc:
127
+ def tag(
128
+ name: str, alias: str | None = None
129
+ ) -> DecoratorTagFunc: # pragma: no cov
334
130
  """Tag decorator function that set function attributes, ``tag`` and ``name``
335
131
  for making registries variable.
336
132
 
@@ -396,9 +192,14 @@ def make_registry(submodule: str) -> dict[str, Registry]:
396
192
  class BaseParam(BaseModel, ABC):
397
193
  """Base Parameter that use to make Params Model."""
398
194
 
399
- desc: Optional[str] = None
400
- required: bool = True
401
- type: str
195
+ desc: Optional[str] = Field(
196
+ default=None, description="A description of parameter providing."
197
+ )
198
+ required: bool = Field(
199
+ default=True,
200
+ description="A require flag that force to pass this parameter value.",
201
+ )
202
+ type: str = Field(description="A type of parameter.")
402
203
 
403
204
  @abstractmethod
404
205
  def receive(self, value: Optional[Any] = None) -> Any:
@@ -406,19 +207,20 @@ class BaseParam(BaseModel, ABC):
406
207
  "Receive value and validate typing before return valid value."
407
208
  )
408
209
 
409
- @field_serializer("type")
410
- def __serializer_type(self, value: str) -> str:
411
- """Serialize the value of the type field.
412
-
413
- :rtype: str
414
- """
415
- return value
416
-
417
210
 
418
211
  class DefaultParam(BaseParam):
419
- """Default Parameter that will check default if it required"""
212
+ """Default Parameter that will check default if it required. This model do
213
+ not implement the receive method.
214
+ """
420
215
 
421
- default: Optional[str] = None
216
+ required: bool = Field(
217
+ default=False,
218
+ description="A require flag for the default-able parameter value.",
219
+ )
220
+ default: Optional[str] = Field(
221
+ default=None,
222
+ description="A default value if parameter does not pass.",
223
+ )
422
224
 
423
225
  @abstractmethod
424
226
  def receive(self, value: Optional[Any] = None) -> Any:
@@ -429,9 +231,9 @@ class DefaultParam(BaseParam):
429
231
  @model_validator(mode="after")
430
232
  def __check_default(self) -> Self:
431
233
  """Check default value should pass when it set required."""
432
- if not self.required and self.default is None:
234
+ if self.required and self.default is None:
433
235
  raise ParamValueException(
434
- "Default should set when this parameter does not required."
236
+ "Default should be set when this parameter was required."
435
237
  )
436
238
  return self
437
239
 
@@ -440,8 +242,7 @@ class DatetimeParam(DefaultParam):
440
242
  """Datetime parameter."""
441
243
 
442
244
  type: Literal["datetime"] = "datetime"
443
- required: bool = False
444
- default: datetime = Field(default_factory=dt_now)
245
+ default: datetime = Field(default_factory=get_dt_now)
445
246
 
446
247
  def receive(self, value: str | datetime | date | None = None) -> datetime:
447
248
  """Receive value that match with datetime. If a input value pass with
@@ -463,7 +264,12 @@ class DatetimeParam(DefaultParam):
463
264
  f"Value that want to convert to datetime does not support for "
464
265
  f"type: {type(value)}"
465
266
  )
466
- return datetime.fromisoformat(value)
267
+ try:
268
+ return datetime.fromisoformat(value)
269
+ except ValueError:
270
+ raise ParamValueException(
271
+ f"Invalid isoformat string: {value!r}"
272
+ ) from None
467
273
 
468
274
 
469
275
  class StrParam(DefaultParam):
@@ -471,7 +277,7 @@ class StrParam(DefaultParam):
471
277
 
472
278
  type: Literal["str"] = "str"
473
279
 
474
- def receive(self, value: Optional[str] = None) -> str | None:
280
+ def receive(self, value: str | None = None) -> str | None:
475
281
  """Receive value that match with str.
476
282
 
477
283
  :param value: A value that want to validate with string parameter type.
@@ -486,8 +292,12 @@ class IntParam(DefaultParam):
486
292
  """Integer parameter."""
487
293
 
488
294
  type: Literal["int"] = "int"
295
+ default: Optional[int] = Field(
296
+ default=None,
297
+ description="A default value if parameter does not pass.",
298
+ )
489
299
 
490
- def receive(self, value: Optional[int] = None) -> int | None:
300
+ def receive(self, value: int | None = None) -> int | None:
491
301
  """Receive value that match with int.
492
302
 
493
303
  :param value: A value that want to validate with integer parameter type.
@@ -498,10 +308,9 @@ class IntParam(DefaultParam):
498
308
  if not isinstance(value, int):
499
309
  try:
500
310
  return int(str(value))
501
- except TypeError as err:
311
+ except ValueError as err:
502
312
  raise ParamValueException(
503
- f"Value that want to convert to integer does not support "
504
- f"for type: {type(value)}"
313
+ f"Value can not convert to int, {value}, with base 10"
505
314
  ) from err
506
315
  return value
507
316
 
@@ -510,15 +319,19 @@ class ChoiceParam(BaseParam):
510
319
  """Choice parameter."""
511
320
 
512
321
  type: Literal["choice"] = "choice"
513
- options: list[str]
322
+ options: list[str] = Field(description="A list of choice parameters.")
323
+
324
+ def receive(self, value: str | None = None) -> str:
325
+ """Receive value that match with options.
514
326
 
515
- def receive(self, value: Optional[str] = None) -> str:
516
- """Receive value that match with options."""
327
+ :param value: A value that want to select from the options field.
328
+ :rtype: str
329
+ """
517
330
  # NOTE:
518
331
  # Return the first value in options if does not pass any input value
519
332
  if value is None:
520
333
  return self.options[0]
521
- if any(value not in self.options):
334
+ if value not in self.options:
522
335
  raise ParamValueException(
523
336
  f"{value!r} does not match any value in choice options."
524
337
  )
@@ -545,7 +358,7 @@ class Result:
545
358
 
546
359
  status: int = field(default=2)
547
360
  context: DictData = field(default_factory=dict)
548
- start_at: datetime = field(default_factory=dt_now, compare=False)
361
+ start_at: datetime = field(default_factory=get_dt_now, compare=False)
549
362
  end_at: Optional[datetime] = field(default=None, compare=False)
550
363
 
551
364
  # NOTE: Ignore this field to compare another result model with __eq__.
@@ -577,15 +390,15 @@ class Result:
577
390
  :param running_id: A running ID that want to update on this model.
578
391
  :rtype: Self
579
392
  """
580
- self._parent_run_id = running_id
393
+ self._parent_run_id: str = running_id
581
394
  return self
582
395
 
583
396
  @property
584
- def parent_run_id(self):
397
+ def parent_run_id(self) -> str:
585
398
  return self._parent_run_id
586
399
 
587
400
  @property
588
- def run_id(self):
401
+ def run_id(self) -> str:
589
402
  return self._run_id
590
403
 
591
404
  def catch(self, status: int, context: DictData) -> Self:
@@ -622,8 +435,8 @@ class Result:
622
435
  self.__dict__["context"]["jobs"].update(result.context)
623
436
 
624
437
  # NOTE: Update running ID from an incoming result.
625
- self._parent_run_id = result.parent_run_id
626
- self._run_id = result.run_id
438
+ self._parent_run_id: str = result.parent_run_id
439
+ self._run_id: str = result.run_id
627
440
  return self
628
441
 
629
442
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.15
3
+ Version: 0.0.16
4
4
  Summary: Lightweight workflow orchestration with less dependencies
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Requires-Python: >=3.9.13
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: ddeutil >=0.4.0
26
- Requires-Dist: ddeutil-io >=0.1.13
25
+ Requires-Dist: ddeutil >=0.4.3
26
+ Requires-Dist: ddeutil-io[yaml] >=0.2.3
27
27
  Requires-Dist: python-dotenv ==1.0.1
28
28
  Requires-Dist: typer <1.0.0,==0.12.5
29
29
  Requires-Dist: schedule <2.0.0,==1.2.2
@@ -179,29 +179,31 @@ The main configuration that use to dynamic changing with your propose of this
179
179
  application. If any configuration values do not set yet, it will use default value
180
180
  and do not raise any error to you.
181
181
 
182
- | Environment | Component | Default | Description |
183
- |-------------------------------------|-----------|----------------------------------|----------------------------------------------------------------------------|
184
- | `WORKFLOW_ROOT_PATH` | Core | . | The root path of the workflow application |
185
- | `WORKFLOW_CORE_REGISTRY` | Core | src.ddeutil.workflow,tests.utils | List of importable string for the hook stage |
186
- | `WORKFLOW_CORE_REGISTRY_FILTER` | Core | ddeutil.workflow.utils | List of importable string for the filter template |
187
- | `WORKFLOW_CORE_PATH_CONF` | Core | conf | The config path that keep all template `.yaml` files |
188
- | `WORKFLOW_CORE_TIMEZONE` | Core | Asia/Bangkok | A Timezone string value that will pass to `ZoneInfo` object |
189
- | `WORKFLOW_CORE_STAGE_DEFAULT_ID` | Core | true | A flag that enable default stage ID that use for catch an execution output |
190
- | `WORKFLOW_CORE_STAGE_RAISE_ERROR` | Core | true | A flag that all stage raise StageException from stage execution |
191
- | `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | |
192
- | `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor |
193
- | `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode |
194
- | `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination |
195
- | `WORKFLOW_APP_PROCESS_WORKER` | Schedule | 2 | The maximum process worker number that run in scheduler app module |
196
- | `WORKFLOW_APP_SCHEDULE_PER_PROCESS` | Schedule | 100 | A schedule per process that run parallel |
197
- | `WORKFLOW_APP_STOP_BOUNDARY_DELTA` | Schedule | '{"minutes": 5, "seconds": 20}' | A time delta value that use to stop scheduler app in json string format |
182
+ | Environment | Component | Default | Description | Remark |
183
+ |:----------------------------------------|-----------|----------------------------------|--------------------------------------------------------------------------------------------------------------------|--------|
184
+ | `WORKFLOW_ROOT_PATH` | Core | . | The root path of the workflow application | |
185
+ | `WORKFLOW_CORE_REGISTRY` | Core | src.ddeutil.workflow,tests.utils | List of importable string for the hook stage | |
186
+ | `WORKFLOW_CORE_REGISTRY_FILTER` | Core | ddeutil.workflow.utils | List of importable string for the filter template | |
187
+ | `WORKFLOW_CORE_PATH_CONF` | Core | conf | The config path that keep all template `.yaml` files | |
188
+ | `WORKFLOW_CORE_TIMEZONE` | Core | Asia/Bangkok | A Timezone string value that will pass to `ZoneInfo` object | |
189
+ | `WORKFLOW_CORE_STAGE_DEFAULT_ID` | Core | true | A flag that enable default stage ID that use for catch an execution output | |
190
+ | `WORKFLOW_CORE_STAGE_RAISE_ERROR` | Core | false | A flag that all stage raise StageException from stage execution | |
191
+ | `WORKFLOW_CORE_JOB_DEFAULT_ID` | Core | false | A flag that enable default job ID that use for catch an execution output. The ID that use will be sequence number. | |
192
+ | `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | | |
193
+ | `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor | |
194
+ | `WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE` | Core | true | | |
195
+ | `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode | |
196
+ | `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination | |
197
+ | `WORKFLOW_APP_MAX_PROCESS` | Schedule | 2 | The maximum process worker number that run in scheduler app module | |
198
+ | `WORKFLOW_APP_MAX_SCHEDULE_PER_PROCESS` | Schedule | 100 | A schedule per process that run parallel | |
199
+ | `WORKFLOW_APP_STOP_BOUNDARY_DELTA` | Schedule | '{"minutes": 5, "seconds": 20}' | A time delta value that use to stop scheduler app in json string format | |
198
200
 
199
201
  **API Application**:
200
202
 
201
- | Environment | Component | Default | Description |
202
- |--------------------------------------|-----------|---------|-----------------------------------------------------------------------------------|
203
- | `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` | API | true | A flag that enable workflow route to manage execute manually and workflow logging |
204
- | `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` | API | true | A flag that enable run scheduler |
203
+ | Environment | Component | Default | Description | Remark |
204
+ |:--------------------------------------|-----------|---------|-----------------------------------------------------------------------------------|--------|
205
+ | `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` | API | true | A flag that enable workflow route to manage execute manually and workflow logging | |
206
+ | `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` | API | true | A flag that enable run scheduler | |
205
207
 
206
208
  ## :rocket: Deployment
207
209
 
@@ -224,3 +226,17 @@ like crontab job but via Python API.
224
226
  > [!NOTE]
225
227
  > If this package already deploy, it able to use
226
228
  > `uvicorn ddeutil.workflow.api:app --host 127.0.0.1 --port 80 --workers 4`
229
+
230
+ ### Docker Container
231
+
232
+ Create Docker image;
233
+
234
+ ```shell
235
+ $ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
236
+ ```
237
+
238
+ Run the above Docker image;
239
+
240
+ ```shell
241
+ $ docker run -i ddeutil-workflow:latest
242
+ ```
@@ -0,0 +1,22 @@
1
+ ddeutil/workflow/__about__.py,sha256=J3F-a05BmGpnyxPdhYxOJtDuTmXUjC0rvelcqfmfPRQ,28
2
+ ddeutil/workflow/__init__.py,sha256=-DIy8SGFsD7_wqp-V-K8v8jTxacmqrcyj_SFx1WS6qg,687
3
+ ddeutil/workflow/__types.py,sha256=yizLXzjQpBt_WPaof2pIyncitJvYeksw4Q1zYJeuCLA,3707
4
+ ddeutil/workflow/api.py,sha256=7LQeR9w2Mq_vFTjqEK3dPiI1de-ENC_eyozr_UG8JMA,4717
5
+ ddeutil/workflow/cli.py,sha256=GgVWPrSrG8ZTUGMTmHHngBi5LK0w5sAIMepciE5G_kc,3294
6
+ ddeutil/workflow/conf.py,sha256=Pzw2LGaKtOUgEvczp9_PRhzO2HBigQx5XUEdrlNQFm4,10244
7
+ ddeutil/workflow/cron.py,sha256=naWefHc3EnVo41Yf1zQeXOzF27YlTlnfj0XnQ6_HO-U,25514
8
+ ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
9
+ ddeutil/workflow/job.py,sha256=8198kktH8WqMR2H6VWx2Pq6TFc2gmBdH3qZvVERRJx0,22668
10
+ ddeutil/workflow/log.py,sha256=uxnxzeYTG7bu2ShchYehRnyr9YAdQyiMsUEhm8RbDDQ,6285
11
+ ddeutil/workflow/on.py,sha256=2Kt0GIgrp_na1lA-TdJz1Wwo_mdcgxhTZdQ5BU2rj0E,7236
12
+ ddeutil/workflow/repeat.py,sha256=mAj2BUDdcCe828gG-1NNxaz57mHebra5aC2VbjEzVJE,4939
13
+ ddeutil/workflow/route.py,sha256=HveQ2c5MDVYW5YdMsF6g30DX2liR6vNt3_20NirMGQ8,6769
14
+ ddeutil/workflow/scheduler.py,sha256=u1r5JhI7HSZzJ9__yruQen3mmo6Pd3slvEMtFfRFeOc,46995
15
+ ddeutil/workflow/stage.py,sha256=I5bGASI-UUCcS-figIHdrV_LycDu_2mwjt4eZBR3Z_E,24268
16
+ ddeutil/workflow/utils.py,sha256=PgPvcLoRYFobUUS56rSUFuN-zTyejgJ-gl36nNqQuWM,25117
17
+ ddeutil_workflow-0.0.16.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
18
+ ddeutil_workflow-0.0.16.dist-info/METADATA,sha256=_1jCgcqLcSKB2EHZcotmCtZec2KPKAYCidy28ZqQTe8,13190
19
+ ddeutil_workflow-0.0.16.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
20
+ ddeutil_workflow-0.0.16.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
21
+ ddeutil_workflow-0.0.16.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
22
+ ddeutil_workflow-0.0.16.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,22 +0,0 @@
1
- ddeutil/workflow/__about__.py,sha256=w_vBOopUg1crMbDyfdE0LgsxsncnhGYp0D39LSSnSVI,28
2
- ddeutil/workflow/__init__.py,sha256=-DIy8SGFsD7_wqp-V-K8v8jTxacmqrcyj_SFx1WS6qg,687
3
- ddeutil/workflow/__types.py,sha256=WWugALcayRiP0IQO-eBWK767_XxK7KGlY7SuVgyaJnk,3196
4
- ddeutil/workflow/api.py,sha256=cwju_qhY6m0kLtaoa77QLglC9tl7RjjZ4UnJYV3SlQQ,4810
5
- ddeutil/workflow/cli.py,sha256=Ikcq526WeIl-737-v55T0PwAZ2pNiZFxlN0Y-DjhDbQ,3374
6
- ddeutil/workflow/conf.py,sha256=D0g7rHXilpGwOD36QwVd9I5kEwqsAUA0Z3tAINS2Pws,1287
7
- ddeutil/workflow/cron.py,sha256=naWefHc3EnVo41Yf1zQeXOzF27YlTlnfj0XnQ6_HO-U,25514
8
- ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
9
- ddeutil/workflow/job.py,sha256=9H_2C0ikD5y6jLVdIBj8de4CdSpS632XOfqYVhM4bHI,21582
10
- ddeutil/workflow/log.py,sha256=Ev-Szi0KC_MmbFY4g4BWv6tUSmcLKWKZ03ZInmYPmgU,6490
11
- ddeutil/workflow/on.py,sha256=vsZG19mNoztDSB_ObD_4ZWPKgHYpBDJMWw97ZiTavNE,7237
12
- ddeutil/workflow/repeat.py,sha256=e3dekPTlMlxCCizfBYsZ8dD8Juy4rtfqDZJU3Iky2oA,5011
13
- ddeutil/workflow/route.py,sha256=ABEk-WlVo9XGFc7zCPbckX33URCNH7woQFU1keX_8PQ,6970
14
- ddeutil/workflow/scheduler.py,sha256=12Dd5CVphOVKjUwoiB8dCHt4WpYRPG3dSOt-pR6NNxc,46167
15
- ddeutil/workflow/stage.py,sha256=Avz1Mbb8WAP6kFn0bnN0p14-EnQ_AzdKr435JRxjkao,23844
16
- ddeutil/workflow/utils.py,sha256=XUD5hoygAyxi4xo1spTacoDNGKN2TlRob_o8qfCj4Pc,30993
17
- ddeutil_workflow-0.0.15.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
18
- ddeutil_workflow-0.0.15.dist-info/METADATA,sha256=6JvY9y-cT3WnirRva45NS582Iz7ZuXJZpsiCtN57OoA,11653
19
- ddeutil_workflow-0.0.15.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
20
- ddeutil_workflow-0.0.15.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
21
- ddeutil_workflow-0.0.15.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
22
- ddeutil_workflow-0.0.15.dist-info/RECORD,,