FlowerPower 0.9.13.1__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.
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.0b1.dist-info/METADATA +324 -0
  60. flowerpower-1.0.0b1.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.0b1.dist-info}/WHEEL +0 -0
  84. {flowerpower-0.9.13.1.dist-info → flowerpower-1.0.0b1.dist-info}/entry_points.txt +0 -0
  85. {flowerpower-0.9.13.1.dist-info → flowerpower-1.0.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,57 @@
1
+ import msgspec
2
+ from munch import munchify
3
+
4
+ from ... import settings
5
+ from ..base import BaseConfig
6
+
7
+
8
+ class HamiltonTrackerConfig(BaseConfig):
9
+ username: str | None = msgspec.field(default=None)
10
+ api_url: str = msgspec.field(default=settings.HAMILTON_API_URL)
11
+ ui_url: str = msgspec.field(default=settings.HAMILTON_UI_URL)
12
+ api_key: str | None = msgspec.field(default=None)
13
+ verify: bool = msgspec.field(default=False)
14
+
15
+
16
+ class MLFlowConfig(BaseConfig):
17
+ tracking_uri: str | None = msgspec.field(default=None)
18
+ registry_uri: str | None = msgspec.field(default=None)
19
+ artifact_location: str | None = msgspec.field(default=None)
20
+
21
+
22
+ class OpenTelemetryConfig(BaseConfig):
23
+ host: str = msgspec.field(default="localhost")
24
+ port: int = msgspec.field(default=6831)
25
+
26
+
27
+ # class OpenLineageConfig(BaseConfig):
28
+ # from openlineage.client import OpenLineageClientOptions
29
+ # from openlineage.client.transport import Transport
30
+ # from openlineage.client.transport import TransportFactory
31
+ # url: str | None = msgspec.field(default=None)
32
+ # options: OpenLineageClientOptions | None = msgspec.field(
33
+ # default=None)
34
+ # transport: Transport | None = msgspec.field(default=None)
35
+ # factory: TransportFactory | None = msgspec.field(
36
+ # default=None)
37
+ # config: dict | None = msgspec.field(default=None)
38
+
39
+
40
+ class RayConfig(BaseConfig):
41
+ ray_init_config: dict | None = msgspec.field(default=None)
42
+ shutdown_ray_on_completion: bool = msgspec.field(default=False)
43
+
44
+ def __post_init__(self):
45
+ if isinstance(self.ray_init_config, dict):
46
+ self.ray_init_config = munchify(self.ray_init_config)
47
+
48
+
49
+ class AdapterConfig(BaseConfig):
50
+ hamilton_tracker: HamiltonTrackerConfig = msgspec.field(
51
+ default_factory=HamiltonTrackerConfig
52
+ )
53
+ mlflow: MLFlowConfig = msgspec.field(default_factory=MLFlowConfig)
54
+ ray: RayConfig = msgspec.field(default_factory=RayConfig)
55
+ opentelemetry: OpenTelemetryConfig = msgspec.field(
56
+ default_factory=OpenTelemetryConfig
57
+ )
@@ -0,0 +1,165 @@
1
+ import datetime as dt
2
+
3
+ import msgspec
4
+
5
+ from ... import settings
6
+ from ..base import BaseConfig
7
+
8
+
9
+
10
+ class JobQueueBackendConfig(BaseConfig):
11
+ """
12
+ Job Queue backend configuration for FlowerPower.
13
+ Inherits from BaseConfig and adapts Redis logic.
14
+ """
15
+
16
+ type: str | None = msgspec.field(default=None)
17
+ uri: str | None = msgspec.field(default=None)
18
+ username: str | None = msgspec.field(default=None)
19
+ password: str | None = msgspec.field(default=None)
20
+ host: str | None = msgspec.field(default=None)
21
+ port: int | None = msgspec.field(default=None)
22
+ database: int | None = msgspec.field(default=None)
23
+ ssl: bool = msgspec.field(default=False)
24
+ cert_file: str | None = msgspec.field(default=None)
25
+ key_file: str | None = msgspec.field(default=None)
26
+ ca_file: str | None = msgspec.field(default=None)
27
+ verify_ssl: bool = msgspec.field(default=False)
28
+
29
+
30
+ class APSDataStoreConfig(JobQueueBackendConfig):
31
+ type: str = msgspec.field(default=settings.APS_BACKEND_DS)
32
+ host: str = msgspec.field(
33
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_DS][
34
+ "default_host"
35
+ ]
36
+ )
37
+ port: int = msgspec.field(
38
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_DS][
39
+ "default_port"
40
+ ]
41
+ )
42
+ schema: str | None = msgspec.field(default=settings.APS_SCHEMA_DS)
43
+ username: str = msgspec.field(
44
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_DS][
45
+ "default_username"
46
+ ]
47
+ )
48
+
49
+
50
+ class APSEventBrokerConfig(JobQueueBackendConfig):
51
+ type: str = msgspec.field(default=settings.APS_BACKEND_EB)
52
+ host: str = msgspec.field(
53
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_EB][
54
+ "default_host"
55
+ ]
56
+ )
57
+ port: int = msgspec.field(
58
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_EB][
59
+ "default_port"
60
+ ]
61
+ )
62
+ username: str = msgspec.field(
63
+ default=settings.BACKEND_PROPERTIES[settings.APS_BACKEND_EB][
64
+ "default_username"
65
+ ]
66
+ )
67
+ from_ds_sqla: bool = msgspec.field(
68
+ default_factory=lambda: settings.APS_BACKEND_EB == "postgresql"
69
+ and settings.APS_BACKEND_DS == "postgresql"
70
+ )
71
+
72
+
73
+ class APSBackendConfig(BaseConfig):
74
+ data_store: APSDataStoreConfig = msgspec.field(default_factory=APSDataStoreConfig)
75
+ event_broker: APSEventBrokerConfig = msgspec.field(
76
+ default_factory=APSEventBrokerConfig
77
+ )
78
+ cleanup_interval: int | float | dt.timedelta = msgspec.field(
79
+ default=settings.APS_CLEANUP_INTERVAL
80
+ ) # int in secods
81
+ max_concurrent_jobs: int = msgspec.field(
82
+ default=settings.APS_MAX_CONCURRENT_JOBS
83
+ )
84
+ default_job_executor: str | None = msgspec.field(default=settings.EXECUTOR)
85
+ num_workers: int | None = msgspec.field(default=settings.APS_NUM_WORKERS)
86
+
87
+
88
+ class RQBackendConfig(JobQueueBackendConfig):
89
+ type: str = msgspec.field(default="redis")
90
+ host: str = msgspec.field(
91
+ default=settings.BACKEND_PROPERTIES["redis"]["default_host"]
92
+ )
93
+ port: int = msgspec.field(
94
+ default=settings.BACKEND_PROPERTIES["redis"]["default_port"]
95
+ )
96
+ database: int = msgspec.field(
97
+ default=settings.BACKEND_PROPERTIES["redis"]["default_database"]
98
+ )
99
+ queues: str | list[str] = msgspec.field(
100
+ default_factory=lambda: settings.RQ_QUEUES
101
+ )
102
+ num_workers: int = msgspec.field(
103
+ default=settings.RQ_NUM_WORKERS
104
+ ) # int in secods
105
+
106
+
107
+ class HueyBackendConfig(JobQueueBackendConfig):
108
+ pass
109
+
110
+
111
+ class JobQueueConfig(BaseConfig):
112
+ type: str | None = msgspec.field(default="rq")
113
+ backend: dict | None = msgspec.field(default=None)
114
+
115
+ def __post_init__(self):
116
+ if self.type is not None:
117
+ self.type = self.type.lower()
118
+ if self.type == "rq":
119
+ if self.backend is None:
120
+ self.backend = RQBackendConfig()
121
+ else:
122
+ if isinstance(self.backend, dict):
123
+ self.backend = RQBackendConfig.from_dict(self.backend)
124
+ elif isinstance(self.backend, RQBackendConfig):
125
+ pass
126
+ else:
127
+ raise ValueError(
128
+ f"Invalid backend type for RQ: {type(self.backend)}"
129
+ )
130
+ elif self.type == "apscheduler":
131
+ if self.backend is None:
132
+ self.backend = APSBackendConfig( )
133
+ else:
134
+ if isinstance(self.backend, dict):
135
+ self.backend = APSBackendConfig.from_dict(self.backend)
136
+ elif isinstance(self.backend, APSBackendConfig):
137
+ pass
138
+ else:
139
+ raise ValueError(
140
+ f"Invalid backend type for APScheduler: {type(self.backend)}"
141
+ )
142
+
143
+ elif self.type == "huey":
144
+ if self.backend is None:
145
+ self.backend = HueyBackendConfig()
146
+ else:
147
+ if isinstance(self.backend, dict):
148
+ self.backend = HueyBackendConfig.from_dict(self.backend)
149
+ elif isinstance(self.backend, HueyBackendConfig):
150
+ pass
151
+ else:
152
+ raise ValueError(
153
+ f"Invalid backend type for Huey: {type(self.backend)}"
154
+ )
155
+ self.backend = HueyBackendConfig(**self.backend)
156
+ else:
157
+ raise ValueError(
158
+ f"Invalid job queue type: {self.type}. Valid types: {['rq', 'apscheduler', 'huey']}"
159
+ )
160
+
161
+ def update_type(self, type: str):
162
+ if type != self.type:
163
+ self.type = type
164
+ self.backend = None
165
+ self.__post_init__()
@@ -1,71 +1,126 @@
1
1
  import importlib
