apache-hamilton 1.90.0.dev0__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 (151) hide show
  1. apache_hamilton-1.90.0.dev0.dist-info/METADATA +407 -0
  2. apache_hamilton-1.90.0.dev0.dist-info/RECORD +151 -0
  3. apache_hamilton-1.90.0.dev0.dist-info/WHEEL +4 -0
  4. apache_hamilton-1.90.0.dev0.dist-info/entry_points.txt +9 -0
  5. apache_hamilton-1.90.0.dev0.dist-info/licenses/DISCLAIMER +10 -0
  6. apache_hamilton-1.90.0.dev0.dist-info/licenses/LICENSE +228 -0
  7. apache_hamilton-1.90.0.dev0.dist-info/licenses/NOTICE +5 -0
  8. hamilton/__init__.py +24 -0
  9. hamilton/ad_hoc_utils.py +132 -0
  10. hamilton/async_driver.py +465 -0
  11. hamilton/base.py +466 -0
  12. hamilton/caching/__init__.py +16 -0
  13. hamilton/caching/adapter.py +1475 -0
  14. hamilton/caching/cache_key.py +70 -0
  15. hamilton/caching/fingerprinting.py +287 -0
  16. hamilton/caching/stores/__init__.py +16 -0
  17. hamilton/caching/stores/base.py +242 -0
  18. hamilton/caching/stores/file.py +140 -0
  19. hamilton/caching/stores/memory.py +297 -0
  20. hamilton/caching/stores/sqlite.py +282 -0
  21. hamilton/caching/stores/utils.py +40 -0
  22. hamilton/cli/__init__.py +16 -0
  23. hamilton/cli/__main__.py +328 -0
  24. hamilton/cli/commands.py +126 -0
  25. hamilton/cli/logic.py +338 -0
  26. hamilton/common/__init__.py +76 -0
  27. hamilton/contrib/__init__.py +41 -0
  28. hamilton/data_quality/__init__.py +16 -0
  29. hamilton/data_quality/base.py +198 -0
  30. hamilton/data_quality/default_validators.py +560 -0
  31. hamilton/data_quality/pandera_validators.py +121 -0
  32. hamilton/dataflows/__init__.py +726 -0
  33. hamilton/dataflows/template/README.md +25 -0
  34. hamilton/dataflows/template/__init__.py +54 -0
  35. hamilton/dataflows/template/author.md +28 -0
  36. hamilton/dataflows/template/requirements.txt +0 -0
  37. hamilton/dataflows/template/tags.json +7 -0
  38. hamilton/dataflows/template/valid_configs.jsonl +1 -0
  39. hamilton/dev_utils/__init__.py +16 -0
  40. hamilton/dev_utils/deprecation.py +204 -0
  41. hamilton/driver.py +2112 -0
  42. hamilton/execution/__init__.py +16 -0
  43. hamilton/execution/debugging_utils.py +56 -0
  44. hamilton/execution/executors.py +502 -0
  45. hamilton/execution/graph_functions.py +421 -0
  46. hamilton/execution/grouping.py +430 -0
  47. hamilton/execution/state.py +539 -0
  48. hamilton/experimental/__init__.py +27 -0
  49. hamilton/experimental/databackend.py +61 -0
  50. hamilton/experimental/decorators/__init__.py +16 -0
  51. hamilton/experimental/decorators/parameterize_frame.py +233 -0
  52. hamilton/experimental/h_async.py +29 -0
  53. hamilton/experimental/h_cache.py +413 -0
  54. hamilton/experimental/h_dask.py +28 -0
  55. hamilton/experimental/h_databackends.py +174 -0
  56. hamilton/experimental/h_ray.py +28 -0
  57. hamilton/experimental/h_spark.py +32 -0
  58. hamilton/function_modifiers/README +40 -0
  59. hamilton/function_modifiers/__init__.py +121 -0
  60. hamilton/function_modifiers/adapters.py +900 -0
  61. hamilton/function_modifiers/base.py +859 -0
  62. hamilton/function_modifiers/configuration.py +310 -0
  63. hamilton/function_modifiers/delayed.py +202 -0
  64. hamilton/function_modifiers/dependencies.py +246 -0
  65. hamilton/function_modifiers/expanders.py +1230 -0
  66. hamilton/function_modifiers/macros.py +1634 -0
  67. hamilton/function_modifiers/metadata.py +434 -0
  68. hamilton/function_modifiers/recursive.py +908 -0
  69. hamilton/function_modifiers/validation.py +289 -0
  70. hamilton/function_modifiers_base.py +31 -0
  71. hamilton/graph.py +1153 -0
  72. hamilton/graph_types.py +264 -0
  73. hamilton/graph_utils.py +41 -0
  74. hamilton/htypes.py +450 -0
  75. hamilton/io/__init__.py +32 -0
  76. hamilton/io/data_adapters.py +216 -0
  77. hamilton/io/default_data_loaders.py +224 -0
  78. hamilton/io/materialization.py +500 -0
  79. hamilton/io/utils.py +158 -0
  80. hamilton/lifecycle/__init__.py +67 -0
  81. hamilton/lifecycle/api.py +833 -0
  82. hamilton/lifecycle/base.py +1130 -0
  83. hamilton/lifecycle/default.py +802 -0
  84. hamilton/log_setup.py +47 -0
  85. hamilton/models.py +92 -0
  86. hamilton/node.py +449 -0
  87. hamilton/plugins/README.md +48 -0
  88. hamilton/plugins/__init__.py +16 -0
  89. hamilton/plugins/dask_extensions.py +47 -0
  90. hamilton/plugins/dlt_extensions.py +161 -0
  91. hamilton/plugins/geopandas_extensions.py +49 -0
  92. hamilton/plugins/h_dask.py +331 -0
  93. hamilton/plugins/h_ddog.py +522 -0
  94. hamilton/plugins/h_diskcache.py +163 -0
  95. hamilton/plugins/h_experiments/__init__.py +22 -0
  96. hamilton/plugins/h_experiments/__main__.py +62 -0
  97. hamilton/plugins/h_experiments/cache.py +39 -0
  98. hamilton/plugins/h_experiments/data_model.py +68 -0
  99. hamilton/plugins/h_experiments/hook.py +219 -0
  100. hamilton/plugins/h_experiments/server.py +375 -0
  101. hamilton/plugins/h_kedro.py +152 -0
  102. hamilton/plugins/h_logging.py +454 -0
  103. hamilton/plugins/h_mcp/__init__.py +28 -0
  104. hamilton/plugins/h_mcp/__main__.py +33 -0
  105. hamilton/plugins/h_mcp/_helpers.py +129 -0
  106. hamilton/plugins/h_mcp/_templates.py +417 -0
  107. hamilton/plugins/h_mcp/server.py +328 -0
  108. hamilton/plugins/h_mlflow.py +335 -0
  109. hamilton/plugins/h_narwhals.py +134 -0
  110. hamilton/plugins/h_openlineage.py +400 -0
  111. hamilton/plugins/h_opentelemetry.py +167 -0
  112. hamilton/plugins/h_pandas.py +257 -0
  113. hamilton/plugins/h_pandera.py +117 -0
  114. hamilton/plugins/h_polars.py +304 -0
  115. hamilton/plugins/h_polars_lazyframe.py +282 -0
  116. hamilton/plugins/h_pyarrow.py +56 -0
  117. hamilton/plugins/h_pydantic.py +127 -0
  118. hamilton/plugins/h_ray.py +242 -0
  119. hamilton/plugins/h_rich.py +142 -0
  120. hamilton/plugins/h_schema.py +493 -0
  121. hamilton/plugins/h_slack.py +100 -0
  122. hamilton/plugins/h_spark.py +1380 -0
  123. hamilton/plugins/h_threadpool.py +125 -0
  124. hamilton/plugins/h_tqdm.py +122 -0
  125. hamilton/plugins/h_vaex.py +129 -0
  126. hamilton/plugins/huggingface_extensions.py +236 -0
  127. hamilton/plugins/ibis_extensions.py +93 -0
  128. hamilton/plugins/jupyter_magic.py +622 -0
  129. hamilton/plugins/kedro_extensions.py +117 -0
  130. hamilton/plugins/lightgbm_extensions.py +99 -0
  131. hamilton/plugins/matplotlib_extensions.py +108 -0
  132. hamilton/plugins/mlflow_extensions.py +216 -0
  133. hamilton/plugins/numpy_extensions.py +105 -0
  134. hamilton/plugins/pandas_extensions.py +1763 -0
  135. hamilton/plugins/plotly_extensions.py +150 -0
  136. hamilton/plugins/polars_extensions.py +71 -0
  137. hamilton/plugins/polars_implementations.py +25 -0
  138. hamilton/plugins/polars_lazyframe_extensions.py +302 -0
  139. hamilton/plugins/polars_post_1_0_0_extensions.py +877 -0
  140. hamilton/plugins/polars_pre_1_0_0_extension.py +836 -0
  141. hamilton/plugins/pydantic_extensions.py +98 -0
  142. hamilton/plugins/pyspark_pandas_extensions.py +47 -0
  143. hamilton/plugins/sklearn_plot_extensions.py +129 -0
  144. hamilton/plugins/spark_extensions.py +105 -0
  145. hamilton/plugins/vaex_extensions.py +51 -0
  146. hamilton/plugins/xgboost_extensions.py +91 -0
  147. hamilton/plugins/yaml_extensions.py +89 -0
  148. hamilton/registry.py +254 -0
  149. hamilton/settings.py +18 -0
  150. hamilton/telemetry.py +50 -0
  151. hamilton/version.py +18 -0
