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,465 @@
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 asyncio
19
+ import inspect
20
+ import logging
21
+ import typing
22
+ import uuid
23
+ from typing import Any
24
+
25
+ import hamilton.lifecycle.base as lifecycle_base
26
+ from hamilton import base, driver, graph, lifecycle, node
27
+ from hamilton.execution.graph_functions import create_error_message
28
+ from hamilton.io.materialization import ExtractorFactory, MaterializerFactory
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ async def await_dict_of_tasks(task_dict: dict[str, typing.Awaitable]) -> dict[str, Any]:
34
+ """Util to await a dictionary of tasks as asyncio.gather is kind of garbage"""
35
+ keys = sorted(task_dict.keys())
36
+ coroutines = [task_dict[key] for key in keys]
37
+ coroutines_gathered = await asyncio.gather(*coroutines)
38
+ return dict(zip(keys, coroutines_gathered, strict=False))
39
+
40
+
41
+ async def process_value(val: Any) -> Any:
42
+ """Helper function to process the value of a potential awaitable.
43
+ This is very simple -- all it does is await the value if its not already resolved.
44
+
45
+ :param val: Value to process.
46
+ :return: The value (awaited if it is a coroutine, raw otherwise).
47
+ """
48
+ if not inspect.isawaitable(val):
49
+ return val
50
+ return await val
51
+
52
+
53
+ class AsyncGraphAdapter(lifecycle_base.BaseDoNodeExecute, lifecycle.ResultBuilder):
54
+ """Graph adapter for use with the :class:`AsyncDriver` class."""
55
+
56
+ def __init__(
57
+ self,
58
+ result_builder: base.ResultMixin = None,
59
+ async_lifecycle_adapters: lifecycle_base.LifecycleAdapterSet | None = None,
60
+ ):
61
+ """Creates an AsyncGraphAdapter class. Note this will *only* work with the AsyncDriver class.
62
+
63
+ Some things to note:
64
+
65
+ 1. This executes everything at the end (recursively). E.G. the final DAG nodes are awaited
66
+ 2. This does *not* work with decorators when the async function is being decorated. That is\
67
+ because that function is called directly within the decorator, so we cannot await it.
68
+ """
69
+ super(AsyncGraphAdapter, self).__init__()
70
+ self.adapter = (
71
+ async_lifecycle_adapters
72
+ if async_lifecycle_adapters is not None
73
+ else lifecycle_base.LifecycleAdapterSet()
74
+ )
75
+ self.result_builder = result_builder or base.PandasDataFrameResult()
76
+ self.is_initialized = False
77
+
78
+ def do_node_execute(
79
+ self,
80
+ *,
81
+ run_id: str,
82
+ node_: node.Node,
83
+ kwargs: dict[str, typing.Any],
84
+ task_id: str | None = None,
85
+ ) -> typing.Any:
86
+ """Executes a node. Note this doesn't actually execute it -- rather, it returns a task.
87
+ This does *not* use async def, as we want it to be awaited on later -- this await is done
88
+ in processing parameters of downstream functions/final results. We can ensure that as
89
+ we also run the driver that this corresponds to.
90
+
91
+ Note that this assumes that everything is awaitable, even if it isn't.
92
+ In that case, it just wraps it in one.
93
+
94
+ :param task_id:
95
+ :param node_:
96
+ :param run_id:
97
+ :param node: Node to wrap
98
+ :param kwargs: Keyword arguments (either coroutines or raw values) to call it with
99
+ :return: A task
100
+ """
101
+ callabl = node_.callable
102
+
103
+ async def new_fn(fn=callabl, **fn_kwargs):
104
+ task_dict = {key: process_value(value) for key, value in fn_kwargs.items()}
105
+ fn_kwargs = await await_dict_of_tasks(task_dict)
106
+ error = None
107
+ result = None
108
+ success = True
109
+ pre_node_execute_errored = False
110
+ try:
111
+ if self.adapter.does_hook("pre_node_execute", is_async=True):
112
+ try:
113
+ await self.adapter.call_all_lifecycle_hooks_async(
114
+ "pre_node_execute",
115
+ run_id=run_id,
116
+ node_=node_,
117
+ kwargs=fn_kwargs,
118
+ task_id=task_id,
119
+ )
120
+ except Exception as e:
121
+ pre_node_execute_errored = True
122
+ raise e
123
+ # TODO -- consider how to use node execution methods in the future
124
+ # This is messy as it is a method called within a method...
125
+ # if self.adapter.does_method("do_node_execute", is_async=False):
126
+ # result = self.adapter.call_lifecycle_method_sync(
127
+ # "do_node_execute",
128
+ # run_id=run_id,
129
+ # node_=node_,
130
+ # kwargs=kwargs,
131
+ # task_id=task_id,
132
+ # )
133
+ # else:
134
+
135
+ result = (
136
+ await fn(**fn_kwargs) if asyncio.iscoroutinefunction(fn) else fn(**fn_kwargs)
137
+ )
138
+ except Exception as e:
139
+ success = False
140
+ error = e
141
+ step = "[pre-node-execute:async]" if pre_node_execute_errored else ""
142
+ message = create_error_message(kwargs, node_, step)
143
+ logger.exception(message)
144
+ raise
145
+ finally:
146
+ if not pre_node_execute_errored and self.adapter.does_hook(
147
+ "post_node_execute", is_async=True
148
+ ):
149
+ try:
150
+ await self.adapter.call_all_lifecycle_hooks_async(
151
+ "post_node_execute",
152
+ run_id=run_id,
153
+ node_=node_,
154
+ kwargs=fn_kwargs,
155
+ success=success,
156
+ error=error,
157
+ result=result,
158
+ task_id=task_id,
159
+ )
160
+ except Exception:
161
+ message = create_error_message(kwargs, node_, "[post-node-execute]")
162
+ logger.exception(message)
163
+ raise
164
+
165
+ return result
166
+
167
+ coroutine = new_fn(**kwargs)
168
+ task = asyncio.create_task(coroutine)
169
+ return task
170
+
171
+ def build_result(self, **outputs: Any) -> Any:
172
+ return self.result_builder.build_result(**outputs)
173
+
174
+
175
+ def separate_sync_from_async(
176
+ adapters: list[lifecycle.LifecycleAdapter],
177
+ ) -> tuple[list[lifecycle.LifecycleAdapter], list[lifecycle.LifecycleAdapter]]:
178
+ """Separates the sync and async adapters from a list of adapters.
179
+ Note this only works with hooks -- we'll be dealing with methods later.
180
+
181
+ :param adapters: List of adapters
182
+ :return: Tuple of sync adapters, async adapters
183
+ """
184
+
185
+ adapter_set = lifecycle_base.LifecycleAdapterSet(*adapters)
186
+ # this is using internal(ish) fields (.sync_hooks/.async_hooks) -- we should probably expose it
187
+ # For now this is OK
188
+ # Note those are dict[hook_name, list[hook]], so we have to flatten
189
+ return (
190
+ [sync_adapter for adapters in adapter_set.sync_hooks.values() for sync_adapter in adapters],
191
+ [
192
+ async_adapter
193
+ for adapters in adapter_set.async_hooks.values()
194
+ for async_adapter in adapters
195
+ ],
196
+ )
197
+
198
+
199
+ class AsyncDriver(driver.Driver):
200
+ """Async driver. This is a driver that uses the AsyncGraphAdapter to execute the graph.
201
+
202
+ .. code-block:: python
203
+
204
+ dr = async_driver.AsyncDriver({}, async_module, result_builder=base.DictResult())
205
+ df = await dr.execute([...], inputs=...)
206
+
207
+ """
208
+
209
+ def __init__(
210
+ self,
211
+ config,
212
+ *modules,
213
+ result_builder: base.ResultMixin | None = None,
214
+ adapters: list[lifecycle.LifecycleAdapter] = None,
215
+ allow_module_overrides: bool = False,
216
+ ):
217
+ """Instantiates an asynchronous driver.
218
+
219
+ You will also need to call `ainit` to initialize the driver if you have any hooks/adapters.
220
+
221
+ Note that this is not the desired API -- you should be using the :py:class:`hamilton.async_driver.Builder` class to create the driver.
222
+
223
+ This will only (currently) work properly with asynchronous lifecycle hooks, and does not support methods or validators.
224
+ You can still pass in synchronous lifecycle hooks, but they may behave strangely.
225
+
226
+ :param config: Config to build the graph
227
+ :param modules: Modules to crawl for fns/graph nodes
228
+ :param result_builder: Results mixin to compile the graph's final results. TBD whether this should be included in the long run.
229
+ :param adapters: Adapters to use for lifecycle methods.
230
+ :param allow_module_overrides: Optional. Same named functions get overridden by later modules.
231
+ The order of listing the modules is important, since later ones will overwrite the previous ones.
232
+ This is a global call affecting all imported modules.
233
+ See https://github.com/apache/hamilton/tree/main/examples/module_overrides for more info.
234
+ """
235
+ if adapters is None:
236
+ adapters = []
237
+ sync_adapters, async_adapters = separate_sync_from_async(adapters)
238
+
239
+ # we'll need to use this in multiple contexts so we'll keep it around for later
240
+
241
+ result_builders = [adapter for adapter in adapters if isinstance(adapter, base.ResultMixin)]
242
+ if result_builder is not None:
243
+ result_builders.append(result_builder)
244
+ if len(result_builders) > 1:
245
+ raise ValueError(
246
+ "You cannot pass more than one result builder to the async driver. "
247
+ "Please pass in a single result builder"
248
+ )
249
+ # it will be defaulted by the graph adapter
250
+ result_builder = result_builders[0] if len(result_builders) == 1 else None
251
+ super(AsyncDriver, self).__init__(
252
+ config,
253
+ *modules,
254
+ adapter=[
255
+ # We pass in the async adapters here as this can call node-level hooks
256
+ # Otherwise we trust the driver/fn graph to call sync adapters
257
+ AsyncGraphAdapter(
258
+ result_builder=result_builder,
259
+ async_lifecycle_adapters=lifecycle_base.LifecycleAdapterSet(*async_adapters),
260
+ ),
261
+ # We pass in the sync adapters here as this can call
262
+ *sync_adapters,
263
+ *async_adapters, # note async adapters will not be called during synchronous execution -- this is for access later
264
+ ],
265
+ allow_module_overrides=allow_module_overrides,
266
+ )
267
+ self.initialized = False
268
+
269
+ async def ainit(self) -> "AsyncDriver":
270
+ """Initializes the driver when using async. This only exists for backwards compatibility.
271
+ In Hamilton 2.0, we will be using an asynchronous constructor.
272
+ See https://dev.to/akarshan/asynchronous-python-magic-how-to-create-awaitable-constructors-with-asyncmixin-18j5.
273
+ """
274
+ if self.initialized:
275
+ # this way it can be called twice
276
+ return self
277
+ if self.adapter.does_hook("post_graph_construct", is_async=True):
278
+ await self.adapter.call_all_lifecycle_hooks_async(
279
+ "post_graph_construct",
280
+ graph=self.graph,
281
+ modules=self.graph_modules,
282
+ config=self.config,
283
+ )
284
+ await self.adapter.ainit()
285
+ self.initialized = True
286
+ return self
287
+
288
+ async def raw_execute(
289
+ self,
290
+ final_vars: list[str],
291
+ overrides: dict[str, Any] = None,
292
+ display_graph: bool = False, # don't care
293
+ inputs: dict[str, Any] = None,
294
+ _fn_graph: graph.FunctionGraph = None,
295
+ ) -> dict[str, Any]:
296
+ """Executes the graph, returning a dictionary of strings (node keys) to final results.
297
+
298
+ :param final_vars: Variables to execute (+ upstream)
299
+ :param overrides: Overrides for nodes
300
+ :param display_graph: whether or not to display graph -- this is not supported.
301
+ :param inputs: Inputs for DAG runtime calculation
302
+ :param _fn_graph: Function graph for compatibility with superclass -- unused
303
+ :return: A dict of key -> result
304
+ """
305
+ assert _fn_graph is None, (
306
+ "_fn_graph must not be provided "
307
+ "-- the only reason you'd do this is to use materialize(), which is not supported yet.."
308
+ )
309
+ run_id = str(uuid.uuid4())
310
+ nodes, user_nodes = self.graph.get_upstream_nodes(final_vars, inputs, overrides)
311
+ memoized_computation = dict() # memoized storage
312
+ if self.adapter.does_hook("pre_graph_execute"):
313
+ await self.adapter.call_all_lifecycle_hooks_sync_and_async(
314
+ "pre_graph_execute",
315
+ run_id=run_id,
316
+ graph=self.graph,
317
+ final_vars=final_vars,
318
+ inputs=inputs,
319
+ overrides=overrides,
320
+ )
321
+ results = None
322
+ error = None
323
+ success = False
324
+ try:
325
+ self.graph.execute(nodes, memoized_computation, overrides, inputs, run_id=run_id)
326
+ if display_graph:
327
+ raise ValueError(
328
+ "display_graph=True is not supported for the async graph adapter. "
329
+ "Instead you should be using visualize_execution."
330
+ )
331
+ task_dict = {
332
+ key: asyncio.create_task(process_value(memoized_computation[key]))
333
+ for key in final_vars
334
+ }
335
+ results = await await_dict_of_tasks(task_dict)
336
+ success = True
337
+ except Exception as e:
338
+ error = e
339
+ success = False
340
+ raise e
341
+ finally:
342
+ if self.adapter.does_hook("post_graph_execute", is_async=None):
343
+ await self.adapter.call_all_lifecycle_hooks_sync_and_async(
344
+ "post_graph_execute",
345
+ run_id=run_id,
346
+ graph=self.graph,
347
+ success=success,
348
+ error=error,
349
+ results=results,
350
+ )
351
+ return results
352
+
353
+ async def execute(
354
+ self,
355
+ final_vars: list[str],
356
+ overrides: dict[str, Any] = None,
357
+ display_graph: bool = False,
358
+ inputs: dict[str, Any] = None,
359
+ ) -> Any:
360
+ """Executes computation.
361
+
362
+ :param final_vars: the final list of variables we want to compute.
363
+ :param overrides: values that will override "nodes" in the DAG.
364
+ :param display_graph: DEPRECATED. Whether we want to display the graph being computed.
365
+ :param inputs: Runtime inputs to the DAG.
366
+ :return: an object consisting of the variables requested, matching the type returned by the GraphAdapter.
367
+ See constructor for how the GraphAdapter is initialized. The default one right now returns a pandas
368
+ dataframe.
369
+ """
370
+ if display_graph:
371
+ raise ValueError(
372
+ "display_graph=True is not supported for the async graph adapter. "
373
+ "Instead you should be using visualize_execution."
374
+ )
375
+ _final_vars = self._create_final_vars(final_vars)
376
+ try:
377
+ outputs = await self.raw_execute(_final_vars, overrides, display_graph, inputs=inputs)
378
+ # Currently we don't allow async build results, but we could.
379
+ if self.adapter.does_method("do_build_result", is_async=False):
380
+ return self.adapter.call_lifecycle_method_sync("do_build_result", outputs=outputs)
381
+ return outputs
382
+ except Exception as e:
383
+ logger.error(driver.SLACK_ERROR_MESSAGE)
384
+ raise e
385
+
386
+
387
+ class Builder(driver.Builder):
388
+ """Builder for the async driver. This is equivalent to the standard builder, but has a more limited API.
389
+ Note this does not support dynamic execution or materializers (for now).
390
+
391
+ Here is an example of how you might use it to get the tracker working:
392
+
393
+ .. code-block:: python
394
+
395
+ from hamilton_sdk import tracker
396
+
397
+ tracker_async = adapters.AsyncHamiltonTracker(
398
+ project_id=1,
399
+ username="elijah",
400
+ dag_name="async_tracker",
401
+ )
402
+ dr = (
403
+ await async_driver.Builder()
404
+ .with_modules(async_module)
405
+ .with_adapters(tracking_async)
406
+ .build()
407
+ )
408
+ """
409
+
410
+ def __init__(self):
411
+ super(Builder, self).__init__()
412
+
413
+ def _not_supported(self, method_name: str, additional_message: str = ""):
414
+ raise ValueError(
415
+ f"Builder().{method_name}() is not supported for the async driver. {additional_message}"
416
+ )
417
+
418
+ def enable_dynamic_execution(self, *, allow_experimental_mode: bool = False) -> "Builder":
419
+ self._not_supported("enable_dynamic_execution")
420
+
421
+ def with_materializers(
422
+ self, *materializers: ExtractorFactory | MaterializerFactory
423
+ ) -> "Builder":
424
+ self._not_supported("with_materializers")
425
+
426
+ def with_adapter(self, adapter: base.HamiltonGraphAdapter) -> "Builder":
427
+ self._not_supported(
428
+ "with_adapter",
429
+ "Use with_adapters instead to pass in the tracker (or other async hooks/methods)",
430
+ )
431
+
432
+ def build_without_init(self) -> AsyncDriver:
433
+ """Allows you to build the async driver without initialization. Use this at
434
+ your own risk -- we highly recommend calling `.ainit` on the final result.
435
+
436
+ :return:
437
+ """
438
+ adapters = self.adapters if self.adapters is not None else []
439
+ if self.legacy_graph_adapter is not None:
440
+ adapters.append(self.legacy_graph_adapter)
441
+
442
+ # We should really be doing this in the constructor
443
+ # but the AsyncGraphAdapter originally used the pandas builder
444
+ # so we pass in the right one to ensure backwards compatibility
445
+ # This will become the default API soon, so it's OK to put the complexity here
446
+ result_builders = [adapter for adapter in adapters if isinstance(adapter, base.ResultMixin)]
447
+ specified_result_builder = base.DictResult() if len(result_builders) == 0 else None
448
+ return AsyncDriver(
449
+ self.config,
450
+ *self.modules,
451
+ adapters=self.adapters,
452
+ result_builder=specified_result_builder,
453
+ allow_module_overrides=self._allow_module_overrides,
454
+ )
455
+
456
+ async def build(self):
457
+ """Builds the async driver. This also initializes it, hence the async definition.
458
+ If you don't want to use async, you can use `build_without_init` and call `ainit` later,
459
+ but we recommend using this in an asynchronous lifespan management function (E.G. in fastAPI),
460
+ or something similar.
461
+
462
+ :return: The fully
463
+ """
464
+ dr = self.build_without_init()
465
+ return await dr.ainit()