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,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__()
|
flowerpower/cli/__init__.py
CHANGED
@@ -1,69 +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
|
9
|
+
from .utils import parse_dict_or_list_param
|
8
10
|
|
9
|
-
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
|
+
)
|
10
15
|
|
11
16
|
|
12
|
-
app.add_typer(pipeline_app, name="pipeline")
|
17
|
+
app.add_typer(pipeline_app, name="pipeline", help="Manage and execute FlowerPower pipelines")
|
13
18
|
|
14
|
-
if importlib.util.find_spec("apscheduler"):
|
15
|
-
from .
|
19
|
+
if importlib.util.find_spec("apscheduler") or importlib.util.find_spec("rq"):
|
20
|
+
from .job_queue import app as job_queue_app
|
16
21
|
|
17
|
-
app.add_typer(
|
22
|
+
app.add_typer(job_queue_app, name="job-queue", help="Manage job queue workers and scheduled tasks")
|
18
23
|
|
19
24
|
if importlib.util.find_spec("paho"):
|
20
25
|
from .mqtt import app as mqtt_app
|
21
26
|
|
22
|
-
app.add_typer(mqtt_app, name="mqtt")
|
27
|
+
app.add_typer(mqtt_app, name="mqtt", help="Connect pipelines to MQTT message brokers")
|
23
28
|
|
24
29
|
|
25
30
|
@app.command()
|
26
31
|
def init(
|
27
|
-
project_name: str = None,
|
28
|
-
base_dir: str = None,
|
29
|
-
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)"),
|
30
36
|
):
|
31
37
|
"""
|
32
|
-
Initialize
|
33
|
-
|
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
|
+
|
34
44
|
Args:
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
38
64
|
"""
|
39
|
-
|
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)
|
40
72
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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)
|
46
83
|
|
47
84
|
|
48
85
|
@app.command()
|
49
86
|
def ui(
|
50
|
-
port: int = 8241,
|
51
|
-
base_dir: str = "~/.hamilton/db",
|
52
|
-
no_migration: bool = False,
|
53
|
-
no_open: bool = False,
|
54
|
-
settings_file: str = "mini",
|
55
|
-
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"),
|
56
93
|
):
|
57
94
|
"""
|
58
|
-
Start the Hamilton UI.
|
59
|
-
|
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
|
+
|
60
101
|
Args:
|
61
|
-
port
|
62
|
-
base_dir
|
63
|
-
no_migration
|
64
|
-
no_open
|
65
|
-
settings_file
|
66
|
-
config_file
|
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
|
67
124
|
"""
|
68
125
|
try:
|
69
126
|
from hamilton_ui import commands
|
@@ -76,7 +133,7 @@ def ui(
|
|
76
133
|
|
77
134
|
commands.run(
|
78
135
|
port=port,
|
79
|
-
base_dir=base_dir,
|
136
|
+
base_dir=os.path.expanduser(base_dir),
|
80
137
|
no_migration=no_migration,
|
81
138
|
no_open=no_open,
|
82
139
|
settings_file=settings_file,
|