2
+ import os
2
3
 
3
4
  import typer
4
5
  from loguru import logger
5
6
 
6
7
  from ..flowerpower import init as init_
7
8
  from .pipeline import app as pipeline_app
8
- from .web import web_app # Import the web app
9
+ from .utils import parse_dict_or_list_param
9
10
 
10
- app = typer.Typer()
11
+ app = typer.Typer(
12
+ help="FlowerPower: A framework for building, executing, and managing data processing pipelines",
13
+ rich_markup_mode="rich",
14
+ )
11
15
 
12
16
 
13
- app.add_typer(pipeline_app, name="pipeline")
14
- app.add_typer(web_app, name="web") # Add the web app as a command
17
+ app.add_typer(pipeline_app, name="pipeline", help="Manage and execute FlowerPower pipelines")
15
18
 
16
- if importlib.util.find_spec("apscheduler"):
17
- from .scheduler import app as scheduler_app
19
+ if importlib.util.find_spec("apscheduler") or importlib.util.find_spec("rq"):
20
+ from .job_queue import app as job_queue_app
18
21
 
19
- app.add_typer(scheduler_app, name="scheduler")
22
+ app.add_typer(job_queue_app, name="job-queue", help="Manage job queue workers and scheduled tasks")
20
23
 
21
24
  if importlib.util.find_spec("paho"):
