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,833 @@
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 abc
19
+ from abc import ABC
20
+ from collections.abc import Collection
21
+ from types import ModuleType
22
+ from typing import TYPE_CHECKING, Any, final
23
+
24
+ from hamilton import graph_types, node
25
+
26
+ # This is only here for a type-hint
27
+ # As python types aren't real (they're determined at runtime), we can't have circular import resolved
28
+ # These are often necessary to handle typing -- as types don't have a perfect DAG of dependencies
29
+ # In this case, we're breaking the following loop:
30
+ # -> lifecycle_api depends on graph_types and FunctionGraph
31
+ # -> graph_types depends on hamilton.base
32
+ # -> hamilton.base depends on lifecycle_api, as some interfaces for graph adapters live there
33
+ # To really fix this we should move everything user-facing out of base, which is a pretty sloppy name for a package anyway
34
+ # And put it where it belongs. For now we're OK with the TYPE_CHECKING hack
35
+ if TYPE_CHECKING:
36
+ from hamilton.execution.grouping import NodeGroupPurpose
37
+ from hamilton.graph import FunctionGraph
38
+ else:
39
+ NodeGroupPurpose = None
40
+
41
+ from hamilton.graph_types import HamiltonGraph, HamiltonNode
42
+ from hamilton.lifecycle.base import (
43
+ BaseDoBuildResult,
44
+ BaseDoCheckEdgeTypesMatch,
45
+ BaseDoNodeExecute,
46
+ BaseDoValidateInput,
47
+ BasePostGraphConstruct,
48
+ BasePostGraphExecute,
49
+ BasePostNodeExecute,
50
+ BasePostTaskExecute,
51
+ BasePostTaskExpand,
52
+ BasePostTaskGroup,
53
+ BasePostTaskReturn,
54
+ BasePreGraphExecute,
55
+ BasePreNodeExecute,
56
+ BasePreTaskExecute,
57
+ BasePreTaskSubmission,
58
+ BaseValidateGraph,
59
+ BaseValidateNode,
60
+ )
61
+
62
+ try:
63
+ from typing import override
64
+ except ImportError:
65
+ override = lambda x: x # noqa E731
66
+
67
+
68
+ class ResultBuilder(BaseDoBuildResult, abc.ABC):
69
+ """Abstract class for building results. All result builders should inherit from this class and implement the build_result function.
70
+ Note that applicable_input_type and output_type are optional, but recommended, for backwards
71
+ compatibility. They let us type-check this. They will default to Any, which means that they'll
72
+ connect to anything."""
73
+
74
+ @abc.abstractmethod
75
+ def build_result(self, **outputs: Any) -> Any:
76
+ """Given a set of outputs, build the result.
77
+
78
+ :param outputs: the outputs from the execution of the graph.
79
+ :return: the result of the execution of the graph.
80
+ """
81
+ pass
82
+
83
+ @override
84
+ @final
85
+ def do_build_result(self, outputs: dict[str, Any]) -> Any:
86
+ """Implements the do_build_result method from the BaseDoBuildResult class.
87
+ This is kept from the user as the public-facing API is build_result, allowing us to change the
88
+ API/implementation of the internal set of hooks"""
89
+ return self.build_result(**outputs)
90
+
91
+ def input_types(self) -> list[type[type]]:
92
+ """Gives the applicable types to this result builder.
93
+ This is optional for backwards compatibility, but is recommended.
94
+
95
+ :return: A list of types that this can apply to.
96
+ """
97
+ return [Any]
98
+
99
+ def output_type(self) -> type:
100
+ """Returns the output type of this result builder
101
+ :return: the type that this creates
102
+ """
103
+ return Any
104
+
105
+
106
+ class LegacyResultMixin(ResultBuilder, ABC):
107
+ """Backwards compatible legacy result builder. This utilizes a static method as we used to do that,
108
+ although often times they got confused. If you want a result builder, use ResultBuilder above instead.
109
+ """
110
+
111
+ @staticmethod
112
+ def build_result(**outputs: Any) -> Any:
113
+ """Given a set of outputs, build the result.
114
+
115
+ :param outputs: the outputs from the execution of the graph.
116
+ :return: the result of the execution of the graph.
117
+ """
118
+ pass
119
+
120
+
121
+ class GraphAdapter(
122
+ BaseDoNodeExecute,
123
+ LegacyResultMixin,
124
+ BaseDoValidateInput,
125
+ BaseDoCheckEdgeTypesMatch,
126
+ abc.ABC,
127
+ ):
128
+ """This is an implementation of HamiltonGraphAdapter, which has now been
129
+ implemented with lifecycle methods/hooks."""
130
+
131
+ @staticmethod
132
+ @abc.abstractmethod
133
+ def check_input_type(node_type: type, input_value: Any) -> bool:
134
+ """Used to check whether the user inputs match what the execution strategy & functions can handle.
135
+
136
+ Static purely for legacy reasons.
137
+
138
+ :param node_type: The type of the node.
139
+ :param input_value: An actual value that we want to inspect matches our expectation.
140
+ :return: True if the input is valid, False otherwise.
141
+ """
142
+ pass
143
+
144
+ @staticmethod
145
+ @abc.abstractmethod
146
+ def check_node_type_equivalence(node_type: type, input_type: type) -> bool:
147
+ """Used to check whether two types are equivalent.
148
+
149
+ Static, purely for legacy reasons.
150
+
151
+ This is used when the function graph is being created and we're statically type checking the annotations
152
+ for compatibility.
153
+
154
+ :param node_type: The type of the node.
155
+ :param input_type: The type of the input that would flow into the node.
156
+ :return: True if the types are equivalent, False otherwise.
157
+ """
158
+ pass
159
+
160
+ @override
161
+ @final
162
+ def do_node_execute(
163
+ self, run_id: str, node_: node.Node, kwargs: dict[str, Any], task_id: str | None = None
164
+ ) -> Any:
165
+ return self.execute_node(node_, kwargs)
166
+
167
+ @override
168
+ @final
169
+ def do_validate_input(self, node_type: type, input_value: Any) -> bool:
170
+ return self.check_input_type(node_type, input_value)
171
+
172
+ @override
173
+ @final
174
+ def do_check_edge_types_match(self, type_from: type, type_to: type) -> bool:
175
+ return self.check_node_type_equivalence(type_to, type_from)
176
+
177
+ @abc.abstractmethod
178
+ def execute_node(self, node: node.Node, kwargs: dict[str, Any]) -> Any:
179
+ """Given a node that represents a hamilton function, execute it.
180
+ Note, in some adapters this might just return some type of "future".
181
+
182
+ :param node: the Hamilton Node
183
+ :param kwargs: the kwargs required to exercise the node function.
184
+ :return: the result of exercising the node.
185
+ """
186
+ pass
187
+
188
+
189
+ class NodeExecutionHook(BasePreNodeExecute, BasePostNodeExecute, abc.ABC):
190
+ """Implement this to hook into the node execution lifecycle. You can call anything before and after the driver"""
191
+
192
+ @abc.abstractmethod
193
+ def run_before_node_execution(
194
+ self,
195
+ *,
196
+ node_name: str,
197
+ node_tags: dict[str, Any],
198
+ node_kwargs: dict[str, Any],
199
+ node_return_type: type,
200
+ task_id: str | None,
201
+ run_id: str,
202
+ node_input_types: dict[str, Any],
203
+ **future_kwargs: Any,
204
+ ):
205
+ """Hook that is executed prior to node execution.
206
+
207
+ :param node_name: Name of the node.
208
+ :param node_tags: Tags of the node
209
+ :param node_kwargs: Keyword arguments to pass to the node
210
+ :param node_return_type: Return type of the node
211
+ :param task_id: The ID of the task, none if not in a task-based environment
212
+ :param run_id: Run ID (unique in process scope) of the current run. Use this to track state.
213
+ :param node_input_types: the input types to the node and what it is expecting
214
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
215
+ """
216
+ pass
217
+
218
+ @override
219
+ @final
220
+ def pre_node_execute(
221
+ self,
222
+ *,
223
+ run_id: str,
224
+ node_: node.Node,
225
+ kwargs: dict[str, Any],
226
+ task_id: str | None = None,
227
+ ):
228
+ """Wraps the before_execution method, providing a bridge to an external-facing API. Do not override this!"""
229
+ self.run_before_node_execution(
230
+ node_name=node_.name,
231
+ node_tags=node_.tags,
232
+ node_kwargs=kwargs,
233
+ node_return_type=node_.type,
234
+ task_id=task_id,
235
+ run_id=run_id,
236
+ node_input_types={k: v[0] for k, v in node_.input_types.items()},
237
+ )
238
+
239
+ @abc.abstractmethod
240
+ def run_after_node_execution(
241
+ self,
242
+ *,
243
+ node_name: str,
244
+ node_tags: dict[str, Any],
245
+ node_kwargs: dict[str, Any],
246
+ node_return_type: type,
247
+ result: Any,
248
+ error: Exception | None,
249
+ success: bool,
250
+ task_id: str | None,
251
+ run_id: str,
252
+ **future_kwargs: Any,
253
+ ):
254
+ """Hook that is executed post node execution.
255
+
256
+ :param node_name: Name of the node in question
257
+ :param node_tags: Tags of the node
258
+ :param node_kwargs: Keyword arguments passed to the node
259
+ :param node_return_type: Return type of the node
260
+ :param result: Output of the node, None if an error occurred
261
+ :param error: Error that occurred, None if no error occurred
262
+ :param success: Whether the node executed successfully
263
+ :param task_id: The ID of the task, none if not in a task-based environment
264
+ :param run_id: Run ID (unique in process scope) of the current run. Use this to track state.
265
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
266
+ """
267
+ pass
268
+
269
+ @override
270
+ @final
271
+ def post_node_execute(
272
+ self,
273
+ *,
274
+ run_id: str,
275
+ node_: node.Node,
276
+ kwargs: dict[str, Any],
277
+ success: bool,
278
+ error: Exception | None,
279
+ result: Any | None,
280
+ task_id: str | None = None,
281
+ ):
282
+ """Wraps the after_execution method, providing a bridge to an external-facing API. Do not override this!"""
283
+ self.run_after_node_execution(
284
+ node_name=node_.name,
285
+ node_tags=node_.tags,
286
+ node_kwargs=kwargs,
287
+ node_return_type=node_.type,
288
+ result=result,
289
+ error=error,
290
+ task_id=task_id,
291
+ success=success,
292
+ run_id=run_id,
293
+ )
294
+
295
+
296
+ class GraphExecutionHook(BasePreGraphExecute, BasePostGraphExecute):
297
+ """Implement this to execute code before and after graph execution. This is useful for logging, etc..."""
298
+
299
+ @override
300
+ @final
301
+ def post_graph_execute(
302
+ self,
303
+ *,
304
+ run_id: str,
305
+ graph: "FunctionGraph",
306
+ success: bool,
307
+ error: Exception | None,
308
+ results: dict[str, Any] | None,
309
+ ):
310
+ """Just delegates to the interface method, passing in the right data."""
311
+ return self.run_after_graph_execution(
312
+ graph=HamiltonGraph.from_graph(graph),
313
+ success=success,
314
+ error=error,
315
+ results=results,
316
+ run_id=run_id,
317
+ )
318
+
319
+ @override
320
+ @final
321
+ def pre_graph_execute(
322
+ self,
323
+ *,
324
+ run_id: str,
325
+ graph: "FunctionGraph",
326
+ final_vars: list[str],
327
+ inputs: dict[str, Any],
328
+ overrides: dict[str, Any],
329
+ ):
330
+ """Implementation of the pre_graph_execute hook. This just converts the inputs to
331
+ the format the user-facing hook is expecting -- performing a walk of the DAG to pass in
332
+ the set of nodes to execute. Delegates to the interface method."""
333
+ all_nodes, user_defined_nodes = graph.get_upstream_nodes(final_vars, inputs, overrides)
334
+ nodes_to_execute = set(all_nodes) - set(user_defined_nodes)
335
+ return self.run_before_graph_execution(
336
+ graph=HamiltonGraph.from_graph(graph),
337
+ final_vars=final_vars,
338
+ inputs=inputs,
339
+ overrides=overrides,
340
+ execution_path=[item.name for item in nodes_to_execute],
341
+ run_id=run_id,
342
+ )
343
+
344
+ @abc.abstractmethod
345
+ def run_before_graph_execution(
346
+ self,
347
+ *,
348
+ graph: graph_types.HamiltonGraph,
349
+ final_vars: list[str],
350
+ inputs: dict[str, Any],
351
+ overrides: dict[str, Any],
352
+ execution_path: Collection[str],
353
+ run_id: str,
354
+ **future_kwargs: Any,
355
+ ):
356
+ """This is run prior to graph execution. This allows you to do anything you want before the graph executes,
357
+ knowing the basic information that was passed in.
358
+
359
+ :param graph: Graph that is being executed
360
+ :param final_vars: Output variables of the graph
361
+ :param inputs: Input variables passed to the graph
362
+ :param overrides: Overrides passed to the graph
363
+ :param execution_path: Collection of nodes that will be executed --
364
+ these are just the nodes (not input nodes) that will be run during the course of execution.
365
+ :param run_id: Run ID (unique in process scope) of the current run. Use this to track state.
366
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
367
+ """
368
+ pass
369
+
370
+ @abc.abstractmethod
371
+ def run_after_graph_execution(
372
+ self,
373
+ *,
374
+ graph: graph_types.HamiltonGraph,
375
+ success: bool,
376
+ error: Exception | None,
377
+ results: dict[str, Any] | None,
378
+ run_id: str,
379
+ **future_kwargs: Any,
380
+ ):
381
+ """This is run after graph execution. This allows you to do anything you want after the graph executes,
382
+ knowing the results of the execution/any errors.
383
+
384
+ :param graph: Graph that is being executed
385
+ :param results: Results of the graph execution
386
+ :param error: Error that occurred, None if no error occurred
387
+ :param success: Whether the graph executed successfully
388
+ :param run_id: Run ID (unique in process scope) of the current run. Use this to track state.
389
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
390
+ """
391
+ pass
392
+
393
+
394
+ class TaskSubmissionHook(BasePreTaskSubmission, abc.ABC):
395
+ """Implement this to hook into the task submission process. Tasks are submitted to an executor,
396
+ which then controls how and where the nodes associated with the task are run."""
397
+
398
+ @override
399
+ def pre_task_submission(
400
+ self,
401
+ *,
402
+ run_id: str,
403
+ task_id: str,
404
+ nodes: list["node.Node"],
405
+ inputs: dict[str, Any],
406
+ overrides: dict[str, Any],
407
+ spawning_task_id: str | None,
408
+ purpose: NodeGroupPurpose,
409
+ ):
410
+ self.run_before_task_submission(
411
+ run_id=run_id,
412
+ task_id=task_id,
413
+ nodes=nodes,
414
+ inputs=inputs,
415
+ overrides=overrides,
416
+ spawning_task_id=spawning_task_id,
417
+ purpose=purpose,
418
+ )
419
+
420
+ @abc.abstractmethod
421
+ def run_before_task_submission(
422
+ self,
423
+ *,
424
+ run_id: str,
425
+ task_id: str,
426
+ nodes: list["node.Node"],
427
+ inputs: dict[str, Any],
428
+ overrides: dict[str, Any],
429
+ spawning_task_id: str | None,
430
+ purpose: NodeGroupPurpose,
431
+ **future_kwargs,
432
+ ):
433
+ """Runs prior to a task being submitted to an executor. By definition this is run *outside*
434
+ of the task executor, on the process that executed the driver.
435
+
436
+ :param run_id: ID of the run this is under.
437
+ :param task_id: ID of the task we're launching.
438
+ :param nodes: Nodes that are part of this task
439
+ :param inputs: Inputs to the task
440
+ :param overrides: Overrides passed to the task
441
+ :param spawning_task_id: ID of the task that spawned this task
442
+ :param purpose: Purpose of the current task group
443
+ :param future_kwargs: Reserved for backwards compatibility.
444
+ """
445
+ pass
446
+
447
+
448
+ class TaskReturnHook(BasePostTaskReturn, abc.ABC):
449
+ """Implement this to hook into the task return process. Tasks are submitted to an executor,
450
+ which executes the task and returns the results (or raises an error)."""
451
+
452
+ @override
453
+ def post_task_return(
454
+ self,
455
+ *,
456
+ run_id: str,
457
+ task_id: str,
458
+ nodes: list["node.Node"],
459
+ result: Any,
460
+ success: bool,
461
+ error: Exception | None,
462
+ spawning_task_id: str | None,
463
+ purpose: NodeGroupPurpose,
464
+ ):
465
+ self.run_after_task_return(
466
+ run_id=run_id,
467
+ task_id=task_id,
468
+ nodes=nodes,
469
+ result=result,
470
+ success=success,
471
+ error=error,
472
+ spawning_task_id=spawning_task_id,
473
+ purpose=purpose,
474
+ )
475
+
476
+ @abc.abstractmethod
477
+ def run_after_task_return(
478
+ self,
479
+ *,
480
+ run_id: str,
481
+ task_id: str,
482
+ nodes: list["node.Node"],
483
+ result: Any,
484
+ success: bool,
485
+ error: Exception | None,
486
+ spawning_task_id: str | None,
487
+ purpose: NodeGroupPurpose,
488
+ **future_kwargs,
489
+ ):
490
+ """Runs after a task has been returned from a executor. By definition this is run *outside*
491
+ of the task executor, on the process that executed the driver.
492
+
493
+ :param run_id: ID of the run this is under.
494
+ :param task_id: ID of the task that was just executed.
495
+ :param nodes: Nodes that were part of this task
496
+ :param result: Result of the task
497
+ :param success: Whether the task was successful
498
+ :param error: The error the task threw, if any
499
+ :param spawning_task_id: ID of the task that spawned this task
500
+ :param purpose: Purpose of the current task group
501
+ :param future_kwargs: Reserved for backwards compatibility.
502
+ """
503
+ pass
504
+
505
+
506
+ class TaskExecutionHook(BasePreTaskExecute, BasePostTaskExecute, abc.ABC):
507
+ """Implement this to hook into the task execution process. Tasks consist of a group of one or
508
+ more nodes that are run on a task executor."""
509
+
510
+ def pre_task_execute(
511
+ self,
512
+ *,
513
+ run_id: str,
514
+ task_id: str,
515
+ nodes: list["node.Node"],
516
+ inputs: dict[str, Any],
517
+ overrides: dict[str, Any],
518
+ spawning_task_id: str | None,
519
+ purpose: NodeGroupPurpose,
520
+ ):
521
+ self.run_before_task_execution(
522
+ run_id=run_id,
523
+ task_id=task_id,
524
+ nodes=[HamiltonNode.from_node(n) for n in nodes],
525
+ inputs=inputs,
526
+ overrides=overrides,
527
+ spawning_task_id=spawning_task_id,
528
+ purpose=purpose,
529
+ )
530
+
531
+ def post_task_execute(
532
+ self,
533
+ *,
534
+ run_id: str,
535
+ task_id: str,
536
+ nodes: list["node.Node"],
537
+ results: dict[str, Any] | None,
538
+ success: bool,
539
+ error: Exception,
540
+ spawning_task_id: str | None,
541
+ purpose: NodeGroupPurpose,
542
+ ):
543
+ self.run_after_task_execution(
544
+ run_id=run_id,
545
+ task_id=task_id,
546
+ nodes=[HamiltonNode.from_node(n) for n in nodes],
547
+ results=results,
548
+ success=success,
549
+ error=error,
550
+ spawning_task_id=spawning_task_id,
551
+ purpose=purpose,
552
+ )
553
+
554
+ @abc.abstractmethod
555
+ def run_before_task_execution(
556
+ self,
557
+ *,
558
+ task_id: str,
559
+ run_id: str,
560
+ nodes: list[HamiltonNode],
561
+ inputs: dict[str, Any],
562
+ overrides: dict[str, Any],
563
+ spawning_task_id: str | None,
564
+ purpose: NodeGroupPurpose,
565
+ **future_kwargs,
566
+ ):
567
+ """Runs prior to any of the nodes associated with a task. By definition this is run *inside*
568
+ of the executor and therefore may be run on separate or distributed processes.
569
+
570
+ :param task_id: ID of the task we're launching.
571
+ :param run_id: ID of the run this is under.
572
+ :param nodes: Nodes that are part of this task
573
+ :param inputs: Inputs to the task
574
+ :param overrides: Overrides passed to the task
575
+ :param future_kwargs: Reserved for backwards compatibility.
576
+ :param spawning_task_id: ID of the task that spawned this task
577
+ :param purpose: Purpose of the current task group
578
+ """
579
+ pass
580
+
581
+ @abc.abstractmethod
582
+ def run_after_task_execution(
583
+ self,
584
+ *,
585
+ task_id: str,
586
+ run_id: str,
587
+ nodes: list[HamiltonNode],
588
+ results: dict[str, Any] | None,
589
+ success: bool,
590
+ error: Exception,
591
+ spawning_task_id: str | None,
592
+ purpose: NodeGroupPurpose,
593
+ **future_kwargs,
594
+ ):
595
+ """Runs after all of the nodes associated with a task have been executed. By definition this
596
+ is run *inside* of the executor and therefore may be run on separate or distributed processes.
597
+
598
+ :param task_id: ID of the task that was just executed
599
+ :param run_id: ID of the run this was under.
600
+ :param nodes: Nodes that were part of this task
601
+ :param results: Results of the task, per-node
602
+ :param success: Whether the task was successful
603
+ :param error: The error the task threw, if any
604
+ :param future_kwargs: Reserved for backwards compatibility.
605
+ :param spawning_task_id: ID of the task that spawned this task
606
+ :param purpose: Purpose of the current task group
607
+ """
608
+ pass
609
+
610
+
611
+ class EdgeConnectionHook(BaseDoCheckEdgeTypesMatch, BaseDoValidateInput, abc.ABC):
612
+ """Implement this to customize edges that are allowed in the graph. You can do customizations around typing here."""
613
+
614
+ @override
615
+ @final
616
+ def do_check_edge_types_match(self, *, type_from: type, type_to: type) -> bool:
617
+ """Wraps the check_edge_types_match method, providing a bridge to an external-facing API. Do not override this!"""
618
+ return self.check_edge_types_match(type_from, type_to)
619
+
620
+ @abc.abstractmethod
621
+ def check_edge_types_match(self, type_from: type, type_to: type, **kwargs: Any) -> bool:
622
+ """This is run to check if edge types match. Note that this is an OR functionality
623
+ -- this is run after we do some default checks, so this can only be permissive.
624
+ Reach out if you want to be more restrictive than the default checks.
625
+
626
+ :param type_from: The type of the node that is the source of the edge.
627
+ :param type_to: The type of the node that is the destination of the edge.
628
+ :param kwargs: This is kept for future backwards compatibility.
629
+ :return: Whether or not the two node types form a valid edge.
630
+ """
631
+ pass
632
+
633
+ @override
634
+ @final
635
+ def do_validate_input(self, *, node_type: type, input_value: Any) -> bool:
636
+ """Wraps the validate_input method, providing a bridge to an external-facing API. Do not override this!"""
637
+ return self.validate_input(node_type=node_type, input_value=input_value)
638
+
639
+ @abc.abstractmethod
640
+ def validate_input(self, node_type: type, input_value: Any, **kwargs: Any) -> bool:
641
+ """This is run to check if the input is valid for the node type. Note that this is an OR functionality
642
+ -- this is run after we do some default checks, so this can only be permissive.
643
+ Reach out if you want to be more restrictive than the default checks.
644
+
645
+ :param node_type: Type of the node that is accepting the input.
646
+ :param input_value: Value of the input
647
+ :param kwargs: Keyword arguments -- this is kept for future backwards compatibility.
648
+ :return: Whether the input is valid for the node type.
649
+ """
650
+ pass
651
+
652
+
653
+ class NodeExecutionMethod(BaseDoNodeExecute):
654
+ """API for executing a node. This takes in tags, callable, node name, and kwargs, and is
655
+ responsible for executing the node and returning the result. Note this is not (currently)
656
+ able to be layered together, although we may add that soon.
657
+ """
658
+
659
+ @override
660
+ @final
661
+ def do_node_execute(
662
+ self,
663
+ *,
664
+ run_id: str,
665
+ node_: node.Node,
666
+ kwargs: dict[str, Any],
667
+ task_id: str | None = None,
668
+ ) -> Any:
669
+ return self.run_to_execute_node(
670
+ node_name=node_.name,
671
+ node_tags=node_.tags,
672
+ node_callable=node_.callable,
673
+ node_kwargs=kwargs,
674
+ task_id=task_id,
675
+ is_expand=node_.node_role == node.NodeType.EXPAND,
676
+ is_collect=node_.node_role == node.NodeType.COLLECT,
677
+ )
678
+
679
+ @abc.abstractmethod
680
+ def run_to_execute_node(
681
+ self,
682
+ *,
683
+ node_name: str,
684
+ node_tags: dict[str, Any],
685
+ node_callable: Any,
686
+ node_kwargs: dict[str, Any],
687
+ task_id: str | None,
688
+ is_expand: bool,
689
+ is_collect: bool,
690
+ **future_kwargs: Any,
691
+ ) -> Any:
692
+ """This method is responsible for executing the node and returning the result.
693
+
694
+ :param node_name: Name of the node.
695
+ :param node_tags: Tags of the node.
696
+ :param node_callable: Callable of the node.
697
+ :param node_kwargs: Keyword arguments to pass to the node.
698
+ :param task_id: The ID of the task, none if not in a task-based environment
699
+ :param is_expand: Whether the node is parallelizable.
700
+ :param is_collect: Whether the node is a collect node.
701
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
702
+ :return: The result of the node execution -- up to you to return this.
703
+ """
704
+ pass
705
+
706
+
707
+ class StaticValidator(BaseValidateGraph, BaseValidateNode):
708
+ """Performs static validation of the DAG. Note that this has the option to perform default validation for each method --
709
+ this means that if you don't implement one of these it is OK.
710
+
711
+ .. code-block:: python
712
+
713
+ class MyTagValidator(api.StaticValidator):
714
+ '''Validates tags on a node'''
715
+
716
+ def run_to_validate_node(
717
+ self, *, node: HamiltonNode, **future_kwargs
718
+ ) -> tuple[bool, Optional[str]]:
719
+ if node.tags.get("node_type", "") == "output":
720
+ table_name = node.tags.get("table_name")
721
+ if not table_name: # None or empty
722
+ error_msg = (f"Node {node.tags['module']}.{node.name} "
723
+ "is an output node, but does not have a table_name tag.")
724
+ return False, error_msg
725
+ return True, None
726
+
727
+ """
728
+
729
+ def run_to_validate_node(
730
+ self, *, node: HamiltonNode, **future_kwargs
731
+ ) -> tuple[bool, str | None]:
732
+ """Override this to build custom node validations! Defaults to just returning that a node is valid so you don't have to implement it if you want to just implement a single method.
733
+ Runs post node construction to validate a node. You have access to a bunch of metadata about the node, stored in the hamilton_node argument
734
+
735
+ :param node: Node to validate
736
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
737
+ :return: A tuple of whether the node is valid and an error
738
+ message in the case of failure. Return [True, None] for a valid node.Otherwise, return a detailed error message -- this should have all context/debugging information, but does not need to
739
+ mention the node name (it will be aggregated with others).
740
+ """
741
+ return True, None
742
+
743
+ def run_to_validate_graph(
744
+ self, graph: HamiltonGraph, **future_kwargs
745
+ ) -> tuple[bool, str | None]:
746
+ """Override this to build custom DAG validations! Default to just returning that the graph is valid, so you don't have to implement it if you want to just implement a single method.
747
+ Runs post graph construction to validate a graph. You have access to a bunch of metadata about the graph, stored in the graph argument.
748
+
749
+ :param graph: Graph to validate.
750
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility
751
+ :return: A tuple of whether the graph is valid and an error message in the case of failure. Return [True, None] for a valid graph.
752
+ Otherwise, return a detailed error message -- this should have all context/debugging information.
753
+ """
754
+ return True, None
755
+
756
+ @override
757
+ @final
758
+ def validate_node(self, *, created_node: node.Node) -> tuple[bool, Exception | None]:
759
+ return self.run_to_validate_node(node=HamiltonNode.from_node(created_node))
760
+
761
+ @override
762
+ @final
763
+ def validate_graph(
764
+ self, *, graph: "FunctionGraph", modules: list[ModuleType], config: dict[str, Any]
765
+ ) -> tuple[bool, Exception | None]:
766
+ return self.run_to_validate_graph(graph=HamiltonGraph.from_graph(graph))
767
+
768
+
769
+ class TaskGroupingHook(BasePostTaskGroup, BasePostTaskExpand):
770
+ """Implement this to run something after task grouping or task expansion. This will allow you to
771
+ capture information about the tasks during `Parallelize`/`Collect` blocks in dynamic DAG execution."""
772
+
773
+ @override
774
+ @final
775
+ def post_task_group(self, *, run_id: str, task_ids: list[str]):
776
+ return self.run_after_task_grouping(run_id=run_id, task_ids=task_ids)
777
+
778
+ @override
779
+ @final
780
+ def post_task_expand(self, *, run_id: str, task_id: str, parameters: dict[str, Any]):
781
+ return self.run_after_task_expansion(run_id=run_id, task_id=task_id, parameters=parameters)
782
+
783
+ @abc.abstractmethod
784
+ def run_after_task_grouping(self, *, run_id: str, task_ids: list[str], **future_kwargs):
785
+ """Runs after task grouping. This allows you to capture information about which tasks were
786
+ created for a given run.
787
+
788
+ :param run_id: ID of the run, unique in scope of the driver.
789
+ :param task_ids: List of tasks that were grouped together.
790
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility.
791
+ """
792
+ pass
793
+
794
+ @abc.abstractmethod
795
+ def run_after_task_expansion(
796
+ self, *, run_id: str, task_id: str, parameters: dict[str, Any], **future_kwargs
797
+ ):
798
+ """Runs after task expansion in Parallelize/Collect blocks. This allows you to capture information
799
+ about the task that was expanded.
800
+
801
+ :param run_id: ID of the run, unique in scope of the driver.
802
+ :param task_id: ID of the task that was expanded.
803
+ :param parameters: Parameters that were passed to the task.
804
+ :param future_kwargs: Additional keyword arguments -- this is kept for backwards compatibility.
805
+ """
806
+ pass
807
+
808
+
809
+ class GraphConstructionHook(BasePostGraphConstruct, abc.ABC):
810
+ """Hook that is run after graph construction. This allows you to register/capture info on the graph.
811
+ Note that, in the case of materialization, this may be called multiple times (once when we create the graph,
812
+ once when we materialize). Currently information into that is not exposed to the user, but we will be adding that in future
813
+ iterations.
814
+ """
815
+
816
+ def post_graph_construct(
817
+ self, *, graph: "FunctionGraph", modules: list[ModuleType], config: dict[str, Any]
818
+ ):
819
+ self.run_after_graph_construction(graph=HamiltonGraph.from_graph(graph), config=config)
820
+
821
+ @abc.abstractmethod
822
+ def run_after_graph_construction(
823
+ self, *, graph: HamiltonGraph, config: dict[str, Any], **future_kwargs: Any
824
+ ):
825
+ """Hook that is run post graph construction. This allows you to register/capture info on the graph.
826
+ A common pattern is to store something in your object's state here so that you can use it later
827
+ (E.G. compute a hash on the graph)
828
+
829
+ :param graph: Graph that was constructed
830
+ :param config: Configuration used to construct the graph
831
+ :param future_kwargs: Reserved for backwards compatibility.
832
+ """
833
+ pass