flowyml 1.1.0__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 (159) hide show
  1. flowyml/__init__.py +207 -0
  2. flowyml/assets/__init__.py +22 -0
  3. flowyml/assets/artifact.py +40 -0
  4. flowyml/assets/base.py +209 -0
  5. flowyml/assets/dataset.py +100 -0
  6. flowyml/assets/featureset.py +301 -0
  7. flowyml/assets/metrics.py +104 -0
  8. flowyml/assets/model.py +82 -0
  9. flowyml/assets/registry.py +157 -0
  10. flowyml/assets/report.py +315 -0
  11. flowyml/cli/__init__.py +5 -0
  12. flowyml/cli/experiment.py +232 -0
  13. flowyml/cli/init.py +256 -0
  14. flowyml/cli/main.py +327 -0
  15. flowyml/cli/run.py +75 -0
  16. flowyml/cli/stack_cli.py +532 -0
  17. flowyml/cli/ui.py +33 -0
  18. flowyml/core/__init__.py +68 -0
  19. flowyml/core/advanced_cache.py +274 -0
  20. flowyml/core/approval.py +64 -0
  21. flowyml/core/cache.py +203 -0
  22. flowyml/core/checkpoint.py +148 -0
  23. flowyml/core/conditional.py +373 -0
  24. flowyml/core/context.py +155 -0
  25. flowyml/core/error_handling.py +419 -0
  26. flowyml/core/executor.py +354 -0
  27. flowyml/core/graph.py +185 -0
  28. flowyml/core/parallel.py +452 -0
  29. flowyml/core/pipeline.py +764 -0
  30. flowyml/core/project.py +253 -0
  31. flowyml/core/resources.py +424 -0
  32. flowyml/core/scheduler.py +630 -0
  33. flowyml/core/scheduler_config.py +32 -0
  34. flowyml/core/step.py +201 -0
  35. flowyml/core/step_grouping.py +292 -0
  36. flowyml/core/templates.py +226 -0
  37. flowyml/core/versioning.py +217 -0
  38. flowyml/integrations/__init__.py +1 -0
  39. flowyml/integrations/keras.py +134 -0
  40. flowyml/monitoring/__init__.py +1 -0
  41. flowyml/monitoring/alerts.py +57 -0
  42. flowyml/monitoring/data.py +102 -0
  43. flowyml/monitoring/llm.py +160 -0
  44. flowyml/monitoring/monitor.py +57 -0
  45. flowyml/monitoring/notifications.py +246 -0
  46. flowyml/registry/__init__.py +5 -0
  47. flowyml/registry/model_registry.py +491 -0
  48. flowyml/registry/pipeline_registry.py +55 -0
  49. flowyml/stacks/__init__.py +27 -0
  50. flowyml/stacks/base.py +77 -0
  51. flowyml/stacks/bridge.py +288 -0
  52. flowyml/stacks/components.py +155 -0
  53. flowyml/stacks/gcp.py +499 -0
  54. flowyml/stacks/local.py +112 -0
  55. flowyml/stacks/migration.py +97 -0
  56. flowyml/stacks/plugin_config.py +78 -0
  57. flowyml/stacks/plugins.py +401 -0
  58. flowyml/stacks/registry.py +226 -0
  59. flowyml/storage/__init__.py +26 -0
  60. flowyml/storage/artifacts.py +246 -0
  61. flowyml/storage/materializers/__init__.py +20 -0
  62. flowyml/storage/materializers/base.py +133 -0
  63. flowyml/storage/materializers/keras.py +185 -0
  64. flowyml/storage/materializers/numpy.py +94 -0
  65. flowyml/storage/materializers/pandas.py +142 -0
  66. flowyml/storage/materializers/pytorch.py +135 -0
  67. flowyml/storage/materializers/sklearn.py +110 -0
  68. flowyml/storage/materializers/tensorflow.py +152 -0
  69. flowyml/storage/metadata.py +931 -0
  70. flowyml/tracking/__init__.py +1 -0
  71. flowyml/tracking/experiment.py +211 -0
  72. flowyml/tracking/leaderboard.py +191 -0
  73. flowyml/tracking/runs.py +145 -0
  74. flowyml/ui/__init__.py +15 -0
  75. flowyml/ui/backend/Dockerfile +31 -0
  76. flowyml/ui/backend/__init__.py +0 -0
  77. flowyml/ui/backend/auth.py +163 -0
  78. flowyml/ui/backend/main.py +187 -0
  79. flowyml/ui/backend/routers/__init__.py +0 -0
  80. flowyml/ui/backend/routers/assets.py +45 -0
  81. flowyml/ui/backend/routers/execution.py +179 -0
  82. flowyml/ui/backend/routers/experiments.py +49 -0
  83. flowyml/ui/backend/routers/leaderboard.py +118 -0
  84. flowyml/ui/backend/routers/notifications.py +72 -0
  85. flowyml/ui/backend/routers/pipelines.py +110 -0
  86. flowyml/ui/backend/routers/plugins.py +192 -0
  87. flowyml/ui/backend/routers/projects.py +85 -0
  88. flowyml/ui/backend/routers/runs.py +66 -0
  89. flowyml/ui/backend/routers/schedules.py +222 -0
  90. flowyml/ui/backend/routers/traces.py +84 -0
  91. flowyml/ui/frontend/Dockerfile +20 -0
  92. flowyml/ui/frontend/README.md +315 -0
  93. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
  94. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
  95. flowyml/ui/frontend/dist/index.html +16 -0
  96. flowyml/ui/frontend/index.html +15 -0
  97. flowyml/ui/frontend/nginx.conf +26 -0
  98. flowyml/ui/frontend/package-lock.json +3545 -0
  99. flowyml/ui/frontend/package.json +33 -0
  100. flowyml/ui/frontend/postcss.config.js +6 -0
  101. flowyml/ui/frontend/src/App.jsx +21 -0
  102. flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
  103. flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
  104. flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
  105. flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
  106. flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
  107. flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
  108. flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
  109. flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
  110. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
  111. flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
  112. flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
  113. flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
  114. flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
  115. flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
  116. flowyml/ui/frontend/src/components/Layout.jsx +108 -0
  117. flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
  118. flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
  119. flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
  120. flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
  121. flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
  122. flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
  123. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
  124. flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
  125. flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
  126. flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
  127. flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
  128. flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
  129. flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
  130. flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
  131. flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
  132. flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
  133. flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
  134. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
  135. flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
  136. flowyml/ui/frontend/src/index.css +11 -0
  137. flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
  138. flowyml/ui/frontend/src/main.jsx +10 -0
  139. flowyml/ui/frontend/src/router/index.jsx +39 -0
  140. flowyml/ui/frontend/src/services/pluginService.js +90 -0
  141. flowyml/ui/frontend/src/utils/api.js +47 -0
  142. flowyml/ui/frontend/src/utils/cn.js +6 -0
  143. flowyml/ui/frontend/tailwind.config.js +31 -0
  144. flowyml/ui/frontend/vite.config.js +21 -0
  145. flowyml/ui/utils.py +77 -0
  146. flowyml/utils/__init__.py +67 -0
  147. flowyml/utils/config.py +308 -0
  148. flowyml/utils/debug.py +240 -0
  149. flowyml/utils/environment.py +346 -0
  150. flowyml/utils/git.py +319 -0
  151. flowyml/utils/logging.py +61 -0
  152. flowyml/utils/performance.py +314 -0
  153. flowyml/utils/stack_config.py +296 -0
  154. flowyml/utils/validation.py +270 -0
  155. flowyml-1.1.0.dist-info/METADATA +372 -0
  156. flowyml-1.1.0.dist-info/RECORD +159 -0
  157. flowyml-1.1.0.dist-info/WHEEL +4 -0
  158. flowyml-1.1.0.dist-info/entry_points.txt +3 -0
  159. flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,142 @@