22
25
  from .mqtt import app as mqtt_app
23
26
 
24
- app.add_typer(mqtt_app, name="mqtt")
27
+ app.add_typer(mqtt_app, name="mqtt", help="Connect pipelines to MQTT message brokers")
25
28
 
26
29
 
27
30
  @app.command()
28
31
  def init(
29
- project_name: str = None,
30
- base_dir: str = None,
31
- storage_options: str = None,
32
+ project_name: str = typer.Option(None, "--name", "-n", help="Name of the FlowerPower project to create"),
33
+ base_dir: str = typer.Option(None, "--base-dir", "-d", help="Base directory where the project will be created"),
34
+ storage_options: str = typer.Option(None, "--storage-options", "-s", help="Storage options as a JSON or dict string"),
35
+ job_queue_type: str = typer.Option("rq", "--job-queue-type", "-q", help="Job queue backend type to use (rq, apscheduler)"),
32
36
  ):
33
37
  """
34
- Initialize the FlowerPower application.
35
-
38
+ Initialize a new FlowerPower project.
39
+
40
+ This command creates a new FlowerPower project with the necessary directory structure
41
+ and configuration files. If no project name is provided, the current directory name
42
+ will be used as the project name.
43
+
36
44
  Args:
37
- name (str): The name of the application.
38
- base_dir (str, optional): The base path of the application. Defaults to "".
39
- storage_options (str, optional): The filesystem storage options for the task. Defaults to None".
45
+ project_name: Name of the FlowerPower project to create. If not provided,
46
+ the current directory name will be used
47
+ base_dir: Base directory where the project will be created. If not provided,
48
+ the current directory's parent will be used
49
+ storage_options: Storage options for filesystem access, as a JSON or dict string
50
+ job_queue_type: Type of job queue backend to use (rq, apscheduler)
51
+
52
+ Examples:
53
+ # Create a project in the current directory using its name
54
+ $ flowerpower init
55
+
56
+ # Create a project with a specific name
57
+ $ flowerpower init --name my-awesome-project
58
+
59
+ # Create a project in a specific location
60
+ $ flowerpower init --name my-project --base-dir /path/to/projects
61
+
62
+ # Create a project with APScheduler as the job queue backend
63
+ $ flowerpower init --job-queue-type apscheduler
40
64
  """
