ddeutil-workflow 0.0.31__py3-none-any.whl → 0.0.33__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.
@@ -51,7 +51,8 @@ except ImportError: # pragma: no cov
51
51
 
52
52
  from .__cron import CronRunner
53
53
  from .__types import DictData, TupleStr
54
- from .conf import Loader, Log, config, get_log, get_logger
54
+ from .audit import Audit, get_audit
55
+ from .conf import Loader, config, get_logger
55
56
  from .cron import On
56
57
  from .exceptions import ScheduleException, WorkflowException
57
58
  from .result import Result
@@ -275,37 +276,6 @@ class Schedule(BaseModel):
275
276
 
276
277
  return cls.model_validate(obj=loader_data)
277
278
 
278
- @classmethod
279
- def extract_tasks(
280
- cls,
281
- schedules: list[str],
282
- start_date: datetime,
283
- queue: dict[str, ReleaseQueue],
284
- externals: DictData | None = None,
285
- ) -> list[WorkflowTask]:
286
- """Return the list of WorkflowTask object from all schedule object that
287
- include in an input schedules argument.
288
-
289
- :param schedules: A list of schedule name that will use `from_loader`
290
- method.
291
- :param start_date: A start date that get from the workflow schedule.
292
- :param queue: A mapping of name and list of datetime for queue.
293
- :param externals: An external parameters that pass to the Loader object.
294
-
295
- :rtype: list[WorkflowTask]
296
- """
297
- tasks: list[WorkflowTask] = []
298
- for name in schedules:
299
- schedule: Schedule = Schedule.from_loader(name, externals=externals)
300
- tasks.extend(
301
- schedule.tasks(
302
- start_date,
303
- queue=queue,
304
- externals=externals,
305
- ),
306
- )
307
- return tasks
308
-
309
279
  def tasks(
310
280
  self,
311
281
  start_date: datetime,
@@ -339,6 +309,99 @@ class Schedule(BaseModel):
339
309
 
340
310
  return workflow_tasks
341
311
 
312
+ def pending(
313
+ self,
314
+ *,
315
+ stop: datetime | None = None,
316
+ externals: DictData | None = None,
317
+ log: type[Audit] | None = None,
318
+ ) -> None: # pragma: no cov
319
+ """Pending this schedule tasks with the schedule package.
320
+
321
+ :param stop: A datetime value that use to stop running schedule.
322
+ :param externals: An external parameters that pass to Loader.
323
+ :param log: A log class that use on the workflow task release for
324
+ writing its release log context.
325
+ """
326
+ try:
327
+ from schedule import Scheduler
328
+ except ImportError:
329
+ raise ImportError(
330
+ "Should install schedule package before use this method."
331
+ ) from None
332
+
333
+ # NOTE: Get default logging.
334
+ log: type[Audit] = log or get_audit()
335
+ scheduler: Scheduler = Scheduler()
336
+
337
+ # NOTE: Create the start and stop datetime.
338
+ start_date: datetime = datetime.now(tz=config.tz)
339
+ stop_date: datetime = stop or (start_date + config.stop_boundary_delta)
340
+
341
+ # IMPORTANT: Create main mapping of queue and thread object.
342
+ queue: dict[str, ReleaseQueue] = {}
343
+ threads: ReleaseThreads = {}
344
+
345
+ start_date_waiting: datetime = start_date.replace(
346
+ second=0, microsecond=0
347
+ ) + timedelta(minutes=1)
348
+
349
+ # NOTE: This schedule job will start every minute at :02 seconds.
350
+ (
351
+ scheduler.every(1)
352
+ .minutes.at(":02")
353
+ .do(
354
+ schedule_task,
355
+ tasks=self.tasks(
356
+ start_date_waiting, queue=queue, externals=externals
357
+ ),
358
+ stop=stop_date,
359
+ queue=queue,
360
+ threads=threads,
361
+ log=log,
362
+ )
363
+ .tag("control")
364
+ )
365
+
366
+ # NOTE: Checking zombie task with schedule job will start every 5 minute at
367
+ # :10 seconds.
368
+ (
369
+ scheduler.every(5)
370
+ .minutes.at(":10")
371
+ .do(
372
+ monitor,
373
+ threads=threads,
374
+ )
375
+ .tag("monitor")
376
+ )
377
+
378
+ # NOTE: Start running schedule
379
+ logger.info(
380
+ f"[SCHEDULE]: Schedule with stopper: {stop_date:%Y-%m-%d %H:%M:%S}"
381
+ )
382
+
383
+ while True:
384
+ scheduler.run_pending()
385
+ time.sleep(1)
386
+
387
+ # NOTE: Break the scheduler when the control job does not exist.
388
+ if not scheduler.get_jobs("control"):
389
+ scheduler.clear("monitor")
390
+
391
+ while len(threads) > 0:
392
+ logger.warning(
393
+ "[SCHEDULE]: Waiting schedule release thread that still "
394
+ "running in background."
395
+ )
396
+ delay(10)
397
+ monitor(threads)
398
+
399
+ break
400
+
401
+ logger.warning(
402
+ f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
403
+ )
404
+
342
405
 
343
406
  ResultOrCancelJob = Union[type[CancelJob], Result]
344
407
  ReturnCancelJob = Callable[P, ResultOrCancelJob]
@@ -388,13 +451,13 @@ def schedule_task(
388
451
  stop: datetime,
389
452
  queue: dict[str, ReleaseQueue],
390
453
  threads: ReleaseThreads,
391
- log: type[Log],
454
+ log: type[Audit],
392
455
  ) -> type[CancelJob] | None:
393
456
  """Schedule task function that generate thread of workflow task release
394
457
  method in background. This function do the same logic as the workflow poke
395
458
  method, but it runs with map of schedules and the on values.
396
459
 
397
- This schedule task start runs every minute at ':02' second and it does
460
+ This schedule task start runs every minute at ':02' second, and it does
398
461
  not allow you to run with offset time.
399
462
 
400
463
  :param tasks: A list of WorkflowTask object.
@@ -414,15 +477,16 @@ def schedule_task(
414
477
  # function. It will deplicate running with different schedule value
415
478
  # because I use current time in this condition.
416
479
  #
417
- # For example, if a workflow A queue has '00:02:00' time that
418
- # should to run and its schedule has '*/2 * * * *' and '*/35 * * * *'.
419
- # This condition will release with 2 threading job.
480
+ # For example, if a queue has a time release be '00:02:00' that should
481
+ # to run and its schedule has '*/2 * * * *' and '*/35 * * * *'.
482
+ # This condition make this function create 2 threading tasks.
420
483
  #
421
- # '00:02:00' --> '*/2 * * * *' --> running
422
- # --> '*/35 * * * *' --> skip
484
+ # '00:02:00' --> '*/2 * * * *' --> run
485
+ # --> '*/35 * * * *' --> skip
423
486
  #
424
487
  for task in tasks:
425
488
 
489
+ # NOTE: Get the ReleaseQueue with an alias of the WorkflowTask.
426
490
  q: ReleaseQueue = queue[task.alias]
427
491
 
428
492
  # NOTE: Start adding queue and move the runner date in the WorkflowTask.
@@ -510,7 +574,7 @@ def schedule_control(
510
574
  stop: datetime | None = None,
511
575
  externals: DictData | None = None,
512
576
  *,
513
- log: type[Log] | None = None,
577
+ log: type[Audit] | None = None,
514
578
  ) -> list[str]: # pragma: no cov
515
579
  """Scheduler control function that run the chuck of schedules every minute
516
580
  and this function release monitoring thread for tracking undead thread in
@@ -533,7 +597,7 @@ def schedule_control(
533
597
  ) from None
534
598
 
535
599
  # NOTE: Get default logging.
536
- log: type[Log] = log or get_log()
600
+ log: type[Audit] = log or get_audit()
537
601
  scheduler: Scheduler = Scheduler()
538
602
 
539
603
  # NOTE: Create the start and stop datetime.
@@ -548,15 +612,24 @@ def schedule_control(
548
612
  second=0, microsecond=0
549
613
  ) + timedelta(minutes=1)
550
614
 
615
+ tasks: list[WorkflowTask] = []
616
+ for name in schedules:
617
+ schedule: Schedule = Schedule.from_loader(name, externals=externals)
618
+ tasks.extend(
619
+ schedule.tasks(
620
+ start_date_waiting,
621
+ queue=queue,
622
+ externals=externals,
623
+ ),
624
+ )
625
+
551
626
  # NOTE: This schedule job will start every minute at :02 seconds.
552
627
  (
553
628
  scheduler.every(1)
554
629
  .minutes.at(":02")
555
630
  .do(
556
631
  schedule_task,
557
- tasks=Schedule.extract_tasks(
558
- schedules, start_date_waiting, queue, externals=externals
559
- ),
632
+ tasks=tasks,
560
633
  stop=stop_date,
561
634
  queue=queue,
562
635
  threads=threads,
@@ -596,7 +669,7 @@ def schedule_control(
596
669
  "[SCHEDULE]: Waiting schedule release thread that still "
597
670
  "running in background."
598
671
  )
599
- delay(15)
672
+ delay(10)
600
673
  monitor(threads)
601
674
 
602
675
  break
ddeutil/workflow/stage.py CHANGED
@@ -45,7 +45,7 @@ from .__types import DictData, DictStr, TupleStr
45
45
  from .conf import config, get_logger
46
46
  from .exceptions import StageException
47
47
  from .hook import TagFunc, extract_hook
48
- from .result import Result
48
+ from .result import Result, Status
49
49
  from .templates import not_in_template, param2template
50
50
  from .utils import (
51
51
  cut_id,
@@ -121,19 +121,26 @@ class BaseStage(BaseModel, ABC):
121
121
  return self
122
122
 
123
123
  @abstractmethod
124
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
124
+ def execute(
125
+ self, params: DictData, *, result: Result | None = None
126
+ ) -> Result:
125
127
  """Execute abstraction method that action something by sub-model class.
126
128
  This is important method that make this class is able to be the stage.
127
129
 
128
130
  :param params: A parameter data that want to use in this execution.
129
- :param run_id: A running stage ID for this execution.
131
+ :param result: (Result) A result object for keeping context and status
132
+ data.
130
133
 
131
134
  :rtype: Result
132
135
  """
133
136
  raise NotImplementedError("Stage should implement ``execute`` method.")
134
137
 
135
138
  def handler_execute(
136
- self, params: DictData, *, run_id: str | None = None
139
+ self,
140
+ params: DictData,
141
+ *,
142
+ run_id: str | None = None,
143
+ result: Result | None = None,
137
144
  ) -> Result:
138
145
  """Handler result from the stage execution.
139
146
 
@@ -158,23 +165,25 @@ class BaseStage(BaseModel, ABC):
158
165
  from current stage ID before release the final result.
159
166
 
160
167
  :param params: A parameter data that want to use in this execution.
161
- :param run_id: A running stage ID for this execution.
168
+ :param run_id: (str) A running stage ID for this execution.
169
+ :param result: (Result) A result object for keeping context and status
170
+ data.
162
171
 
163
172
  :rtype: Result
164
173
  """
165
- if not run_id:
166
- run_id: str = gen_id(self.name + (self.id or ""), unique=True)
174
+ if result is None: # pragma: no cov
175
+ result: Result = Result(
176
+ run_id=(
177
+ run_id or gen_id(self.name + (self.id or ""), unique=True)
178
+ ),
179
+ )
167
180
 
168
- rs_raise: Result = Result(status=1, run_id=run_id)
169
181
  try:
170
182
  # NOTE: Start calling origin function with a passing args.
171
- return self.execute(params, run_id=run_id)
183
+ return self.execute(params, result=result)
172
184
  except Exception as err:
173
185
  # NOTE: Start catching error from the stage execution.
174
- logger.error(
175
- f"({cut_id(run_id)}) [STAGE]: {err.__class__.__name__}: "
176
- f"{err}"
177
- )
186
+ result.trace.error(f"[STAGE]: {err.__class__.__name__}: {err}")
178
187
  if config.stage_raise_error:
179
188
  # NOTE: If error that raise from stage execution course by
180
189
  # itself, it will return that error with previous
@@ -190,8 +199,8 @@ class BaseStage(BaseModel, ABC):
190
199
 
191
200
  # NOTE: Catching exception error object to result with
192
201
  # error_message and error keys.
193
- return rs_raise.catch(
194
- status=1,
202
+ return result.catch(
203
+ status=Status.FAILED,
195
204
  context={
196
205
  "error": err,
197
206
  "error_message": f"{err.__class__.__name__}: {err}",
@@ -295,7 +304,9 @@ class EmptyStage(BaseStage):
295
304
  ge=0,
296
305
  )
297
306
 
298
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
307
+ def execute(
308
+ self, params: DictData, *, result: Result | None = None
309
+ ) -> Result:
299
310
  """Execution method for the Empty stage that do only logging out to
300
311
  stdout. This method does not use the `handler_result` decorator because
301
312
  it does not get any error from logging function.
@@ -305,22 +316,21 @@ class EmptyStage(BaseStage):
305
316
 
306
317
  :param params: A context data that want to add output result. But this
307
318
  stage does not pass any output.
308
- :param run_id: A running stage ID for this execution.
319
+ :param result: (Result) A result object for keeping context and status
320
+ data.
309
321
 
310
322
  :rtype: Result
311
323
  """
312
- logger.info(
313
- f"({cut_id(run_id)}) [STAGE]: Empty-Execute: {self.name!r}: "
324
+ result.trace.info(
325
+ f"[STAGE]: Empty-Execute: {self.name!r}: "
314
326
  f"( {param2template(self.echo, params=params) or '...'} )"
315
327
  )
316
328
  if self.sleep > 0:
317
329
  if self.sleep > 30:
318
- logger.info(
319
- f"({cut_id(run_id)}) [STAGE]: ... sleep "
320
- f"({self.sleep} seconds)"
321
- )
330
+ result.trace.info(f"[STAGE]: ... sleep ({self.sleep} seconds)")
322
331
  time.sleep(self.sleep)
323
- return Result(status=0, context={}, run_id=run_id)
332
+
333
+ return result.catch(status=Status.SUCCESS)
324
334
 
325
335
 
326
336
  class BashStage(BaseStage):
@@ -334,7 +344,7 @@ class BashStage(BaseStage):
334
344
 
335
345
  Data Validate:
336
346
  >>> stage = {
337
- ... "name": "Shell stage execution",
347
+ ... "name": "The Shell stage execution",
338
348
  ... "bash": 'echo "Hello $FOO"',
339
349
  ... "env": {
340
350
  ... "FOO": "BAR",
@@ -391,20 +401,25 @@ class BashStage(BaseStage):
391
401
  # Note: Remove .sh file that use to run bash.
392
402
  Path(f"./{f_name}").unlink()
393
403
 
394
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
404
+ def execute(
405
+ self, params: DictData, *, result: Result | None = None
406
+ ) -> Result:
395
407
  """Execute the Bash statement with the Python build-in ``subprocess``
396
408
  package.
397
409
 
398
410
  :param params: A parameter data that want to use in this execution.
399
- :param run_id: A running stage ID for this execution.
411
+ :param result: (Result) A result object for keeping context and status
412
+ data.
400
413
 
401
414
  :rtype: Result
402
415
  """
403
416
  bash: str = param2template(dedent(self.bash), params)
404
417
 
405
- logger.info(f"({cut_id(run_id)}) [STAGE]: Shell-Execute: {self.name}")
418
+ result.trace.info(f"[STAGE]: Shell-Execute: {self.name}")
406
419
  with self.create_sh_file(
407
- bash=bash, env=param2template(self.env, params), run_id=run_id
420
+ bash=bash,
421
+ env=param2template(self.env, params),
422
+ run_id=result.run_id,
408
423
  ) as sh:
409
424
  rs: CompletedProcess = subprocess.run(
410
425
  sh, shell=False, capture_output=True, text=True
@@ -420,14 +435,13 @@ class BashStage(BaseStage):
420
435
  f"Subprocess: {err}\nRunning Statement:\n---\n"
421
436
  f"```bash\n{bash}\n```"
422
437
  )
423
- return Result(
424
- status=0,
438
+ return result.catch(
439
+ status=Status.SUCCESS,
425
440
  context={
426
441
  "return_code": rs.returncode,
427
442
  "stdout": rs.stdout.rstrip("\n") or None,
428
443
  "stderr": rs.stderr.rstrip("\n") or None,
429
444
  },
430
- run_id=run_id,
431
445
  )
432
446
 
433
447
 
@@ -492,12 +506,15 @@ class PyStage(BaseStage):
492
506
  to.update({k: gb[k] for k in to if k in gb})
493
507
  return to
494
508
 
495
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
509
+ def execute(
510
+ self, params: DictData, *, result: Result | None = None
511
+ ) -> Result:
496
512
  """Execute the Python statement that pass all globals and input params
497
513
  to globals argument on ``exec`` build-in function.
498
514
 
499
515
  :param params: A parameter that want to pass before run any statement.
500
- :param run_id: A running stage ID for this execution.
516
+ :param result: (Result) A result object for keeping context and status
517
+ data.
501
518
 
502
519
  :rtype: Result
503
520
  """
@@ -511,16 +528,14 @@ class PyStage(BaseStage):
511
528
  lc: DictData = {}
512
529
 
513
530
  # NOTE: Start exec the run statement.
514
- logger.info(f"({cut_id(run_id)}) [STAGE]: Py-Execute: {self.name}")
531
+ result.trace.info(f"[STAGE]: Py-Execute: {self.name}")
515
532
 
516
533
  # WARNING: The exec build-in function is very dangerous. So, it
517
534
  # should use the re module to validate exec-string before running.
518
535
  exec(run, _globals, lc)
519
536
 
520
- return Result(
521
- status=0,
522
- context={"locals": lc, "globals": _globals},
523
- run_id=run_id,
537
+ return result.catch(
538
+ status=Status.SUCCESS, context={"locals": lc, "globals": _globals}
524
539
  )
525
540
 
526
541
 
@@ -552,7 +567,9 @@ class HookStage(BaseStage):
552
567
  alias="with",
553
568
  )
554
569
 
555
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
570
+ def execute(
571
+ self, params: DictData, *, result: Result | None = None
572
+ ) -> Result:
556
573
  """Execute the Hook function that already in the hook registry.
557
574
 
558
575
  :raise ValueError: When the necessary arguments of hook function do not
@@ -562,7 +579,8 @@ class HookStage(BaseStage):
562
579
 
563
580
  :param params: A parameter that want to pass before run any statement.
564
581
  :type params: DictData
565
- :param run_id: A running stage ID for this execution.
582
+ :param result: (Result) A result object for keeping context and status
583
+ data.
566
584
  :type: str | None
567
585
 
568
586
  :rtype: Result
@@ -571,7 +589,7 @@ class HookStage(BaseStage):
571
589
 
572
590
  # VALIDATE: check input task caller parameters that exists before
573
591
  # calling.
574
- args: DictData = param2template(self.args, params)
592
+ args: DictData = {"result": result} | param2template(self.args, params)
575
593
  ips = inspect.signature(t_func)
576
594
  if any(
577
595
  (k.removeprefix("_") not in args and k not in args)
@@ -587,10 +605,10 @@ class HookStage(BaseStage):
587
605
  if k.removeprefix("_") in args:
588
606
  args[k] = args.pop(k.removeprefix("_"))
589
607
 
590
- logger.info(
591
- f"({cut_id(run_id)}) [STAGE]: Hook-Execute: "
592
- f"{t_func.name}@{t_func.tag}"
593
- )
608
+ if "result" not in ips.parameters:
609
+ args.pop("result")
610
+
611
+ result.trace.info(f"[STAGE]: Hook-Execute: {t_func.name}@{t_func.tag}")
594
612
  rs: DictData = t_func(**param2template(args, params))
595
613
 
596
614
  # VALIDATE:
@@ -600,7 +618,7 @@ class HookStage(BaseStage):
600
618
  f"Return type: '{t_func.name}@{t_func.tag}' does not serialize "
601
619
  f"to result model, you change return type to `dict`."
602
620
  )
603
- return Result(status=0, context=rs, run_id=run_id)
621
+ return result.catch(status=Status.SUCCESS, context=rs)
604
622
 
605
623
 
606
624
  class TriggerStage(BaseStage):
@@ -626,12 +644,15 @@ class TriggerStage(BaseStage):
626
644
  description="A parameter that want to pass to workflow execution.",
627
645
  )
628
646
 
629
- def execute(self, params: DictData, *, run_id: str | None = None) -> Result:
647
+ def execute(
648
+ self, params: DictData, *, result: Result | None = None
649
+ ) -> Result:
630
650
  """Trigger another workflow execution. It will wait the trigger
631
651
  workflow running complete before catching its result.
632
652
 
633
653
  :param params: A parameter data that want to use in this execution.
634
- :param run_id: A running stage ID for this execution.
654
+ :param result: (Result) A result object for keeping context and status
655
+ data.
635
656
 
636
657
  :rtype: Result
637
658
  """
@@ -644,13 +665,11 @@ class TriggerStage(BaseStage):
644
665
  # NOTE: Set running workflow ID from running stage ID to external
645
666
  # params on Loader object.
646
667
  wf: Workflow = Workflow.from_loader(name=_trigger)
647
- logger.info(
648
- f"({cut_id(run_id)}) [STAGE]: Trigger-Execute: {_trigger!r}"
649
- )
668
+ result.trace.info(f"[STAGE]: Trigger-Execute: {_trigger!r}")
650
669
  return wf.execute(
651
670
  params=param2template(self.params, params),
652
- run_id=run_id,
653
- ).set_run_id(run_id)
671
+ result=result,
672
+ )
654
673
 
655
674
 
656
675
  # NOTE:
@@ -668,12 +687,12 @@ Stage = Union[
668
687
 
669
688
 
670
689
  # TODO: Not implement this stages yet
671
- class ParallelStage(BaseModel):
690
+ class ParallelStage(BaseModel): # pragma: no cov
672
691
  parallel: list[Stage]
673
692
  max_parallel_core: int = Field(default=2)
674
693
 
675
694
 
676
695
  # TODO: Not implement this stages yet
677
- class ForEachStage(BaseModel):
696
+ class ForEachStage(BaseModel): # pragma: no cov
678
697
  foreach: list[str]
679
698
  stages: list[Stage]
@@ -79,7 +79,7 @@ def custom_filter(name: str) -> Callable[P, FilterFunc]:
79
79
  def make_filter_registry() -> dict[str, FilterRegistry]:
80
80
  """Return registries of all functions that able to called with task.
81
81
 
82
- :rtype: dict[str, Registry]
82
+ :rtype: dict[str, FilterRegistry]
83
83
  """
84
84
  rs: dict[str, FilterRegistry] = {}
85
85
  for module in config.regis_filter:
@@ -108,6 +108,8 @@ def get_args_const(
108
108
  ) -> tuple[str, list[Constant], dict[str, Constant]]:
109
109
  """Get arguments and keyword-arguments from function calling string.
110
110
 
111
+ :param expr: An expr string value.
112
+
111
113
  :rtype: tuple[str, list[Constant], dict[str, Constant]]
112
114
  """
113
115
  try:
@@ -150,6 +152,11 @@ def get_args_from_filter(
150
152
  ) -> tuple[str, FilterRegistry, list[Any], dict[Any, Any]]: # pragma: no cov
151
153
  """Get arguments and keyword-arguments from filter function calling string.
152
154
  and validate it with the filter functions mapping dict.
155
+
156
+ :param ft:
157
+ :param filters:
158
+
159
+ :rtype: tuple[str, FilterRegistry, list[Any], dict[Any, Any]]
153
160
  """
154
161
  func_name, _args, _kwargs = get_args_const(ft)
155
162
  args: list[Any] = [arg.value for arg in _args]
@@ -243,7 +250,7 @@ def str2template(
243
250
  params: DictData,
244
251
  *,
245
252
  filters: dict[str, FilterRegistry] | None = None,
246
- ) -> Any:
253
+ ) -> str:
247
254
  """(Sub-function) Pass param to template string that can search by
248
255
  ``RE_CALLER`` regular expression.
249
256
 
@@ -255,6 +262,8 @@ def str2template(
255
262
  :param params: A parameter value that getting with matched regular
256
263
  expression.
257
264
  :param filters:
265
+
266
+ :rtype: str
258
267
  """
259
268
  filters: dict[str, FilterRegistry] = filters or make_filter_registry()
260
269
 
@@ -295,7 +304,7 @@ def str2template(
295
304
  return search_env_replace(value)
296
305
 
297
306
 
298
- def param2template(value: Any, params: DictData) -> Any:
307
+ def param2template(value: T, params: DictData) -> T:
299
308
  """Pass param to template string that can search by ``RE_CALLER`` regular
300
309
  expression.
301
310
 
@@ -303,7 +312,7 @@ def param2template(value: Any, params: DictData) -> Any:
303
312
  :param params: A parameter value that getting with matched regular
304
313
  expression.
305
314
 
306
- :rtype: Any
315
+ :rtype: T
307
316
  :returns: An any getter value from the params input.
308
317
  """
309
318
  filters: dict[str, FilterRegistry] = make_filter_registry()