FlowerPower 0.30.0__py3-none-any.whl → 0.31.1__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/cfg/__init__.py +143 -25
- flowerpower/cfg/base.py +132 -11
- flowerpower/cfg/exceptions.py +53 -0
- flowerpower/cfg/pipeline/__init__.py +151 -35
- flowerpower/cfg/pipeline/adapter.py +1 -0
- flowerpower/cfg/pipeline/builder.py +24 -25
- flowerpower/cfg/pipeline/builder_adapter.py +142 -0
- flowerpower/cfg/pipeline/builder_executor.py +101 -0
- flowerpower/cfg/pipeline/run.py +99 -40
- flowerpower/cfg/project/__init__.py +59 -14
- flowerpower/cfg/project/adapter.py +6 -0
- flowerpower/cli/__init__.py +8 -2
- flowerpower/cli/cfg.py +0 -38
- flowerpower/cli/pipeline.py +121 -83
- flowerpower/cli/utils.py +120 -71
- flowerpower/flowerpower.py +94 -120
- flowerpower/pipeline/config_manager.py +180 -0
- flowerpower/pipeline/executor.py +126 -0
- flowerpower/pipeline/lifecycle_manager.py +231 -0
- flowerpower/pipeline/manager.py +121 -274
- flowerpower/pipeline/pipeline.py +66 -278
- flowerpower/pipeline/registry.py +45 -4
- flowerpower/utils/__init__.py +19 -0
- flowerpower/utils/adapter.py +286 -0
- flowerpower/utils/callback.py +73 -67
- flowerpower/utils/config.py +306 -0
- flowerpower/utils/executor.py +178 -0
- flowerpower/utils/filesystem.py +194 -0
- flowerpower/utils/misc.py +312 -138
- flowerpower/utils/security.py +221 -0
- {flowerpower-0.30.0.dist-info → flowerpower-0.31.1.dist-info}/METADATA +2 -2
- flowerpower-0.31.1.dist-info/RECORD +53 -0
- flowerpower/cfg/pipeline/_schedule.py +0 -32
- flowerpower-0.30.0.dist-info/RECORD +0 -42
- {flowerpower-0.30.0.dist-info → flowerpower-0.31.1.dist-info}/WHEEL +0 -0
- {flowerpower-0.30.0.dist-info → flowerpower-0.31.1.dist-info}/entry_points.txt +0 -0
- {flowerpower-0.30.0.dist-info → flowerpower-0.31.1.dist-info}/licenses/LICENSE +0 -0
- {flowerpower-0.30.0.dist-info → flowerpower-0.31.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
"""
|
2
|
+
Adapter utilities for FlowerPower pipeline management.
|
3
|
+
|
4
|
+
This module provides helper classes for managing adapter configurations
|
5
|
+
and creating adapter instances with proper error handling and validation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from typing import Any, Dict, Optional
|
10
|
+
|
11
|
+
import msgspec
|
12
|
+
from loguru import logger
|
13
|
+
|
14
|
+
|
15
|
+
class AdapterManager:
|
16
|
+
"""
|
17
|
+
Helper class for adapter configuration and instance creation.
|
18
|
+
|
19
|
+
This class centralizes adapter configuration merging, validation,
|
20
|
+
and instance creation to reduce complexity in the Pipeline class.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self):
|
24
|
+
"""Initialize the adapter manager."""
|
25
|
+
self._adapter_cache: Dict[str, Any] = {}
|
26
|
+
|
27
|
+
def _merge_configs(self, base_config: Any, override_config: Any) -> Any:
|
28
|
+
"""Merge override config into base config."""
|
29
|
+
if not override_config:
|
30
|
+
return base_config
|
31
|
+
return base_config.merge(override_config) if base_config else override_config
|
32
|
+
|
33
|
+
def resolve_with_adapter_config(
|
34
|
+
self,
|
35
|
+
with_adapter_cfg: dict | Any | None,
|
36
|
+
base_config: Any
|
37
|
+
) -> Any:
|
38
|
+
"""
|
39
|
+
Resolve and merge WithAdapterConfig.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
with_adapter_cfg: Input configuration (dict or instance)
|
43
|
+
base_config: Base configuration to merge with
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
WithAdapterConfig: Merged configuration
|
47
|
+
"""
|
48
|
+
from ..cfg.pipeline.run import WithAdapterConfig
|
49
|
+
|
50
|
+
if with_adapter_cfg:
|
51
|
+
if isinstance(with_adapter_cfg, dict):
|
52
|
+
with_adapter_cfg = WithAdapterConfig.from_dict(with_adapter_cfg)
|
53
|
+
elif not isinstance(with_adapter_cfg, WithAdapterConfig):
|
54
|
+
raise TypeError(
|
55
|
+
"with_adapter must be a dictionary or WithAdapterConfig instance."
|
56
|
+
)
|
57
|
+
|
58
|
+
return self._merge_configs(base_config, with_adapter_cfg)
|
59
|
+
|
60
|
+
return base_config
|
61
|
+
|
62
|
+
def resolve_pipeline_adapter_config(
|
63
|
+
self,
|
64
|
+
pipeline_adapter_cfg: dict | Any | None,
|
65
|
+
base_config: Any
|
66
|
+
) -> Any:
|
67
|
+
"""
|
68
|
+
Resolve and merge PipelineAdapterConfig.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
pipeline_adapter_cfg: Input configuration (dict or instance)
|
72
|
+
base_config: Base configuration to merge with
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
PipelineAdapterConfig: Merged configuration
|
76
|
+
"""
|
77
|
+
from ..cfg.pipeline.adapter import AdapterConfig as PipelineAdapterConfig
|
78
|
+
|
79
|
+
if pipeline_adapter_cfg:
|
80
|
+
if isinstance(pipeline_adapter_cfg, dict):
|
81
|
+
pipeline_adapter_cfg = PipelineAdapterConfig.from_dict(
|
82
|
+
pipeline_adapter_cfg
|
83
|
+
)
|
84
|
+
elif not isinstance(pipeline_adapter_cfg, PipelineAdapterConfig):
|
85
|
+
raise TypeError(
|
86
|
+
"pipeline_adapter_cfg must be a dictionary or PipelineAdapterConfig instance."
|
87
|
+
)
|
88
|
+
|
89
|
+
return self._merge_configs(base_config, pipeline_adapter_cfg)
|
90
|
+
|
91
|
+
return base_config
|
92
|
+
|
93
|
+
def resolve_project_adapter_config(
|
94
|
+
self,
|
95
|
+
project_adapter_cfg: dict | Any | None,
|
96
|
+
project_context: Any
|
97
|
+
) -> Any:
|
98
|
+
"""
|
99
|
+
Resolve and merge ProjectAdapterConfig from project context.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
project_adapter_cfg: Input configuration (dict or instance)
|
103
|
+
project_context: Project context to extract base config from
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
ProjectAdapterConfig: Merged configuration
|
107
|
+
"""
|
108
|
+
from ..cfg.project.adapter import AdapterConfig as ProjectAdapterConfig
|
109
|
+
|
110
|
+
# Get base configuration from project context
|
111
|
+
base_cfg = self._extract_project_adapter_config(project_context)
|
112
|
+
|
113
|
+
if project_adapter_cfg:
|
114
|
+
if isinstance(project_adapter_cfg, dict):
|
115
|
+
project_adapter_cfg = ProjectAdapterConfig.from_dict(
|
116
|
+
project_adapter_cfg
|
117
|
+
)
|
118
|
+
elif not isinstance(project_adapter_cfg, ProjectAdapterConfig):
|
119
|
+
raise TypeError(
|
120
|
+
"project_adapter_cfg must be a dictionary or ProjectAdapterConfig instance."
|
121
|
+
)
|
122
|
+
|
123
|
+
return self._merge_configs(base_cfg, project_adapter_cfg)
|
124
|
+
else:
|
125
|
+
# Use base configuration or create default
|
126
|
+
return base_cfg or ProjectAdapterConfig()
|
127
|
+
|
128
|
+
def _extract_project_adapter_config(
|
129
|
+
self,
|
130
|
+
project_context: Any
|
131
|
+
) -> Optional[Any]:
|
132
|
+
"""
|
133
|
+
Extract adapter configuration from project context.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
project_context: Project context (PipelineManager or FlowerPowerProject)
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
ProjectAdapterConfig or None: Extracted configuration
|
140
|
+
"""
|
141
|
+
# Try direct access to project config
|
142
|
+
project_cfg = getattr(project_context, "project_cfg", None) or getattr(project_context, "_project_cfg", None)
|
143
|
+
if project_cfg and hasattr(project_cfg, "adapter"):
|
144
|
+
return project_cfg.adapter
|
145
|
+
|
146
|
+
# Try via pipeline_manager if available
|
147
|
+
if hasattr(project_context, "pipeline_manager"):
|
148
|
+
pm = project_context.pipeline_manager
|
149
|
+
pm_cfg = getattr(pm, "project_cfg", None) or getattr(pm, "_project_cfg", None)
|
150
|
+
if pm_cfg and hasattr(pm_cfg, "adapter"):
|
151
|
+
return pm_cfg.adapter
|
152
|
+
|
153
|
+
return None
|
154
|
+
|
155
|
+
def create_adapters(
|
156
|
+
self,
|
157
|
+
with_adapter_cfg: Any,
|
158
|
+
pipeline_adapter_cfg: Any,
|
159
|
+
project_adapter_cfg: Any
|
160
|
+
) -> list:
|
161
|
+
"""
|
162
|
+
Create adapter instances based on configurations.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
with_adapter_cfg: WithAdapter configuration
|
166
|
+
pipeline_adapter_cfg: Pipeline adapter configuration
|
167
|
+
project_adapter_cfg: Project adapter configuration
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
list: List of adapter instances
|
171
|
+
"""
|
172
|
+
adapters = []
|
173
|
+
|
174
|
+
# Hamilton Tracker adapter
|
175
|
+
if with_adapter_cfg.hamilton_tracker:
|
176
|
+
adapter = self._create_hamilton_tracker(
|
177
|
+
pipeline_adapter_cfg.hamilton_tracker,
|
178
|
+
project_adapter_cfg.hamilton_tracker
|
179
|
+
)
|
180
|
+
if adapter:
|
181
|
+
adapters.append(adapter)
|
182
|
+
|
183
|
+
# MLFlow adapter
|
184
|
+
if with_adapter_cfg.mlflow:
|
185
|
+
adapter = self._create_mlflow_adapter(
|
186
|
+
pipeline_adapter_cfg.mlflow,
|
187
|
+
project_adapter_cfg.mlflow
|
188
|
+
)
|
189
|
+
if adapter:
|
190
|
+
adapters.append(adapter)
|
191
|
+
|
192
|
+
# OpenTelemetry adapter
|
193
|
+
if with_adapter_cfg.opentelemetry:
|
194
|
+
adapter = self._create_opentelemetry_adapter(
|
195
|
+
pipeline_adapter_cfg.opentelemetry,
|
196
|
+
project_adapter_cfg.opentelemetry
|
197
|
+
)
|
198
|
+
if adapter:
|
199
|
+
adapters.append(adapter)
|
200
|
+
|
201
|
+
return adapters
|
202
|
+
|
203
|
+
def _create_hamilton_tracker(
|
204
|
+
self,
|
205
|
+
pipeline_config: Any,
|
206
|
+
project_config: Any
|
207
|
+
) -> Optional[Any]:
|
208
|
+
"""Create HamiltonTracker adapter instance."""
|
209
|
+
try:
|
210
|
+
from hamilton.adapters import HamiltonTracker
|
211
|
+
from hamilton import constants
|
212
|
+
from ..settings import settings
|
213
|
+
except ImportError:
|
214
|
+
logger.warning("Hamilton tracker dependencies not installed")
|
215
|
+
return None
|
216
|
+
|
217
|
+
tracker_kwargs = project_config.to_dict()
|
218
|
+
tracker_kwargs.update(pipeline_config.to_dict())
|
219
|
+
tracker_kwargs["hamilton_api_url"] = tracker_kwargs.pop("api_url", None)
|
220
|
+
tracker_kwargs["hamilton_ui_url"] = tracker_kwargs.pop("ui_url", None)
|
221
|
+
|
222
|
+
# Set capture constants
|
223
|
+
constants.MAX_DICT_LENGTH_CAPTURE = (
|
224
|
+
tracker_kwargs.pop("max_dict_length_capture", None)
|
225
|
+
or settings.HAMILTON_MAX_DICT_LENGTH_CAPTURE
|
226
|
+
)
|
227
|
+
constants.MAX_LIST_LENGTH_CAPTURE = (
|
228
|
+
tracker_kwargs.pop("max_list_length_capture", None)
|
229
|
+
or settings.HAMILTON_MAX_LIST_LENGTH_CAPTURE
|
230
|
+
)
|
231
|
+
constants.CAPTURE_DATA_STATISTICS = (
|
232
|
+
tracker_kwargs.pop("capture_data_statistics", None)
|
233
|
+
or settings.HAMILTON_CAPTURE_DATA_STATISTICS
|
234
|
+
)
|
235
|
+
|
236
|
+
return HamiltonTracker(**tracker_kwargs)
|
237
|
+
|
238
|
+
def _create_mlflow_adapter(
|
239
|
+
self,
|
240
|
+
pipeline_config: Any,
|
241
|
+
project_config: Any
|
242
|
+
) -> Optional[Any]:
|
243
|
+
"""Create MLFlow adapter instance."""
|
244
|
+
try:
|
245
|
+
from hamilton.experimental import h_mlflow
|
246
|
+
except ImportError:
|
247
|
+
logger.warning("MLFlow is not installed. Skipping MLFlow adapter.")
|
248
|
+
return None
|
249
|
+
|
250
|
+
mlflow_kwargs = project_config.to_dict()
|
251
|
+
mlflow_kwargs.update(pipeline_config.to_dict())
|
252
|
+
return h_mlflow.MLFlowTracker(**mlflow_kwargs)
|
253
|
+
|
254
|
+
def _create_opentelemetry_adapter(
|
255
|
+
self,
|
256
|
+
pipeline_config: Any,
|
257
|
+
project_config: Any
|
258
|
+
) -> Optional[Any]:
|
259
|
+
"""Create OpenTelemetry adapter instance."""
|
260
|
+
try:
|
261
|
+
from hamilton.experimental import h_opentelemetry
|
262
|
+
from ..utils.open_telemetry import init_tracer
|
263
|
+
except ImportError:
|
264
|
+
logger.warning(
|
265
|
+
"OpenTelemetry is not installed. Skipping OpenTelemetry adapter."
|
266
|
+
)
|
267
|
+
return None
|
268
|
+
|
269
|
+
otel_kwargs = project_config.to_dict()
|
270
|
+
otel_kwargs.update(pipeline_config.to_dict())
|
271
|
+
init_tracer()
|
272
|
+
return h_opentelemetry.OpenTelemetryTracker(**otel_kwargs)
|
273
|
+
|
274
|
+
def clear_cache(self) -> None:
|
275
|
+
"""Clear the adapter cache."""
|
276
|
+
self._adapter_cache.clear()
|
277
|
+
|
278
|
+
|
279
|
+
def create_adapter_manager() -> AdapterManager:
|
280
|
+
"""
|
281
|
+
Factory function to create an AdapterManager instance.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
AdapterManager: Configured manager instance
|
285
|
+
"""
|
286
|
+
return AdapterManager()
|
flowerpower/utils/callback.py
CHANGED
@@ -14,80 +14,86 @@ from .logging import setup_logging
|
|
14
14
|
setup_logging(level=LOG_LEVEL)
|
15
15
|
|
16
16
|
|
17
|
-
def
|
18
|
-
"""
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
def _add_exception_to_simple_callback(callback_fn: Callable, context_exception: Exception, cb_args: list, cb_kwargs: Dict[str, Any]):
|
18
|
+
"""Add exception to simple callback arguments."""
|
19
|
+
try:
|
20
|
+
sig = inspect.signature(callback_fn)
|
21
|
+
if len(sig.parameters) == 1:
|
22
|
+
first_param = next(iter(sig.parameters.values()))
|
23
|
+
if first_param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY):
|
24
|
+
cb_args.append(context_exception)
|
25
|
+
elif "exception" in sig.parameters:
|
26
|
+
cb_kwargs["exception"] = context_exception
|
27
|
+
except (ValueError, TypeError):
|
28
|
+
logger.debug(
|
29
|
+
f"Could not inspect signature for simple callback {getattr(callback_fn, '__name__', str(callback_fn))}. Exception not passed automatically."
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def _parse_tuple_callback_args(callback_info: tuple, cb_args: list, cb_kwargs: Dict[str, Any]):
|
34
|
+
"""Parse args and kwargs from tuple callback info."""
|
35
|
+
callback_fn = callback_info[0]
|
36
|
+
|
37
|
+
# Args: callback_info[1]
|
38
|
+
if len(callback_info) > 1 and callback_info[1] is not None:
|
39
|
+
if isinstance(callback_info[1], tuple):
|
40
|
+
cb_args.extend(callback_info[1])
|
41
|
+
else:
|
42
|
+
logger.warning(
|
43
|
+
f"Callback args for {getattr(callback_fn, '__name__', str(callback_fn))} "
|
44
|
+
f"expected tuple, got {type(callback_info[1])}. Ignoring args."
|
45
|
+
)
|
46
|
+
|
47
|
+
# Kwargs: callback_info[2]
|
48
|
+
if len(callback_info) > 2 and callback_info[2] is not None:
|
49
|
+
if isinstance(callback_info[2], dict):
|
50
|
+
cb_kwargs.update(callback_info[2])
|
51
|
+
else:
|
52
|
+
logger.warning(
|
53
|
+
f"Callback kwargs for {getattr(callback_fn, '__name__', str(callback_fn))} "
|
54
|
+
f"expected dict, got {type(callback_info[2])}. Ignoring kwargs."
|
55
|
+
)
|
56
|
+
|
25
57
|
|
26
|
-
|
27
|
-
|
28
|
-
|
58
|
+
def _add_exception_to_tuple_callback(callback_fn: Callable, context_exception: Exception, cb_kwargs: Dict[str, Any]):
|
59
|
+
"""Add exception to tuple callback kwargs if accepted."""
|
60
|
+
try:
|
61
|
+
sig = inspect.signature(callback_fn)
|
62
|
+
if "exception" in sig.parameters:
|
63
|
+
cb_kwargs["exception"] = context_exception
|
64
|
+
except (ValueError, TypeError):
|
65
|
+
pass
|
29
66
|
|
30
|
-
is_simple_callable = isinstance(callback_info, Callable)
|
31
67
|
|
32
|
-
|
68
|
+
def _prepare_callback_details(callback_info: Any, context_exception: Exception = None) -> tuple[Callable | None, tuple, Dict[str, Any]]:
|
69
|
+
"""Prepare callback function and arguments for execution."""
|
70
|
+
if not callback_info:
|
71
|
+
return None, (), {}
|
72
|
+
|
73
|
+
callback_fn = None
|
74
|
+
cb_args = []
|
75
|
+
cb_kwargs = {}
|
76
|
+
|
77
|
+
if isinstance(callback_info, Callable):
|
33
78
|
callback_fn = callback_info
|
34
|
-
# For a simple callable in an on_failure context, try to pass the exception.
|
35
79
|
if context_exception:
|
36
|
-
|
37
|
-
|
38
|
-
if len(sig.parameters) == 1: # Assumes it takes one positional argument
|
39
|
-
first_param_name = list(sig.parameters.keys())[0]
|
40
|
-
# Avoid passing if it's a **kwargs style param and we have no other indication
|
41
|
-
if sig.parameters[first_param_name].kind in [
|
42
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
43
|
-
inspect.Parameter.POSITIONAL_ONLY,
|
44
|
-
]:
|
45
|
-
cb_args = (context_exception,)
|
46
|
-
elif (
|
47
|
-
"exception" in sig.parameters
|
48
|
-
): # Or if it explicitly takes 'exception' kwarg
|
49
|
-
cb_kwargs["exception"] = context_exception
|
50
|
-
except (ValueError, TypeError): # Some callables might not be inspectable
|
51
|
-
logger.debug(
|
52
|
-
f"Could not inspect signature for simple callback {getattr(callback_fn, '__name__', str(callback_fn))}. Exception not passed automatically."
|
53
|
-
)
|
54
|
-
|
55
|
-
elif (
|
56
|
-
isinstance(callback_info, tuple)
|
57
|
-
and len(callback_info) > 0
|
58
|
-
and isinstance(callback_info[0], Callable)
|
59
|
-
):
|
80
|
+
_add_exception_to_simple_callback(callback_fn, context_exception, cb_args, cb_kwargs)
|
81
|
+
elif isinstance(callback_info, tuple) and len(callback_info) > 0 and isinstance(callback_info[0], Callable):
|
60
82
|
callback_fn = callback_info[0]
|
61
|
-
|
62
|
-
# Args: callback_info[1]
|
63
|
-
if len(callback_info) > 1 and callback_info[1] is not None:
|
64
|
-
if isinstance(callback_info[1], tuple):
|
65
|
-
cb_args = callback_info[1]
|
66
|
-
else:
|
67
|
-
logger.warning(
|
68
|
-
f"Callback args for {getattr(callback_fn, '__name__', str(callback_fn))} "
|
69
|
-
f"expected tuple, got {type(callback_info[1])}. Ignoring args."
|
70
|
-
)
|
71
|
-
|
72
|
-
# Kwargs: callback_info[2]
|
73
|
-
if len(callback_info) > 2 and callback_info[2] is not None:
|
74
|
-
if isinstance(callback_info[2], dict):
|
75
|
-
cb_kwargs = callback_info[2].copy() # Use a copy
|
76
|
-
else:
|
77
|
-
logger.warning(
|
78
|
-
f"Callback kwargs for {getattr(callback_fn, '__name__', str(callback_fn))} "
|
79
|
-
f"expected dict, got {type(callback_info[2])}. Ignoring kwargs."
|
80
|
-
)
|
81
|
-
|
82
|
-
# If this is an on_failure call and an exception occurred,
|
83
|
-
# pass it if 'exception' kwarg is not set and the callback accepts it.
|
83
|
+
_parse_tuple_callback_args(callback_info, cb_args, cb_kwargs)
|
84
84
|
if context_exception and "exception" not in cb_kwargs:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
85
|
+
_add_exception_to_tuple_callback(callback_fn, context_exception, cb_kwargs)
|
86
|
+
|
87
|
+
return callback_fn, tuple(cb_args), cb_kwargs
|
88
|
+
|
89
|
+
|
90
|
+
def _execute_callback(callback_info: Any, context_exception: Exception = None):
|
91
|
+
"""
|
92
|
+
Helper to execute a callback.
|
93
|
+
The callback_info can be a callable, or a tuple (callable, args_tuple, kwargs_dict).
|
94
|
+
If context_exception is provided (for on_failure), it can be passed to the callback.
|
95
|
+
"""
|
96
|
+
callback_fn, cb_args, cb_kwargs = _prepare_callback_details(callback_info, context_exception)
|
91
97
|
|
92
98
|
if callback_fn:
|
93
99
|
try:
|