41
- storage_options = eval(storage_options) if storage_options is not None else {}
65
+ parsed_storage_options = {}
66
+ if storage_options:
67
+ try:
68
+ parsed_storage_options = parse_dict_or_list_param(storage_options, "dict") or {}
69
+ except Exception as e:
70
+ logger.error(f"Error parsing storage options: {e}")
71
+ raise typer.Exit(code=1)
42
72
 
43
- init_(
44
- name=project_name,
45
- base_dir=base_dir,
46
- storage_options=storage_options,
47
- )
73
+ try:
74
+ init_(
75
+ name=project_name,
76
+ base_dir=base_dir,
77
+ storage_options=parsed_storage_options,
78
+ job_queue_type=job_queue_type,
79
+ )
80
+ except Exception as e:
81
+ logger.error(f"Error initializing project: {e}")
82
+ raise typer.Exit(code=1)
48
83
 
49
84
 
50
85
  @app.command()
51
86
  def ui(
52
- port: int = 8241,
53
- base_dir: str = "~/.hamilton/db",
54
- no_migration: bool = False,
55
- no_open: bool = False,
56
- settings_file: str = "mini",
57
- config_file: str = None,
87
+ port: int = typer.Option(8241, "--port", "-p", help="Port to run the UI server on"),
88
+ base_dir: str = typer.Option("~/.hamilton/db", "--base-dir", "-d", help="Base directory for Hamilton UI data"),
89
+ no_migration: bool = typer.Option(False, "--no-migration", help="Skip running database migrations"),
90
+ no_open: bool = typer.Option(False, "--no-open", help="Don't automatically open the UI in a browser"),
91
+ settings_file: str = typer.Option("mini", "--settings", "-s", help="Settings file to use for the UI"),
92
+ config_file: str = typer.Option(None, "--config", "-c", help="Configuration file to use for the UI"),
58
93
  ):
59
94
  """
60
- Start the Hamilton UI.
61
-
95
+ Start the Hamilton UI web application.
96
+
97
+ This command launches the Hamilton UI, which provides a web interface for
98
+ visualizing and interacting with your FlowerPower pipelines. The UI allows you
99
+ to explore pipeline execution graphs, view results, and manage jobs.
100
+
62
101
  Args:
63
- port (int, optional): The port to run the UI on. Defaults to 8241.
64
- base_dir (str, optional): The base path for the UI. Defaults to "~/.hamilton/db".
65
- no_migration (bool, optional): Whether to run the migration. Defaults to False.
66
- no_open (bool, optional): Whether to open the UI in the browser. Defaults to False.
67
- settings_file (str, optional): The settings file to use. Defaults to "mini".
68
- config_file (str, optional): The config file to use. Defaults to None.
102
+ port: Port to run the UI server on
103
+ base_dir: Base directory where the UI will store its data
104
+ no_migration: Skip running database migrations on startup
105
+ no_open: Prevent automatically opening the browser
106
+ settings_file: Settings profile to use (mini, dev, prod)
107
+ config_file: Optional custom configuration file path
108
+
109
+ Examples:
110
+ # Start the UI with default settings
111
+ $ flowerpower ui
112
+
113
+ # Run the UI on a specific port
114
+ $ flowerpower ui --port 9000
115
+
116
+ # Use a custom data directory
117
+ $ flowerpower ui --base-dir ~/my-project/.hamilton-data
118
+
119
+ # Start without opening a browser
120
+ $ flowerpower ui --no-open
121
+
122
+ # Use production settings
123
+ $ flowerpower ui --settings prod
69
124
  """
70
125
  try:
71
126
  from hamilton_ui import commands
@@ -78,7 +133,7 @@ def ui(
78
133
 
79
134
  commands.run(
80
135
  port=port,
81
- base_dir=base_dir,
136
+ base_dir=os.path.expanduser(base_dir),
82
137
  no_migration=no_migration,
83
138
  no_open=no_open,
84
139
  settings_file=settings_file,