@@ -0,0 +1,126 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ from pathlib import Path
19
+
20
+ from hamilton import ad_hoc_utils, driver
21
+ from hamilton.cli import logic
22
+
23
+
24
+ def build(modules: list[Path], context_path: Path | None = None):
25
+ """Build a Hamilton driver from the passed modules, and
26
+ load the Driver config from the context file.
27
+
28
+ Dynamic execution is enabled by default to support dataflow
29
+ using Parallelizable/Collect. This only matters if we are to
30
+ execute code.
31
+ """
32
+ context = logic.load_context(context_path) if context_path else {}
33
+ module_objects = [ad_hoc_utils.module_from_source(p.read_text()) for p in modules]
34
+ return (
35
+ driver.Builder()
36
+ .enable_dynamic_execution(allow_experimental_mode=True)
37
+ .with_modules(*module_objects)
38
+ .with_config(context.get("HAMILTON_CONFIG", {}))
39
+ .build()
40
+ )
41
+
42
+
43
+ def diff(
44
+ current_dr: driver.Driver,
45
+ modules: list[Path],
46
+ git_reference: str | None = "HEAD",
47
+ view: bool = False,
48
+ output_file_path: Path = Path("./diff.png"),
49
+ context_path: Path | None = None,
50
+ ) -> dict:
51
+ """Get the diff of"""
52
+ context = logic.load_context(context_path) if context_path else {}
53
+
54
+ current_version = logic.hash_hamilton_nodes(current_dr)
55
+ current_node_to_func = logic.map_nodes_to_functions(current_dr)
56
+
57
+ reference_modules = logic.load_modules_from_git(modules, git_reference)
58
+ reference_dr = (
59
+ driver.Builder()
60
+ .enable_dynamic_execution(allow_experimental_mode=True)
61
+ .with_modules(*reference_modules)
62
+ .with_config(context.get("HAMILTON_CONFIG", {}))
63
+ .build()
64
+ )
65
+ reference_version = logic.hash_hamilton_nodes(reference_dr)
66
+ reference_node_to_func = logic.map_nodes_to_functions(reference_dr)
67
+
68
+ nodes_diff = logic.diff_versions(
69
+ reference_map=reference_version,
70
+ current_map=current_version,
71
+ )
72
+
73
+ full_diff = dict()
74
+ for status, node_names in nodes_diff.items():
75
+ full_diff[status] = dict()
76
+ for node_name in node_names:
77
+ if current_node_to_func.get(node_name):
78
+ func_name = current_node_to_func.get(node_name)
79
+ else:
80
+ func_name = reference_node_to_func.get(node_name)
81
+
82
+ full_diff[status][node_name] = func_name
83
+
84
+ if view:
85
+ dot = logic.visualize_diff(current_dr=current_dr, reference_dr=reference_dr, **nodes_diff)
86
+
87
+ # simplified logic from hamilton.graph.display()
88
+ output_format = "png"
89
+ if output_file_path: # infer format from path
90
+ if output_file_path.suffix != "":
91
+ output_format = output_file_path.suffix.partition(".")[-1]
92
+
93
+ dot.render(output_file_path.with_suffix(""), format=output_format)
94
+
95
+ return full_diff
96
+
97
+
98
+ def validate(dr: driver.Driver, context_path: Path) -> dict:
99
+ """Use driver.validate_execution() with values from the context file"""
100
+ context = logic.load_context(context_path)
101
+
102
+ try:
103
+ dr.validate_execution(
104
+ final_vars=context["HAMILTON_FINAL_VARS"],
105
+ inputs=context["HAMILTON_INPUTS"],
106
+ overrides=context["HAMILTON_OVERRIDES"],
107
+ )
108
+ except ValueError as e:
109
+ raise e
110
+
111
+ return context
112
+
113
+
114
+ def version(dr: driver.Driver) -> dict:
115
+ """Get the node and dataflow versions from the instantiated Driver"""
116
+ nodes_hash = logic.hash_hamilton_nodes(dr)
117
+ dataflow_hash = logic.hash_dataflow(nodes_hash)
118
+ return dict(
119
+ nodes_hash=nodes_hash,
120
+ dataflow_hash=dataflow_hash,
121
+ )
122
+
123
+
124
+ def view(dr: driver.Driver, output_file_path: Path = Path("dag.png")) -> None:
125
+ """Display all functions of the instantiated Driver"""
126
+ dr.display_all_functions(output_file_path)
hamilton/cli/logic.py ADDED
@@ -0,0 +1,338 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ from pathlib import Path
19
+ from types import ModuleType
20
+
21
+ from hamilton import driver
22
+
23
+ CONFIG_HEADER = "HAMILTON_CONFIG"
24
+ FINAL_VARS_HEADER = "HAMILTON_FINAL_VARS"
25
+ INPUTS_HEADER = "HAMILTON_INPUTS"
26
+ OVERRIDES_HEADER = "HAMILTON_OVERRIDES"
27
+
28
+
29
+ def get_git_base_directory() -> str:
30
+ """Get the base path of the current git directory"""
31
+ import subprocess
32
+
33
+ try:
34
+ result = subprocess.run(
35
+ ["git", "rev-parse", "--show-toplevel"],
36
+ stdout=subprocess.PIPE,
37
+ stderr=subprocess.PIPE,
38
+ text=True,
39
+ )
40
+
41
+ if result.returncode == 0:
42
+ return result.stdout.strip()
43
+ else:
44
+ print("Error:", result.stderr.strip())
45
+ raise OSError(f"{result.stderr.strip()}")
46
+ except FileNotFoundError as e:
47
+ raise FileNotFoundError("Git command not found. Please make sure Git is installed.") from e
48
+
49
+
50
+ def get_git_reference(git_relative_path: str | Path, git_reference: str) -> str:
51
+ """Get the source code from the specified file and git reference"""
52
+ import subprocess
53
+
54
+ try:
55
+ result = subprocess.run(
56
+ ["git", "show", f"{git_reference}:{git_relative_path}"],
57
+ stdout=subprocess.PIPE,
58
+ stderr=subprocess.PIPE,
59
+ text=True,
60
+ )
61
+
62
+ if result.returncode == 0:
63
+ return result.stdout.strip()
64
+ elif result.returncode == 128:
65
+ # TODO catch the following error:
66
+ # fatal: Path '{git_relative_path}' exists on disk, but not in '{git_revision}'.
67
+ return
68
+ else:
69
+ return
70
+ except FileNotFoundError as e:
71
+ raise FileNotFoundError("Git command not found. Please make sure Git is installed.") from e
72
+
73
+
74
+ def version_hamilton_functions(module: ModuleType) -> dict[str, str]:
75
+ """Hash the source code of Hamilton functions from a module"""
76
+ from hamilton import graph_types, graph_utils
77
+
78
+ origins_version: dict[str, str] = dict()
79
+
80
+ for origin_name, _ in graph_utils.find_functions(module):
81
+ origin_callable = getattr(module, origin_name)
82
+ origins_version[origin_name] = graph_types.hash_source_code(origin_callable, strip=True)
83
+
84
+ return origins_version
85
+
86
+
87
+ def hash_hamilton_nodes(dr: driver.Driver) -> dict[str, str]:
88
+ """Hash the source code of Hamilton functions from nodes in a Driver"""
89
+ from hamilton import graph_types
90
+
91
+ graph = graph_types.HamiltonGraph.from_graph(dr.graph)
92
+ return {n.name: n.version for n in graph.nodes}
93
+
94
+
95
+ def map_nodes_to_functions(dr: driver.Driver) -> dict[str, str]:
96
+ """Get a mapping from node name to Hamilton function name"""
97
+ from hamilton import graph_types
98
+
99
+ graph = graph_types.HamiltonGraph.from_graph(dr.graph)
100
+
101
+ node_to_function = dict()
102
+ for n in graph.nodes:
103
+ # is None for config nodes
104
+ if n.originating_functions is None:
105
+ continue
106
+
107
+ node_callable = n.originating_functions[0]
108
+ node_to_function[n.name] = node_callable.__name__
109
+
110
+ return node_to_function
111
+
112
+
113
+ def hash_dataflow(nodes_version: dict[str, str]) -> str:
114
+ """Create a dataflow hash from the hashes of its nodes"""
115
+ import hashlib
116
+
117
+ sorted_nodes = sorted(nodes_version.values())
118
+ return hashlib.sha256(str(sorted_nodes).encode()).hexdigest()
119
+
120
+
121
+ def load_modules_from_git(
122
+ module_paths: list[Path], git_reference: str = "HEAD"
123
+ ) -> list[ModuleType]:
124
+ """Dynamically import modules for a git reference"""
125
+ from hamilton import ad_hoc_utils
126
+
127
+ git_base_dir = Path(get_git_base_directory())
128
+
129
+ modules = []
130
+ for module_path in module_paths:
131
+ relative_to_git = module_path.relative_to(git_base_dir)
132
+ previous_revision = get_git_reference(relative_to_git, git_reference)
133
+ module = ad_hoc_utils.module_from_source(previous_revision)
134
+ modules.append(module)
135
+
136
+ return modules
137
+
138
+
139
+ def diff_nodes_against_functions(
140
+ nodes_version: dict[str, str],
141
+ origins_version: dict[str, str],
142
+ node_to_origin: dict[str, str],
143
+ ) -> dict:
144
+ """Compare the nodes version from a built Driver to
145
+ the origins version from module source code when a second
146
+ Driver can't be instantiated
147
+
148
+ :nodes_version: mapping from node name to its origin function hash
149
+ :origins_version: mapping from origin function name to origin function hash
150
+ :node_to_origin: mapping from node name to origin name
151
+ """
152
+ node_added = []
153
+ code_diff = []
154
+ origins_removed = []
155
+
156
+ # iterate over nodes from built Driver
157
+ for node_name, node_version in nodes_version.items():
158
+ origin_name = node_to_origin[node_name]
159
+ origin_version = origins_version.get(origin_name)
160
+
161
+ # if origin_version is None, the node is new
162
+ if origin_version is None:
163
+ node_added.append(node_name)
164
+ continue
165
+
166
+ # if node_version != origin_version, the current node's origin
167
+ # function existed before, but was edited since origin_version
168
+ if node_version != origin_version:
169
+ code_diff.append(node_name)
170
+
171
+ nodes_origin_version = set(nodes_version.values())
172
+ # iterate over origin functions from source code
173
+ for origin_name, origin_version in origins_version.items():
174
+ # if origin_version not found in nodes_version, the
175
+ # origin function is not used by the built Driver
176
+ if origin_version not in nodes_origin_version:
177
+ origins_removed.append(origin_name)
178
+
179
+ return dict(
180
+ nodes_added=node_added,
181
+ code_diff=code_diff,
182
+ origins_removed=origins_removed,
183
+ )
184
+
185
+
186
+ def diff_versions(current_map: dict[str, str], reference_map: dict[str, str]) -> dict:
187
+ """Generic diff of two {name: hash} mappings (can be node or origin name)
188
+
189
+ :mapping_v1: mapping from node (or function) name to its function hash
190
+ :mapping_v2: mapping from node (or function) name to its function hash
191
+ """
192
+ current_only, reference_only, edit = [], [], []
193
+
194
+ for node_name, v1 in current_map.items():
195
+ v2 = reference_map.get(node_name)
196
+ if v2 is None:
197
+ current_only.append(node_name)
198
+ continue
199
+
200
+ if v1 != v2:
201
+ edit.append(node_name)
202
+
203
+ for node_name, _ in reference_map.items():
204
+ v1 = current_map.get(node_name)
205
+ if v1 is None:
206
+ reference_only.append(node_name)
207
+ continue
208
+
209
+ return dict(current_only=current_only, reference_only=reference_only, edit=edit)
210
+
211
+
212
+ def _custom_diff_style(
213
+ *,
214
+ node,
215
+ node_class,
216
+ current_only: list[str],
217
+ reference_only: list[str],
218
+ edit: list[str],
219
+ ):
220
+ """Custom visualization style for the diff of 2 dataflows"""
221
+ if node.name in current_only:
222
+ style = ({"fillcolor": "greenyellow"}, node_class, "Current only")
223
+
224
+ elif node.name in reference_only:
225
+ style = ({"fillcolor": "tomato1"}, node_class, "Reference only")
226
+
227
+ elif node.name in edit:
228
+ style = ({"fillcolor": "yellow2"}, node_class, "Edited")
229
+
230
+ else:
231
+ style = ({}, node_class, None)
232
+
233
+ return style
234
+
235
+
236
+ def visualize_diff(
237
+ current_dr: driver.Driver,
238
+ reference_dr: driver.Driver,
239
+ current_only: list[str],
240
+ reference_only: list[str],
241
+ edit: list[str],
242
+ ):
243
+ """Visualize the diff of 2 dataflows.
244
+
245
+ Uses the union of sets of nodes from driver 1 and driver 2.
246
+ """
247
+ import functools
248
+
249
+ from hamilton import graph
250
+
251
+ all_nodes = set(reference_dr.graph.get_nodes()).union(set(current_dr.graph.get_nodes()))
252
+
253
+ diff_style = functools.partial(
254
+ _custom_diff_style,
255
+ current_only=current_only,
256
+ reference_only=reference_only,
257
+ edit=edit,
258
+ )
259
+
260
+ return graph.create_graphviz_graph(
261
+ nodes=all_nodes,
262
+ comment="Diff viz",
263
+ graphviz_kwargs=dict(),
264
+ custom_style_function=diff_style,
265
+ node_modifiers=dict(),
266
+ strictly_display_only_nodes_passed_in=True,
267
+ )
268
+
269
+
270
+ # TODO refactor ContextLoader to a class
271
+ # TODO support loading from pyproject.toml
272
+ def load_context(file_path: Path) -> dict:
273
+ if not file_path.exists():
274
+ raise FileNotFoundError(f"`{file_path}` doesn't exist.")
275
+
276
+ extension = file_path.suffix
277
+ if extension == ".json":
278
+ context = _read_json_context(file_path)
279
+ elif extension == ".py":
280
+ context = _read_py_context(file_path)
281
+ else:
282
+ raise ValueError(f"Received extension `{extension}` is unsupported.")
283
+
284
+ context = _validate_context(context)
285
+ return context
286
+
287
+
288
+ def _validate_context(context: dict) -> dict:
289
+ if context[CONFIG_HEADER] is None:
290
+ context[CONFIG_HEADER] = {}
291
+
292
+ if context[FINAL_VARS_HEADER] is None:
293
+ context[FINAL_VARS_HEADER] = []
294
+
295
+ if context[INPUTS_HEADER] is None:
296
+ context[INPUTS_HEADER] = {}
297
+
298
+ if context[OVERRIDES_HEADER] is None:
299
+ context[OVERRIDES_HEADER] = {}
300
+
301
+ return context
302
+
303
+
304
+ def _read_json_context(file_path: Path) -> dict:
305
+ """"""
306
+ import json
307
+
308
+ data = json.load(file_path.open())
309
+
310
+ context = {}
311
+ for k in [
312
+ CONFIG_HEADER,
313
+ FINAL_VARS_HEADER,
314
+ INPUTS_HEADER,
315
+ OVERRIDES_HEADER,
316
+ ]:
317
+ context[k] = data.get(k, None)
318
+
319
+ return context
320
+
321
+
322
+ def _read_py_context(file_path: Path) -> dict:
323
+ import importlib
324
+
325
+ spec = importlib.util.spec_from_file_location("cli_config", file_path)
326
+ module = importlib.util.module_from_spec(spec)
327
+ spec.loader.exec_module(module)
328
+
329
+ context = {}
330
+ for k in [
331
+ CONFIG_HEADER,
332
+ FINAL_VARS_HEADER,
333
+ INPUTS_HEADER,
334
+ OVERRIDES_HEADER,
335
+ ]:
336
+ context[k] = getattr(module, k, None)
337
+
338
+ return context
@@ -0,0 +1,76 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # code in this module should no depend on much
19
+ from collections.abc import Callable
20
+ from typing import Any, List, Optional, Set, Tuple, Union
21
+
22
+
23
+ def convert_output_value(
24
+ output_value: str | Callable | Any, module_set: set[str]
25
+ ) -> tuple[str | None, str | None]:
26
+ """Converts output values that one can request into strings.
27
+
28
+ It checks that if it's a function, it's in the passed in module set.
29
+
30
+ :param output_value: the value we want to convert into a string. We don't annotate driver.Variable here for
31
+ import reasons.
32
+ :param module_set: the set of modules functions could come from.
33
+ :return: a tuple, (string value, string error). One or the other is returned, never both.
34
+ """
35
+ if isinstance(output_value, str):
36
+ return output_value, None
37
+ elif hasattr(output_value, "name"):
38
+ return output_value.name, None
39
+ elif isinstance(output_value, Callable):
40
+ if output_value.__module__ in module_set:
41
+ return output_value.__name__, None
42
+ else:
43
+ return None, (
44
+ f"Function {output_value.__module__}.{output_value.__name__} is a function not "
45
+ f"in a "
46
+ f"module given to the materializer. Valid choices are {module_set}."
47
+ )
48
+ else:
49
+ return None, (
50
+ f"Materializer dependency {output_value} is not a string, a function, or a driver.Variable."
51
+ )
52
+
53
+
54
+ def convert_output_values(
55
+ output_values: list[str | Callable | Any], module_set: set[str]
56
+ ) -> list[str]:
57
+ """Checks & converts outputs values to strings. This is used in building dependencies for the DAG.
58
+
59
+ :param output_values: the values to convert.
60
+ :param module_set: the modules any functions could come from.
61
+ :return: the final values
62
+ :raises ValueError: if there are values that can't be used/converted.
63
+ """
64
+ final_values = []
65
+ errors = []
66
+ for final_var in output_values:
67
+ _val, _error = convert_output_value(final_var, module_set)
68
+ if _val:
69
+ final_values.append(_val)
70
+ if _error:
71
+ errors.append(_error)
72
+ if errors:
73
+ errors.sort()
74
+ error_str = f"{len(errors)} errors encountered:\n " + "\n ".join(errors)
75
+ raise ValueError(error_str)
76
+ return final_values
@@ -0,0 +1,41 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ """This module exists so that people can download dataflows without the sf-hamilton-contrib package.
19
+
20
+ It will get clobbered when sf-hamilton-contrib is installed, which is good.
21
+ """
22
+
23
+ import logging
24
+ from contextlib import contextmanager
25
+
26
+ __version__ = "__unknown__" # this will be overwritten once sf-hamilton-contrib is installed.
27
+
28
+
29
+ @contextmanager
30
+ def catch_import_errors(module_name: str, file_location: str, logger: logging.Logger):
31
+ try:
32
+ # Yield control to the inner block which will have the import statements.
33
+ yield
34
+ except ImportError as e:
35
+ location = file_location[: file_location.rfind("/")]
36
+ logger.error("ImportError: %s", e)
37
+ logger.error(
38
+ "Please install the required packages. Options:\n"
39
+ f"(1): with `pip install -r {location}/requirements.txt`\n"
40
+ )
41
+ raise e
@@ -0,0 +1,16 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.