ddeutil-workflow 0.0.29__tar.gz → 0.0.31__tar.gz

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.
Files changed (62) hide show
  1. {ddeutil_workflow-0.0.29/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.31}/PKG-INFO +17 -12
  2. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/README.md +16 -11
  3. ddeutil_workflow-0.0.31/src/ddeutil/workflow/__about__.py +1 -0
  4. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/__cron.py +1 -0
  5. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/__init__.py +5 -1
  6. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/api/api.py +2 -2
  7. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/conf.py +4 -0
  8. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/cron.py +77 -21
  9. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/exceptions.py +3 -0
  10. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/hook.py +3 -1
  11. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/params.py +18 -1
  12. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/result.py +1 -0
  13. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/scheduler.py +101 -67
  14. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/stage.py +13 -1
  15. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/utils.py +36 -10
  16. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/workflow.py +179 -141
  17. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31/src/ddeutil_workflow.egg-info}/PKG-INFO +17 -12
  18. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil_workflow.egg-info/SOURCES.txt +5 -5
  19. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_cron_on.py +9 -0
  20. ddeutil_workflow-0.0.31/tests/test_release.py +62 -0
  21. ddeutil_workflow-0.0.31/tests/test_release_queue.py +66 -0
  22. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_schedule_control.py +6 -0
  23. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_schedule_tasks.py +4 -4
  24. ddeutil_workflow-0.0.29/tests/test_workflow_schedule.py → ddeutil_workflow-0.0.31/tests/test_schedule_workflow.py +31 -31
  25. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_stage_handler_exec.py +63 -36
  26. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_utils.py +15 -0
  27. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_workflow_exec.py +126 -0
  28. ddeutil_workflow-0.0.29/tests/test_workflow_poke.py → ddeutil_workflow-0.0.31/tests/test_workflow_exec_poke.py +14 -7
  29. ddeutil_workflow-0.0.29/tests/test_workflow_release.py → ddeutil_workflow-0.0.31/tests/test_workflow_exec_release.py +25 -9
  30. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_workflow_task.py +15 -6
  31. ddeutil_workflow-0.0.29/src/ddeutil/workflow/__about__.py +0 -1
  32. ddeutil_workflow-0.0.29/tests/test_workflow_exec_hook.py +0 -91
  33. ddeutil_workflow-0.0.29/tests/test_workflow_release_and_queue.py +0 -77
  34. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/LICENSE +0 -0
  35. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/pyproject.toml +0 -0
  36. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/setup.cfg +0 -0
  37. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/__types.py +0 -0
  38. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/api/__init__.py +0 -0
  39. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/api/repeat.py +0 -0
  40. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/api/route.py +0 -0
  41. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/job.py +0 -0
  42. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil/workflow/templates.py +0 -0
  43. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  44. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil_workflow.egg-info/requires.txt +0 -0
  45. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  46. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test__cron.py +0 -0
  47. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test__regex.py +0 -0
  48. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_conf.py +0 -0
  49. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_conf_log.py +0 -0
  50. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_hook_tag.py +0 -0
  51. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_job.py +0 -0
  52. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_job_exec_py.py +0 -0
  53. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_job_exec_strategy.py +0 -0
  54. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_job_strategy.py +0 -0
  55. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_params.py +0 -0
  56. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_result.py +0 -0
  57. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_schedule.py +0 -0
  58. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_stage.py +0 -0
  59. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_templates.py +0 -0
  60. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_templates_filter.py +0 -0
  61. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_workflow.py +0 -0
  62. {ddeutil_workflow-0.0.29 → ddeutil_workflow-0.0.31}/tests/test_workflow_job_exec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -192,6 +192,15 @@ The above workflow template is main executor pipeline that you want to do. If yo
192
192
  want to schedule this workflow, you want to dynamic its parameters change base on
193
193
  execution time such as `run-date` should change base on that workflow running date.
194
194
 
