ddeutil-workflow 0.0.54__tar.gz → 0.0.56__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.
- {ddeutil_workflow-0.0.54/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.56}/PKG-INFO +5 -7
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/README.md +4 -6
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/pyproject.toml +4 -3
- ddeutil_workflow-0.0.56/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/__init__.py +4 -2
- ddeutil_workflow-0.0.56/src/ddeutil/workflow/__main__.py +30 -0
- ddeutil_workflow-0.0.54/src/ddeutil/workflow/api/api.py → ddeutil_workflow-0.0.56/src/ddeutil/workflow/api/__init__.py +1 -1
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/job.py +22 -21
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/schedules.py +0 -2
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/workflows.py +3 -4
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/conf.py +144 -94
- ddeutil_workflow-0.0.54/src/ddeutil/workflow/cron.py → ddeutil_workflow-0.0.56/src/ddeutil/workflow/event.py +36 -20
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/exceptions.py +10 -1
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/job.py +23 -14
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/result.py +1 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/scheduler.py +33 -74
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/stages.py +169 -116
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/workflow.py +57 -106
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56/src/ddeutil_workflow.egg-info}/PKG-INFO +5 -7
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil_workflow.egg-info/SOURCES.txt +3 -3
- ddeutil_workflow-0.0.56/src/ddeutil_workflow.egg-info/entry_points.txt +2 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test__cron.py +0 -6
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_conf.py +25 -38
- ddeutil_workflow-0.0.54/tests/test_cron_on.py → ddeutil_workflow-0.0.56/tests/test_event.py +32 -21
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_job_exec.py +13 -13
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_job_exec_strategy.py +7 -7
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_release_queue.py +6 -2
- ddeutil_workflow-0.0.56/tests/test_reusables_call_tag.py +121 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_schedule.py +6 -9
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_schedule_workflow.py +1 -1
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_stage.py +22 -27
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_stage_handler_exec.py +67 -68
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_utils.py +2 -3
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow.py +102 -115
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow_exec.py +4 -4
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow_exec_job.py +1 -1
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow_task.py +1 -5
- ddeutil_workflow-0.0.54/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.54/src/ddeutil/workflow/__main__.py +0 -0
- ddeutil_workflow-0.0.54/src/ddeutil/workflow/api/__init__.py +0 -1
- ddeutil_workflow-0.0.54/tests/test_reusables_call_tag.py +0 -252
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/LICENSE +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/logs.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/logs.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/utils.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/logs.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/params.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/reusables.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/utils.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil_workflow.egg-info/requires.txt +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_logs_audit.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_logs_trace.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_release.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_result.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_reusables_template.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_reusables_template_filter.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_schedule_pending.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_schedule_tasks.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_scheduler_control.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_strategy.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow_exec_poke.py +0 -0
- {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/tests/test_workflow_exec_release.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.56
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -121,12 +121,10 @@ flowchart LR
|
|
121
121
|
|
122
122
|
> [!WARNING]
|
123
123
|
> _**Disclaimer**_: I inspire the dynamic YAML statement from the [**GitHub Action**](https://github.com/features/actions),
|
124
|
-
> and
|
125
|
-
>
|
126
|
-
|
127
|
-
>
|
128
|
-
> Other workflow orchestration tools that I interest and pick them to be inspiration
|
129
|
-
> some for this package:
|
124
|
+
> and my experience of data framework configs pattern. :grimacing:
|
125
|
+
>
|
126
|
+
> Other workflow orchestration services that I interest and pick them to be
|
127
|
+
> this project inspiration:
|
130
128
|
>
|
131
129
|
> - [Google **Workflows**](https://cloud.google.com/workflows)
|
132
130
|
> - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
|
@@ -76,12 +76,10 @@ flowchart LR
|
|
76
76
|
|
77
77
|
> [!WARNING]
|
78
78
|
> _**Disclaimer**_: I inspire the dynamic YAML statement from the [**GitHub Action**](https://github.com/features/actions),
|
79
|
-
> and
|
80
|
-
>
|
81
|
-
|
82
|
-
>
|
83
|
-
> Other workflow orchestration tools that I interest and pick them to be inspiration
|
84
|
-
> some for this package:
|
79
|
+
> and my experience of data framework configs pattern. :grimacing:
|
80
|
+
>
|
81
|
+
> Other workflow orchestration services that I interest and pick them to be
|
82
|
+
> this project inspiration:
|
85
83
|
>
|
86
84
|
> - [Google **Workflows**](https://cloud.google.com/workflows)
|
87
85
|
> - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
|
@@ -57,6 +57,9 @@ docker = [
|
|
57
57
|
Homepage = "https://github.com/ddeutils/ddeutil-workflow/"
|
58
58
|
"Source Code" = "https://github.com/ddeutils/ddeutil-workflow/"
|
59
59
|
|
60
|
+
[project.scripts]
|
61
|
+
workflow-cli = "ddeutil.workflow.__main__:app"
|
62
|
+
|
60
63
|
[tool.setuptools.dynamic]
|
61
64
|
version = {attr = "ddeutil.workflow.__about__.__version__"}
|
62
65
|
|
@@ -79,11 +82,9 @@ source = ["src.ddeutil.workflow"]
|
|
79
82
|
omit = [
|
80
83
|
"src/ddeutil/workflow/__about__.py",
|
81
84
|
"src/ddeutil/workflow/__cron.py",
|
82
|
-
"src/ddeutil/workflow/context.py",
|
83
85
|
"src/ddeutil/workflow/api/__init__.py",
|
84
|
-
"src/ddeutil/workflow/api/api.py",
|
85
86
|
"src/ddeutil/workflow/api/log.py",
|
86
|
-
"src/ddeutil/workflow/api/
|
87
|
+
"src/ddeutil/workflow/api/utils.py",
|
87
88
|
"src/ddeutil/workflow/api/routes/__init__.py",
|
88
89
|
"src/ddeutil/workflow/api/routes/job.py",
|
89
90
|
"src/ddeutil/workflow/api/routes/logs.py",
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.56"
|
@@ -7,16 +7,18 @@ from .__cron import CronJob, CronRunner
|
|
7
7
|
from .__types import DictData, DictStr, Matrix, Re, TupleStr
|
8
8
|
from .conf import (
|
9
9
|
Config,
|
10
|
-
|
10
|
+
FileLoad,
|
11
11
|
config,
|
12
12
|
env,
|
13
13
|
)
|
14
|
-
from .
|
14
|
+
from .event import *
|
15
15
|
from .exceptions import *
|
16
16
|
from .job import *
|
17
17
|
from .logs import (
|
18
18
|
Audit,
|
19
19
|
AuditModel,
|
20
|
+
FileAudit,
|
21
|
+
FileTrace,
|
20
22
|
Trace,
|
21
23
|
TraceData,
|
22
24
|
TraceMeta,
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import typer
|
2
|
+
|
3
|
+
app = typer.Typer()
|
4
|
+
|
5
|
+
|
6
|
+
@app.callback()
|
7
|
+
def callback():
|
8
|
+
"""
|
9
|
+
Awesome Portal Gun
|
10
|
+
"""
|
11
|
+
|
12
|
+
|
13
|
+
@app.command()
|
14
|
+
def provision():
|
15
|
+
"""
|
16
|
+
Shoot the portal gun
|
17
|
+
"""
|
18
|
+
typer.echo("Shooting portal gun")
|
19
|
+
|
20
|
+
|
21
|
+
@app.command()
|
22
|
+
def job():
|
23
|
+
"""
|
24
|
+
Load the portal gun
|
25
|
+
"""
|
26
|
+
typer.echo("Loading portal gun")
|
27
|
+
|
28
|
+
|
29
|
+
if __name__ == "__main__":
|
30
|
+
app()
|
@@ -90,7 +90,7 @@ app.add_middleware(
|
|
90
90
|
)
|
91
91
|
|
92
92
|
|
93
|
-
@app.get("/")
|
93
|
+
@app.get(path="/", response_class=UJSONResponse)
|
94
94
|
async def health():
|
95
95
|
"""Index view that not return any template without json status."""
|
96
96
|
return {"message": "Workflow already start up with healthy status."}
|
@@ -9,7 +9,7 @@ from typing import Any, Optional
|
|
9
9
|
|
10
10
|
from fastapi import APIRouter
|
11
11
|
from fastapi.responses import UJSONResponse
|
12
|
-
from pydantic import BaseModel
|
12
|
+
from pydantic import BaseModel, Field
|
13
13
|
|
14
14
|
from ...__types import DictData
|
15
15
|
from ...exceptions import JobException
|
@@ -18,33 +18,37 @@ from ...logs import get_logger
|
|
18
18
|
from ...result import Result
|
19
19
|
|
20
20
|
logger = get_logger("uvicorn.error")
|
21
|
+
job_route = APIRouter(prefix="/job", tags=["job"])
|
21
22
|
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
)
|
24
|
+
class ResultCreate(BaseModel):
|
25
|
+
"""Create Result model for receive running IDs to create the Result
|
26
|
+
dataclass.
|
27
|
+
"""
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
parent_run_id: Optional[str] = None
|
29
|
+
run_id: str = Field(description="A running ID.")
|
30
|
+
parent_run_id: Optional[str] = Field(
|
31
|
+
default=None, description="A parent running ID."
|
32
|
+
)
|
34
33
|
|
35
34
|
|
36
|
-
@job_route.post(path="/execute/")
|
35
|
+
@job_route.post(path="/execute/", response_class=UJSONResponse)
|
37
36
|
async def job_execute(
|
38
|
-
result:
|
37
|
+
result: ResultCreate,
|
39
38
|
job: Job,
|
40
39
|
params: dict[str, Any],
|
40
|
+
extras: Optional[dict[str, Any]] = None,
|
41
41
|
):
|
42
|
-
"""Execute job via RestAPI."""
|
42
|
+
"""Execute job via RestAPI with execute route path."""
|
43
43
|
rs: Result = Result(
|
44
|
-
context=result.context,
|
45
44
|
run_id=result.run_id,
|
46
45
|
parent_run_id=result.parent_run_id,
|
46
|
+
extras=extras or {},
|
47
47
|
)
|
48
|
+
|
49
|
+
if extras:
|
50
|
+
job.extras = extras
|
51
|
+
|
48
52
|
context: DictData = {}
|
49
53
|
try:
|
50
54
|
job.set_outputs(
|
@@ -59,14 +63,11 @@ async def job_execute(
|
|
59
63
|
rs.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
|
60
64
|
|
61
65
|
return {
|
62
|
-
"message": "
|
63
|
-
"result": {
|
64
|
-
"run_id": rs.run_id,
|
65
|
-
"parent_run_id": rs.parent_run_id,
|
66
|
-
},
|
66
|
+
"message": "Execute job via RestAPI.",
|
67
|
+
"result": {"run_id": rs.run_id, "parent_run_id": rs.parent_run_id},
|
67
68
|
"job": job.model_dump(
|
68
69
|
by_alias=True,
|
69
|
-
exclude_none=
|
70
|
+
exclude_none=False,
|
70
71
|
exclude_unset=True,
|
71
72
|
exclude_defaults=True,
|
72
73
|
),
|
{ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/schedules.py
RENAMED
@@ -17,7 +17,6 @@ from ...logs import get_logger
|
|
17
17
|
from ...scheduler import Schedule
|
18
18
|
|
19
19
|
logger = get_logger("uvicorn.error")
|
20
|
-
|
21
20
|
schedule_route = APIRouter(
|
22
21
|
prefix="/schedules",
|
23
22
|
tags=["schedules"],
|
@@ -108,7 +107,6 @@ async def add_deploy_scheduler(request: Request, name: str):
|
|
108
107
|
schedule.tasks(
|
109
108
|
start_date_waiting,
|
110
109
|
queue=request.state.workflow_queue,
|
111
|
-
extras={},
|
112
110
|
),
|
113
111
|
)
|
114
112
|
return {
|
{ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.56}/src/ddeutil/workflow/api/routes/workflows.py
RENAMED
@@ -21,7 +21,6 @@ from ...result import Result
|
|
21
21
|
from ...workflow import Workflow
|
22
22
|
|
23
23
|
logger = get_logger("uvicorn.error")
|
24
|
-
|
25
24
|
workflow_route = APIRouter(
|
26
25
|
prefix="/workflows",
|
27
26
|
tags=["workflows"],
|
@@ -55,7 +54,7 @@ async def get_workflow_by_name(name: str) -> DictData:
|
|
55
54
|
) from None
|
56
55
|
return workflow.model_dump(
|
57
56
|
by_alias=True,
|
58
|
-
exclude_none=
|
57
|
+
exclude_none=False,
|
59
58
|
exclude_unset=True,
|
60
59
|
exclude_defaults=True,
|
61
60
|
)
|
@@ -98,7 +97,7 @@ async def get_workflow_audits(name: str):
|
|
98
97
|
"audits": [
|
99
98
|
audit.model_dump(
|
100
99
|
by_alias=True,
|
101
|
-
exclude_none=
|
100
|
+
exclude_none=False,
|
102
101
|
exclude_unset=True,
|
103
102
|
exclude_defaults=True,
|
104
103
|
)
|
@@ -132,7 +131,7 @@ async def get_workflow_release_audit(name: str, release: str):
|
|
132
131
|
"message": f"Getting workflow {name!r} audit in release {release}",
|
133
132
|
"audit": audit.model_dump(
|
134
133
|
by_alias=True,
|
135
|
-
exclude_none=
|
134
|
+
exclude_none=False,
|
136
135
|
exclude_unset=True,
|
137
136
|
exclude_defaults=True,
|
138
137
|
),
|
@@ -7,24 +7,26 @@ from __future__ import annotations
|
|
7
7
|
|
8
8
|
import json
|
9
9
|
import os
|
10
|
+
from abc import ABC, abstractmethod
|
10
11
|
from collections.abc import Iterator
|
11
12
|
from datetime import timedelta
|
12
13
|
from functools import cached_property
|
14
|
+
from inspect import isclass
|
13
15
|
from pathlib import Path
|
14
|
-
from typing import Final, Optional, TypeVar
|
16
|
+
from typing import Final, Optional, Protocol, TypeVar, Union
|
15
17
|
from zoneinfo import ZoneInfo
|
16
18
|
|
17
19
|
from ddeutil.core import str2bool
|
18
20
|
from ddeutil.io import YamlFlResolve
|
19
21
|
from ddeutil.io.paths import glob_files, is_ignored, read_ignore
|
20
22
|
|
21
|
-
from .__types import DictData
|
23
|
+
from .__types import DictData
|
22
24
|
|
23
25
|
T = TypeVar("T")
|
24
26
|
PREFIX: Final[str] = "WORKFLOW"
|
25
27
|
|
26
28
|
|
27
|
-
def env(var: str, default: str | None = None) -> str | None:
|
29
|
+
def env(var: str, default: str | None = None) -> str | None:
|
28
30
|
"""Get environment variable with uppercase and adding prefix string.
|
29
31
|
|
30
32
|
:param var: (str) A env variable name.
|
@@ -35,17 +37,6 @@ def env(var: str, default: str | None = None) -> str | None: # pragma: no cov
|
|
35
37
|
return os.getenv(f"{PREFIX}_{var.upper().replace(' ', '_')}", default)
|
36
38
|
|
37
39
|
|
38
|
-
__all__: TupleStr = (
|
39
|
-
"api_config",
|
40
|
-
"env",
|
41
|
-
"Config",
|
42
|
-
"SimLoad",
|
43
|
-
"Loader",
|
44
|
-
"config",
|
45
|
-
"dynamic",
|
46
|
-
)
|
47
|
-
|
48
|
-
|
49
40
|
class Config: # pragma: no cov
|
50
41
|
"""Config object for keeping core configurations on the current session
|
51
42
|
without changing when if the application still running.
|
@@ -188,7 +179,7 @@ class Config: # pragma: no cov
|
|
188
179
|
return timedelta(**json.loads(stop_boundary_delta_str))
|
189
180
|
except Exception as err:
|
190
181
|
raise ValueError(
|
191
|
-
"Config
|
182
|
+
"Config `WORKFLOW_APP_STOP_BOUNDARY_DELTA` can not parsing to"
|
192
183
|
f"timedelta with {stop_boundary_delta_str}."
|
193
184
|
) from err
|
194
185
|
|
@@ -209,110 +200,194 @@ class APIConfig:
|
|
209
200
|
return str2bool(env("API_ENABLE_ROUTE_SCHEDULE", "true"))
|
210
201
|
|
211
202
|
|
212
|
-
class
|
213
|
-
|
214
|
-
|
203
|
+
class BaseLoad(ABC):
|
204
|
+
|
205
|
+
@classmethod
|
206
|
+
@abstractmethod
|
207
|
+
def find(cls, name: str, *args, **kwargs) -> DictData: ...
|
208
|
+
|
209
|
+
@classmethod
|
210
|
+
@abstractmethod
|
211
|
+
def finds(
|
212
|
+
cls, obj: object, *args, **kwargs
|
213
|
+
) -> Iterator[tuple[str, DictData]]: ...
|
215
214
|
|
216
|
-
|
217
|
-
|
218
|
-
|
215
|
+
|
216
|
+
class FileLoad(BaseLoad):
|
217
|
+
"""Base Load object that use to search config data by given some identity
|
218
|
+
value like name of `Workflow` or `On` templates.
|
219
|
+
|
220
|
+
:param name: (str) A name of key of config data that read with YAML
|
221
|
+
Environment object.
|
222
|
+
:param path: (Path) A config path object.
|
223
|
+
:param externals: (DictData) An external config data that want to add to
|
224
|
+
loaded config data.
|
225
|
+
:param extras: (DictDdata) An extra parameters that use to override core
|
226
|
+
config values.
|
227
|
+
|
228
|
+
:raise ValueError: If the data does not find on the config path with the
|
229
|
+
name parameter.
|
219
230
|
|
220
231
|
Noted:
|
221
|
-
The config data should have
|
232
|
+
The config data should have `type` key for modeling validation that
|
222
233
|
make this loader know what is config should to do pass to.
|
223
234
|
|
224
235
|
... <identity-key>:
|
225
236
|
... type: <importable-object>
|
226
237
|
... <key-data-1>: <value-data-1>
|
227
238
|
... <key-data-2>: <value-data-2>
|
239
|
+
|
240
|
+
This object support multiple config paths if you pass the `conf_paths`
|
241
|
+
key to the `extras` parameter.
|
228
242
|
"""
|
229
243
|
|
230
244
|
def __init__(
|
231
245
|
self,
|
232
246
|
name: str,
|
233
|
-
|
247
|
+
*,
|
248
|
+
path: Optional[Union[str, Path]] = None,
|
234
249
|
externals: DictData | None = None,
|
250
|
+
extras: DictData | None = None,
|
235
251
|
) -> None:
|
236
|
-
self.
|
252
|
+
self.path: Path = Path(dynamic("conf_path", f=path, extras=extras))
|
237
253
|
self.externals: DictData = externals or {}
|
238
|
-
|
239
|
-
self.data: DictData =
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
if data := self.filter_yaml(file, name=name):
|
246
|
-
self.data = data
|
254
|
+
self.extras: DictData = extras or {}
|
255
|
+
self.data: DictData = self.find(
|
256
|
+
name,
|
257
|
+
path=path,
|
258
|
+
paths=self.extras.get("conf_paths"),
|
259
|
+
extras=extras,
|
260
|
+
)
|
247
261
|
|
248
262
|
# VALIDATE: check the data that reading should not empty.
|
249
263
|
if not self.data:
|
250
264
|
raise ValueError(
|
251
|
-
f"Config {name!r} does not found on conf path: "
|
252
|
-
f"{self.conf_path}."
|
265
|
+
f"Config {name!r} does not found on the conf path: {self.path}."
|
253
266
|
)
|
254
267
|
|
255
268
|
self.data.update(self.externals)
|
256
269
|
|
270
|
+
@classmethod
|
271
|
+
def find(
|
272
|
+
cls,
|
273
|
+
name: str,
|
274
|
+
*,
|
275
|
+
path: Optional[Path] = None,
|
276
|
+
paths: Optional[list[Path]] = None,
|
277
|
+
extras: Optional[DictData] = None,
|
278
|
+
) -> DictData:
|
279
|
+
"""Find data with specific key and return the latest modify date data if
|
280
|
+
this key exists multiple files.
|
281
|
+
|
282
|
+
:param name: (str) A name of data that want to find.
|
283
|
+
:param path: (Path) A config path object.
|
284
|
+
:param paths: (list[Path]) A list of config path object.
|
285
|
+
:param extras: (DictData) An extra parameter that use to override core
|
286
|
+
config values.
|
287
|
+
|
288
|
+
:rtype: DictData
|
289
|
+
"""
|
290
|
+
path: Path = dynamic("conf_path", f=path, extras=extras)
|
291
|
+
if not paths:
|
292
|
+
paths: list[Path] = [path]
|
293
|
+
elif not isinstance(paths, list):
|
294
|
+
raise TypeError(
|
295
|
+
f"Multi-config paths does not support for type: {type(paths)}"
|
296
|
+
)
|
297
|
+
else:
|
298
|
+
paths.append(path)
|
299
|
+
|
300
|
+
all_data: list[tuple[float, DictData]] = []
|
301
|
+
for path in paths:
|
302
|
+
for file in glob_files(path):
|
303
|
+
|
304
|
+
if cls.is_ignore(file, path):
|
305
|
+
continue
|
306
|
+
|
307
|
+
if data := cls.filter_yaml(file, name=name):
|
308
|
+
all_data.append((file.lstat().st_mtime, data))
|
309
|
+
|
310
|
+
return {} if not all_data else max(all_data, key=lambda x: x[0])[1]
|
311
|
+
|
257
312
|
@classmethod
|
258
313
|
def finds(
|
259
314
|
cls,
|
260
315
|
obj: object,
|
261
|
-
conf_path: Path,
|
262
316
|
*,
|
263
|
-
|
317
|
+
path: Optional[Path] = None,
|
318
|
+
paths: Optional[list[Path]] = None,
|
264
319
|
excluded: list[str] | None = None,
|
320
|
+
extras: Optional[DictData] = None,
|
265
321
|
) -> Iterator[tuple[str, DictData]]:
|
266
322
|
"""Find all data that match with object type in config path. This class
|
267
323
|
method can use include and exclude list of identity name for filter and
|
268
324
|
adds-on.
|
269
325
|
|
270
326
|
:param obj: An object that want to validate matching before return.
|
271
|
-
:param
|
272
|
-
:param
|
273
|
-
data if any key exist.
|
327
|
+
:param path: A config path object.
|
328
|
+
:param paths: (list[Path]) A list of config path object.
|
274
329
|
:param excluded: An included list of data key that want to filter from
|
275
330
|
data.
|
331
|
+
:param extras: (DictData) An extra parameter that use to override core
|
332
|
+
config values.
|
276
333
|
|
277
334
|
:rtype: Iterator[tuple[str, DictData]]
|
278
335
|
"""
|
279
|
-
|
280
|
-
|
336
|
+
excluded: list[str] = excluded or []
|
337
|
+
path: Path = dynamic("conf_path", f=path, extras=extras)
|
338
|
+
if not paths:
|
339
|
+
paths: list[Path] = [path]
|
340
|
+
else:
|
341
|
+
paths.append(path)
|
342
|
+
|
343
|
+
all_data: dict[str, list[tuple[float, DictData]]] = {}
|
344
|
+
for path in paths:
|
345
|
+
for file in glob_files(path):
|
346
|
+
|
347
|
+
if cls.is_ignore(file, path):
|
348
|
+
continue
|
281
349
|
|
282
|
-
|
283
|
-
continue
|
350
|
+
for key, data in cls.filter_yaml(file).items():
|
284
351
|
|
285
|
-
|
352
|
+
if key in excluded:
|
353
|
+
continue
|
286
354
|
|
287
|
-
|
288
|
-
|
355
|
+
if (
|
356
|
+
data.get("type", "")
|
357
|
+
== (obj if isclass(obj) else obj.__class__).__name__
|
358
|
+
):
|
359
|
+
marking: tuple[float, DictData] = (
|
360
|
+
file.lstat().st_mtime,
|
361
|
+
data,
|
362
|
+
)
|
363
|
+
if key in all_data:
|
364
|
+
all_data[key].append(marking)
|
365
|
+
else:
|
366
|
+
all_data[key] = [marking]
|
289
367
|
|
290
|
-
|
291
|
-
|
292
|
-
{k: data[k] for k in data if k in included}
|
293
|
-
if included
|
294
|
-
else data
|
295
|
-
)
|
368
|
+
for key in all_data:
|
369
|
+
yield key, max(all_data[key], key=lambda x: x[0])[1]
|
296
370
|
|
297
371
|
@classmethod
|
298
372
|
def is_ignore(
|
299
373
|
cls,
|
300
374
|
file: Path,
|
301
|
-
|
375
|
+
path: Path,
|
302
376
|
*,
|
303
377
|
ignore_filename: Optional[str] = None,
|
304
378
|
) -> bool:
|
305
379
|
"""Check this file was ignored.
|
306
380
|
|
307
381
|
:param file: (Path) A file path that want to check.
|
308
|
-
:param
|
382
|
+
:param path: (Path) A config path that want to read the config
|
309
383
|
ignore file.
|
310
|
-
:param ignore_filename: (str) An ignore filename.
|
384
|
+
:param ignore_filename: (str) An ignore filename. Default is
|
385
|
+
`.confignore` filename.
|
311
386
|
|
312
387
|
:rtype: bool
|
313
388
|
"""
|
314
389
|
ignore_filename: str = ignore_filename or ".confignore"
|
315
|
-
return is_ignored(file, read_ignore(
|
390
|
+
return is_ignored(file, read_ignore(path / ignore_filename))
|
316
391
|
|
317
392
|
@classmethod
|
318
393
|
def filter_yaml(cls, file: Path, name: str | None = None) -> DictData:
|
@@ -369,44 +444,19 @@ def dynamic(
|
|
369
444
|
return rsx if rsx is not None else rs
|
370
445
|
|
371
446
|
|
372
|
-
class Loader(
|
373
|
-
|
447
|
+
class Loader(Protocol): # pragma: no cov
|
448
|
+
type: str
|
449
|
+
path: Path
|
450
|
+
data: DictData
|
451
|
+
extras: DictData
|
452
|
+
externals: DictData
|
374
453
|
|
375
|
-
|
376
|
-
:param externals: (DictData) An external parameters
|
377
|
-
"""
|
454
|
+
def __init__(self, *args, **kwargs) -> None: ...
|
378
455
|
|
379
456
|
@classmethod
|
380
|
-
def
|
381
|
-
cls,
|
382
|
-
obj: object,
|
383
|
-
*,
|
384
|
-
path: Path | None = None,
|
385
|
-
included: list[str] | None = None,
|
386
|
-
excluded: list[str] | None = None,
|
387
|
-
**kwargs,
|
388
|
-
) -> Iterator[tuple[str, DictData]]:
|
389
|
-
"""Override the find class method from the Simple Loader object.
|
457
|
+
def find(cls, name: str, *args, **kwargs) -> DictData: ...
|
390
458
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
:param excluded: An included list of data key that want to filter from
|
396
|
-
data.
|
397
|
-
|
398
|
-
:rtype: Iterator[tuple[str, DictData]]
|
399
|
-
"""
|
400
|
-
return super().finds(
|
401
|
-
obj=obj,
|
402
|
-
conf_path=(path or config.conf_path),
|
403
|
-
included=included,
|
404
|
-
excluded=excluded,
|
405
|
-
)
|
406
|
-
|
407
|
-
def __init__(self, name: str, externals: DictData) -> None:
|
408
|
-
super().__init__(
|
409
|
-
name,
|
410
|
-
conf_path=dynamic("conf_path", extras=externals),
|
411
|
-
externals=externals,
|
412
|
-
)
|
459
|
+
@classmethod
|
460
|
+
def finds(
|
461
|
+
cls, obj: object, *args, **kwargs
|
462
|
+
) -> Iterator[tuple[str, DictData]]: ...
|