1
+ """Pandas materializer for DataFrame serialization."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from flowyml.storage.materializers.base import BaseMaterializer, register_materializer
8
+
9
+ try:
10
+ import pandas as pd
11
+
12
+ PANDAS_AVAILABLE = True
13
+ except ImportError:
14
+ PANDAS_AVAILABLE = False
15
+
16
+
17
+ if PANDAS_AVAILABLE:
18
+
19
+ class PandasMaterializer(BaseMaterializer):
20
+ """Materializer for Pandas DataFrames and Series."""
21
+
22
+ def save(self, obj: Any, path: Path) -> None:
23
+ """Save Pandas object to path.
24
+
25
+ Args:
26
+ obj: Pandas DataFrame or Series
27
+ path: Directory path where object should be saved
28
+ """
29
+ path.mkdir(parents=True, exist_ok=True)
30
+
31
+ if isinstance(obj, pd.DataFrame):
32
+ # Save DataFrame as parquet (efficient) or CSV (fallback)
33
+ try:
34
+ data_path = path / "data.parquet"
35
+ obj.to_parquet(data_path, index=True)
36
+ format_used = "parquet"
37
+ except Exception:
38
+ data_path = path / "data.csv"
39
+ obj.to_csv(data_path, index=True)
40
+ format_used = "csv"
41
+
42
+ # Save metadata
43
+ metadata = {
44
+ "type": "pandas_dataframe",
45
+ "format": format_used,
46
+ "shape": list(obj.shape),
47
+ "columns": obj.columns.tolist(),
48
+ "dtypes": {col: str(dtype) for col, dtype in obj.dtypes.items()},
49
+ "index_name": obj.index.name,
50
+ }
51
+
52
+ with open(path / "metadata.json", "w") as f:
53
+ json.dump(metadata, f, indent=2)
54
+
55
+ elif isinstance(obj, pd.Series):
56
+ # Save Series
57
+ try:
58
+ data_path = path / "data.parquet"
59
+ obj.to_frame().to_parquet(data_path, index=True)
60
+ format_used = "parquet"
61
+ except Exception:
62
+ data_path = path / "data.csv"
63
+ obj.to_csv(data_path, index=True, header=True)
64
+ format_used = "csv"
65
+
66
+ metadata = {
67
+ "type": "pandas_series",
68
+ "format": format_used,
69
+ "shape": len(obj),
70
+ "name": obj.name,
71
+ "dtype": str(obj.dtype),
72
+ "index_name": obj.index.name,
73
+ }
74
+
75
+ with open(path / "metadata.json", "w") as f:
76
+ json.dump(metadata, f, indent=2)
77
+
78
+ def load(self, path: Path) -> Any:
79
+ """Load Pandas object from path.
80
+
81
+ Args:
82
+ path: Directory path from which to load object
83
+
84
+ Returns:
85
+ Loaded Pandas DataFrame or Series
86
+ """
87
+ # Load metadata
88
+ metadata_path = path / "metadata.json"
89
+ if metadata_path.exists():
90
+ with open(metadata_path) as f:
91
+ metadata = json.load(f)
92
+ else:
93
+ metadata = {}
94
+
95
+ obj_type = metadata.get("type", "pandas_dataframe")
96
+ format_used = metadata.get("format", "parquet")
97
+
98
+ if obj_type == "pandas_dataframe":
99
+ if format_used == "parquet":
100
+ data_path = path / "data.parquet"
101
+ return pd.read_parquet(data_path)
102
+ else:
103
+ data_path = path / "data.csv"
104
+ return pd.read_csv(data_path, index_col=0)
105
+
106
+ elif obj_type == "pandas_series":
107
+ if format_used == "parquet":
108
+ data_path = path / "data.parquet"
109
+ df = pd.read_parquet(data_path)
110
+ return df.iloc[:, 0]
111
+ else:
112
+ data_path = path / "data.csv"
113
+ series = pd.read_csv(data_path, index_col=0)
114
+ if isinstance(series, pd.DataFrame):
115
+ series = series.iloc[:, 0]
116
+ return series
117
+
118
+ else:
119
+ raise ValueError(f"Unknown Pandas object type: {obj_type}")
120
+
121
+ @classmethod
122
+ def supported_types(cls) -> list[type]:
123
+ """Return Pandas types supported by this materializer."""
124
+ return [pd.DataFrame, pd.Series]
125
+
126
+ # Auto-register
127
+ register_materializer(PandasMaterializer)
128
+
129
+ else:
130
+ # Placeholder when Pandas not available
131
+ class PandasMaterializer(BaseMaterializer):
132
+ """Placeholder materializer when Pandas is not installed."""
133
+
134
+ def save(self, obj: Any, path: Path) -> None:
135
+ raise ImportError("Pandas is not installed. Install with: pip install pandas")
136
+
137
+ def load(self, path: Path) -> Any:
138
+ raise ImportError("Pandas is not installed. Install with: pip install pandas")
139
+
140
+ @classmethod
141
+ def supported_types(cls) -> list[type]:
142
+ return []
@@ -0,0 +1,135 @@
1
+ """PyTorch materializer for model serialization."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from flowyml.storage.materializers.base import BaseMaterializer, register_materializer
8
+ import contextlib
9
+ import builtins
10
+
11
+ try:
12
+ import torch
13
+ import torch.nn as nn
14
+
15
+ # Verify PyTorch has expected attributes
16
+ _ = nn.Module
17
+ _ = torch.Tensor
18
+ PYTORCH_AVAILABLE = True
19
+ except (ImportError, AttributeError):
20
+ PYTORCH_AVAILABLE = False
21
+ torch = None
22
+ nn = None
23
+
24
+
25
+ if PYTORCH_AVAILABLE:
26
+
27
+ class PyTorchMaterializer(BaseMaterializer):
28
+ """Materializer for PyTorch models and tensors."""
29
+
30
+ def save(self, obj: Any, path: Path) -> None:
31
+ """Save PyTorch object to path.
32
+
33
+ Args:
34
+ obj: PyTorch model or tensor
35
+ path: Directory path where object should be saved
36
+ """
37
+ path.mkdir(parents=True, exist_ok=True)
38
+
39
+ if isinstance(obj, nn.Module):
40
+ # Save model
41
+ model_path = path / "model.pt"
42
+ torch.save(obj.state_dict(), model_path)
43
+
44
+ # Save model architecture info
45
+ metadata = {
46
+ "type": "pytorch_model",
47
+ "class_name": obj.__class__.__name__,
48
+ "module": obj.__class__.__module__,
49
+ }
50
+
51
+ # Try to capture model architecture
52
+ with contextlib.suppress(builtins.BaseException):
53
+ metadata["architecture"] = str(obj)
54
+
55
+ with open(path / "metadata.json", "w") as f:
56
+ json.dump(metadata, f, indent=2)
57
+
58
+ elif isinstance(obj, torch.Tensor):
59
+ # Save tensor
60
+ tensor_path = path / "tensor.pt"
61
+ torch.save(obj, tensor_path)
62
+
63
+ metadata = {
64
+ "type": "pytorch_tensor",
65
+ "shape": list(obj.shape),
66
+ "dtype": str(obj.dtype),
67
+ "device": str(obj.device),
68
+ }
69
+
70
+ with open(path / "metadata.json", "w") as f:
71
+ json.dump(metadata, f, indent=2)
72
+
73
+ else:
74
+ # Generic PyTorch object
75
+ obj_path = path / "object.pt"
76
+ torch.save(obj, obj_path)
77
+
78
+ metadata = {"type": "pytorch_object"}
79
+ with open(path / "metadata.json", "w") as f:
80
+ json.dump(metadata, f, indent=2)
81
+
82
+ def load(self, path: Path) -> Any:
83
+ """Load PyTorch object from path.
84
+
85
+ Args:
86
+ path: Directory path from which to load object
87
+
88
+ Returns:
89
+ Loaded PyTorch object
90
+ """
91
+ # Load metadata
92
+ metadata_path = path / "metadata.json"
93
+ if metadata_path.exists():
94
+ with open(metadata_path) as f:
95
+ metadata = json.load(f)
96
+ else:
97
+ metadata = {}
98
+
99
+ obj_type = metadata.get("type", "pytorch_object")
100
+
101
+ if obj_type == "pytorch_model":
102
+ # Load model state dict only (user needs to provide architecture)
103
+ model_path = path / "model.pt"
104
+ return torch.load(model_path, weights_only=True)
105
+
106
+ elif obj_type == "pytorch_tensor":
107
+ tensor_path = path / "tensor.pt"
108
+ return torch.load(tensor_path, weights_only=True)
109
+
110
+ else:
111
+ obj_path = path / "object.pt"
112
+ return torch.load(obj_path, weights_only=False)
113
+
114
+ @classmethod
115
+ def supported_types(cls) -> list[type]:
116
+ """Return PyTorch types supported by this materializer."""
117
+ return [nn.Module, torch.Tensor]
118
+
119
+ # Auto-register
120
+ register_materializer(PyTorchMaterializer)
121
+
122
+ else:
123
+ # Placeholder when PyTorch not available
124
+ class PyTorchMaterializer(BaseMaterializer):
125
+ """Placeholder materializer when PyTorch is not installed."""
126
+
127
+ def save(self, obj: Any, path: Path) -> None:
128
+ raise ImportError("PyTorch is not installed. Install with: pip install torch")
129
+
130
+ def load(self, path: Path) -> Any:
131
+ raise ImportError("PyTorch is not installed. Install with: pip install torch")
132
+
133
+ @classmethod
134
+ def supported_types(cls) -> list[type]:
135
+ return []
@@ -0,0 +1,110 @@
1
+ """Scikit-learn materializer for model serialization."""
2
+
3
+ import json
4
+ import pickle
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from flowyml.storage.materializers.base import BaseMaterializer, register_materializer
9
+ import contextlib
10
+ import builtins
11
+
12
+ try:
13
+ from sklearn.base import BaseEstimator
14
+
15
+ SKLEARN_AVAILABLE = True
16
+ except ImportError:
17
+ SKLEARN_AVAILABLE = False
18
+
19
+
20
+ if SKLEARN_AVAILABLE:
21
+
22
+ class SklearnMaterializer(BaseMaterializer):
23
+ """Materializer for scikit-learn models."""
24
+
25
+ def save(self, obj: Any, path: Path) -> None:
26
+ """Save scikit-learn model to path.
27
+
28
+ Args:
29
+ obj: Scikit-learn model
30
+ path: Directory path where object should be saved
31
+ """
32
+ path.mkdir(parents=True, exist_ok=True)
33
+
34
+ # Save model using pickle
35
+ model_path = path / "model.pkl"
36
+ with open(model_path, "wb") as f:
37
+ pickle.dump(obj, f)
38
+
39
+ # Save metadata
40
+ metadata = {
41
+ "type": "sklearn_model",
42
+ "class_name": obj.__class__.__name__,
43
+ "module": obj.__class__.__module__,
44
+ }
45
+
46
+ # Capture model parameters
47
+ with contextlib.suppress(builtins.BaseException):
48
+ metadata["params"] = obj.get_params()
49
+
50
+ # Capture feature importance if available
51
+ if hasattr(obj, "feature_importances_"):
52
+ with contextlib.suppress(builtins.BaseException):
53
+ metadata["feature_importances"] = obj.feature_importances_.tolist()
54
+
55
+ # Capture number of features
56
+ if hasattr(obj, "n_features_in_"):
57
+ metadata["n_features"] = int(obj.n_features_in_)
58
+
59
+ # Capture feature names if available
60
+ if hasattr(obj, "feature_names_in_"):
61
+ with contextlib.suppress(builtins.BaseException):
62
+ metadata["feature_names"] = obj.feature_names_in_.tolist()
63
+
64
+ # Capture classes if classifier
65
+ if hasattr(obj, "classes_"):
66
+ with contextlib.suppress(builtins.BaseException):
67
+ metadata["classes"] = obj.classes_.tolist()
68
+
69
+ with open(path / "metadata.json", "w") as f:
70
+ json.dump(metadata, f, indent=2, default=str)
71
+
72
+ def load(self, path: Path) -> Any:
73
+ """Load scikit-learn model from path.
74
+
75
+ Args:
76
+ path: Directory path from which to load object
77
+
78
+ Returns:
79
+ Loaded scikit-learn model
80
+ """
81
+ model_path = path / "model.pkl"
82
+
83
+ if not model_path.exists():
84
+ raise FileNotFoundError(f"Model file not found at {model_path}")
85
+
86
+ with open(model_path, "rb") as f:
87
+ return pickle.load(f)
88
+
89
+ @classmethod
90
+ def supported_types(cls) -> list[type]:
91
+ """Return scikit-learn types supported by this materializer."""
92
+ return [BaseEstimator]
93
+
94
+ # Auto-register
95
+ register_materializer(SklearnMaterializer)
96
+
97
+ else:
98
+ # Placeholder when scikit-learn not available
99
+ class SklearnMaterializer(BaseMaterializer):
100
+ """Placeholder materializer when scikit-learn is not installed."""
101
+
102
+ def save(self, obj: Any, path: Path) -> None:
103
+ raise ImportError("Scikit-learn is not installed. Install with: pip install scikit-learn")
104
+
105
+ def load(self, path: Path) -> Any:
106
+ raise ImportError("Scikit-learn is not installed. Install with: pip install scikit-learn")
107
+
108
+ @classmethod
109
+ def supported_types(cls) -> list[type]:
110
+ return []
@@ -0,0 +1,152 @@
1
+ """TensorFlow materializer for model serialization."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from flowyml.storage.materializers.base import BaseMaterializer, register_materializer
8
+ import contextlib
9
+ import builtins
10
+
11
+ try:
12
+ import tensorflow as tf
13
+
14
+ # Verify TensorFlow has expected attributes
15
+ _ = tf.keras.Model
16
+ _ = tf.Tensor
17
+ _ = tf.Variable
18
+ TENSORFLOW_AVAILABLE = True
19
+ except (ImportError, AttributeError):
20
+ TENSORFLOW_AVAILABLE = False
21
+ tf = None
22
+
23
+
24
+ if TENSORFLOW_AVAILABLE:
25
+
26
+ class TensorFlowMaterializer(BaseMaterializer):
27
+ """Materializer for TensorFlow/Keras models."""
28
+
29
+ def save(self, obj: Any, path: Path) -> None:
30
+ """Save TensorFlow object to path.
31
+
32
+ Args:
33
+ obj: TensorFlow model or tensor
34
+ path: Directory path where object should be saved
35
+ """
36
+ path.mkdir(parents=True, exist_ok=True)
37
+
38
+ if isinstance(obj, tf.keras.Model):
39
+ # Save Keras model in SavedModel format
40
+ model_path = path / "saved_model"
41
+ obj.save(model_path, save_format="tf")
42
+
43
+ # Save metadata
44
+ metadata = {
45
+ "type": "tensorflow_keras_model",
46
+ "class_name": obj.__class__.__name__,
47
+ "input_shape": str(obj.input_shape) if hasattr(obj, "input_shape") else None,
48
+ "output_shape": str(obj.output_shape) if hasattr(obj, "output_shape") else None,
49
+ }
50
+
51
+ # Try to get model config
52
+ with contextlib.suppress(builtins.BaseException):
53
+ metadata["config"] = obj.get_config()
54
+
55
+ with open(path / "metadata.json", "w") as f:
56
+ json.dump(metadata, f, indent=2, default=str)
57
+
58
+ elif isinstance(obj, tf.Tensor):
59
+ # Save tensor as numpy array
60
+ import numpy as np
61
+
62
+ tensor_path = path / "tensor.npy"
63
+ np.save(tensor_path, obj.numpy())
64
+
65
+ metadata = {
66
+ "type": "tensorflow_tensor",
67
+ "shape": list(obj.shape),
68
+ "dtype": str(obj.dtype),
69
+ }
70
+
71
+ with open(path / "metadata.json", "w") as f:
72
+ json.dump(metadata, f, indent=2)
73
+
74
+ elif isinstance(obj, tf.Variable):
75
+ # Save variable
76
+ import numpy as np
77
+
78
+ var_path = path / "variable.npy"
79
+ np.save(var_path, obj.numpy())
80
+
81
+ metadata = {
82
+ "type": "tensorflow_variable",
83
+ "shape": list(obj.shape),
84
+ "dtype": str(obj.dtype),
85
+ "name": obj.name,
86
+ }
87
+
88
+ with open(path / "metadata.json", "w") as f:
89
+ json.dump(metadata, f, indent=2)
90
+
91
+ def load(self, path: Path) -> Any:
92
+ """Load TensorFlow object from path.
93
+
94
+ Args:
95
+ path: Directory path from which to load object
96
+
97
+ Returns:
98
+ Loaded TensorFlow object
99
+ """
100
+ # Load metadata
101
+ metadata_path = path / "metadata.json"
102
+ if metadata_path.exists():
103
+ with open(metadata_path) as f:
104
+ metadata = json.load(f)
105
+ else:
106
+ metadata = {}
107
+
108
+ obj_type = metadata.get("type", "tensorflow_object")
109
+
110
+ if obj_type == "tensorflow_keras_model":
111
+ model_path = path / "saved_model"
112
+ return tf.keras.models.load_model(model_path)
113
+
114
+ elif obj_type == "tensorflow_tensor":
115
+ import numpy as np
116
+
117
+ tensor_path = path / "tensor.npy"
118
+ array = np.load(tensor_path)
119
+ return tf.convert_to_tensor(array)
120
+
121
+ elif obj_type == "tensorflow_variable":
122
+ import numpy as np
123
+
124
+ var_path = path / "variable.npy"
125
+ array = np.load(var_path)
126
+ return tf.Variable(array, name=metadata.get("name"))
127
+
128
+ else:
129
+ raise ValueError(f"Unknown TensorFlow object type: {obj_type}")
130
+
131
+ @classmethod
132
+ def supported_types(cls) -> list[type]:
133
+ """Return TensorFlow types supported by this materializer."""
134
+ return [tf.keras.Model, tf.Tensor, tf.Variable]
135
+
136
+ # Auto-register
137
+ register_materializer(TensorFlowMaterializer)
138
+
139
+ else:
140
+ # Placeholder when TensorFlow not available
141
+ class TensorFlowMaterializer(BaseMaterializer):
142
+ """Placeholder materializer when TensorFlow is not installed."""
143
+
144
+ def save(self, obj: Any, path: Path) -> None:
145
+ raise ImportError("TensorFlow is not installed. Install with: pip install tensorflow")
146
+
147
+ def load(self, path: Path) -> Any:
148
+ raise ImportError("TensorFlow is not installed. Install with: pip install tensorflow")
149
+
150
+ @classmethod
151
+ def supported_types(cls) -> list[type]:
152
+ return []