FlowerPower 0.9.12.4__py3-none-any.whl → 1.0.0b1__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.
- flowerpower/__init__.py +17 -2
- flowerpower/cfg/__init__.py +201 -149
- flowerpower/cfg/base.py +122 -24
- flowerpower/cfg/pipeline/__init__.py +254 -0
- flowerpower/cfg/pipeline/adapter.py +66 -0
- flowerpower/cfg/pipeline/run.py +40 -11
- flowerpower/cfg/pipeline/schedule.py +69 -79
- flowerpower/cfg/project/__init__.py +149 -0
- flowerpower/cfg/project/adapter.py +57 -0
- flowerpower/cfg/project/job_queue.py +165 -0
- flowerpower/cli/__init__.py +92 -35
- flowerpower/cli/job_queue.py +878 -0
- flowerpower/cli/mqtt.py +49 -4
- flowerpower/cli/pipeline.py +576 -381
- flowerpower/cli/utils.py +55 -0
- flowerpower/flowerpower.py +12 -7
- flowerpower/fs/__init__.py +20 -2
- flowerpower/fs/base.py +350 -26
- flowerpower/fs/ext.py +797 -216
- flowerpower/fs/storage_options.py +1097 -55
- flowerpower/io/base.py +13 -18
- flowerpower/io/loader/__init__.py +28 -0
- flowerpower/io/loader/deltatable.py +7 -10
- flowerpower/io/metadata.py +1 -0
- flowerpower/io/saver/__init__.py +28 -0
- flowerpower/io/saver/deltatable.py +4 -3
- flowerpower/job_queue/__init__.py +252 -0
- flowerpower/job_queue/apscheduler/__init__.py +11 -0
- flowerpower/job_queue/apscheduler/_setup/datastore.py +110 -0
- flowerpower/job_queue/apscheduler/_setup/eventbroker.py +93 -0
- flowerpower/job_queue/apscheduler/manager.py +1063 -0
- flowerpower/job_queue/apscheduler/setup.py +524 -0
- flowerpower/job_queue/apscheduler/trigger.py +169 -0
- flowerpower/job_queue/apscheduler/utils.py +309 -0
- flowerpower/job_queue/base.py +382 -0
- flowerpower/job_queue/rq/__init__.py +10 -0
- flowerpower/job_queue/rq/_trigger.py +37 -0
- flowerpower/job_queue/rq/concurrent_workers/gevent_worker.py +226 -0
- flowerpower/job_queue/rq/concurrent_workers/thread_worker.py +231 -0
- flowerpower/job_queue/rq/manager.py +1449 -0
- flowerpower/job_queue/rq/setup.py +150 -0
- flowerpower/job_queue/rq/utils.py +69 -0
- flowerpower/pipeline/__init__.py +5 -0
- flowerpower/pipeline/base.py +118 -0
- flowerpower/pipeline/io.py +407 -0
- flowerpower/pipeline/job_queue.py +505 -0
- flowerpower/pipeline/manager.py +1586 -0
- flowerpower/pipeline/registry.py +560 -0
- flowerpower/pipeline/runner.py +560 -0
- flowerpower/pipeline/visualizer.py +142 -0
- flowerpower/plugins/mqtt/__init__.py +12 -0
- flowerpower/plugins/mqtt/cfg.py +16 -0
- flowerpower/plugins/mqtt/manager.py +789 -0
- flowerpower/settings.py +110 -0
- flowerpower/utils/logging.py +21 -0
- flowerpower/utils/misc.py +57 -9
- flowerpower/utils/sql.py +122 -24
- flowerpower/utils/templates.py +18 -142
- flowerpower/web/app.py +0 -0
- flowerpower-1.0.0b1.dist-info/METADATA +324 -0
- flowerpower-1.0.0b1.dist-info/RECORD +94 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/WHEEL +1 -1
- flowerpower/cfg/pipeline/tracker.py +0 -14
- flowerpower/cfg/project/open_telemetry.py +0 -8
- flowerpower/cfg/project/tracker.py +0 -11
- flowerpower/cfg/project/worker.py +0 -19
- flowerpower/cli/scheduler.py +0 -309
- flowerpower/event_handler.py +0 -23
- flowerpower/mqtt.py +0 -525
- flowerpower/pipeline.py +0 -2419
- flowerpower/scheduler.py +0 -680
- flowerpower/tui.py +0 -79
- flowerpower/utils/datastore.py +0 -186
- flowerpower/utils/eventbroker.py +0 -127
- flowerpower/utils/executor.py +0 -58
- flowerpower/utils/trigger.py +0 -140
- flowerpower-0.9.12.4.dist-info/METADATA +0 -575
- flowerpower-0.9.12.4.dist-info/RECORD +0 -70
- /flowerpower/{cfg/pipeline/params.py → cli/worker.py} +0 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/entry_points.txt +0 -0
- {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,254 @@
|
|
1
|
+
import msgspec
|
2
|
+
import yaml
|
3
|
+
from hamilton.function_modifiers import source, value
|
4
|
+
from munch import Munch, munchify
|
5
|
+
|
6
|
+
from ...fs import AbstractFileSystem, get_filesystem
|
7
|
+
from ..base import BaseConfig
|
8
|
+
from .adapter import AdapterConfig
|
9
|
+
from .run import RunConfig
|
10
|
+
from .schedule import ScheduleConfig
|
11
|
+
|
12
|
+
|
13
|
+
class PipelineConfig(BaseConfig):
|
14
|
+
"""Configuration class for managing pipeline settings in FlowerPower.
|
15
|
+
|
16
|
+
This class handles pipeline-specific configuration including run settings, scheduling,
|
17
|
+
parameters, and adapter settings. It supports Hamilton-style parameter configuration
|
18
|
+
and YAML serialization.
|
19
|
+
|
20
|
+
Attributes:
|
21
|
+
name (str | None): The name of the pipeline.
|
22
|
+
run (RunConfig): Configuration for pipeline execution.
|
23
|
+
schedule (ScheduleConfig): Configuration for pipeline scheduling.
|
24
|
+
params (dict): Pipeline parameters.
|
25
|
+
adapter (AdapterConfig): Configuration for the pipeline adapter.
|
26
|
+
h_params (dict): Hamilton-formatted parameters.
|
27
|
+
|
28
|
+
Example:
|
29
|
+
```python
|
30
|
+
# Create a new pipeline config
|
31
|
+
pipeline = PipelineConfig(name="data-transform")
|
32
|
+
|
33
|
+
# Set parameters
|
34
|
+
pipeline.params = {
|
35
|
+
"input_path": "data/input",
|
36
|
+
"batch_size": 100
|
37
|
+
}
|
38
|
+
|
39
|
+
# Save configuration
|
40
|
+
pipeline.save(name="data-transform")
|
41
|
+
```
|
42
|
+
"""
|
43
|
+
|
44
|
+
name: str | None = msgspec.field(default=None)
|
45
|
+
run: RunConfig = msgspec.field(default_factory=RunConfig)
|
46
|
+
schedule: ScheduleConfig = msgspec.field(default_factory=ScheduleConfig)
|
47
|
+
params: dict = msgspec.field(default_factory=dict)
|
48
|
+
adapter: AdapterConfig = msgspec.field(default_factory=AdapterConfig)
|
49
|
+
h_params: dict = msgspec.field(default_factory=dict)
|
50
|
+
|
51
|
+
def __post_init__(self):
|
52
|
+
if isinstance(self.params, dict):
|
53
|
+
self.h_params = munchify(self.to_h_params(self.params))
|
54
|
+
self.params = munchify(self.params)
|
55
|
+
|
56
|
+
def to_yaml(self, path: str, fs: AbstractFileSystem):
|
57
|
+
try:
|
58
|
+
fs.makedirs(fs._parent(path), exist_ok=True)
|
59
|
+
with fs.open(path, "w") as f:
|
60
|
+
d = self.to_dict()
|
61
|
+
d.pop("name")
|
62
|
+
d.pop("h_params")
|
63
|
+
yaml.dump(d, f, default_flow_style=False)
|
64
|
+
except NotImplementedError:
|
65
|
+
raise NotImplementedError(
|
66
|
+
"The filesystem "
|
67
|
+
f"{self.fs.fs.protocol[0] if isinstance(self.fs.fs.protocol, tuple) else self.fs.fs.protocol} "
|
68
|
+
"does not support writing files."
|
69
|
+
)
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def from_dict(cls, name: str, data: dict | Munch):
|
73
|
+
data.update({"name": name})
|
74
|
+
return msgspec.convert(data, cls)
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def from_yaml(cls, name: str, path: str, fs: AbstractFileSystem):
|
78
|
+
with fs.open(path) as f:
|
79
|
+
data = yaml.full_load(f)
|
80
|
+
return cls.from_dict(name=name, data=data)
|
81
|
+
|
82
|
+
def update(self, d: dict | Munch):
|
83
|
+
for k, v in d.items():
|
84
|
+
eval(f"self.{k}.update({v})")
|
85
|
+
if k == "params":
|
86
|
+
self.params.update(munchify(v))
|
87
|
+
self.h_params = munchify(self.to_h_params(self.params))
|
88
|
+
# self.params = munchify(self.params)
|
89
|
+
if "params" in d:
|
90
|
+
self.h_params = munchify(self.to_h_params(self.params))
|
91
|
+
self.params = munchify(self.params)
|
92
|
+
|
93
|
+
@staticmethod
|
94
|
+
def to_h_params(d: dict) -> dict:
|
95
|
+
"""Convert a dictionary of parameters to Hamilton-compatible format.
|
96
|
+
|
97
|
+
This method transforms regular parameter dictionaries into Hamilton's function parameter
|
98
|
+
format, supporting nested parameters and source/value decorators.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
d (dict): The input parameter dictionary.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
dict: Hamilton-formatted parameter dictionary.
|
105
|
+
|
106
|
+
Example:
|
107
|
+
```python
|
108
|
+
params = {
|
109
|
+
"batch_size": 100,
|
110
|
+
"paths": {"input": "data/in", "output": "data/out"}
|
111
|
+
}
|
112
|
+
h_params = PipelineConfig.to_h_params(params)
|
113
|
+
```
|
114
|
+
"""
|
115
|
+
|
116
|
+
def transform_recursive(val, original_dict, depth=1):
|
117
|
+
if isinstance(val, dict):
|
118
|
+
# If we're at depth 3, wrap the entire dictionary in value()
|
119
|
+
if depth == 3:
|
120
|
+
return value(val)
|
121
|
+
# Otherwise, continue recursing
|
122
|
+
return {
|
123
|
+
k: transform_recursive(v, original_dict, depth + 1)
|
124
|
+
for k, v in val.items()
|
125
|
+
}
|
126
|
+
# If it's a string and matches a key in the original dictionary
|
127
|
+
elif isinstance(val, str) and val in original_dict:
|
128
|
+
return source(val)
|
129
|
+
# For non-dictionary values at depth 3
|
130
|
+
elif depth == 3:
|
131
|
+
return value(val)
|
132
|
+
# For all other values
|
133
|
+
return val
|
134
|
+
|
135
|
+
# Step 1: Replace each value with a dictionary containing key and value
|
136
|
+
result = {k: {k: d[k]} for k in d}
|
137
|
+
|
138
|
+
# Step 2: Transform all values recursively
|
139
|
+
return {k: transform_recursive(v, d) for k, v in result.items()}
|
140
|
+
|
141
|
+
@classmethod
|
142
|
+
def load(
|
143
|
+
cls,
|
144
|
+
base_dir: str = ".",
|
145
|
+
name: str | None = None,
|
146
|
+
fs: AbstractFileSystem | None = None,
|
147
|
+
storage_options: dict | Munch = Munch(),
|
148
|
+
):
|
149
|
+
"""Load pipeline configuration from a YAML file.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
base_dir (str, optional): Base directory for the pipeline. Defaults to ".".
|
153
|
+
name (str | None, optional): Pipeline name. Defaults to None.
|
154
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
155
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
PipelineConfig: Loaded pipeline configuration.
|
159
|
+
|
160
|
+
Example:
|
161
|
+
```python
|
162
|
+
pipeline = PipelineConfig.load(
|
163
|
+
base_dir="my_project",
|
164
|
+
name="data-pipeline"
|
165
|
+
)
|
166
|
+
```
|
167
|
+
"""
|
168
|
+
if fs is None:
|
169
|
+
fs = get_filesystem(base_dir, cached=False, dirfs=True, **storage_options)
|
170
|
+
if fs.exists("conf/pipelines"):
|
171
|
+
if name is not None:
|
172
|
+
pipeline = PipelineConfig.from_yaml(
|
173
|
+
name=name,
|
174
|
+
path=f"conf/pipelines/{name}.yml",
|
175
|
+
fs=fs,
|
176
|
+
)
|
177
|
+
else:
|
178
|
+
pipeline = PipelineConfig(name=name)
|
179
|
+
else:
|
180
|
+
pipeline = PipelineConfig(name=name)
|
181
|
+
|
182
|
+
return pipeline
|
183
|
+
|
184
|
+
def save(
|
185
|
+
self,
|
186
|
+
name: str | None = None,
|
187
|
+
base_dir: str = ".",
|
188
|
+
fs: AbstractFileSystem | None = None,
|
189
|
+
storage_options: dict | Munch = Munch(),
|
190
|
+
):
|
191
|
+
"""Save pipeline configuration to a YAML file.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
name (str | None, optional): Pipeline name. Defaults to None.
|
195
|
+
base_dir (str, optional): Base directory for the pipeline. Defaults to ".".
|
196
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
197
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
198
|
+
|
199
|
+
Raises:
|
200
|
+
ValueError: If pipeline name is not set.
|
201
|
+
|
202
|
+
Example:
|
203
|
+
```python
|
204
|
+
pipeline_config.save(name="data-pipeline", base_dir="my_project")
|
205
|
+
```
|
206
|
+
"""
|
207
|
+
if fs is None:
|
208
|
+
fs = get_filesystem(base_dir, cached=True, dirfs=True, **storage_options)
|
209
|
+
|
210
|
+
fs.makedirs("conf/pipelines", exist_ok=True)
|
211
|
+
if name is not None:
|
212
|
+
self.name = name
|
213
|
+
if self.name is None:
|
214
|
+
raise ValueError("Pipeline name is not set. Please provide a name.")
|
215
|
+
|
216
|
+
h_params = getattr(self, "h_params")
|
217
|
+
|
218
|
+
self.to_yaml(path=f"conf/pipelines/{self.name}.yml", fs=fs)
|
219
|
+
|
220
|
+
setattr(self, "h_params", h_params)
|
221
|
+
|
222
|
+
|
223
|
+
def init_pipeline_config(
|
224
|
+
base_dir: str = ".",
|
225
|
+
name: str | None = None,
|
226
|
+
fs: AbstractFileSystem | None = None,
|
227
|
+
storage_options: dict | Munch = Munch(),
|
228
|
+
):
|
229
|
+
"""Initialize a new pipeline configuration.
|
230
|
+
|
231
|
+
This function creates a new pipeline configuration and saves it to disk.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
base_dir (str, optional): Base directory for the pipeline. Defaults to ".".
|
235
|
+
name (str | None, optional): Pipeline name. Defaults to None.
|
236
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
237
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
238
|
+
|
239
|
+
Returns:
|
240
|
+
PipelineConfig: The initialized pipeline configuration.
|
241
|
+
|
242
|
+
Example:
|
243
|
+
```python
|
244
|
+
pipeline = init_pipeline_config(
|
245
|
+
base_dir="my_project",
|
246
|
+
name="etl-pipeline"
|
247
|
+
)
|
248
|
+
```
|
249
|
+
"""
|
250
|
+
pipeline = PipelineConfig.load(
|
251
|
+
base_dir=base_dir, name=name, fs=fs, storage_options=storage_options
|
252
|
+
)
|
253
|
+
pipeline.save(name=name, base_dir=base_dir, fs=fs, storage_options=storage_options)
|
254
|
+
return pipeline
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import msgspec
|
2
|
+
from munch import munchify
|
3
|
+
|
4
|
+
from ... import settings
|
5
|
+
from ..base import BaseConfig
|
6
|
+
|
7
|
+
|
8
|
+
class HamiltonTracerConfig(BaseConfig):
|
9
|
+
project_id: int | None = msgspec.field(default=None)
|
10
|
+
dag_name: str | None = msgspec.field(default=None)
|
11
|
+
tags: dict = msgspec.field(default_factory=dict)
|
12
|
+
capture_data_statistics: bool = msgspec.field(
|
13
|
+
default=settings.HAMILTON_CAPTURE_DATA_STATISTICS
|
14
|
+
)
|
15
|
+
max_list_length_capture: int = msgspec.field(
|
16
|
+
default=settings.HAMILTON_MAX_LIST_LENGTH_CAPTURE
|
17
|
+
)
|
18
|
+
max_dict_length_capture: int = msgspec.field(
|
19
|
+
default=settings.HAMILTON_MAX_DICT_LENGTH_CAPTURE
|
20
|
+
)
|
21
|
+
|
22
|
+
def __post_init__(self):
|
23
|
+
self.tags = munchify(self.tags)
|
24
|
+
|
25
|
+
|
26
|
+
class MLFlowConfig(BaseConfig):
|
27
|
+
experiment_name: str | None = msgspec.field(default=None)
|
28
|
+
experiment_tags: dict | None = msgspec.field(default_factory=dict)
|
29
|
+
experiment_description: str | None = msgspec.field(default=None)
|
30
|
+
run_id: str | None = msgspec.field(default=None)
|
31
|
+
run_name: str | None = msgspec.field(default=None)
|
32
|
+
run_tags: dict | None = msgspec.field(default_factory=dict)
|
33
|
+
run_description: str | None = msgspec.field(default=None)
|
34
|
+
|
35
|
+
def __post_init__(self):
|
36
|
+
if isinstance(self.experiment_tags, dict):
|
37
|
+
self.experiment_tags = munchify(self.experiment_tags)
|
38
|
+
if isinstance(self.run_tags, dict):
|
39
|
+
self.run_tags = munchify(self.run_tags)
|
40
|
+
|
41
|
+
|
42
|
+
# class OpenLineageConfig(BaseConfig):
|
43
|
+
# namespace : str | None = msgspec.field(default=None)
|
44
|
+
# job_name : str | None = msgspec.field(default=None)
|
45
|
+
|
46
|
+
|
47
|
+
class AdapterConfig(BaseConfig):
|
48
|
+
hamilton_tracker: HamiltonTracerConfig = msgspec.field(
|
49
|
+
default_factory=HamiltonTracerConfig
|
50
|
+
)
|
51
|
+
mlflow: MLFlowConfig = msgspec.field(default_factory=MLFlowConfig)
|
52
|
+
# openlineage: OpenLineageConfig | dict = msgspec.field(default_factory=OpenLineageConfig)
|
53
|
+
|
54
|
+
def __post_init__(self):
|
55
|
+
if isinstance(self.hamilton_tracker, dict):
|
56
|
+
self.hamilton_tracker = HamiltonTracerConfig.from_dict(
|
57
|
+
self.hamilton_tracker
|
58
|
+
)
|
59
|
+
if isinstance(self.mlflow, dict):
|
60
|
+
self.mlflow = MLFlowConfig.from_dict(self.mlflow)
|
61
|
+
if self.hamilton_tracker.project_id is not None:
|
62
|
+
self.mlflow.experiment_name = (
|
63
|
+
f"flowerpower_project_{self.hamilton_tracker.project_id}"
|
64
|
+
)
|
65
|
+
if self.hamilton_tracker.dag_name is not None:
|
66
|
+
self.mlflow.run_name = self.hamilton_tracker.dag_name
|
flowerpower/cfg/pipeline/run.py
CHANGED
@@ -1,18 +1,47 @@
|
|
1
|
-
|
2
|
-
from
|
1
|
+
import msgspec
|
2
|
+
from munch import munchify
|
3
3
|
|
4
|
+
from ... import settings
|
4
5
|
from ..base import BaseConfig
|
5
6
|
|
6
7
|
|
7
|
-
class
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
class WithAdapterConfig(BaseConfig):
|
9
|
+
tracker: bool = msgspec.field(default=False)
|
10
|
+
mlflow: bool = msgspec.field(default=False)
|
11
|
+
# openlineage: bool = msgspec.field(default=False)
|
12
|
+
ray: bool = msgspec.field(default=False)
|
13
|
+
opentelemetry: bool = msgspec.field(default=False)
|
14
|
+
progressbar: bool = msgspec.field(default=False)
|
15
|
+
future: bool = msgspec.field(default=False)
|
15
16
|
|
16
|
-
|
17
|
+
|
18
|
+
class ExecutorConfig(BaseConfig):
|
19
|
+
type: str | None = msgspec.field(default=settings.EXECUTOR)
|
20
|
+
max_workers: int | None = msgspec.field(default=settings.EXECUTOR_MAX_WORKERS)
|
21
|
+
num_cpus: int | None = msgspec.field(default=settings.EXECUTOR_NUM_CPUS)
|
22
|
+
|
23
|
+
|
24
|
+
class RunConfig(BaseConfig):
|
25
|
+
inputs: dict | None = msgspec.field(default_factory=dict)
|
26
|
+
final_vars: list[str] | None = msgspec.field(default_factory=list)
|
27
|
+
config: dict | None = msgspec.field(default_factory=dict)
|
28
|
+
cache: dict | bool | None = msgspec.field(default=False)
|
29
|
+
with_adapter: WithAdapterConfig = msgspec.field(default_factory=WithAdapterConfig)
|
30
|
+
executor: ExecutorConfig = msgspec.field(default_factory=ExecutorConfig)
|
31
|
+
log_level: str | None = msgspec.field(default=None)
|
32
|
+
max_retries: int = msgspec.field(default=3)
|
33
|
+
retry_delay: int | float = msgspec.field(default=1)
|
34
|
+
jitter_factor: float | None = msgspec.field(default=0.1)
|
35
|
+
retry_exceptions: tuple[str] = msgspec.field(default_factory=lambda: ("Exception",))
|
36
|
+
|
37
|
+
def __post_init__(self):
|
17
38
|
if isinstance(self.inputs, dict):
|
18
39
|
self.inputs = munchify(self.inputs)
|
40
|
+
if isinstance(self.config, dict):
|
41
|
+
self.config = munchify(self.config)
|
42
|
+
if isinstance(self.cache, (dict)):
|
43
|
+
self.cache = munchify(self.cache)
|
44
|
+
if isinstance(self.with_adapter, dict):
|
45
|
+
self.with_adapter = WithAdapterConfig.from_dict(self.with_adapter)
|
46
|
+
if isinstance(self.executor, dict):
|
47
|
+
self.executor = ExecutorConfig.from_dict(self.executor)
|
@@ -1,84 +1,74 @@
|
|
1
1
|
import datetime as dt
|
2
2
|
|
3
|
-
|
3
|
+
import msgspec
|
4
|
+
from munch import munchify
|
4
5
|
|
5
6
|
from ..base import BaseConfig
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
max_running_jobs: int | None = None
|
75
|
-
conflict_policy: str | None = (
|
76
|
-
"do_nothing" # other options are "replace" and "exception"
|
77
|
-
)
|
78
|
-
|
79
|
-
|
80
|
-
class PipelineScheduleConfig(BaseConfig):
|
81
|
-
run: PipelineScheduleRunConfig = Field(default_factory=PipelineScheduleRunConfig)
|
82
|
-
trigger: PipelineScheduleTriggerConfig = Field(
|
83
|
-
default_factory=PipelineScheduleTriggerConfig
|
84
|
-
)
|
8
|
+
# class ScheduleCronTriggerConfig(BaseConfig):
|
9
|
+
# year: str | int | None = None
|
10
|
+
# month: str | int | None = None
|
11
|
+
# week: str | int | None = None
|
12
|
+
# day: str | int | None = None
|
13
|
+
# day_of_week: str | int | None = None
|
14
|
+
# hour: str | int | None = None
|
15
|
+
# minute: str | int | None = None
|
16
|
+
# second: str | int | None = None
|
17
|
+
# start_time: dt.datetime | None = None
|
18
|
+
# end_time: dt.datetime | None = None
|
19
|
+
# timezone: str | None = None
|
20
|
+
# crontab: str | None = None
|
21
|
+
|
22
|
+
|
23
|
+
# class ScheduleIntervalTriggerConfig(BaseConfig):
|
24
|
+
# weeks: int | float | None = None
|
25
|
+
# days: int | float | None = None
|
26
|
+
# hours: int | float | None = None
|
27
|
+
# minutes: int | float | None = None
|
28
|
+
# seconds: int | float | None = None
|
29
|
+
# microseconds: int | float | None = None
|
30
|
+
# start_time: dt.datetime | None = None
|
31
|
+
# end_time: dt.datetime | None = None
|
32
|
+
|
33
|
+
|
34
|
+
# class ScheduleCalendarTriggerConfig(BaseConfig):
|
35
|
+
# years: int | float | None = None
|
36
|
+
# months: int | float | None = None
|
37
|
+
# weeks: int | float | None = None
|
38
|
+
# days: int | float | None = None
|
39
|
+
# hour: int | float | None = None
|
40
|
+
# minute: int | float | None = None
|
41
|
+
# second: int | float | None = None
|
42
|
+
# start_date: dt.datetime | None = None
|
43
|
+
# end_date: dt.datetime | None = None
|
44
|
+
# timezone: str | None = None
|
45
|
+
|
46
|
+
|
47
|
+
# class ScheduleDateTriggerConfig(BaseConfig):
|
48
|
+
# run_time: dt.datetime | None = None
|
49
|
+
|
50
|
+
|
51
|
+
class ScheduleConfig(BaseConfig):
|
52
|
+
cron: str | dict | None = msgspec.field(default=None)
|
53
|
+
interval: str | int | dict | None = msgspec.field(default=None)
|
54
|
+
date: str | None = msgspec.field(default=None)
|
55
|
+
|
56
|
+
def __post_init__(self):
|
57
|
+
if isinstance(self.date, str):
|
58
|
+
try:
|
59
|
+
self.date = dt.datetime.fromisoformat(self.date)
|
60
|
+
except ValueError:
|
61
|
+
raise ValueError(
|
62
|
+
f"Invalid date format: {self.date}. Expected ISO format."
|
63
|
+
)
|
64
|
+
if isinstance(self.cron, dict):
|
65
|
+
self.cron = munchify(self.cron)
|
66
|
+
if isinstance(self.interval, dict):
|
67
|
+
self.interval = munchify(self.interval)
|
68
|
+
|
69
|
+
|
70
|
+
# class ScheduleConfig(BaseConfig):
|
71
|
+
# run: ScheduleRunConfig = msgspec.field(default_factory=ScheduleRunConfig)
|
72
|
+
# trigger: ScheduleTriggerConfig = msgspec.field(
|
73
|
+
# default_factory=ScheduleTriggerConfig
|
74
|
+
# )
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import msgspec
|
2
|
+
from munch import Munch
|
3
|
+
|
4
|
+
from ...fs import AbstractFileSystem, get_filesystem
|
5
|
+
from ..base import BaseConfig
|
6
|
+
from .adapter import AdapterConfig
|
7
|
+
from .job_queue import JobQueueConfig
|
8
|
+
|
9
|
+
|
10
|
+
class ProjectConfig(BaseConfig):
|
11
|
+
"""A configuration class for managing project-level settings in FlowerPower.
|
12
|
+
|
13
|
+
This class handles project-wide configuration including job queue and adapter settings.
|
14
|
+
It supports loading from and saving to YAML files, with filesystem abstraction.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
name (str | None): The name of the project.
|
18
|
+
job_queue (JobQueueConfig): Configuration for the job queue component.
|
19
|
+
adapter (AdapterConfig): Configuration for the adapter component.
|
20
|
+
|
21
|
+
Example:
|
22
|
+
```python
|
23
|
+
# Create a new project config
|
24
|
+
project = ProjectConfig(name="my-project")
|
25
|
+
|
26
|
+
# Load existing project config
|
27
|
+
project = ProjectConfig.load(base_dir="path/to/project")
|
28
|
+
|
29
|
+
# Save project config
|
30
|
+
project.save(base_dir="path/to/project")
|
31
|
+
```
|
32
|
+
"""
|
33
|
+
|
34
|
+
name: str | None = msgspec.field(default=None)
|
35
|
+
job_queue: JobQueueConfig = msgspec.field(default_factory=JobQueueConfig)
|
36
|
+
adapter: AdapterConfig = msgspec.field(default_factory=AdapterConfig)
|
37
|
+
|
38
|
+
def __post_init__(self):
|
39
|
+
if isinstance(self.job_queue, dict):
|
40
|
+
self.job_queue = JobQueueConfig.from_dict(self.job_queue)
|
41
|
+
if isinstance(self.adapter, dict):
|
42
|
+
self.adapter = AdapterConfig.from_dict(self.adapter)
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def load(
|
46
|
+
cls,
|
47
|
+
base_dir: str = ".",
|
48
|
+
name: str | None = None,
|
49
|
+
job_queue_type: str | None = None,
|
50
|
+
fs: AbstractFileSystem | None = None,
|
51
|
+
storage_options: dict | Munch = Munch(),
|
52
|
+
):
|
53
|
+
"""Load project configuration from a YAML file.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
base_dir (str, optional): Base directory for the project. Defaults to ".".
|
57
|
+
name (str | None, optional): Project name. Defaults to None.
|
58
|
+
job_queue_type (str | None, optional): Type of job queue to use. Defaults to None.
|
59
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
60
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
ProjectConfig: Loaded project configuration.
|
64
|
+
|
65
|
+
Example:
|
66
|
+
```python
|
67
|
+
project = ProjectConfig.load(
|
68
|
+
base_dir="my_project",
|
69
|
+
name="pipeline1",
|
70
|
+
job_queue_type="rq"
|
71
|
+
)
|
72
|
+
```
|
73
|
+
"""
|
74
|
+
if fs is None:
|
75
|
+
fs = get_filesystem(base_dir, cached=False, dirfs=True, **storage_options)
|
76
|
+
if fs.exists("conf/project.yml"):
|
77
|
+
project = ProjectConfig.from_yaml(path="conf/project.yml", fs=fs)
|
78
|
+
else:
|
79
|
+
project = ProjectConfig(name=name)
|
80
|
+
if job_queue_type is not None:
|
81
|
+
if job_queue_type != project.job_queue.type:
|
82
|
+
project.job_queue.update_type(job_queue_type)
|
83
|
+
|
84
|
+
return project
|
85
|
+
|
86
|
+
def save(
|
87
|
+
self,
|
88
|
+
base_dir: str = ".",
|
89
|
+
fs: AbstractFileSystem | None = None,
|
90
|
+
storage_options: dict | Munch = Munch(),
|
91
|
+
):
|
92
|
+
"""Save project configuration to a YAML file.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
base_dir (str, optional): Base directory for the project. Defaults to ".".
|
96
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
97
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
98
|
+
|
99
|
+
Example:
|
100
|
+
```python
|
101
|
+
project_config.save(base_dir="my_project")
|
102
|
+
```
|
103
|
+
"""
|
104
|
+
if fs is None:
|
105
|
+
fs = get_filesystem(base_dir, cached=True, dirfs=True, **storage_options)
|
106
|
+
|
107
|
+
fs.makedirs("conf", exist_ok=True)
|
108
|
+
self.to_yaml(path="conf/project.yml", fs=fs)
|
109
|
+
|
110
|
+
|
111
|
+
def init_project_config(
|
112
|
+
base_dir: str = ".",
|
113
|
+
name: str | None = None,
|
114
|
+
job_queue_type: str | None = None,
|
115
|
+
fs: AbstractFileSystem | None = None,
|
116
|
+
storage_options: dict | Munch = Munch(),
|
117
|
+
):
|
118
|
+
"""Initialize a new project configuration.
|
119
|
+
|
120
|
+
This function creates a new project configuration and saves it to disk.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
base_dir (str, optional): Base directory for the project. Defaults to ".".
|
124
|
+
name (str | None, optional): Project name. Defaults to None.
|
125
|
+
job_queue_type (str | None, optional): Type of job queue to use. Defaults to None.
|
126
|
+
fs (AbstractFileSystem | None, optional): Filesystem to use. Defaults to None.
|
127
|
+
storage_options (dict | Munch, optional): Options for filesystem. Defaults to empty Munch.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
ProjectConfig: The initialized project configuration.
|
131
|
+
|
132
|
+
Example:
|
133
|
+
```python
|
134
|
+
project = init_project_config(
|
135
|
+
base_dir="my_project",
|
136
|
+
name="test_project",
|
137
|
+
job_queue_type="rq"
|
138
|
+
)
|
139
|
+
```
|
140
|
+
"""
|
141
|
+
project = ProjectConfig.load(
|
142
|
+
base_dir=base_dir,
|
143
|
+
name=name,
|
144
|
+
job_queue_type=job_queue_type,
|
145
|
+
fs=fs,
|
146
|
+
storage_options=storage_options,
|
147
|
+
)
|
148
|
+
project.save(base_dir=base_dir, fs=fs, storage_options=storage_options)
|
149
|
+
return project
|