FlowerPower 0.9.13.1__py3-none-any.whl → 1.0.0b2__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.
Files changed (85) hide show
  1. flowerpower/__init__.py +17 -2
  2. flowerpower/cfg/__init__.py +201 -149
  3. flowerpower/cfg/base.py +122 -24
  4. flowerpower/cfg/pipeline/__init__.py +254 -0
  5. flowerpower/cfg/pipeline/adapter.py +66 -0
  6. flowerpower/cfg/pipeline/run.py +40 -11
  7. flowerpower/cfg/pipeline/schedule.py +69 -79
  8. flowerpower/cfg/project/__init__.py +149 -0
  9. flowerpower/cfg/project/adapter.py +57 -0
  10. flowerpower/cfg/project/job_queue.py +165 -0
  11. flowerpower/cli/__init__.py +92 -37
  12. flowerpower/cli/job_queue.py +878 -0
  13. flowerpower/cli/mqtt.py +32 -1
  14. flowerpower/cli/pipeline.py +559 -406
  15. flowerpower/cli/utils.py +29 -18
  16. flowerpower/flowerpower.py +12 -8
  17. flowerpower/fs/__init__.py +20 -2
  18. flowerpower/fs/base.py +350 -26
  19. flowerpower/fs/ext.py +797 -216
  20. flowerpower/fs/storage_options.py +1097 -55
  21. flowerpower/io/base.py +13 -18
  22. flowerpower/io/loader/__init__.py +28 -0
  23. flowerpower/io/loader/deltatable.py +7 -10
  24. flowerpower/io/metadata.py +1 -0
  25. flowerpower/io/saver/__init__.py +28 -0
  26. flowerpower/io/saver/deltatable.py +4 -3
  27. flowerpower/job_queue/__init__.py +252 -0
  28. flowerpower/job_queue/apscheduler/__init__.py +11 -0
  29. flowerpower/job_queue/apscheduler/_setup/datastore.py +110 -0
  30. flowerpower/job_queue/apscheduler/_setup/eventbroker.py +93 -0
  31. flowerpower/job_queue/apscheduler/manager.py +1063 -0
  32. flowerpower/job_queue/apscheduler/setup.py +524 -0
  33. flowerpower/job_queue/apscheduler/trigger.py +169 -0
  34. flowerpower/job_queue/apscheduler/utils.py +309 -0
  35. flowerpower/job_queue/base.py +382 -0
  36. flowerpower/job_queue/rq/__init__.py +10 -0
  37. flowerpower/job_queue/rq/_trigger.py +37 -0
  38. flowerpower/job_queue/rq/concurrent_workers/gevent_worker.py +226 -0
  39. flowerpower/job_queue/rq/concurrent_workers/thread_worker.py +231 -0
  40. flowerpower/job_queue/rq/manager.py +1449 -0
  41. flowerpower/job_queue/rq/setup.py +150 -0
  42. flowerpower/job_queue/rq/utils.py +69 -0
  43. flowerpower/pipeline/__init__.py +5 -0
  44. flowerpower/pipeline/base.py +118 -0
  45. flowerpower/pipeline/io.py +407 -0
  46. flowerpower/pipeline/job_queue.py +505 -0
  47. flowerpower/pipeline/manager.py +1586 -0
  48. flowerpower/pipeline/registry.py +560 -0
  49. flowerpower/pipeline/runner.py +560 -0
  50. flowerpower/pipeline/visualizer.py +142 -0
  51. flowerpower/plugins/mqtt/__init__.py +12 -0
  52. flowerpower/plugins/mqtt/cfg.py +16 -0
  53. flowerpower/plugins/mqtt/manager.py +789 -0
  54. flowerpower/settings.py +110 -0
  55. flowerpower/utils/logging.py +21 -0
  56. flowerpower/utils/misc.py +57 -9
  57. flowerpower/utils/sql.py +122 -24
  58. flowerpower/utils/templates.py +2 -142
  59. flowerpower-1.0.0b2.dist-info/METADATA +324 -0
  60. flowerpower-1.0.0b2.dist-info/RECORD +94 -0
  61. flowerpower/_web/__init__.py +0 -61
  62. flowerpower/_web/routes/config.py +0 -103
  63. flowerpower/_web/routes/pipelines.py +0 -173
  64. flowerpower/_web/routes/scheduler.py +0 -136
  65. flowerpower/cfg/pipeline/tracker.py +0 -14
  66. flowerpower/cfg/project/open_telemetry.py +0 -8
  67. flowerpower/cfg/project/tracker.py +0 -11
  68. flowerpower/cfg/project/worker.py +0 -19
  69. flowerpower/cli/scheduler.py +0 -309
  70. flowerpower/cli/web.py +0 -44
  71. flowerpower/event_handler.py +0 -23
  72. flowerpower/mqtt.py +0 -609
  73. flowerpower/pipeline.py +0 -2499
  74. flowerpower/scheduler.py +0 -680
  75. flowerpower/tui.py +0 -79
  76. flowerpower/utils/datastore.py +0 -186
  77. flowerpower/utils/eventbroker.py +0 -127
  78. flowerpower/utils/executor.py +0 -58
  79. flowerpower/utils/trigger.py +0 -140
  80. flowerpower-0.9.13.1.dist-info/METADATA +0 -586
  81. flowerpower-0.9.13.1.dist-info/RECORD +0 -76
  82. /flowerpower/{cfg/pipeline/params.py → cli/worker.py} +0 -0
  83. {flowerpower-0.9.13.1.dist-info → flowerpower-1.0.0b2.dist-info}/WHEEL +0 -0
  84. {flowerpower-0.9.13.1.dist-info → flowerpower-1.0.0b2.dist-info}/entry_points.txt +0 -0
  85. {flowerpower-0.9.13.1.dist-info → flowerpower-1.0.0b2.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
@@ -1,18 +1,47 @@
1
- from munch import Munch, munchify
2
- from pydantic import Field
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 PipelineRunConfig(BaseConfig):
8
- final_vars: list[str] = Field(default_factory=list)
9
- inputs: dict | Munch = Field(default_factory=dict)
10
- executor: str | None = None
11
- config: dict | Munch = Field(default_factory=dict)
12
- with_tracker: bool = False
13
- with_opentelemetry: bool = False
14
- with_progressbar: bool = False
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
- def model_post_init(self, __context):
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
- from pydantic import Field
3
+ import msgspec
4
+ from munch import munchify
4
5
 
5
6
  from ..base import BaseConfig
6
7
 
7
-
8
- class PipelineScheduleCronTriggerConfig(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 PipelineScheduleIntervalTriggerConfig(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 PipelineScheduleCalendarTriggerConfig(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 PipelineScheduleDateTriggerConfig(BaseConfig):
48
- run_time: dt.datetime | None = None
49
-
50
-
51
- class PipelineScheduleTriggerConfig(BaseConfig):
52
- cron: PipelineScheduleCronTriggerConfig = Field(
53
- default_factory=PipelineScheduleCronTriggerConfig
54
- )
55
- interval: PipelineScheduleIntervalTriggerConfig = Field(
56
- default_factory=PipelineScheduleIntervalTriggerConfig
57
- )
58
- calendar: PipelineScheduleCalendarTriggerConfig = Field(
59
- default_factory=PipelineScheduleCalendarTriggerConfig
60
- )
61
- date: PipelineScheduleDateTriggerConfig = Field(
62
- default_factory=PipelineScheduleDateTriggerConfig
63
- )
64
- type_: str | None = None
65
-
66
-
67
- class PipelineScheduleRunConfig(BaseConfig):
68
- id_: str | None = None
69
- executor: str | None = None
70
- paused: bool = False
71
- coalesce: str = "latest" # other options are "all" and "earliest"
72
- misfire_grace_time: int | float | dt.timedelta | None = None
73
- max_jitter: int | float | dt.timedelta | None = None
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