ddeutil-workflow 0.0.27__py3-none-any.whl → 0.0.29__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 +1 @@
1
- __version__: str = "0.0.27"
1
+ __version__: str = "0.0.29"
@@ -4,6 +4,7 @@
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
6
  from .__cron import CronJob, CronRunner
7
+ from .__types import Re
7
8
  from .conf import (
8
9
  Config,
9
10
  Loader,
@@ -27,6 +27,8 @@ Matrix = dict[str, Union[list[str], list[int]]]
27
27
 
28
28
 
29
29
  class Context(TypedDict):
30
+ """TypeDict support the Context."""
31
+
30
32
  params: dict[str, Any]
31
33
  jobs: dict[str, Any]
32
34
 
@@ -71,14 +73,14 @@ class Re:
71
73
  # - ${{ params.source?.schema }}
72
74
  #
73
75
  __re_caller: str = r"""
74
- \$
75
- {{
76
- \s*
76
+ \$ # start with $
77
+ {{ # value open with {{
78
+ \s* # whitespace or not
77
79
  (?P<caller>
78
80
  (?P<caller_prefix>(?:[a-zA-Z_-]+\??\.)*)
79
81
  (?P<caller_last>[a-zA-Z0-9_\-.'\"(\)[\]{}]+\??)
80
82
  )
81
- \s*
83
+ \s* # whitespace or not
82
84
  (?P<post_filters>
83
85
  (?:
84
86
  \|\s*
@@ -88,7 +90,7 @@ class Re:
88
90
  )\s*
89
91
  )*
90
92
  )
91
- }}
93
+ }} # value close with }}
92
94
  """
93
95
  RE_CALLER: Pattern = re.compile(
94
96
  __re_caller, MULTILINE | IGNORECASE | UNICODE | VERBOSE
@@ -103,13 +105,13 @@ class Re:
103
105
  # - tasks/function@dummy
104
106
  #
105
107
  __re_task_fmt: str = r"""
106
- ^
108
+ ^ # start task format
107
109
  (?P<path>[^/@]+)
108
- /
110
+ / # start get function with /
109
111
  (?P<func>[^@]+)
110
- @
112
+ @ # start tag with @
111
113
  (?P<tag>.+)
112
- $
114
+ $ # end task format
113
115
  """
114
116
  RE_TASK_FMT: Pattern = re.compile(
115
117
  __re_task_fmt, MULTILINE | IGNORECASE | UNICODE | VERBOSE
@@ -5,25 +5,21 @@
5
5
  # ------------------------------------------------------------------------------
6
6
  from __future__ import annotations
7
7
 
8
- import asyncio
9
8
  import contextlib
10
- import uuid
11
9
  from collections.abc import AsyncIterator
12
10
  from datetime import datetime, timedelta
13
- from queue import Empty, Queue
14
11
  from typing import TypedDict
15
12
 
16
13
  from dotenv import load_dotenv
17
14
  from fastapi import FastAPI
18
15
  from fastapi.middleware.gzip import GZipMiddleware
19
16
  from fastapi.responses import UJSONResponse
20
- from pydantic import BaseModel
21
17
 
22
18
  from ..__about__ import __version__
23
19
  from ..conf import config, get_logger
24
20
  from ..scheduler import ReleaseThread, ReleaseThreads
25
21
  from ..workflow import WorkflowQueue, WorkflowTask
26
- from .repeat import repeat_at, repeat_every
22
+ from .repeat import repeat_at
27
23
 
28
24
  load_dotenv()
29
25
  logger = get_logger("ddeutil.workflow")
@@ -32,11 +28,6 @@ logger = get_logger("ddeutil.workflow")
32
28
  class State(TypedDict):
33
29
  """TypeDict for State of FastAPI application."""
34
30
 
35
- # NOTE: For upper queue route.
36
- upper_queue: Queue
37
- upper_result: dict[str, str]
38
-
39
- # NOTE: For schedule listener.
40
31
  scheduler: list[str]
41
32
  workflow_threads: ReleaseThreads
42
33
  workflow_tasks: list[WorkflowTask]
@@ -46,15 +37,11 @@ class State(TypedDict):
46
37
  @contextlib.asynccontextmanager
47
38
  async def lifespan(a: FastAPI) -> AsyncIterator[State]:
48
39
  """Lifespan function for the FastAPI application."""
49
- a.state.upper_queue = Queue()
50
- a.state.upper_result = {}
51
40
  a.state.scheduler = []
52
41
  a.state.workflow_threads = {}
53
42
  a.state.workflow_tasks = []
54
43
  a.state.workflow_queue = {}
55
44
 
56
- await asyncio.create_task(broker_upper_messages())
57
-
58
45
  yield {
59
46
  "upper_queue": a.state.upper_queue,
60
47
  "upper_result": a.state.upper_result,
@@ -87,50 +74,11 @@ app = FastAPI(
87
74
  app.add_middleware(GZipMiddleware, minimum_size=1000)
88
75
 
89
76
 
90
- @repeat_every(seconds=10)
91
- async def broker_upper_messages():
92
- """Broker for receive message from the `/upper` path and change it to upper
93
- case. This broker use interval running in background every 10 seconds.
94
- """
95
- for _ in range(10):
96
- try:
97
- obj = app.state.upper_queue.get_nowait()
98
- app.state.upper_result[obj["request_id"]] = obj["text"].upper()
99
- logger.info(f"Upper message: {app.state.upper_result}")
100
- except Empty:
101
- pass
102
- await asyncio.sleep(0.0001)
103
-
104
-
105
- class Payload(BaseModel):
106
- text: str
107
-
108
-
109
- async def get_result(request_id: str) -> dict[str, str]:
110
- """Get data from output dict that global."""
111
- while True:
112
- if request_id in app.state.upper_result:
113
- result: str = app.state.upper_result[request_id]
114
- del app.state.upper_result[request_id]
115
- return {"message": result}
116
- await asyncio.sleep(0.0025)
117
-
118
-
119
77
  @app.get("/")
120
78
  async def health():
121
79
  return {"message": "Workflow API already start up"}
122
80
 
123
81
 
124
- @app.post(f"{config.prefix_path}/upper")
125
- async def message_upper(payload: Payload):
126
- """Convert message from any case to the upper case."""
127
- request_id: str = str(uuid.uuid4())
128
- app.state.upper_queue.put(
129
- {"text": payload.text, "request_id": request_id},
130
- )
131
- return await get_result(request_id)
132
-
133
-
134
82
  # NOTE: Enable the workflow route.
135
83
  if config.enable_route_workflow:
136
84
  from .route import workflow_route
ddeutil/workflow/conf.py CHANGED
@@ -13,7 +13,7 @@ from collections.abc import Iterator
13
13
  from datetime import datetime, timedelta
14
14
  from functools import cached_property, lru_cache
15
15
  from pathlib import Path
16
- from typing import ClassVar, Optional, Union
16
+ from typing import ClassVar, Optional, TypeVar, Union
17
17
  from zoneinfo import ZoneInfo
18
18
 
19
19
  from ddeutil.core import str2bool
@@ -24,9 +24,11 @@ from typing_extensions import Self
24
24
 
25
25
  from .__types import DictData, TupleStr
26
26
 
27
+ PREFIX: str = "WORKFLOW"
28
+
27
29
 
28
30
  def env(var: str, default: str | None = None) -> str | None: # pragma: no cov
29
- return os.getenv(f"WORKFLOW_{var}", default)
31
+ return os.getenv(f"{PREFIX}_{var.upper().replace(' ', '_')}", default)
30
32
 
31
33
 
32
34
  def glob_files(path: Path) -> Iterator[Path]: # pragma: no cov
@@ -37,6 +39,7 @@ __all__: TupleStr = (
37
39
  "env",
38
40
  "get_logger",
39
41
  "get_log",
42
+ "C",
40
43
  "Config",
41
44
  "SimLoad",
42
45
  "Loader",
@@ -79,15 +82,43 @@ def get_logger(name: str):
79
82
  return lg
80
83
 
81
84
 
82
- class Config: # pragma: no cov
83
- """Config object for keeping application configuration on current session
85
+ class BaseConfig: # pragma: no cov
86
+ """BaseConfig object inheritable."""
87
+
88
+ __slots__ = ()
89
+
90
+ @property
91
+ def root_path(self) -> Path:
92
+ """Root path or the project path.
93
+
94
+ :rtype: Path
95
+ """
96
+ return Path(os.getenv("ROOT_PATH", "."))
97
+
98
+ @property
99
+ def conf_path(self) -> Path:
100
+ """Config path that use root_path class argument for this construction.
101
+
102
+ :rtype: Path
103
+ """
104
+ return self.root_path / os.getenv("CONF_PATH", "conf")
105
+
106
+
107
+ class Config(BaseConfig): # pragma: no cov
108
+ """Config object for keeping core configurations on the current session
84
109
  without changing when if the application still running.
110
+
111
+ The config value can change when you call that config property again.
85
112
  """
86
113
 
87
114
  # NOTE: Core
88
115
  @property
89
116
  def root_path(self) -> Path:
90
- return Path(env("ROOT_PATH", "."))
117
+ """Root path or the project path.
118
+
119
+ :rtype: Path
120
+ """
121
+ return Path(env("CORE_ROOT_PATH", "."))
91
122
 
92
123
  @property
93
124
  def conf_path(self) -> Path:
@@ -95,7 +126,7 @@ class Config: # pragma: no cov
95
126
 
96
127
  :rtype: Path
97
128
  """
98
- return self.root_path / env("CORE_PATH_CONF", "conf")
129
+ return self.root_path / env("CORE_CONF_PATH", "conf")
99
130
 
100
131
  @property
101
132
  def tz(self) -> ZoneInfo:
@@ -108,7 +139,7 @@ class Config: # pragma: no cov
108
139
  # NOTE: Register
109
140
  @property
110
141
  def regis_hook(self) -> list[str]:
111
- regis_hook_str: str = env("CORE_REGISTRY", "src")
142
+ regis_hook_str: str = env("CORE_REGISTRY", ".")
112
143
  return [r.strip() for r in regis_hook_str.split(",")]
113
144
 
114
145
  @property
@@ -214,6 +245,9 @@ class Config: # pragma: no cov
214
245
  return str2bool(env("API_ENABLE_ROUTE_SCHEDULE", "true"))
215
246
 
216
247
 
248
+ C = TypeVar("C", bound=BaseConfig)
249
+
250
+
217
251
  class SimLoad:
218
252
  """Simple Load Object that will search config data by given some identity
219
253
  value like name of workflow or on.
@@ -237,7 +271,7 @@ class SimLoad:
237
271
  def __init__(
238
272
  self,
239
273
  name: str,
240
- conf: Config,
274
+ conf: C,
241
275
  externals: DictData | None = None,
242
276
  ) -> None:
243
277
  self.data: DictData = {}
@@ -250,7 +284,7 @@ class SimLoad:
250
284
  if not self.data:
251
285
  raise ValueError(f"Config {name!r} does not found on conf path")
252
286
 
253
- self.conf: Config = conf
287
+ self.conf: C = conf
254
288
  self.externals: DictData = externals or {}
255
289
  self.data.update(self.externals)
256
290
 
@@ -258,7 +292,7 @@ class SimLoad:
258
292
  def finds(
259
293
  cls,
260
294
  obj: object,
261
- conf: Config,
295
+ conf: C,
262
296
  *,
263
297
  included: list[str] | None = None,
264
298
  excluded: list[str] | None = None,
@@ -267,7 +301,7 @@ class SimLoad:
267
301
  method can use include and exclude list of identity name for filter and
268
302
  adds-on.
269
303
 
270
- :param obj: A object that want to validate matching before return.
304
+ :param obj: An object that want to validate matching before return.
271
305
  :param conf: A config object.
272
306
  :param included:
273
307
  :param excluded:
@@ -332,7 +366,7 @@ class Loader(SimLoad):
332
366
  ) -> Iterator[tuple[str, DictData]]:
333
367
  """Override the find class method from the Simple Loader object.
334
368
 
335
- :param obj: A object that want to validate matching before return.
369
+ :param obj: An object that want to validate matching before return.
336
370
  :param included:
337
371
  :param excluded:
338
372
 
@@ -349,7 +383,7 @@ class Loader(SimLoad):
349
383
  class BaseLog(BaseModel, ABC):
350
384
  """Base Log Pydantic Model with abstraction class property that implement
351
385
  only model fields. This model should to use with inherit to logging
352
- sub-class like file, sqlite, etc.
386
+ subclass like file, sqlite, etc.
353
387
  """
354
388
 
355
389
  name: str = Field(description="A workflow name.")
@@ -357,9 +391,7 @@ class BaseLog(BaseModel, ABC):
357
391
  type: str = Field(description="A running type before logging.")
358
392
  context: DictData = Field(
359
393
  default_factory=dict,
360
- description=(
361
- "A context data that receive from a workflow execution result.",
362
- ),
394
+ description="A context that receive from a workflow execution result.",
363
395
  )
364
396
  parent_run_id: Optional[str] = Field(default=None)
365
397
  run_id: str
@@ -386,7 +418,7 @@ class BaseLog(BaseModel, ABC):
386
418
 
387
419
  class FileLog(BaseLog):
388
420
  """File Log Pydantic Model that use to saving log data from result of
389
- workflow execution. It inherit from BaseLog model that implement the
421
+ workflow execution. It inherits from BaseLog model that implement the
390
422
  ``self.save`` method for file.
391
423
  """
392
424
 
@@ -526,7 +558,7 @@ class SQLiteLog(BaseLog): # pragma: no cov
526
558
  primary key ( run_id )
527
559
  """
528
560
 
529
- def save(self, excluded: list[str] | None) -> None:
561
+ def save(self, excluded: list[str] | None) -> SQLiteLog:
530
562
  """Save logging data that receive a context data from a workflow
531
563
  execution result.
532
564
  """
@@ -549,7 +581,7 @@ Log = Union[
549
581
  ]
550
582
 
551
583
 
552
- def get_log() -> Log: # pragma: no cov
584
+ def get_log() -> type[Log]: # pragma: no cov
553
585
  if config.log_path.is_file():
554
586
  return SQLiteLog
555
587
  return FileLog
ddeutil/workflow/cron.py CHANGED
@@ -32,10 +32,10 @@ def interval2crontab(
32
32
  ) -> str:
33
33
  """Return the crontab string that was generated from specific values.
34
34
 
35
- :param interval: A interval value that is one of 'daily', 'weekly', or
35
+ :param interval: An interval value that is one of 'daily', 'weekly', or
36
36
  'monthly'.
37
37
  :param day: A day value that will be day of week. The default value is
38
- monday if it be weekly interval.
38
+ monday if it is weekly interval.
39
39
  :param time: A time value that passing with format '%H:%M'.
40
40
 
41
41
  Examples:
@@ -50,9 +50,9 @@ def interval2crontab(
50
50
  """
51
51
  d: str = "*"
52
52
  if interval == "weekly":
53
- d = WEEKDAYS[(day or "monday")[:3].title()]
53
+ d = str(WEEKDAYS[(day or "monday")[:3].title()])
54
54
  elif interval == "monthly" and day:
55
- d = WEEKDAYS[day[:3].title()]
55
+ d = str(WEEKDAYS[day[:3].title()])
56
56
 
57
57
  h, m = tuple(
58
58
  i.lstrip("0") if i != "00" else "0" for i in time.split(":", maxsplit=1)
@@ -95,7 +95,7 @@ class On(BaseModel):
95
95
 
96
96
  :param value: A mapping value that will generate crontab before create
97
97
  schedule model.
98
- :param externals: A extras external parameter that will keep in extras.
98
+ :param externals: An extras external parameter that will keep in extras.
99
99
  """
100
100
  passing: DictStr = {}
101
101
  if "timezone" in value:
@@ -114,8 +114,8 @@ class On(BaseModel):
114
114
  """Constructor from the name of config that will use loader object for
115
115
  getting the data.
116
116
 
117
- :param name: A name of config that will getting from loader.
118
- :param externals: A extras external parameter that will keep in extras.
117
+ :param name: A name of config that will get from loader.
118
+ :param externals: An extras external parameter that will keep in extras.
119
119
  """
120
120
  externals: DictData = externals or {}
121
121
  loader: Loader = Loader(name, externals=externals)
@@ -4,7 +4,7 @@
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
6
  """Exception objects for this package do not do anything because I want to
7
- create the lightweight workflow package. So, this module do just a exception
7
+ create the lightweight workflow package. So, this module do just an exception
8
8
  annotate for handle error only.
9
9
  """
10
10
  from __future__ import annotations
ddeutil/workflow/hook.py CHANGED
@@ -50,6 +50,7 @@ def tag(
50
50
  :param: name: A tag name for make different use-case of a function.
51
51
  :param: alias: A alias function name that keeping in registries. If this
52
52
  value does not supply, it will use original function name from __name__.
53
+
53
54
  :rtype: Callable[P, TagFunc]
54
55
  """
55
56
 
@@ -58,7 +59,7 @@ def tag(
58
59
  func.name = alias or func.__name__.replace("_", "-")
59
60
 
60
61
  @wraps(func)
61
- def wrapped(*args, **kwargs):
62
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
62
63
  # NOTE: Able to do anything before calling hook function.
63
64
  return func(*args, **kwargs)
64
65
 
@@ -74,10 +75,11 @@ def make_registry(submodule: str) -> dict[str, Registry]:
74
75
  """Return registries of all functions that able to called with task.
75
76
 
76
77
  :param submodule: A module prefix that want to import registry.
78
+
77
79
  :rtype: dict[str, Registry]
78
80
  """
79
81
  rs: dict[str, Registry] = {}
80
- for module in config.regis_hook:
82
+ for module in config.regis_hook | ["ddeutil.vendors"]:
81
83
  # NOTE: try to sequential import task functions
82
84
  try:
83
85
  importer = import_module(f"{module}.{submodule}")
@@ -87,9 +89,12 @@ def make_registry(submodule: str) -> dict[str, Registry]:
87
89
  for fstr, func in inspect.getmembers(importer, inspect.isfunction):
88
90
  # NOTE: check function attribute that already set tag by
89
91
  # ``utils.tag`` decorator.
90
- if not hasattr(func, "tag"):
92
+ if not (hasattr(func, "tag") and hasattr(func, "name")):
91
93
  continue
92
94
 
95
+ # NOTE: Define type of the func value.
96
+ func: TagFunc
97
+
93
98
  # NOTE: Create new register name if it not exists
94
99
  if func.name not in rs:
95
100
  rs[func.name] = {func.tag: lazy(f"{module}.{submodule}.{fstr}")}
@@ -124,9 +129,21 @@ def extract_hook(hook: str) -> Callable[[], TagFunc]:
124
129
  :raise NotImplementedError: When the searching hook's function result does
125
130
  not exist in the registry.
126
131
  :raise NotImplementedError: When the searching hook's tag result does not
127
- exists in the registry with its function key.
132
+ exist in the registry with its function key.
128
133
 
129
134
  :param hook: A hook value that able to match with Task regex.
135
+
136
+ The format of hook value should contain 3 regular expression groups
137
+ which match with the below config format:
138
+
139
+ >>> "^(?P<path>[^/@]+)/(?P<func>[^@]+)@(?P<tag>.+)$"
140
+
141
+ Examples:
142
+ >>> extract_hook("tasks/el-postgres-to-delta@polars")
143
+ ...
144
+ >>> extract_hook("tasks/return-type-not-valid@raise")
145
+ ...
146
+
130
147
  :rtype: Callable[[], TagFunc]
131
148
  """
132
149
  if not (found := Re.RE_TASK_FMT.search(hook)):
ddeutil/workflow/job.py CHANGED
@@ -83,7 +83,7 @@ def make(
83
83
  if len(matrix) == 0:
84
84
  return [{}]
85
85
 
86
- # NOTE: Remove matrix that exists on the exclude.
86
+ # NOTE: Remove matrix that exists on the excluded.
87
87
  final: list[DictStr] = []
88
88
  for r in cross_product(matrix=matrix):
89
89
  if any(
@@ -101,7 +101,7 @@ def make(
101
101
  add: list[DictStr] = []
102
102
  for inc in include:
103
103
  # VALIDATE:
104
- # Validate any key in include list should be a subset of some one
104
+ # Validate any key in include list should be a subset of someone
105
105
  # in matrix.
106
106
  if all(not (set(inc.keys()) <= set(m.keys())) for m in final):
107
107
  raise ValueError(
@@ -128,9 +128,9 @@ class Strategy(BaseModel):
128
128
  special job with combination of matrix data.
129
129
 
130
130
  This model does not be the part of job only because you can use it to
131
- any model object. The propose of this model is generate metrix result that
132
- comming from combination logic with any matrix values for running it with
133
- parallelism.
131
+ any model object. The objective of this model is generating metrix result
132
+ that comming from combination logic with any matrix values for running it
133
+ with parallelism.
134
134
 
135
135
  [1, 2, 3] x [a, b] --> [1a], [1b], [2a], [2b], [3a], [3b]
136
136
 
@@ -180,7 +180,7 @@ class Strategy(BaseModel):
180
180
  """Rename key that use dash to underscore because Python does not
181
181
  support this character exist in any variable name.
182
182
 
183
- :param values: A parsing values to this models
183
+ :param values: A parsing values to these models
184
184
  :rtype: DictData
185
185
  """
186
186
  dash2underscore("max-parallel", values)
@@ -226,7 +226,7 @@ class Job(BaseModel):
226
226
  """Job Pydantic model object (short descripte: a group of stages).
227
227
 
228
228
  This job model allow you to use for-loop that call matrix strategy. If
229
- you pass matrix mapping and it able to generate, you will see it running
229
+ you pass matrix mapping, and it is able to generate, you will see it running
230
230
  with loop of matrix values.
231
231
 
232
232
  Data Validate:
@@ -355,7 +355,7 @@ class Job(BaseModel):
355
355
  return all(need in jobs for need in self.needs)
356
356
 
357
357
  def set_outputs(self, output: DictData, to: DictData) -> DictData:
358
- """Set an outputs from execution process to the receive context. The
358
+ """Set an outputs from execution process to the received context. The
359
359
  result from execution will pass to value of ``strategies`` key.
360
360
 
361
361
  For example of setting output method, If you receive execute output
@@ -420,7 +420,7 @@ class Job(BaseModel):
420
420
  strategy and return with context of this strategy data.
421
421
 
422
422
  The result of this execution will return result with strategy ID
423
- that generated from the `gen_id` function with a input strategy value.
423
+ that generated from the `gen_id` function with an input strategy value.
424
424
 
425
425
  :raise JobException: If it has any error from ``StageException`` or
426
426
  ``UtilException``.
@@ -429,7 +429,7 @@ class Job(BaseModel):
429
429
  This value will pass to the `matrix` key for templating.
430
430
  :param params: A dynamic parameters that will deepcopy to the context.
431
431
  :param run_id: A job running ID for this strategy execution.
432
- :param event: An manger event that pass to the PoolThreadExecutor.
432
+ :param event: An event manager that pass to the PoolThreadExecutor.
433
433
 
434
434
  :rtype: Result
435
435
  """
@@ -496,7 +496,7 @@ class Job(BaseModel):
496
496
  # PARAGRAPH:
497
497
  #
498
498
  # I do not use below syntax because `params` dict be the
499
- # reference memory pointer and it was changed when I action
499
+ # reference memory pointer, and it was changed when I action
500
500
  # anything like update or re-construct this.
501
501
  #
502
502
  # ... params |= stage.execute(params=params)
@@ -513,7 +513,9 @@ class Job(BaseModel):
513
513
  #
514
514
  try:
515
515
  stage.set_outputs(
516
- stage.execute(params=context, run_id=run_id).context,
516
+ stage.handler_execute(
517
+ params=context, run_id=run_id
518
+ ).context,
517
519
  to=context,
518
520
  )
519
521
  except (StageException, UtilException) as err:
@@ -566,7 +568,7 @@ class Job(BaseModel):
566
568
  run_id: str = run_id or gen_id(self.id or "", unique=True)
567
569
  context: DictData = {}
568
570
 
569
- # NOTE: Normal Job execution without parallel strategy matrix. It use
571
+ # NOTE: Normal Job execution without parallel strategy matrix. It uses
570
572
  # for-loop to control strategy execution sequentially.
571
573
  if (not self.strategy.is_set()) or self.strategy.max_parallel == 1:
572
574
  for strategy in self.strategy.make():
@@ -585,8 +587,7 @@ class Job(BaseModel):
585
587
  event: Event = Event()
586
588
 
587
589
  # IMPORTANT: Start running strategy execution by multithreading because
588
- # it will running by strategy values without waiting previous
589
- # execution.
590
+ # it will run by strategy values without waiting previous execution.
590
591
  with ThreadPoolExecutor(
591
592
  max_workers=self.strategy.max_parallel,
592
593
  thread_name_prefix="job_strategy_exec_",
@@ -618,7 +619,7 @@ class Job(BaseModel):
618
619
  timeout: int = 1800,
619
620
  ) -> Result:
620
621
  """Job parallel pool futures catching with fail-fast mode. That will
621
- stop and set event on all not done futures if it receive the first
622
+ stop and set event on all not done futures if it receives the first
622
623
  exception from all running futures.
623
624
 
624
625
  :param event: An event manager instance that able to set stopper on the
@@ -75,7 +75,7 @@ class DatetimeParam(DefaultParam):
75
75
  default: datetime = Field(default_factory=get_dt_now)
76
76
 
77
77
  def receive(self, value: str | datetime | date | None = None) -> datetime:
78
- """Receive value that match with datetime. If a input value pass with
78
+ """Receive value that match with datetime. If an input value pass with
79
79
  None, it will use default value instead.
80
80
 
81
81
  :param value: A value that want to validate with datetime parameter
@@ -98,7 +98,7 @@ class DatetimeParam(DefaultParam):
98
98
  return datetime.fromisoformat(value)
99
99
  except ValueError:
100
100
  raise ParamValueException(
101
- f"Invalid isoformat string: {value!r}"
101
+ f"Invalid the ISO format string: {value!r}"
102
102
  ) from None
103
103
 
104
104
 
@@ -158,7 +158,7 @@ class ChoiceParam(BaseParam):
158
158
  :rtype: str
159
159
  """
160
160
  # NOTE:
161
- # Return the first value in options if does not pass any input value
161
+ # Return the first value in options if it does not pass any input value
162
162
  if value is None:
163
163
  return self.options[0]
164
164
  if value not in self.options:
@@ -37,8 +37,8 @@ class Result:
37
37
 
38
38
  @model_validator(mode="after")
39
39
  def __prepare_run_id(self) -> Self:
40
- """Prepare running ID which use default ID if it initialize at the first
41
- time
40
+ """Prepare running ID which use default ID if it initializes at the
41
+ first time.
42
42
 
43
43
  :rtype: Self
44
44
  """
@@ -84,7 +84,7 @@ class Result:
84
84
 
85
85
  def receive_jobs(self, result: Result) -> Self:
86
86
  """Receive context from another result object that use on the workflow
87
- execution which create a ``jobs`` keys on the context if it do not
87
+ execution which create a ``jobs`` keys on the context if it does not
88
88
  exist.
89
89
 
90
90
  :rtype: Self