ddeutil-workflow 0.0.77__py3-none-any.whl → 0.0.79__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/__about__.py +1 -1
- ddeutil/workflow/__init__.py +1 -5
- ddeutil/workflow/api/routes/job.py +2 -2
- ddeutil/workflow/audits.py +554 -112
- ddeutil/workflow/cli.py +25 -3
- ddeutil/workflow/conf.py +16 -28
- ddeutil/workflow/errors.py +13 -15
- ddeutil/workflow/event.py +37 -41
- ddeutil/workflow/job.py +161 -92
- ddeutil/workflow/params.py +172 -58
- ddeutil/workflow/plugins/__init__.py +0 -0
- ddeutil/workflow/plugins/providers/__init__.py +0 -0
- ddeutil/workflow/plugins/providers/aws.py +908 -0
- ddeutil/workflow/plugins/providers/az.py +1003 -0
- ddeutil/workflow/plugins/providers/container.py +703 -0
- ddeutil/workflow/plugins/providers/gcs.py +826 -0
- ddeutil/workflow/result.py +35 -37
- ddeutil/workflow/reusables.py +153 -96
- ddeutil/workflow/stages.py +84 -60
- ddeutil/workflow/traces.py +1660 -521
- ddeutil/workflow/utils.py +111 -69
- ddeutil/workflow/workflow.py +74 -47
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/METADATA +52 -20
- ddeutil_workflow-0.0.79.dist-info/RECORD +36 -0
- ddeutil_workflow-0.0.77.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/top_level.txt +0 -0
ddeutil/workflow/stages.py
CHANGED
@@ -77,7 +77,15 @@ from pathlib import Path
|
|
77
77
|
from subprocess import CompletedProcess
|
78
78
|
from textwrap import dedent
|
79
79
|
from threading import Event
|
80
|
-
from typing import
|
80
|
+
from typing import (
|
81
|
+
Annotated,
|
82
|
+
Any,
|
83
|
+
Callable,
|
84
|
+
Optional,
|
85
|
+
TypeVar,
|
86
|
+
Union,
|
87
|
+
get_type_hints,
|
88
|
+
)
|
81
89
|
|
82
90
|
from ddeutil.core import str2list
|
83
91
|
from pydantic import BaseModel, Field, ValidationError
|
@@ -106,7 +114,7 @@ from .reusables import (
|
|
106
114
|
not_in_template,
|
107
115
|
param2template,
|
108
116
|
)
|
109
|
-
from .traces import
|
117
|
+
from .traces import TraceManager, get_trace
|
110
118
|
from .utils import (
|
111
119
|
delay,
|
112
120
|
dump_all,
|
@@ -177,7 +185,7 @@ class BaseStage(BaseModel, ABC):
|
|
177
185
|
"A stage description that use to logging when start execution."
|
178
186
|
),
|
179
187
|
)
|
180
|
-
condition:
|
188
|
+
condition: Optional[Union[str, bool]] = Field(
|
181
189
|
default=None,
|
182
190
|
description=(
|
183
191
|
"A stage condition statement to allow stage executable. This field "
|
@@ -296,7 +304,7 @@ class BaseStage(BaseModel, ABC):
|
|
296
304
|
parent_run_id: str = run_id
|
297
305
|
run_id: str = run_id or gen_id(self.iden, unique=True)
|
298
306
|
context: DictData = {"status": WAIT}
|
299
|
-
trace:
|
307
|
+
trace: TraceManager = get_trace(
|
300
308
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
301
309
|
)
|
302
310
|
try:
|
@@ -513,6 +521,9 @@ class BaseStage(BaseModel, ABC):
|
|
513
521
|
if not self.condition:
|
514
522
|
return False
|
515
523
|
|
524
|
+
if isinstance(self.condition, bool):
|
525
|
+
return self.condition
|
526
|
+
|
516
527
|
try:
|
517
528
|
# WARNING: The eval build-in function is very dangerous. So, it
|
518
529
|
# should use the `re` module to validate eval-string before
|
@@ -623,7 +634,7 @@ class BaseAsyncStage(BaseStage, ABC):
|
|
623
634
|
parent_run_id: StrOrNone = run_id
|
624
635
|
run_id: str = run_id or gen_id(self.iden, unique=True)
|
625
636
|
context: DictData = {}
|
626
|
-
trace:
|
637
|
+
trace: TraceManager = get_trace(
|
627
638
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
628
639
|
)
|
629
640
|
try:
|
@@ -766,7 +777,7 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
|
|
766
777
|
current_retry: int = 0
|
767
778
|
exception: Exception
|
768
779
|
catch(context, status=WAIT)
|
769
|
-
trace:
|
780
|
+
trace: TraceManager = get_trace(
|
770
781
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
771
782
|
)
|
772
783
|
|
@@ -839,7 +850,7 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
|
|
839
850
|
current_retry: int = 0
|
840
851
|
exception: Exception
|
841
852
|
catch(context, status=WAIT)
|
842
|
-
trace:
|
853
|
+
trace: TraceManager = get_trace(
|
843
854
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
844
855
|
)
|
845
856
|
|
@@ -971,7 +982,7 @@ class EmptyStage(BaseAsyncStage):
|
|
971
982
|
Returns:
|
972
983
|
Result: The execution result with status and context data.
|
973
984
|
"""
|
974
|
-
trace:
|
985
|
+
trace: TraceManager = get_trace(
|
975
986
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
976
987
|
)
|
977
988
|
message: str = (
|
@@ -1024,7 +1035,7 @@ class EmptyStage(BaseAsyncStage):
|
|
1024
1035
|
Returns:
|
1025
1036
|
Result: The execution result with status and context data.
|
1026
1037
|
"""
|
1027
|
-
trace:
|
1038
|
+
trace: TraceManager = get_trace(
|
1028
1039
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1029
1040
|
)
|
1030
1041
|
message: str = (
|
@@ -1189,7 +1200,7 @@ class BashStage(BaseRetryStage):
|
|
1189
1200
|
Returns:
|
1190
1201
|
Result: The execution result with status and context data.
|
1191
1202
|
"""
|
1192
|
-
trace:
|
1203
|
+
trace: TraceManager = get_trace(
|
1193
1204
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1194
1205
|
)
|
1195
1206
|
bash: str = param2template(
|
@@ -1253,7 +1264,7 @@ class BashStage(BaseRetryStage):
|
|
1253
1264
|
Returns:
|
1254
1265
|
Result: The execution result with status and context data.
|
1255
1266
|
"""
|
1256
|
-
trace:
|
1267
|
+
trace: TraceManager = get_trace(
|
1257
1268
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1258
1269
|
)
|
1259
1270
|
bash: str = param2template(
|
@@ -1394,7 +1405,7 @@ class PyStage(BaseRetryStage):
|
|
1394
1405
|
Returns:
|
1395
1406
|
Result: The execution result with status and context data.
|
1396
1407
|
"""
|
1397
|
-
trace:
|
1408
|
+
trace: TraceManager = get_trace(
|
1398
1409
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1399
1410
|
)
|
1400
1411
|
trace.info("[STAGE]: Prepare `globals` and `locals` variables.")
|
@@ -1475,7 +1486,7 @@ class PyStage(BaseRetryStage):
|
|
1475
1486
|
Returns:
|
1476
1487
|
Result: The execution result with status and context data.
|
1477
1488
|
"""
|
1478
|
-
trace:
|
1489
|
+
trace: TraceManager = get_trace(
|
1479
1490
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1480
1491
|
)
|
1481
1492
|
await trace.ainfo("[STAGE]: Prepare `globals` and `locals` variables.")
|
@@ -1580,15 +1591,23 @@ class CallStage(BaseRetryStage):
|
|
1580
1591
|
|
1581
1592
|
:rtype: Any
|
1582
1593
|
"""
|
1583
|
-
if isinstance(value, dict)
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1594
|
+
if isinstance(value, dict) and any(
|
1595
|
+
k in value for k in ("result", "extras")
|
1596
|
+
):
|
1597
|
+
raise ValueError(
|
1598
|
+
"The argument on workflow template for the caller stage "
|
1599
|
+
"should not pass `result` and `extras`. They are special "
|
1600
|
+
"arguments."
|
1601
|
+
)
|
1590
1602
|
return value
|
1591
1603
|
|
1604
|
+
def get_caller(self, params: DictData) -> Callable[[], TagFunc]:
|
1605
|
+
"""Get the lazy TagFuc object from registry."""
|
1606
|
+
return extract_call(
|
1607
|
+
param2template(self.uses, params, extras=self.extras),
|
1608
|
+
registries=self.extras.get("registry_caller"),
|
1609
|
+
)
|
1610
|
+
|
1592
1611
|
def process(
|
1593
1612
|
self,
|
1594
1613
|
params: DictData,
|
@@ -1612,14 +1631,10 @@ class CallStage(BaseRetryStage):
|
|
1612
1631
|
Returns:
|
1613
1632
|
Result: The execution result with status and context data.
|
1614
1633
|
"""
|
1615
|
-
trace:
|
1634
|
+
trace: TraceManager = get_trace(
|
1616
1635
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1617
1636
|
)
|
1618
|
-
call_func: TagFunc =
|
1619
|
-
param2template(self.uses, params, extras=self.extras),
|
1620
|
-
registries=self.extras.get("registry_caller"),
|
1621
|
-
)()
|
1622
|
-
|
1637
|
+
call_func: TagFunc = self.get_caller(params=params)()
|
1623
1638
|
trace.info(f"[STAGE]: Caller Func: '{call_func.name}@{call_func.tag}'")
|
1624
1639
|
|
1625
1640
|
# VALIDATE: check input task caller parameters that exists before
|
@@ -1677,7 +1692,7 @@ class CallStage(BaseRetryStage):
|
|
1677
1692
|
)
|
1678
1693
|
|
1679
1694
|
args: DictData = self.validate_model_args(
|
1680
|
-
call_func, args, run_id, parent_run_id
|
1695
|
+
call_func, args, run_id, parent_run_id, extras=self.extras
|
1681
1696
|
)
|
1682
1697
|
if inspect.iscoroutinefunction(call_func):
|
1683
1698
|
loop = asyncio.get_event_loop()
|
@@ -1735,14 +1750,10 @@ class CallStage(BaseRetryStage):
|
|
1735
1750
|
Returns:
|
1736
1751
|
Result: The execution result with status and context data.
|
1737
1752
|
"""
|
1738
|
-
trace:
|
1753
|
+
trace: TraceManager = get_trace(
|
1739
1754
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1740
1755
|
)
|
1741
|
-
call_func: TagFunc =
|
1742
|
-
param2template(self.uses, params, extras=self.extras),
|
1743
|
-
registries=self.extras.get("registry_caller"),
|
1744
|
-
)()
|
1745
|
-
|
1756
|
+
call_func: TagFunc = self.get_caller(params=params)()
|
1746
1757
|
await trace.ainfo(
|
1747
1758
|
f"[STAGE]: Caller Func: '{call_func.name}@{call_func.tag}'"
|
1748
1759
|
)
|
@@ -1795,8 +1806,13 @@ class CallStage(BaseRetryStage):
|
|
1795
1806
|
if "extras" not in sig.parameters and not has_keyword:
|
1796
1807
|
args.pop("extras")
|
1797
1808
|
|
1809
|
+
if event and event.is_set():
|
1810
|
+
raise StageCancelError(
|
1811
|
+
"Execution was canceled from the event before start parallel."
|
1812
|
+
)
|
1813
|
+
|
1798
1814
|
args: DictData = self.validate_model_args(
|
1799
|
-
call_func, args, run_id, parent_run_id
|
1815
|
+
call_func, args, run_id, parent_run_id, extras=self.extras
|
1800
1816
|
)
|
1801
1817
|
if inspect.iscoroutinefunction(call_func):
|
1802
1818
|
rs: DictOrModel = await call_func(
|
@@ -1829,12 +1845,13 @@ class CallStage(BaseRetryStage):
|
|
1829
1845
|
extras=self.extras,
|
1830
1846
|
)
|
1831
1847
|
|
1848
|
+
@staticmethod
|
1832
1849
|
def validate_model_args(
|
1833
|
-
self,
|
1834
1850
|
func: TagFunc,
|
1835
1851
|
args: DictData,
|
1836
1852
|
run_id: str,
|
1837
1853
|
parent_run_id: Optional[str] = None,
|
1854
|
+
extras: Optional[DictData] = None,
|
1838
1855
|
) -> DictData:
|
1839
1856
|
"""Validate an input arguments before passing to the caller function.
|
1840
1857
|
|
@@ -1846,18 +1863,18 @@ class CallStage(BaseRetryStage):
|
|
1846
1863
|
:rtype: DictData
|
1847
1864
|
"""
|
1848
1865
|
try:
|
1849
|
-
|
1850
|
-
func
|
1851
|
-
)
|
1852
|
-
override: DictData = dict(model_instance)
|
1866
|
+
override: DictData = dict(
|
1867
|
+
create_model_from_caller(func).model_validate(args)
|
1868
|
+
)
|
1853
1869
|
args.update(override)
|
1870
|
+
|
1854
1871
|
type_hints: dict[str, Any] = get_type_hints(func)
|
1855
1872
|
for arg in type_hints:
|
1856
1873
|
|
1857
1874
|
if arg == "return":
|
1858
1875
|
continue
|
1859
1876
|
|
1860
|
-
if arg.removeprefix("_") in args:
|
1877
|
+
if arg.startswith("_") and arg.removeprefix("_") in args:
|
1861
1878
|
args[arg] = args.pop(arg.removeprefix("_"))
|
1862
1879
|
continue
|
1863
1880
|
|
@@ -1867,8 +1884,8 @@ class CallStage(BaseRetryStage):
|
|
1867
1884
|
"Validate argument from the caller function raise invalid type."
|
1868
1885
|
) from e
|
1869
1886
|
except TypeError as e:
|
1870
|
-
trace:
|
1871
|
-
run_id, parent_run_id=parent_run_id, extras=
|
1887
|
+
trace: TraceManager = get_trace(
|
1888
|
+
run_id, parent_run_id=parent_run_id, extras=extras
|
1872
1889
|
)
|
1873
1890
|
trace.warning(
|
1874
1891
|
f"[STAGE]: Get type hint raise TypeError: {e}, so, it skip "
|
@@ -1992,7 +2009,7 @@ class TriggerStage(BaseNestedStage):
|
|
1992
2009
|
"""
|
1993
2010
|
from .workflow import Workflow
|
1994
2011
|
|
1995
|
-
trace:
|
2012
|
+
trace: TraceManager = get_trace(
|
1996
2013
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1997
2014
|
)
|
1998
2015
|
_trigger: str = param2template(self.trigger, params, extras=self.extras)
|
@@ -2097,7 +2114,7 @@ class ParallelStage(BaseNestedStage):
|
|
2097
2114
|
|
2098
2115
|
:rtype: tuple[Status, DictData]
|
2099
2116
|
"""
|
2100
|
-
trace:
|
2117
|
+
trace: TraceManager = get_trace(
|
2101
2118
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2102
2119
|
)
|
2103
2120
|
trace.debug(f"[STAGE]: Execute Branch: {branch!r}")
|
@@ -2227,7 +2244,7 @@ class ParallelStage(BaseNestedStage):
|
|
2227
2244
|
Returns:
|
2228
2245
|
Result: The execution result with status and context data.
|
2229
2246
|
"""
|
2230
|
-
trace:
|
2247
|
+
trace: TraceManager = get_trace(
|
2231
2248
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2232
2249
|
)
|
2233
2250
|
event: Event = event or Event()
|
@@ -2295,7 +2312,13 @@ class ForEachStage(BaseNestedStage):
|
|
2295
2312
|
... }
|
2296
2313
|
"""
|
2297
2314
|
|
2298
|
-
foreach: Union[
|
2315
|
+
foreach: Union[
|
2316
|
+
list[str],
|
2317
|
+
list[int],
|
2318
|
+
str,
|
2319
|
+
dict[str, Any],
|
2320
|
+
dict[int, Any],
|
2321
|
+
] = Field(
|
2299
2322
|
description=(
|
2300
2323
|
"A items for passing to stages via ${{ item }} template parameter."
|
2301
2324
|
),
|
@@ -2360,7 +2383,7 @@ class ForEachStage(BaseNestedStage):
|
|
2360
2383
|
|
2361
2384
|
:rtype: tuple[Status, Result]
|
2362
2385
|
"""
|
2363
|
-
trace:
|
2386
|
+
trace: TraceManager = get_trace(
|
2364
2387
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2365
2388
|
)
|
2366
2389
|
trace.debug(f"[STAGE]: Execute Item: {item!r}")
|
@@ -2497,11 +2520,11 @@ class ForEachStage(BaseNestedStage):
|
|
2497
2520
|
Returns:
|
2498
2521
|
Result: The execution result with status and context data.
|
2499
2522
|
"""
|
2500
|
-
trace:
|
2523
|
+
trace: TraceManager = get_trace(
|
2501
2524
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2502
2525
|
)
|
2503
2526
|
event: Event = event or Event()
|
2504
|
-
foreach: Union[list[str], list[int]] = pass_env(
|
2527
|
+
foreach: Union[list[str], list[int], str] = pass_env(
|
2505
2528
|
param2template(self.foreach, params, extras=self.extras)
|
2506
2529
|
)
|
2507
2530
|
|
@@ -2516,9 +2539,10 @@ class ForEachStage(BaseNestedStage):
|
|
2516
2539
|
) from e
|
2517
2540
|
|
2518
2541
|
# [VALIDATE]: Type of the foreach should be `list` type.
|
2519
|
-
elif
|
2542
|
+
elif isinstance(foreach, dict):
|
2520
2543
|
raise TypeError(
|
2521
|
-
f"Does not support foreach: {foreach!r} ({type(foreach)})"
|
2544
|
+
f"Does not support dict foreach: {foreach!r} ({type(foreach)}) "
|
2545
|
+
f"yet."
|
2522
2546
|
)
|
2523
2547
|
# [Validate]: Value in the foreach item should not be duplicate when the
|
2524
2548
|
# `use_index_as_key` field did not set.
|
@@ -2681,7 +2705,7 @@ class UntilStage(BaseNestedStage):
|
|
2681
2705
|
:rtype: tuple[Status, DictData, T]
|
2682
2706
|
:return: Return a pair of Result and changed item.
|
2683
2707
|
"""
|
2684
|
-
trace:
|
2708
|
+
trace: TraceManager = get_trace(
|
2685
2709
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2686
2710
|
)
|
2687
2711
|
trace.debug(f"[STAGE]: Execute Loop: {loop} (Item {item!r})")
|
@@ -2823,7 +2847,7 @@ class UntilStage(BaseNestedStage):
|
|
2823
2847
|
Returns:
|
2824
2848
|
Result: The execution result with status and context data.
|
2825
2849
|
"""
|
2826
|
-
trace:
|
2850
|
+
trace: TraceManager = get_trace(
|
2827
2851
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2828
2852
|
)
|
2829
2853
|
event: Event = event or Event()
|
@@ -2975,7 +2999,7 @@ class CaseStage(BaseNestedStage):
|
|
2975
2999
|
|
2976
3000
|
:rtype: DictData
|
2977
3001
|
"""
|
2978
|
-
trace:
|
3002
|
+
trace: TraceManager = get_trace(
|
2979
3003
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2980
3004
|
)
|
2981
3005
|
trace.debug(f"[STAGE]: Execute Case: {case!r}")
|
@@ -3056,7 +3080,7 @@ class CaseStage(BaseNestedStage):
|
|
3056
3080
|
Returns:
|
3057
3081
|
Result: The execution result with status and context data.
|
3058
3082
|
"""
|
3059
|
-
trace:
|
3083
|
+
trace: TraceManager = get_trace(
|
3060
3084
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3061
3085
|
)
|
3062
3086
|
|
@@ -3154,7 +3178,7 @@ class RaiseStage(BaseAsyncStage):
|
|
3154
3178
|
Returns:
|
3155
3179
|
Result: The execution result with status and context data.
|
3156
3180
|
"""
|
3157
|
-
trace:
|
3181
|
+
trace: TraceManager = get_trace(
|
3158
3182
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3159
3183
|
)
|
3160
3184
|
message: str = param2template(self.message, params, extras=self.extras)
|
@@ -3185,7 +3209,7 @@ class RaiseStage(BaseAsyncStage):
|
|
3185
3209
|
Returns:
|
3186
3210
|
Result: The execution result with status and context data.
|
3187
3211
|
"""
|
3188
|
-
trace:
|
3212
|
+
trace: TraceManager = get_trace(
|
3189
3213
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3190
3214
|
)
|
3191
3215
|
message: str = param2template(self.message, params, extras=self.extras)
|
@@ -3266,7 +3290,7 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
3266
3290
|
"by `pip install docker` first."
|
3267
3291
|
) from None
|
3268
3292
|
|
3269
|
-
trace:
|
3293
|
+
trace: TraceManager = get_trace(
|
3270
3294
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3271
3295
|
)
|
3272
3296
|
client = DockerClient(
|
@@ -3366,7 +3390,7 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
3366
3390
|
Returns:
|
3367
3391
|
Result: The execution result with status and context data.
|
3368
3392
|
"""
|
3369
|
-
trace:
|
3393
|
+
trace: TraceManager = get_trace(
|
3370
3394
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3371
3395
|
)
|
3372
3396
|
trace.info(f"[STAGE]: Docker: {self.image}:{self.tag}")
|
@@ -3476,7 +3500,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
3476
3500
|
Returns:
|
3477
3501
|
Result: The execution result with status and context data.
|
3478
3502
|
"""
|
3479
|
-
trace:
|
3503
|
+
trace: TraceManager = get_trace(
|
3480
3504
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
3481
3505
|
)
|
3482
3506
|
run: str = param2template(dedent(self.run), params, extras=self.extras)
|