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,264 @@
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
+ """Module for external-facing graph constructs. These help the user navigate/manage the graph as needed."""
19
+
20
+ import ast
21
+ import functools
22
+ import hashlib
23
+ import inspect
24
+ import logging
25
+ import typing
26
+ from dataclasses import dataclass
27
+
28
+ from hamilton import htypes, node
29
+ from hamilton.htypes import get_type_as_string
30
+
31
+ # This is a little ugly -- its just required for graph build, and works
32
+ # This indicates a larger smell though -- we need to have the right level of
33
+ # hierarchy to ensure we don't have to deal with this.
34
+ # The larger problem is that we have a few interfaces that are referred to by
35
+ # The core system (in defaults), and we have not managed to disentangle it yet.
36
+ if typing.TYPE_CHECKING:
37
+ from hamilton import graph
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ def _remove_docs_and_comments(source: str) -> str:
43
+ """Remove the docs and comments from a source code string.
44
+
45
+ The use of `ast.unparse()` requires Python 3.9
46
+
47
+ 1. Parsing then unparsing the AST of the source code will
48
+ create a code object and convert it back to a string. In the
49
+ process, comments are stripped.
50
+
51
+ 2. walk the AST to check if first element after `def` is a
52
+ docstring. If so, edit AST to skip the docstring
53
+
54
+ NOTE. The ast parsing will fail if `source` has syntax errors. For the
55
+ majority of cases this is caught upstream (e.g., by calling `import`).
56
+ The foreseeable edge case is if `source` is the result of `inspect.getsource`
57
+ on a nested function, method, or callable where `def` isn't at column 0.
58
+ Standard usage of Hamilton requires users to define functions/nodes at the top
59
+ level of a module, and therefore no issues should arise.
60
+ """
61
+ parsed = ast.parse(source)
62
+ for n in ast.walk(parsed):
63
+ if not isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)):
64
+ continue
65
+
66
+ if not len(n.body):
67
+ continue
68
+
69
+ # check if 1st node is a docstring
70
+ if not isinstance(n.body[0], ast.Expr):
71
+ continue
72
+
73
+ # In Python 3.8+, string literals (including docstrings) are ast.Constant nodes
74
+ # ast.Str is deprecated and will be removed in Python 3.14
75
+ if not hasattr(n.body[0], "value"):
76
+ continue
77
+
78
+ value = n.body[0].value
79
+ is_docstring = isinstance(value, ast.Constant) and isinstance(value.value, str)
80
+
81
+ if not is_docstring:
82
+ continue
83
+
84
+ # skip docstring
85
+ n.body = n.body[1:]
86
+
87
+ return ast.unparse(parsed)
88
+
89
+
90
+ def hash_source_code(source: str | typing.Callable, strip: bool = False) -> str:
91
+ """Hashes the source code of a function (str).
92
+
93
+ The `strip` parameter requires Python 3.9
94
+
95
+ If strip, try to remove docs and comments from source code string. Since
96
+ they don't impact function behavior, they shouldn't influence the hash.
97
+ """
98
+ if isinstance(source, typing.Callable):
99
+ source = inspect.getsource(source)
100
+
101
+ source = source.strip()
102
+
103
+ if strip:
104
+ try:
105
+ # could fail if source is indented code.
106
+ # see `remove_docs_and_comments` docstring for details.
107
+ source = _remove_docs_and_comments(source)
108
+ except Exception:
109
+ pass
110
+
111
+ return hashlib.sha256(source.encode()).hexdigest()
112
+
113
+
114
+ @dataclass
115
+ class HamiltonNode:
116
+ """External facing API for hamilton Nodes. Having this as a dataclass allows us
117
+ to hide the internals of the system but expose what the user might need.
118
+ Furthermore, we can always add attributes and maintain backwards compatibility."""
119
+
120
+ name: str
121
+ type: type
122
+ tags: dict[str, str | list[str]]
123
+ is_external_input: bool
124
+ originating_functions: tuple[typing.Callable, ...] | None
125
+ documentation: str | None
126
+ required_dependencies: set[str]
127
+ optional_dependencies: set[str]
128
+ optional_dependencies_default_values: dict[str, typing.Any]
129
+
130
+ def as_dict(self, include_optional_dependencies_default_values: bool = False) -> dict:
131
+ """Create a dictionary representation of the Node that is JSON serializable.
132
+
133
+ :param include_optional_dependencies_default_values: Include optional dependencies default values in the output.
134
+ Note: optional values could be anything and might not be JSON serializable.
135
+ """
136
+ dict_representation = {
137
+ "name": self.name,
138
+ "tags": self.tags,
139
+ "output_type": (get_type_as_string(self.type) or ""),
140
+ "required_dependencies": sorted(self.required_dependencies),
141
+ "optional_dependencies": sorted(self.optional_dependencies),
142
+ "source": (
143
+ inspect.getsource(self.originating_functions[0])
144
+ if self.originating_functions
145
+ else None
146
+ ),
147
+ "documentation": self.documentation,
148
+ "version": self.version,
149
+ }
150
+ if include_optional_dependencies_default_values:
151
+ dict_representation["optional_dependencies_default_values"] = (
152
+ self.optional_dependencies_default_values
153
+ )
154
+ return dict_representation
155
+
156
+ @staticmethod
157
+ def from_node(n: node.Node) -> "HamiltonNode":
158
+ """Creates a HamiltonNode from a Node (Hamilton's internal representation).
159
+
160
+ :param n: Node to create the Variable from.
161
+ :return: HamiltonNode created from the Node.
162
+ """
163
+ return HamiltonNode(
164
+ name=n.name,
165
+ type=n.type,
166
+ tags=n.tags,
167
+ is_external_input=n.user_defined,
168
+ originating_functions=n.originating_functions,
169
+ documentation=n.documentation,
170
+ required_dependencies={
171
+ dep
172
+ for dep, (type_, dep_type) in n.input_types.items()
173
+ if dep_type == node.DependencyType.REQUIRED
174
+ },
175
+ optional_dependencies={
176
+ dep
177
+ for dep, (type_, dep_type) in n.input_types.items()
178
+ if dep_type == node.DependencyType.OPTIONAL
179
+ },
180
+ optional_dependencies_default_values={
181
+ name: value for name, value in n.default_parameter_values.items()
182
+ },
183
+ )
184
+
185
+ @functools.cached_property
186
+ def version(self) -> str | None:
187
+ """Generate a hash of the node originating function source code.
188
+
189
+ Note that this will be `None` if the node is an external input/has no
190
+ originating functions.
191
+
192
+ The option `strip=True` means docstring and comments are ignored
193
+ when hashing the function.
194
+ """
195
+ if self.originating_functions is None or len(self.originating_functions) == 0:
196
+ if self.is_external_input:
197
+ # return the name of the config node. (we could add type but skipping for now)
198
+ return self.name
199
+ return None # this shouldn't happen often.
200
+ try:
201
+ # return hash of first function. It could be that others are Hamilton framework code.
202
+ return hash_source_code(self.originating_functions[0], strip=True)
203
+ except OSError: # TODO -- ensure we can get the node hash in a databricks environment when using jupyter magic
204
+ logger.warning(
205
+ f"Failed to hash source code for node {self.name}. Certain environments (such as databricks) do not allow it."
206
+ " In this case, version will be None."
207
+ )
208
+ return None
209
+
210
+ def __repr__(self):
211
+ return f'Node("{self.name}": {htypes.get_type_as_string(self.type)})'
212
+
213
+
214
+ @dataclass
215
+ class HamiltonGraph:
216
+ """External facing API for Hamilton Graphs. Currently a list of nodes that
217
+ allow you to trace forward/backwards in the graph. Will likely be adding some more capabilities:
218
+ 1. More metadata -- config + modules
219
+ 2. More utility functions -- make it easy to walk/do an action at each node
220
+ For now, you have to implement walking on your own if you care about order.
221
+
222
+ Note that you do not construct this class directly -- instead, you will get this at various points in the API.
223
+ """
224
+
225
+ nodes: list[HamiltonNode]
226
+ # store the original graph for internal use
227
+
228
+ @staticmethod
229
+ def from_graph(fn_graph: "graph.FunctionGraph") -> "HamiltonGraph":
230
+ """Creates a HamiltonGraph from a FunctionGraph (Hamilton's internal representation).
231
+
232
+ :param fn_graph: FunctionGraph to convert
233
+ :return: HamiltonGraph created from the FunctionGraph
234
+ """
235
+ return HamiltonGraph(
236
+ nodes=[HamiltonNode.from_node(n) for n in fn_graph.nodes.values()],
237
+ )
238
+
239
+ @functools.cached_property
240
+ def version(self) -> str:
241
+ """Generate a hash of the dataflow based on the collection of node hashes.
242
+
243
+ Node hashes are in a sorted list, then concatenated as a string before hashing.
244
+ To find differences between dataflows, you need to inspect the node level.
245
+ """
246
+ sorted_node_versions = sorted([n.version for n in self.nodes if n.version is not None])
247
+ return hashlib.sha256(str(sorted_node_versions).encode()).hexdigest()
248
+
249
+ @functools.cached_property
250
+ def __nodes_lookup(self) -> dict[str, HamiltonNode]:
251
+ """Cache the mapping {node_name: node} for faster `__getitem__`"""
252
+ return {n.name: n for n in self.nodes}
253
+
254
+ def __getitem__(self, key: str) -> HamiltonNode:
255
+ """Get an HamiltonNode by name
256
+
257
+ :param key: Hamilton node name
258
+ :return: Hamilton node
259
+ """
260
+ return self.__nodes_lookup[key]
261
+
262
+ def filter_nodes(self, filter: typing.Callable[[HamiltonNode], bool]) -> list[HamiltonNode]:
263
+ """Return Hamilton nodes matching the filter criteria"""
264
+ return [n for n in self.nodes if filter(n) is True]
@@ -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
+ import inspect
19
+ from collections.abc import Callable
20
+ from types import ModuleType
21
+
22
+
23
+ def is_submodule(child: ModuleType, parent: ModuleType):
24
+ return parent.__name__ in child.__name__
25
+
26
+
27
+ def find_functions(function_module: ModuleType) -> list[tuple[str, Callable]]:
28
+ """Function to determine the set of functions we want to build a graph from.
29
+
30
+ This iterates through the function module and grabs all function definitions.
31
+ :return: list of tuples of (func_name, function).
32
+ """
33
+
34
+ def valid_fn(fn):
35
+ return (
36
+ inspect.isfunction(fn)
37
+ and not fn.__name__.startswith("_")
38
+ and is_submodule(inspect.getmodule(fn), function_module)
39
+ )
40
+
41
+ return [f for f in inspect.getmembers(function_module, predicate=valid_fn)]