195
+ ```python
196
+ from ddeutil.workflow import Workflow, Result
197
+
198
+ workflow: Workflow = Workflow.from_loader('run-py-local')
199
+ result: Result = workflow.execute(
200
+ params={"source-extract": "USD-THB", "asat-dt": "2024-01-01"}
201
+ )
202
+ ```
203
+
195
204
  So, this package provide the `Schedule` template for this action.
196
205
 
197
206
  ```yaml
@@ -212,9 +221,9 @@ schedule-run-local-wf:
212
221
 
213
222
  ## :cookie: Configuration
214
223
 
215
- The main configuration that use to dynamic changing with your objective of this
216
- application. If any configuration values do not set yet, it will use default value
217
- and do not raise any error to you.
224
+ The main configuration that use to dynamic changing this workflow engine for your
225
+ objective use environment variable only. If any configuration values do not set yet,
226
+ it will use default value and do not raise any error to you.
218
227
 
219
228
  > [!IMPORTANT]
220
229
  > The config value that you will set on the environment should combine with
@@ -246,6 +255,9 @@ and do not raise any error to you.
246
255
 
247
256
  **API Application**:
248
257
 
258
+ This config part use for the workflow application that build from the FastAPI
259
+ only.
260
+
249
261
  | Environment | Component | Default | Description |
250
262
  |:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
251
263
  | **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
@@ -272,16 +284,9 @@ like crontab job but via Python API.
272
284
 
273
285
  ### Docker Container
274
286
 
275
- Create Docker image;
276
-
277
287
  ```shell
278
288
  $ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
279
- ```
280
-
281
- Run the above Docker image;
282
-
283
- ```shell
284
- $ docker run -i ddeutil-workflow:latest
289
+ $ docker run -i ddeutil-workflow:latest ddeutil-workflow
285
290
  ```
286
291
 
287
292
  ## :speech_balloon: Contribute
@@ -160,6 +160,15 @@ The above workflow template is main executor pipeline that you want to do. If yo
160
160
  want to schedule this workflow, you want to dynamic its parameters change base on
161
161
  execution time such as `run-date` should change base on that workflow running date.
162
162
 
163
+ ```python
164
+ from ddeutil.workflow import Workflow, Result
165
+
166
+ workflow: Workflow = Workflow.from_loader('run-py-local')
167
+ result: Result = workflow.execute(
168
+ params={"source-extract": "USD-THB", "asat-dt": "2024-01-01"}
169
+ )
170
+ ```
171
+
163
172
  So, this package provide the `Schedule` template for this action.
164
173
 
165
174
  ```yaml
@@ -180,9 +189,9 @@ schedule-run-local-wf:
180
189
 
181
190
  ## :cookie: Configuration
182
191
 
183
- The main configuration that use to dynamic changing with your objective of this
184
- application. If any configuration values do not set yet, it will use default value
185
- and do not raise any error to you.
192
+ The main configuration that use to dynamic changing this workflow engine for your
193
+ objective use environment variable only. If any configuration values do not set yet,
194
+ it will use default value and do not raise any error to you.
186
195
 
187
196
  > [!IMPORTANT]
188
197
  > The config value that you will set on the environment should combine with
@@ -214,6 +223,9 @@ and do not raise any error to you.
214
223
 
215
224
  **API Application**:
216
225
 
226
+ This config part use for the workflow application that build from the FastAPI
227
+ only.
228
+
217
229
  | Environment | Component | Default | Description |
218
230
  |:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
219
231
  | **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
@@ -240,16 +252,9 @@ like crontab job but via Python API.
240
252
 
241
253
  ### Docker Container
242
254
 
243
- Create Docker image;
244
-
245
255
  ```shell
246
256
  $ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
247
- ```
248
-
249
- Run the above Docker image;
250
-
251
- ```shell
252
- $ docker run -i ddeutil-workflow:latest
257
+ $ docker run -i ddeutil-workflow:latest ddeutil-workflow
253
258
  ```
