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,407 @@
1
+ # -*- coding: utf-8 -*-
2
+ # mypy: disable-error-code="attr-defined"
3
+ # pylint: disable=no-member, E1136, W0212, W0201
4
+ """
5
+ Manages the import and export of pipelines.
6
+ """
7
+
8
+ import posixpath
9
+
10
+ from rich.console import Console
11
+
12
+ # Import necessary config types and utility functions
13
+ from ..fs.base import AbstractFileSystem, BaseStorageOptions, get_filesystem
14
+ from .registry import PipelineRegistry
15
+
16
+ console = Console()
17
+
18
+
19
+ class PipelineIOManager:
20
+ """Handles importing and exporting pipeline configurations and code."""
21
+
22
+ def __init__(
23
+ self,
24
+ registry: PipelineRegistry,
25
+ ):
26
+ """
27
+ Initializes the PipelineIOManager.
28
+
29
+ Args:
30
+ registry: The pipeline registry instance.
31
+ """
32
+ self.project_cfg = registry.project_cfg
33
+ self.registry = registry
34
+ self._fs = registry._fs
35
+ self._cfg_dir = registry._cfg_dir
36
+ self._pipelines_dir = registry._pipelines_dir
37
+
38
+ def import_pipeline(
39
+ self,
40
+ name: str,
41
+ src_base_dir: str,
42
+ src_fs: AbstractFileSystem | None = None,
43
+ src_storage_options: BaseStorageOptions | None = None,
44
+ overwrite: bool = False,
45
+ ):
46
+ """
47
+ Import a pipeline from a given path.
48
+
49
+ Args:
50
+ name (str): The name of the pipeline.
51
+ src_base_dir (str): The path of the flowerpower project directory.
52
+ src_fs (AbstractFileSystem | None, optional): The source filesystem. Defaults to None.
53
+ src_storage_options (BaseStorageOptions | None, optional): The storage options. Defaults to None.
54
+ overwrite (bool, optional): Whether to overwrite an existing pipeline. Defaults to False.
55
+
56
+ Returns:
57
+ None
58
+
59
+ Raises:
60
+ ValueError: If the pipeline already exists and overwrite is False.
61
+
62
+ Examples:
63
+ ```python
64
+ pm = PipelineManager()
65
+ pm.import_pipeline("my_pipeline", "/path/to/pipeline")
66
+ ```
67
+ """
68
+ if src_fs is None:
69
+ src_fs = get_filesystem(src_base_dir, **(src_storage_options or {}))
70
+
71
+ # Use project_cfg attributes for destination paths and filesystem
72
+ dest_pipeline_file = posixpath.join(
73
+ self._pipelines_dir, f"{name.replace('.', '/')}.py"
74
+ )
75
+ dest_cfg_file = posixpath.join(
76
+ self._cfg_dir, "pipelines", f"{name.replace('.', '/')}.yml"
77
+ )
78
+ # Assume standard structure in source base_dir
79
+ src_pipeline_file = posixpath.join(
80
+ src_base_dir, "pipelines", f"{name.replace('.', '/')}.py"
81
+ )
82
+ src_cfg_file = posixpath.join(
83
+ src_base_dir, "conf", "pipelines", f"{name.replace('.', '/')}.yml"
84
+ )
85
+
86
+ if not src_fs.exists(src_pipeline_file):
87
+ raise ValueError(
88
+ f"Source pipeline module file not found at: {src_pipeline_file}"
89
+ )
90
+ if not src_fs.exists(src_cfg_file):
91
+ raise ValueError(
92
+ f"Source pipeline config file not found at: {src_cfg_file}"
93
+ )
94
+
95
+ # Check existence in destination using _fs
96
+ if self._fs.exists(dest_pipeline_file) or self._fs.exists(dest_cfg_file):
97
+ if overwrite:
98
+ if self._fs.exists(dest_pipeline_file):
99
+ self._fs.rm(dest_pipeline_file)
100
+ if self._fs.exists(dest_cfg_file):
101
+ self._fs.rm(dest_cfg_file)
102
+ else:
103
+ # Use project_cfg.name directly
104
+ raise ValueError(
105
+ f"Pipeline {self.project_cfg.name}.{name.replace('.', '/')} already exists in destination. "
106
+ "Use `overwrite=True` to overwrite."
107
+ )
108
+
109
+ # Create directories in destination
110
+ self._fs.makedirs(posixpath.dirname(dest_pipeline_file), exist_ok=True)
111
+ self._fs.makedirs(posixpath.dirname(dest_cfg_file), exist_ok=True)
112
+
113
+ # Copy files using correct filesystems
114
+ self._fs.write_bytes(dest_pipeline_file, src_fs.read_bytes(src_pipeline_file))
115
+ self._fs.write_bytes(dest_cfg_file, src_fs.read_bytes(src_cfg_file))
116
+
117
+ # Use project_cfg.name directly
118
+ console.print(
119
+ f"✅ Imported pipeline [bold blue]{self.project_cfg.name}.{name}[/bold blue] from [green]{src_base_dir}[/green]"
120
+ )
121
+
122
+ def import_many(
123
+ self,
124
+ pipelines: dict[str, str] | list[str],
125
+ src_base_dir: str,
126
+ src_fs: AbstractFileSystem | None = None,
127
+ src_storage_options: BaseStorageOptions | None = None,
128
+ overwrite: bool = False,
129
+ ):
130
+ """
131
+ Import multiple pipelines from given paths.
132
+
133
+ Args:
134
+ pipelines (dict[str, str] | list[str]): A dictionary where keys are pipeline names and values are paths or
135
+ a list of pipeline names to import.
136
+ src_base_dir (str): The base path of the flowerpower project directory.
137
+ src_fs (AbstractFileSystem | None, optional): The source filesystem. Defaults to None.
138
+ src_storage_options (BaseStorageOptions | None, optional): The storage options. Defaults to None.
139
+ overwrite (bool, optional): Whether to overwrite existing pipelines. Defaults to False.
140
+
141
+ Returns:
142
+ None
143
+
144
+ Examples:
145
+ ```python
146
+ pm = PipelineManager()
147
+ pipelines_to_import = {
148
+ "pipeline1": "/path/to/pipeline1",
149
+ "pipeline2": "s3://bucket/pipeline2"
150
+ }
151
+ pm.import_many(pipelines_to_import, overwrite=True)
152
+ ```
153
+ """
154
+ if isinstance(pipelines, list):
155
+ pipelines = {name: src_base_dir for name in pipelines}
156
+
157
+ for name, src_base_dir in pipelines.items():
158
+ try:
159
+ self.import_pipeline(
160
+ name=name,
161
+ src_base_dir=src_base_dir,
162
+ src_fs=src_fs,
163
+ src_storage_options=src_storage_options,
164
+ overwrite=overwrite,
165
+ )
166
+ except Exception as e:
167
+ console.print(
168
+ f"❌ Failed to import pipeline [bold blue]{name}[/bold blue] from [red]{src_base_dir}[/red]: {e}",
169
+ style="red",
170
+ )
171
+
172
+ def import_all(
173
+ self,
174
+ src_base_dir: str,
175
+ src_fs: AbstractFileSystem | None = None,
176
+ src_storage_options: BaseStorageOptions | None = None,
177
+ overwrite: bool = False,
178
+ ):
179
+ """Import all pipelines from a given path.
180
+
181
+ Assumes the source path has a structure similar to the target `pipelines_dir` and `cfg_dir`/pipelines.
182
+
183
+ Args:
184
+ src_base_dir (str): The base path containing pipeline modules and configurations.
185
+ src_fs (AbstractFileSystem | None, optional): The source filesystem. Defaults to None.
186
+ src_storage_options (BaseStorageOptions | None, optional): Storage options for the source path. Defaults to None.
187
+ overwrite (bool, optional): Whether to overwrite existing pipelines. Defaults to False.
188
+
189
+ Returns:
190
+ None
191
+
192
+ Examples:
193
+ ```python
194
+ pm = PipelineManager()
195
+ # Import all pipelines from a local directory
196
+ pm.import_all("/path/to/exported_pipelines", overwrite=True)
197
+ # Import all pipelines from an S3 bucket
198
+ # pm.import_all("s3://my-bucket/pipelines_backup", storage_options={"key": "...", "secret": "..."}, overwrite=False)
199
+ ```
200
+ """
201
+ if not src_fs:
202
+ src_fs = get_filesystem(src_base_dir, **(src_storage_options or {}))
203
+
204
+ console.print(f"🔍 Search pipelines in [green]{src_base_dir}[/green]...")
205
+
206
+ # Find all .py files in the source path (recursively)
207
+ try:
208
+ # Assuming pipelines are directly under the path, adjust if nested deeper e.g. path/pipelines/*.py
209
+ pipeline_files = src_fs.glob("**/*.py", recursive=True)
210
+ except NotImplementedError:
211
+ # Fallback for filesystems that don't support recursive glob
212
+ pipeline_files = src_fs.glob("*.py") # Check top level
213
+ # Add logic here to check common subdirs like 'pipelines' if needed
214
+
215
+ names = [
216
+ f.replace(f"{src_base_dir}/", "").replace(".py", "").replace("/", ".")
217
+ for f in pipeline_files
218
+ if not f.endswith("__init__.py") # Exclude __init__.py
219
+ ]
220
+
221
+ if not names:
222
+ console.print(
223
+ "🤷 No pipeline modules (.py files) found in the specified path.",
224
+ style="yellow",
225
+ )
226
+ return
227
+
228
+ console.print(f"Found {len(names)} potential pipeline modules. Importing...")
229
+
230
+ pipelines_to_import = {name: src_base_dir for name in names}
231
+ self.import_many(
232
+ pipelines=pipelines_to_import,
233
+ src_base_dir=src_base_dir,
234
+ src_fs=src_fs,
235
+ src_storage_options=src_storage_options,
236
+ overwrite=overwrite,
237
+ )
238
+
239
+ def export_pipeline(
240
+ self,
241
+ name: str,
242
+ dest_base_dir: str,
243
+ dest_fs: AbstractFileSystem | None = None,
244
+ des_storage_options: BaseStorageOptions | None = None,
245
+ overwrite: bool = False,
246
+ ):
247
+ """
248
+ Export a pipeline to a given path.
249
+
250
+ Args:
251
+ name (str): The name of the pipeline.
252
+ dest_base_dir (str): The destination path.
253
+ dest_fs (AbstractFileSystem | None, optional): The destination filesystem. Defaults to None.
254
+ dest_storage_options (BaseStorageOptions | None, optional): Storage options for the destination path. Defaults to None.
255
+ overwrite (bool, optional): Whether to overwrite existing files at the destination. Defaults to False.
256
+
257
+ Returns:
258
+ None
259
+
260
+ Raises:
261
+ ValueError: If the pipeline does not exist or if the destination exists and overwrite is False.
262
+
263
+ Examples:
264
+ ```python
265
+ pm = PipelineManager()
266
+ pm.export("my_pipeline", "/path/to/export_dir")
267
+ # Export to S3
268
+ # pm.export("my_pipeline", "s3://my-bucket/exports", storage_options={"key": "...", "secret": "..."})
269
+ ```
270
+ """
271
+ # Use registry to check existence (accessing public property/method)
272
+ if (
273
+ name not in self.registry.list_pipelines()
274
+ ): # Assuming list_pipelines is the public way
275
+ raise ValueError(f"Pipeline {self.project_cfg.name}.{name} does not exist.")
276
+
277
+ if dest_fs is None:
278
+ dest_fs = get_filesystem(dest_base_dir, **(des_storage_options or {}))
279
+
280
+ # Define destination paths relative to base_dir
281
+ dest_pipeline_file = posixpath.join(
282
+ dest_base_dir, "pipelines", f"{name.replace('.', '/')}.py"
283
+ )
284
+ dest_cfg_file = posixpath.join(
285
+ dest_base_dir, "conf", "pipelines", f"{name.replace('.', '/')}.yml"
286
+ )
287
+ # Define source paths using project_cfg attributes
288
+ src_pipeline_file = posixpath.join(
289
+ self._pipelines_dir, f"{name.replace('.', '/')}.py"
290
+ )
291
+ src_cfg_file = posixpath.join(
292
+ self._cfg_dir, "pipelines", f"{name.replace('.', '/')}.yml"
293
+ )
294
+
295
+ # Check overwrite condition for destination files using dest_fs
296
+ if not overwrite and (
297
+ dest_fs.exists(dest_pipeline_file) or dest_fs.exists(dest_cfg_file)
298
+ ):
299
+ raise ValueError(
300
+ f"Destination path {dest_base_dir} for pipeline {name.replace('.', '/')} already contains files. Use `overwrite=True` to overwrite."
301
+ )
302
+
303
+ # Create necessary subdirectories in the destination using dest_fs
304
+ dest_fs.makedirs(posixpath.dirname(dest_pipeline_file), exist_ok=True)
305
+ dest_fs.makedirs(posixpath.dirname(dest_cfg_file), exist_ok=True)
306
+
307
+ # Copy pipeline module and config using correct filesystems
308
+ dest_fs.write_bytes(dest_pipeline_file, self._fs.read_bytes(src_pipeline_file))
309
+ dest_fs.write_bytes(dest_cfg_file, self._fs.read_bytes(src_cfg_file))
310
+
311
+ # Use project_cfg.name directly
312
+ console.print(
313
+ f"✅ Exported pipeline [bold blue]{self.project_cfg.name}.{name}[/bold blue] to [green]{dest_base_dir}[/green]"
314
+ )
315
+
316
+ def export_many(
317
+ self,
318
+ pipelines: list[str],
319
+ dest_base_dir: str,
320
+ dest_fs: AbstractFileSystem | None = None,
321
+ dest_storage_options: BaseStorageOptions | None = None,
322
+ overwrite: bool = False,
323
+ ):
324
+ """
325
+ Export multiple pipelines to a directory.
326
+
327
+ Args:
328
+ pipelines (list[str]): A list of pipeline names to export.
329
+ dest_base_dir (str): The destination directory path.
330
+ dest_fs (AbstractFileSystem | None, optional): The destination filesystem. Defaults to None.
331
+ dest_storage_options (BaseStorageOptions | None, optional): Storage options for the destination path. Defaults to None.
332
+ overwrite (bool, optional): Whether to overwrite existing files at the destination. Defaults to False.
333
+
334
+ Returns:
335
+ None
336
+
337
+ Examples:
338
+ ```python
339
+ pm = PipelineManager()
340
+ pipelines_to_export = ["pipeline1", "pipeline2.subpipeline"]
341
+ pm.export_many(pipelines_to_export, "/path/to/export_dir", overwrite=True)
342
+ ```
343
+ """
344
+ for name in pipelines:
345
+ try:
346
+ self.export_pipeline(
347
+ name=name,
348
+ dest_base_dir=dest_base_dir,
349
+ dest_fs=dest_fs,
350
+ des_storage_options=dest_storage_options,
351
+ overwrite=overwrite,
352
+ )
353
+ except Exception as e:
354
+ # Use project_cfg.name directly
355
+ console.print(
356
+ f"❌ Failed to export pipeline [bold blue]{self.project_cfg.name}.{name}[/bold blue] to [red]{dest_base_dir}[/red]: {e}",
357
+ style="red",
358
+ )
359
+
360
+ def export_all(
361
+ self,
362
+ dest_base_dir: str,
363
+ dest_fs: AbstractFileSystem | None = None,
364
+ dest_storage_options: BaseStorageOptions | None = None,
365
+ overwrite: bool = False,
366
+ ):
367
+ """Export all pipelines to a given path.
368
+
369
+ Args:
370
+ dest_base_dir (str): The destination directory path.
371
+ dest_fs (AbstractFileSystem | None, optional): The destination filesystem. Defaults to None.
372
+ dest_storage_options (BaseStorageOptions | None, optional): Storage options for the destination path. Defaults to None.
373
+ overwrite (bool, optional): Whether to overwrite existing files at the destination. Defaults to False.
374
+
375
+ Returns:
376
+ None
377
+
378
+ Examples:
379
+ ```python
380
+ pm = PipelineManager()
381
+ # Export all pipelines to a local directory
382
+ pm.export_all("/path/to/backup_dir", overwrite=True)
383
+ # Export all pipelines to S3
384
+ # pm.export_all("s3://my-bucket/pipelines_backup", storage_options={"key": "...", "secret": "..."}, overwrite=False)
385
+ ```
386
+ """
387
+
388
+ # Use registry to get all pipeline names
389
+ # Use registry's public method/property
390
+ pipelines = self.registry.list_pipelines() # Assuming list_pipelines is public
391
+ if not pipelines:
392
+ console.print(
393
+ "🤷 No pipelines found in the registry to export.", style="yellow"
394
+ )
395
+ return
396
+
397
+ console.print(
398
+ f"Found {len(pipelines)} pipelines in the registry. Exporting all to [green]{dest_base_dir}[/green]..."
399
+ )
400
+
401
+ self.export_many(
402
+ pipelines=pipelines,
403
+ dest_base_dir=dest_base_dir,
404
+ dest_fs=dest_fs,
405
+ dest_storage_options=dest_storage_options,
406
+ overwrite=overwrite,
407
+ )