ddeutil-workflow 0.0.48__py3-none-any.whl → 0.0.49__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 +4 -1
- ddeutil/workflow/api/routes/logs.py +6 -5
- ddeutil/workflow/conf.py +31 -31
- ddeutil/workflow/job.py +10 -5
- ddeutil/workflow/logs.py +144 -80
- ddeutil/workflow/result.py +8 -6
- ddeutil/workflow/reusables.py +3 -3
- ddeutil/workflow/scheduler.py +54 -44
- ddeutil/workflow/stages.py +278 -78
- ddeutil/workflow/utils.py +3 -3
- ddeutil/workflow/workflow.py +107 -87
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/METADATA +3 -5
- ddeutil_workflow-0.0.49.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.48.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/top_level.txt +0 -0
ddeutil/workflow/stages.py
CHANGED
@@ -43,7 +43,7 @@ from pathlib import Path
|
|
43
43
|
from subprocess import CompletedProcess
|
44
44
|
from textwrap import dedent
|
45
45
|
from threading import Event
|
46
|
-
from typing import Annotated, Any, Optional, Union, get_type_hints
|
46
|
+
from typing import Annotated, Any, Optional, TypeVar, Union, get_type_hints
|
47
47
|
|
48
48
|
from pydantic import BaseModel, Field
|
49
49
|
from pydantic.functional_validators import model_validator
|
@@ -59,17 +59,7 @@ from .utils import (
|
|
59
59
|
make_exec,
|
60
60
|
)
|
61
61
|
|
62
|
-
|
63
|
-
"EmptyStage",
|
64
|
-
"BashStage",
|
65
|
-
"PyStage",
|
66
|
-
"CallStage",
|
67
|
-
"TriggerStage",
|
68
|
-
"ForEachStage",
|
69
|
-
"ParallelStage",
|
70
|
-
"RaiseStage",
|
71
|
-
"Stage",
|
72
|
-
)
|
62
|
+
T = TypeVar("T")
|
73
63
|
|
74
64
|
|
75
65
|
class BaseStage(BaseModel, ABC):
|
@@ -284,6 +274,25 @@ class BaseStage(BaseModel, ABC):
|
|
284
274
|
to["stages"][_id] = {"outputs": output, **skipping, **errors}
|
285
275
|
return to
|
286
276
|
|
277
|
+
def get_outputs(self, outputs: DictData) -> DictData:
|
278
|
+
"""Get the outputs from stages data.
|
279
|
+
|
280
|
+
:rtype: DictData
|
281
|
+
"""
|
282
|
+
if self.id is None and not dynamic(
|
283
|
+
"stage_default_id", extras=self.extras
|
284
|
+
):
|
285
|
+
return {}
|
286
|
+
|
287
|
+
_id: str = (
|
288
|
+
param2template(self.id, params=outputs, extras=self.extras)
|
289
|
+
if self.id
|
290
|
+
else gen_id(
|
291
|
+
param2template(self.name, params=outputs, extras=self.extras)
|
292
|
+
)
|
293
|
+
)
|
294
|
+
return outputs.get("stages", {}).get(_id, {})
|
295
|
+
|
287
296
|
def is_skipped(self, params: DictData | None = None) -> bool:
|
288
297
|
"""Return true if condition of this stage do not correct. This process
|
289
298
|
use build-in eval function to execute the if-condition.
|
@@ -819,7 +828,7 @@ class CallStage(BaseStage):
|
|
819
828
|
has_keyword: bool = False
|
820
829
|
call_func: TagFunc = extract_call(
|
821
830
|
param2template(self.uses, params, extras=self.extras),
|
822
|
-
registries=self.extras.get("
|
831
|
+
registries=self.extras.get("registry_caller"),
|
823
832
|
)()
|
824
833
|
|
825
834
|
# VALIDATE: check input task caller parameters that exists before
|
@@ -1146,13 +1155,59 @@ class ForEachStage(BaseStage):
|
|
1146
1155
|
)
|
1147
1156
|
concurrent: int = Field(
|
1148
1157
|
default=1,
|
1149
|
-
|
1158
|
+
ge=1,
|
1159
|
+
lt=10,
|
1150
1160
|
description=(
|
1151
1161
|
"A concurrent value allow to run each item at the same time. It "
|
1152
1162
|
"will be sequential mode if this value equal 1."
|
1153
1163
|
),
|
1154
1164
|
)
|
1155
1165
|
|
1166
|
+
def execute_item(
|
1167
|
+
self,
|
1168
|
+
item: Union[str, int],
|
1169
|
+
params: DictData,
|
1170
|
+
context: DictData,
|
1171
|
+
result: Result,
|
1172
|
+
) -> tuple[Status, DictData]:
|
1173
|
+
"""Execute foreach item from list of item.
|
1174
|
+
|
1175
|
+
:param item: (str | int) An item that want to execution.
|
1176
|
+
:param params: (DictData) A parameter that want to pass to stage
|
1177
|
+
execution.
|
1178
|
+
:param context: (DictData)
|
1179
|
+
:param result: (Result)
|
1180
|
+
|
1181
|
+
:rtype: tuple[Status, DictData]
|
1182
|
+
"""
|
1183
|
+
result.trace.debug(f"[STAGE]: Execute foreach item: {item!r}")
|
1184
|
+
params["item"] = item
|
1185
|
+
to: DictData = {"item": item, "stages": {}}
|
1186
|
+
status: Status = SUCCESS
|
1187
|
+
for stage in self.stages:
|
1188
|
+
|
1189
|
+
if self.extras:
|
1190
|
+
stage.extras = self.extras
|
1191
|
+
|
1192
|
+
try:
|
1193
|
+
stage.set_outputs(
|
1194
|
+
stage.handler_execute(
|
1195
|
+
params=params,
|
1196
|
+
run_id=result.run_id,
|
1197
|
+
parent_run_id=result.parent_run_id,
|
1198
|
+
).context,
|
1199
|
+
to=to,
|
1200
|
+
)
|
1201
|
+
except StageException as e: # pragma: no cov
|
1202
|
+
status = FAILED
|
1203
|
+
result.trace.error(
|
1204
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1205
|
+
)
|
1206
|
+
to.update({"errors": e.to_dict()})
|
1207
|
+
|
1208
|
+
context["foreach"][item] = to
|
1209
|
+
return status, context
|
1210
|
+
|
1156
1211
|
def execute(
|
1157
1212
|
self,
|
1158
1213
|
params: DictData,
|
@@ -1186,41 +1241,26 @@ class ForEachStage(BaseStage):
|
|
1186
1241
|
)
|
1187
1242
|
|
1188
1243
|
result.trace.info(f"[STAGE]: Foreach-Execute: {foreach!r}.")
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
params=params,
|
1206
|
-
run_id=result.run_id,
|
1207
|
-
parent_run_id=result.parent_run_id,
|
1208
|
-
).context,
|
1209
|
-
to=context,
|
1210
|
-
)
|
1211
|
-
except StageException as e: # pragma: no cov
|
1212
|
-
status = FAILED
|
1213
|
-
result.trace.error(
|
1214
|
-
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1215
|
-
)
|
1216
|
-
context.update({"errors": e.to_dict()})
|
1217
|
-
|
1218
|
-
rs["foreach"][item] = context
|
1244
|
+
context: DictData = {"items": foreach, "foreach": {}}
|
1245
|
+
statuses: list[Union[SUCCESS, FAILED]] = []
|
1246
|
+
with ThreadPoolExecutor(max_workers=self.concurrent) as executor:
|
1247
|
+
futures: list[Future] = [
|
1248
|
+
executor.submit(
|
1249
|
+
self.execute_item,
|
1250
|
+
item=item,
|
1251
|
+
params=params.copy(),
|
1252
|
+
context=context,
|
1253
|
+
result=result,
|
1254
|
+
)
|
1255
|
+
for item in foreach
|
1256
|
+
]
|
1257
|
+
for future in as_completed(futures):
|
1258
|
+
status, context = future.result()
|
1259
|
+
statuses.append(status)
|
1219
1260
|
|
1220
|
-
return result.catch(status=
|
1261
|
+
return result.catch(status=max(statuses), context=context)
|
1221
1262
|
|
1222
1263
|
|
1223
|
-
# TODO: Not implement this stages yet
|
1224
1264
|
class UntilStage(BaseStage): # pragma: no cov
|
1225
1265
|
"""Until execution stage.
|
1226
1266
|
|
@@ -1247,27 +1287,144 @@ class UntilStage(BaseStage): # pragma: no cov
|
|
1247
1287
|
"correct."
|
1248
1288
|
),
|
1249
1289
|
)
|
1250
|
-
|
1251
|
-
default=
|
1252
|
-
|
1290
|
+
max_until_loop: int = Field(
|
1291
|
+
default=10,
|
1292
|
+
ge=1,
|
1293
|
+
lt=100,
|
1294
|
+
description="The maximum value of loop for this until stage.",
|
1253
1295
|
)
|
1254
1296
|
|
1297
|
+
def execute_item(
|
1298
|
+
self,
|
1299
|
+
item: T,
|
1300
|
+
loop: int,
|
1301
|
+
params: DictData,
|
1302
|
+
context: DictData,
|
1303
|
+
result: Result,
|
1304
|
+
) -> tuple[Status, DictData, T]:
|
1305
|
+
"""Execute until item set item by some stage or by default loop
|
1306
|
+
variable.
|
1307
|
+
|
1308
|
+
:param item: (T) An item that want to execution.
|
1309
|
+
:param loop: (int) A number of loop.
|
1310
|
+
:param params: (DictData) A parameter that want to pass to stage
|
1311
|
+
execution.
|
1312
|
+
:param context: (DictData)
|
1313
|
+
:param result: (Result)
|
1314
|
+
|
1315
|
+
:rtype: tuple[Status, DictData, T]
|
1316
|
+
"""
|
1317
|
+
result.trace.debug(f"[STAGE]: Execute until item: {item!r}")
|
1318
|
+
params["item"] = item
|
1319
|
+
to: DictData = {"item": item, "stages": {}}
|
1320
|
+
status: Status = SUCCESS
|
1321
|
+
next_item: T = None
|
1322
|
+
for stage in self.stages:
|
1323
|
+
|
1324
|
+
if self.extras:
|
1325
|
+
stage.extras = self.extras
|
1326
|
+
|
1327
|
+
try:
|
1328
|
+
stage.set_outputs(
|
1329
|
+
stage.handler_execute(
|
1330
|
+
params=params,
|
1331
|
+
run_id=result.run_id,
|
1332
|
+
parent_run_id=result.parent_run_id,
|
1333
|
+
).context,
|
1334
|
+
to=to,
|
1335
|
+
)
|
1336
|
+
if "item" in (
|
1337
|
+
outputs := stage.get_outputs(to).get("outputs", {})
|
1338
|
+
):
|
1339
|
+
next_item = outputs["item"]
|
1340
|
+
except StageException as e:
|
1341
|
+
status = FAILED
|
1342
|
+
result.trace.error(
|
1343
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1344
|
+
)
|
1345
|
+
to.update({"errors": e.to_dict()})
|
1346
|
+
|
1347
|
+
context["until"][loop] = to
|
1348
|
+
return status, context, next_item
|
1349
|
+
|
1255
1350
|
def execute(
|
1256
1351
|
self,
|
1257
1352
|
params: DictData,
|
1258
1353
|
*,
|
1259
1354
|
result: Result | None = None,
|
1260
1355
|
event: Event | None = None,
|
1261
|
-
) -> Result:
|
1356
|
+
) -> Result:
|
1357
|
+
"""Execute the stages that pass item from until condition field and
|
1358
|
+
setter step.
|
1359
|
+
|
1360
|
+
:param params: A parameter that want to pass before run any statement.
|
1361
|
+
:param result: (Result) A result object for keeping context and status
|
1362
|
+
data.
|
1363
|
+
:param event: (Event) An event manager that use to track parent execute
|
1364
|
+
was not force stopped.
|
1365
|
+
|
1366
|
+
:rtype: Result
|
1367
|
+
"""
|
1368
|
+
if result is None: # pragma: no cov
|
1369
|
+
result: Result = Result(
|
1370
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1371
|
+
)
|
1372
|
+
|
1373
|
+
item: Union[str, int, bool] = param2template(
|
1374
|
+
self.item, params, extras=self.extras
|
1375
|
+
)
|
1376
|
+
result.trace.info(f"[STAGE]: Until-Execution: {self.until}")
|
1377
|
+
track: bool = True
|
1378
|
+
exceed_loop: bool = False
|
1379
|
+
loop: int = 1
|
1380
|
+
context: DictData = {"until": {}}
|
1381
|
+
statuses: list[Union[SUCCESS, FAILED]] = []
|
1382
|
+
while track and not (exceed_loop := loop >= self.max_until_loop):
|
1383
|
+
status, context, item = self.execute_item(
|
1384
|
+
item=item,
|
1385
|
+
loop=loop,
|
1386
|
+
params=params.copy(),
|
1387
|
+
context=context,
|
1388
|
+
result=result,
|
1389
|
+
)
|
1390
|
+
|
1391
|
+
loop += 1
|
1392
|
+
if item is None:
|
1393
|
+
result.trace.warning(
|
1394
|
+
"... Does not have set item stage. It will use loop by "
|
1395
|
+
"default."
|
1396
|
+
)
|
1397
|
+
item = loop
|
1398
|
+
|
1399
|
+
next_track: bool = eval(
|
1400
|
+
param2template(
|
1401
|
+
self.until, params | {"item": item}, extras=self.extras
|
1402
|
+
),
|
1403
|
+
globals() | params | {"item": item},
|
1404
|
+
{},
|
1405
|
+
)
|
1406
|
+
if not isinstance(next_track, bool):
|
1407
|
+
raise TypeError(
|
1408
|
+
"Return type of until condition does not be boolean, it"
|
1409
|
+
f"return: {next_track!r}"
|
1410
|
+
)
|
1411
|
+
track = not next_track
|
1412
|
+
statuses.append(status)
|
1413
|
+
|
1414
|
+
if exceed_loop:
|
1415
|
+
raise StageException(
|
1416
|
+
f"The until loop was exceed {self.max_until_loop} loops"
|
1417
|
+
)
|
1418
|
+
return result.catch(status=max(statuses), context=context)
|
1262
1419
|
|
1263
1420
|
|
1264
|
-
# TODO: Not implement this stages yet
|
1265
1421
|
class Match(BaseModel):
|
1422
|
+
"""Match model for the Case Stage."""
|
1423
|
+
|
1266
1424
|
case: Union[str, int]
|
1267
1425
|
stage: Stage
|
1268
1426
|
|
1269
1427
|
|
1270
|
-
# TODO: Not implement this stages yet
|
1271
1428
|
class CaseStage(BaseStage): # pragma: no cov
|
1272
1429
|
"""Case execution stage.
|
1273
1430
|
|
@@ -1303,7 +1460,9 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1303
1460
|
"""
|
1304
1461
|
|
1305
1462
|
case: str = Field(description="A case condition for routing.")
|
1306
|
-
match: list[Match]
|
1463
|
+
match: list[Match] = Field(
|
1464
|
+
description="A list of Match model that should not be an empty list.",
|
1465
|
+
)
|
1307
1466
|
|
1308
1467
|
def execute(
|
1309
1468
|
self,
|
@@ -1326,10 +1485,14 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1326
1485
|
result: Result = Result(
|
1327
1486
|
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1328
1487
|
)
|
1329
|
-
|
1488
|
+
|
1330
1489
|
_case = param2template(self.case, params, extras=self.extras)
|
1490
|
+
|
1491
|
+
result.trace.info(f"[STAGE]: Case-Execute: {_case!r}.")
|
1331
1492
|
_else = None
|
1493
|
+
stage: Optional[Stage] = None
|
1332
1494
|
context = {}
|
1495
|
+
status = SUCCESS
|
1333
1496
|
for match in self.match:
|
1334
1497
|
if (c := match.case) != "_":
|
1335
1498
|
_condition = param2template(c, params, extras=self.extras)
|
@@ -1337,27 +1500,35 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1337
1500
|
_else = match
|
1338
1501
|
continue
|
1339
1502
|
|
1340
|
-
if
|
1503
|
+
if stage is None and _case == _condition:
|
1341
1504
|
stage: Stage = match.stage
|
1342
|
-
if self.extras:
|
1343
|
-
stage.extras = self.extras
|
1344
|
-
|
1345
|
-
try:
|
1346
|
-
stage.set_outputs(
|
1347
|
-
stage.handler_execute(
|
1348
|
-
params=params,
|
1349
|
-
run_id=result.run_id,
|
1350
|
-
parent_run_id=result.parent_run_id,
|
1351
|
-
).context,
|
1352
|
-
to=context,
|
1353
|
-
)
|
1354
|
-
except StageException as e: # pragma: no cov
|
1355
|
-
status = FAILED
|
1356
|
-
result.trace.error(
|
1357
|
-
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1358
|
-
)
|
1359
|
-
context.update({"errors": e.to_dict()})
|
1360
1505
|
|
1506
|
+
if stage is None:
|
1507
|
+
if _else is None:
|
1508
|
+
raise StageException(
|
1509
|
+
"This stage does not set else for support not match "
|
1510
|
+
"any case."
|
1511
|
+
)
|
1512
|
+
|
1513
|
+
stage: Stage = _else.stage
|
1514
|
+
|
1515
|
+
if self.extras:
|
1516
|
+
stage.extras = self.extras
|
1517
|
+
|
1518
|
+
try:
|
1519
|
+
context.update(
|
1520
|
+
stage.handler_execute(
|
1521
|
+
params=params,
|
1522
|
+
run_id=result.run_id,
|
1523
|
+
parent_run_id=result.parent_run_id,
|
1524
|
+
).context
|
1525
|
+
)
|
1526
|
+
except StageException as e: # pragma: no cov
|
1527
|
+
status = FAILED
|
1528
|
+
result.trace.error(
|
1529
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1530
|
+
)
|
1531
|
+
context.update({"errors": e.to_dict()})
|
1361
1532
|
return result.catch(status=status, context=context)
|
1362
1533
|
|
1363
1534
|
|
@@ -1408,7 +1579,8 @@ class HookStage(BaseStage): # pragma: no cov
|
|
1408
1579
|
*,
|
1409
1580
|
result: Result | None = None,
|
1410
1581
|
event: Event | None = None,
|
1411
|
-
) -> Result:
|
1582
|
+
) -> Result:
|
1583
|
+
raise NotImplementedError("Hook Stage does not implement yet.")
|
1412
1584
|
|
1413
1585
|
|
1414
1586
|
# TODO: Not implement this stages yet
|
@@ -1441,15 +1613,20 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
1441
1613
|
*,
|
1442
1614
|
result: Result | None = None,
|
1443
1615
|
event: Event | None = None,
|
1444
|
-
) -> Result:
|
1616
|
+
) -> Result:
|
1617
|
+
raise NotImplementedError("Docker Stage does not implement yet.")
|
1445
1618
|
|
1446
1619
|
|
1447
1620
|
# TODO: Not implement this stages yet
|
1448
1621
|
class VirtualPyStage(PyStage): # pragma: no cov
|
1449
1622
|
"""Python Virtual Environment stage execution."""
|
1450
1623
|
|
1451
|
-
|
1452
|
-
|
1624
|
+
deps: list[str] = Field(
|
1625
|
+
description=(
|
1626
|
+
"list of Python dependency that want to install before execution "
|
1627
|
+
"stage."
|
1628
|
+
),
|
1629
|
+
)
|
1453
1630
|
|
1454
1631
|
def create_py_file(self, py: str, run_id: str | None): ...
|
1455
1632
|
|
@@ -1460,7 +1637,25 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
1460
1637
|
result: Result | None = None,
|
1461
1638
|
event: Event | None = None,
|
1462
1639
|
) -> Result:
|
1463
|
-
|
1640
|
+
"""Execute the Python statement via Python virtual environment.
|
1641
|
+
|
1642
|
+
:param params: A parameter that want to pass before run any statement.
|
1643
|
+
:param result: (Result) A result object for keeping context and status
|
1644
|
+
data.
|
1645
|
+
:param event: (Event) An event manager that use to track parent execute
|
1646
|
+
was not force stopped.
|
1647
|
+
|
1648
|
+
:rtype: Result
|
1649
|
+
"""
|
1650
|
+
if result is None: # pragma: no cov
|
1651
|
+
result: Result = Result(
|
1652
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1653
|
+
)
|
1654
|
+
|
1655
|
+
result.trace.info(f"[STAGE]: Py-Virtual-Execute: {self.name}")
|
1656
|
+
raise NotImplementedError(
|
1657
|
+
"Python Virtual Stage does not implement yet."
|
1658
|
+
)
|
1464
1659
|
|
1465
1660
|
|
1466
1661
|
# NOTE:
|
@@ -1470,11 +1665,16 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
1470
1665
|
#
|
1471
1666
|
Stage = Annotated[
|
1472
1667
|
Union[
|
1668
|
+
DockerStage,
|
1473
1669
|
BashStage,
|
1474
1670
|
CallStage,
|
1671
|
+
HookStage,
|
1475
1672
|
TriggerStage,
|
1476
1673
|
ForEachStage,
|
1674
|
+
UntilStage,
|
1477
1675
|
ParallelStage,
|
1676
|
+
CaseStage,
|
1677
|
+
VirtualPyStage,
|
1478
1678
|
PyStage,
|
1479
1679
|
RaiseStage,
|
1480
1680
|
EmptyStage,
|
ddeutil/workflow/utils.py
CHANGED
@@ -15,7 +15,7 @@ from inspect import isfunction
|
|
15
15
|
from itertools import chain, islice, product
|
16
16
|
from pathlib import Path
|
17
17
|
from random import randrange
|
18
|
-
from typing import Any, TypeVar
|
18
|
+
from typing import Any, Final, TypeVar
|
19
19
|
from zoneinfo import ZoneInfo
|
20
20
|
|
21
21
|
from ddeutil.core import hash_str
|
@@ -23,7 +23,7 @@ from ddeutil.core import hash_str
|
|
23
23
|
from .__types import DictData, Matrix
|
24
24
|
|
25
25
|
T = TypeVar("T")
|
26
|
-
UTC = ZoneInfo("UTC")
|
26
|
+
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
27
27
|
|
28
28
|
|
29
29
|
def get_dt_now(
|
@@ -142,7 +142,7 @@ def gen_id(
|
|
142
142
|
if not isinstance(value, str):
|
143
143
|
value: str = str(value)
|
144
144
|
|
145
|
-
if config.
|
145
|
+
if config.generate_id_simple_mode:
|
146
146
|
return (
|
147
147
|
f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}T" if unique else ""
|
148
148
|
) + hash_str(f"{(value if sensitive else value.lower())}", n=10)
|