254
259
 
255
260
  ## :speech_balloon: Contribute
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.31"
@@ -845,5 +845,6 @@ __all__ = (
845
845
  "CronJob",
846
846
  "CronJobYear",
847
847
  "CronRunner",
848
+ "Options",
848
849
  "WEEKDAYS",
849
850
  )
@@ -47,8 +47,10 @@ from .params import (
47
47
  from .result import Result
48
48
  from .scheduler import (
49
49
  Schedule,
50
- WorkflowSchedule,
50
+ ScheduleWorkflow,
51
+ schedule_control,
51
52
  schedule_runner,
53
+ schedule_task,
52
54
  )
53
55
  from .stage import (
54
56
  BashStage,
@@ -83,6 +85,8 @@ from .utils import (
83
85
  make_exec,
84
86
  )
85
87
  from .workflow import (
88
+ Release,
89
+ ReleaseQueue,
86
90
  Workflow,
87
91
  WorkflowTask,
88
92
  )
@@ -18,7 +18,7 @@ from fastapi.responses import UJSONResponse
18
18
  from ..__about__ import __version__
19
19
  from ..conf import config, get_logger
20
20
  from ..scheduler import ReleaseThread, ReleaseThreads
21
- from ..workflow import WorkflowQueue, WorkflowTask
21
+ from ..workflow import ReleaseQueue, WorkflowTask
22
22
  from .repeat import repeat_at
23
23
 
24
24
  load_dotenv()
@@ -31,7 +31,7 @@ class State(TypedDict):
31
31
  scheduler: list[str]
32
32
  workflow_threads: ReleaseThreads
33
33
  workflow_tasks: list[WorkflowTask]
34
- workflow_queue: dict[str, WorkflowQueue]
34
+ workflow_queue: dict[str, ReleaseQueue]
35
35
 
36
36
 
37
37
  @contextlib.asynccontextmanager
@@ -582,6 +582,10 @@ Log = Union[
582
582
 
583
583
 
584
584
  def get_log() -> type[Log]: # pragma: no cov
585
+ """Get logging class that dynamic base on the config log path value.
586
+
587
+ :rtype: type[Log]
588
+ """
585
589
  if config.log_path.is_file():
586
590
  return SQLiteLog
587
591
  return FileLog
@@ -5,16 +5,17 @@
5
5
  # ------------------------------------------------------------------------------
6
6
  from __future__ import annotations
7
7
 
8
+ from dataclasses import fields
8
9
  from datetime import datetime
9
- from typing import Annotated, Literal
10
+ from typing import Annotated, Literal, Union
10
11
  from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
11
12
 
12
- from pydantic import BaseModel, ConfigDict, Field
13
+ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
13
14
  from pydantic.functional_serializers import field_serializer
14
15
  from pydantic.functional_validators import field_validator, model_validator
15
16
  from typing_extensions import Self
16
17
 
17
- from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner
18
+ from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
18
19
  from .__types import DictData, DictStr, TupleStr
19
20
  from .conf import Loader
20
21
 
@@ -47,6 +48,8 @@ def interval2crontab(
47
48
  '0 0 1 * *'
48
49
  >>> interval2crontab(interval='monthly', day='tuesday', time='12:00')
49
50
  '12 0 1 * 2'
51
+
52
+ :rtype: str
50
53
  """
51
54
  d: str = "*"
52
55
  if interval == "weekly":
@@ -64,12 +67,19 @@ class On(BaseModel):
64
67
  """On Pydantic model (Warped crontab object by model).
65
68
 
66
69
  See Also:
67
- * ``generate()`` is the main usecase of this schedule object.
70
+ * ``generate()`` is the main use-case of this schedule object.
68
71
  """
69
72
 
70
73
  model_config = ConfigDict(arbitrary_types_allowed=True)
71
74
 
72
75
  # NOTE: This is fields of the base schedule.
76
+ extras: Annotated[
77
+ DictData,
78
+ Field(
79
+ default_factory=dict,
80
+ description="An extras mapping parameters",
81
+ ),
82
+ ]
73
83
  cronjob: Annotated[
74
84
  CronJob,
75
85
  Field(description="Cron job of this schedule"),
@@ -81,13 +91,6 @@ class On(BaseModel):
81
91
  alias="timezone",
82
92
  ),
83
93
  ] = "Etc/UTC"
84
- extras: Annotated[
85
- DictData,
86
- Field(
87
- default_factory=dict,
88
- description="An extras mapping parameters",
89
- ),
90
- ]
91
94
 
92
95
  @classmethod
93
96
  def from_value(cls, value: DictStr, externals: DictData) -> Self:
@@ -153,6 +156,7 @@ class On(BaseModel):
153
156
 
154
157
  @model_validator(mode="before")
155
158
  def __prepare_values(cls, values: DictData) -> DictData:
159
+ """Extract tz key from value and change name to timezone key."""
156
160
  if tz := values.pop("tz", None):
157
161
  values["timezone"] = tz
158
162
  return values
@@ -160,24 +164,55 @@ class On(BaseModel):
160
164
  @field_validator("tz")
161
165
  def __validate_tz(cls, value: str) -> str:
162
166
  """Validate timezone value that able to initialize with ZoneInfo after
163
- it passing to this model in before mode."""
167
+ it passing to this model in before mode.
168
+
169
+ :rtype: str
170
+ """
164
171
  try:
165
172
  _ = ZoneInfo(value)
166
173
  return value
167
174
  except ZoneInfoNotFoundError as err:
168
175
  raise ValueError(f"Invalid timezone: {value}") from err
169
176
 
170
- @field_validator("cronjob", mode="before")
171
- def __prepare_cronjob(cls, value: str | CronJob) -> CronJob:
172
- """Prepare crontab value that able to receive with string type."""
173
- return CronJob(value) if isinstance(value, str) else value
177
+ @field_validator(
178
+ "cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
179
+ )
180
+ def __prepare_cronjob(
181
+ cls, value: str | CronJob, info: ValidationInfo
182
+ ) -> CronJob:
183
+ """Prepare crontab value that able to receive with string type.
184
+ This step will get options kwargs from extras and pass to the
185
+ CronJob object.
186
+
187
+ :rtype: CronJob
188
+ """
189
+ extras: DictData = info.data.get("extras", {})
190
+ return (
191
+ CronJob(
192
+ value,
193
+ option={
194
+ name: extras[name]
195
+ for name in (f.name for f in fields(Options))
196
+ if name in extras
197
+ },
198
+ )
199
+ if isinstance(value, str)
200
+ else value
201
+ )
174
202
 
175
203
  @field_serializer("cronjob")
176
204
  def __serialize_cronjob(self, value: CronJob) -> str:
205
+ """Serialize the cronjob field that store with CronJob object.
206
+
207
+ :rtype: str
208
+ """
177
209
  return str(value)
178
210
 
179
211
  def generate(self, start: str | datetime) -> CronRunner:
180
- """Return Cron runner object."""
212
+ """Return Cron runner object.
213
+
214
+ :rtype: CronRunner
215
+ """
181
216
  if isinstance(start, str):
182
217
  start: datetime = datetime.fromisoformat(start)
183
218
  elif not isinstance(start, datetime):
@@ -187,6 +222,8 @@ class On(BaseModel):
187
222
  def next(self, start: str | datetime) -> CronRunner:
188
223
  """Return a next datetime from Cron runner object that start with any
189
224
  date that given from input.
225
+
226
+ :rtype: CronRunner
190
227
  """
191
228
  runner: CronRunner = self.generate(start=start)
192
229
 
@@ -209,7 +246,26 @@ class YearOn(On):
209
246
  Field(description="Cron job of this schedule"),
210
247
  ]
211
248
 
212
- @field_validator("cronjob", mode="before")
213
- def __prepare_cronjob(cls, value: str | CronJobYear) -> CronJobYear:
214
- """Prepare crontab value that able to receive with string type."""
215
- return CronJobYear(value) if isinstance(value, str) else value
249
+ @field_validator(
250
+ "cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
251
+ )
252
+ def __prepare_cronjob(
253
+ cls, value: str | CronJobYear, info: ValidationInfo
254
+ ) -> CronJobYear:
255
+ """Prepare crontab value that able to receive with string type.
256
+
257
+ :rtype: CronJobYear
258
+ """
259
+ extras: DictData = info.data.get("extras", {})
260
+ return (
261
+ CronJobYear(
262
+ value,
263
+ option={
264
+ name: extras[name]
265
+ for name in (f.name for f in fields(Options))
266
+ if name in extras
267
+ },
268
+ )
269
+ if isinstance(value, str)
270
+ else value
271
+ )
@@ -29,3 +29,6 @@ class WorkflowFailException(WorkflowException): ...
29
29
 
30
30
 
31
31
  class ParamValueException(WorkflowException): ...
32
+
33
+
34
+ class ScheduleException(BaseWorkflowException): ...
@@ -79,7 +79,9 @@ def make_registry(submodule: str) -> dict[str, Registry]:
79
79
  :rtype: dict[str, Registry]
80
80
  """
81
81
  rs: dict[str, Registry] = {}
82
- for module in config.regis_hook | ["ddeutil.vendors"]:
82
+ regis_hooks: list[str] = config.regis_hook
83
+ regis_hooks.extend(["ddeutil.vendors"])
84
+ for module in regis_hooks:
83
85
  # NOTE: try to sequential import task functions
84
86
  try:
85
87
  importer = import_module(f"{module}.{submodule}")
@@ -5,6 +5,7 @@
5
5
  # ------------------------------------------------------------------------------
6
6
  from __future__ import annotations
7
7
 
8
+ import decimal
8
9
  import logging
9
10
  from abc import ABC, abstractmethod
10
11
  from datetime import date, datetime
@@ -49,7 +50,7 @@ class BaseParam(BaseModel, ABC):
49
50
 
50
51
  class DefaultParam(BaseParam):
51
52
  """Default Parameter that will check default if it required. This model do
52
- not implement the receive method.
53
+ not implement the `receive` method.
53
54
  """
54
55
 
55
56
  required: bool = Field(
@@ -68,6 +69,15 @@ class DefaultParam(BaseParam):
68
69
  )
69
70
 
70
71
 
72
+ # TODO: Not implement this parameter yet
73
+ class DateParam(DefaultParam):
74
+ """Date parameter."""
75
+
76
+ type: Literal["date"] = "date"
77
+
78
+ def receive(self, value: Optional[str | date] = None) -> date: ...
79
+
80
+
71
81
  class DatetimeParam(DefaultParam):
72
82
  """Datetime parameter."""
73
83
 
@@ -145,6 +155,13 @@ class IntParam(DefaultParam):
145
155
  return value
146
156
 
147
157
 
158
+ # TODO: Not implement this parameter yet
159
+ class DecimalParam(DefaultParam):
160
+ type: Literal["decimal"] = "decimal"
161
+
162
+ def receive(self, value: float | None = None) -> decimal.Decimal: ...
163
+
164
+
148
165
  class ChoiceParam(BaseParam):
149
166
  """Choice parameter."""
150
167
 
@@ -94,6 +94,7 @@ class Result:
94
94
  # NOTE: Check the context has jobs key.
95
95
  if "jobs" not in self.__dict__["context"]:
96
96
  self.__dict__["context"]["jobs"] = {}
97
+
97
98
  self.__dict__["context"]["jobs"].update(result.context)
98
99
 
99
100
  # NOTE: Update running ID from an incoming result.