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.
- apache_hamilton-1.90.0.dev0.dist-info/METADATA +407 -0
- apache_hamilton-1.90.0.dev0.dist-info/RECORD +151 -0
- apache_hamilton-1.90.0.dev0.dist-info/WHEEL +4 -0
- apache_hamilton-1.90.0.dev0.dist-info/entry_points.txt +9 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/DISCLAIMER +10 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/LICENSE +228 -0
- apache_hamilton-1.90.0.dev0.dist-info/licenses/NOTICE +5 -0
- hamilton/__init__.py +24 -0
- hamilton/ad_hoc_utils.py +132 -0
- hamilton/async_driver.py +465 -0
- hamilton/base.py +466 -0
- hamilton/caching/__init__.py +16 -0
- hamilton/caching/adapter.py +1475 -0
- hamilton/caching/cache_key.py +70 -0
- hamilton/caching/fingerprinting.py +287 -0
- hamilton/caching/stores/__init__.py +16 -0
- hamilton/caching/stores/base.py +242 -0
- hamilton/caching/stores/file.py +140 -0
- hamilton/caching/stores/memory.py +297 -0
- hamilton/caching/stores/sqlite.py +282 -0
- hamilton/caching/stores/utils.py +40 -0
- hamilton/cli/__init__.py +16 -0
- hamilton/cli/__main__.py +328 -0
- hamilton/cli/commands.py +126 -0
- hamilton/cli/logic.py +338 -0
- hamilton/common/__init__.py +76 -0
- hamilton/contrib/__init__.py +41 -0
- hamilton/data_quality/__init__.py +16 -0
- hamilton/data_quality/base.py +198 -0
- hamilton/data_quality/default_validators.py +560 -0
- hamilton/data_quality/pandera_validators.py +121 -0
- hamilton/dataflows/__init__.py +726 -0
- hamilton/dataflows/template/README.md +25 -0
- hamilton/dataflows/template/__init__.py +54 -0
- hamilton/dataflows/template/author.md +28 -0
- hamilton/dataflows/template/requirements.txt +0 -0
- hamilton/dataflows/template/tags.json +7 -0
- hamilton/dataflows/template/valid_configs.jsonl +1 -0
- hamilton/dev_utils/__init__.py +16 -0
- hamilton/dev_utils/deprecation.py +204 -0
- hamilton/driver.py +2112 -0
- hamilton/execution/__init__.py +16 -0
- hamilton/execution/debugging_utils.py +56 -0
- hamilton/execution/executors.py +502 -0
- hamilton/execution/graph_functions.py +421 -0
- hamilton/execution/grouping.py +430 -0
- hamilton/execution/state.py +539 -0
- hamilton/experimental/__init__.py +27 -0
- hamilton/experimental/databackend.py +61 -0
- hamilton/experimental/decorators/__init__.py +16 -0
- hamilton/experimental/decorators/parameterize_frame.py +233 -0
- hamilton/experimental/h_async.py +29 -0
- hamilton/experimental/h_cache.py +413 -0
- hamilton/experimental/h_dask.py +28 -0
- hamilton/experimental/h_databackends.py +174 -0
- hamilton/experimental/h_ray.py +28 -0
- hamilton/experimental/h_spark.py +32 -0
- hamilton/function_modifiers/README +40 -0
- hamilton/function_modifiers/__init__.py +121 -0
- hamilton/function_modifiers/adapters.py +900 -0
- hamilton/function_modifiers/base.py +859 -0
- hamilton/function_modifiers/configuration.py +310 -0
- hamilton/function_modifiers/delayed.py +202 -0
- hamilton/function_modifiers/dependencies.py +246 -0
- hamilton/function_modifiers/expanders.py +1230 -0
- hamilton/function_modifiers/macros.py +1634 -0
- hamilton/function_modifiers/metadata.py +434 -0
- hamilton/function_modifiers/recursive.py +908 -0
- hamilton/function_modifiers/validation.py +289 -0
- hamilton/function_modifiers_base.py +31 -0
- hamilton/graph.py +1153 -0
- hamilton/graph_types.py +264 -0
- hamilton/graph_utils.py +41 -0
- hamilton/htypes.py +450 -0
- hamilton/io/__init__.py +32 -0
- hamilton/io/data_adapters.py +216 -0
- hamilton/io/default_data_loaders.py +224 -0
- hamilton/io/materialization.py +500 -0
- hamilton/io/utils.py +158 -0
- hamilton/lifecycle/__init__.py +67 -0
- hamilton/lifecycle/api.py +833 -0
- hamilton/lifecycle/base.py +1130 -0
- hamilton/lifecycle/default.py +802 -0
- hamilton/log_setup.py +47 -0
- hamilton/models.py +92 -0
- hamilton/node.py +449 -0
- hamilton/plugins/README.md +48 -0
- hamilton/plugins/__init__.py +16 -0
- hamilton/plugins/dask_extensions.py +47 -0
- hamilton/plugins/dlt_extensions.py +161 -0
- hamilton/plugins/geopandas_extensions.py +49 -0
- hamilton/plugins/h_dask.py +331 -0
- hamilton/plugins/h_ddog.py +522 -0
- hamilton/plugins/h_diskcache.py +163 -0
- hamilton/plugins/h_experiments/__init__.py +22 -0
- hamilton/plugins/h_experiments/__main__.py +62 -0
- hamilton/plugins/h_experiments/cache.py +39 -0
- hamilton/plugins/h_experiments/data_model.py +68 -0
- hamilton/plugins/h_experiments/hook.py +219 -0
- hamilton/plugins/h_experiments/server.py +375 -0
- hamilton/plugins/h_kedro.py +152 -0
- hamilton/plugins/h_logging.py +454 -0
- hamilton/plugins/h_mcp/__init__.py +28 -0
- hamilton/plugins/h_mcp/__main__.py +33 -0
- hamilton/plugins/h_mcp/_helpers.py +129 -0
- hamilton/plugins/h_mcp/_templates.py +417 -0
- hamilton/plugins/h_mcp/server.py +328 -0
- hamilton/plugins/h_mlflow.py +335 -0
- hamilton/plugins/h_narwhals.py +134 -0
- hamilton/plugins/h_openlineage.py +400 -0
- hamilton/plugins/h_opentelemetry.py +167 -0
- hamilton/plugins/h_pandas.py +257 -0
- hamilton/plugins/h_pandera.py +117 -0
- hamilton/plugins/h_polars.py +304 -0
- hamilton/plugins/h_polars_lazyframe.py +282 -0
- hamilton/plugins/h_pyarrow.py +56 -0
- hamilton/plugins/h_pydantic.py +127 -0
- hamilton/plugins/h_ray.py +242 -0
- hamilton/plugins/h_rich.py +142 -0
- hamilton/plugins/h_schema.py +493 -0
- hamilton/plugins/h_slack.py +100 -0
- hamilton/plugins/h_spark.py +1380 -0
- hamilton/plugins/h_threadpool.py +125 -0
- hamilton/plugins/h_tqdm.py +122 -0
- hamilton/plugins/h_vaex.py +129 -0
- hamilton/plugins/huggingface_extensions.py +236 -0
- hamilton/plugins/ibis_extensions.py +93 -0
- hamilton/plugins/jupyter_magic.py +622 -0
- hamilton/plugins/kedro_extensions.py +117 -0
- hamilton/plugins/lightgbm_extensions.py +99 -0
- hamilton/plugins/matplotlib_extensions.py +108 -0
- hamilton/plugins/mlflow_extensions.py +216 -0
- hamilton/plugins/numpy_extensions.py +105 -0
- hamilton/plugins/pandas_extensions.py +1763 -0
- hamilton/plugins/plotly_extensions.py +150 -0
- hamilton/plugins/polars_extensions.py +71 -0
- hamilton/plugins/polars_implementations.py +25 -0
- hamilton/plugins/polars_lazyframe_extensions.py +302 -0
- hamilton/plugins/polars_post_1_0_0_extensions.py +877 -0
- hamilton/plugins/polars_pre_1_0_0_extension.py +836 -0
- hamilton/plugins/pydantic_extensions.py +98 -0
- hamilton/plugins/pyspark_pandas_extensions.py +47 -0
- hamilton/plugins/sklearn_plot_extensions.py +129 -0
- hamilton/plugins/spark_extensions.py +105 -0
- hamilton/plugins/vaex_extensions.py +51 -0
- hamilton/plugins/xgboost_extensions.py +91 -0
- hamilton/plugins/yaml_extensions.py +89 -0
- hamilton/registry.py +254 -0
- hamilton/settings.py +18 -0
- hamilton/telemetry.py +50 -0
- hamilton/version.py +18 -0
|
@@ -0,0 +1,726 @@
|
|
|
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
|
+
"""
|
|
19
|
+
This module houses functions and tools to interact with off-the-shelf
|
|
20
|
+
dataflows.
|
|
21
|
+
|
|
22
|
+
TODO: expect this to have a CLI interface in the future.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import importlib
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
import shutil
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
import urllib.error
|
|
33
|
+
import urllib.request
|
|
34
|
+
from collections.abc import Callable
|
|
35
|
+
from types import ModuleType
|
|
36
|
+
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple, Type, Union
|
|
37
|
+
|
|
38
|
+
from hamilton import driver
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
import builtins
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
Paths that we care about.
|
|
47
|
+
Assumptions:
|
|
48
|
+
- ~/.hamilton/dataflows/ is where we store dataflows. This is fine because we should be able to save locally.
|
|
49
|
+
- This will act as a cache for dataflows.
|
|
50
|
+
- People can modify this location globally.
|
|
51
|
+
- Directory should mirror github (e.g. contrib/hamilton/contrib/user/{user}/{dataflow})
|
|
52
|
+
- We don't have to deal with the python path because this module knows how to import it.
|
|
53
|
+
|
|
54
|
+
TODOs:
|
|
55
|
+
- finish init
|
|
56
|
+
- make sure it works on windows
|
|
57
|
+
- make functions more robust (see comments in functions).
|
|
58
|
+
"""
|
|
59
|
+
COMMON_PATH = "{commit_ish}/contrib/hamilton/contrib"
|
|
60
|
+
BASE_URL = f"https://raw.githubusercontent.com/apache/hamilton/{COMMON_PATH}"
|
|
61
|
+
DATAFLOW_FOLDER = os.path.expanduser("~/.hamilton/dataflows")
|
|
62
|
+
USER_PATH = DATAFLOW_FOLDER + "/" + COMMON_PATH + "/user/{user}/{dataflow}"
|
|
63
|
+
OFFICIAL_PATH = DATAFLOW_FOLDER + "/" + COMMON_PATH + "/dagworks/{dataflow}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _track_function_call(call_fn: Callable) -> Callable:
|
|
67
|
+
"""No-op decorator kept for backwards compatibility.
|
|
68
|
+
|
|
69
|
+
:param call_fn: the function.
|
|
70
|
+
:return: the same function, unwrapped.
|
|
71
|
+
"""
|
|
72
|
+
return call_fn
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _track_download(is_official: bool, user: str | None, dataflow_name: str, version: str):
|
|
76
|
+
"""No-op. Telemetry has been removed."""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_request(url: str) -> tuple[int, str]:
|
|
81
|
+
"""Makes a GET request to the given URL and returns the status code and response data.
|
|
82
|
+
|
|
83
|
+
:param url: the url to make the request to.
|
|
84
|
+
:return: tuple of status code and text response decoded as utf-8.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
with urllib.request.urlopen(url) as response:
|
|
88
|
+
data = response.read().decode("utf-8")
|
|
89
|
+
return response.status, data
|
|
90
|
+
|
|
91
|
+
except urllib.error.HTTPError as e:
|
|
92
|
+
return e.code, e.reason
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@_track_function_call
|
|
96
|
+
def clear_storage():
|
|
97
|
+
"""Clears all the data under DATAFLOW_FOLDER. By default its "~/.hamilton/dataflows"."""
|
|
98
|
+
if os.path.exists(DATAFLOW_FOLDER):
|
|
99
|
+
shutil.rmtree(DATAFLOW_FOLDER)
|
|
100
|
+
logger.info(f"Cleared storage at {DATAFLOW_FOLDER}")
|
|
101
|
+
else:
|
|
102
|
+
logger.info(f"Folder {DATAFLOW_FOLDER} does not exist.")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# want TTL cache on this call to not get rate limited.
|
|
106
|
+
last_time_called = None
|
|
107
|
+
last_resolve_value = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@_track_function_call
|
|
111
|
+
def resolve_latest_branch_commit(branch: str = "main") -> str:
|
|
112
|
+
"""Resolves https://api.github.com/repos/DAGWorks-Inc/hamilton/git/refs/heads/{branch}
|
|
113
|
+
|
|
114
|
+
:return: commit for what "main" is.
|
|
115
|
+
"""
|
|
116
|
+
global last_time_called
|
|
117
|
+
global last_resolve_value
|
|
118
|
+
if last_time_called is not None and last_resolve_value is not None:
|
|
119
|
+
if (last_time_called + 60.0) > time.time():
|
|
120
|
+
logger.info("using cached resolve_latest")
|
|
121
|
+
return last_resolve_value
|
|
122
|
+
url = f"https://api.github.com/repos/DAGWorks-Inc/hamilton/git/refs/heads/{branch}"
|
|
123
|
+
status_code, text = _get_request(url)
|
|
124
|
+
# response = requests.get(url)
|
|
125
|
+
if status_code != 200:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Failed to resolve latest commit for branch {branch}:\n{status_code}\n{text}"
|
|
128
|
+
)
|
|
129
|
+
commit_sha = json.loads(text)["object"]["sha"]
|
|
130
|
+
last_time_called = time.time()
|
|
131
|
+
last_resolve_value = commit_sha
|
|
132
|
+
return commit_sha
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@_track_function_call
|
|
136
|
+
def latest_commit(dataflow: str, user: str = None) -> str:
|
|
137
|
+
"""Determines the latest commit for a dataflow.
|
|
138
|
+
|
|
139
|
+
This is useful to know if you want to pull the latest version of a dataflow.
|
|
140
|
+
|
|
141
|
+
:param dataflow: the string name of the dataflow
|
|
142
|
+
:param user: the name of the user. None if official.
|
|
143
|
+
:return: the commit sha.
|
|
144
|
+
"""
|
|
145
|
+
if user:
|
|
146
|
+
url = f"https://hub.dagworks.io/commits/Users/{user}/{dataflow}/commit.txt"
|
|
147
|
+
else:
|
|
148
|
+
url = f"https://hub.dagworks.io/commits/DAGWorks/{dataflow}/commit.txt"
|
|
149
|
+
status_code, text = _get_request(url)
|
|
150
|
+
if status_code != 200:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"Failed to resolve latest commit [{url}] for {user}/{dataflow}:\n{status_code}\n{text}"
|
|
153
|
+
)
|
|
154
|
+
chunk_index = text.find("[commit::")
|
|
155
|
+
if chunk_index < 0:
|
|
156
|
+
raise ValueError("No commit found")
|
|
157
|
+
# Gets the latest commit from potentially multiple.
|
|
158
|
+
commit = text[chunk_index + len("[commit::") :]
|
|
159
|
+
commit_sha = commit[: commit.find("]")]
|
|
160
|
+
return commit_sha
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@_track_function_call
|
|
164
|
+
def pull_module(dataflow: str, user: str = None, version: str = "latest", overwrite: bool = False):
|
|
165
|
+
"""Pulls a dataflow module.
|
|
166
|
+
|
|
167
|
+
Saves to hamilton.dataflow.USER_PATH. An import should just work right after doing this.
|
|
168
|
+
|
|
169
|
+
It performs the following:
|
|
170
|
+
|
|
171
|
+
1. Creates a URL to pull from github.
|
|
172
|
+
2. Pulls the code for the dataflow.
|
|
173
|
+
3. Save to the local location based on hamilton.dataflow.USER_PATH.
|
|
174
|
+
|
|
175
|
+
:param dataflow: the dataflow name.
|
|
176
|
+
:param user: the user's github handle.
|
|
177
|
+
:param version: the commit version. "latest" will resolve to the most recent commit, else pass \
|
|
178
|
+
a commit SHA.
|
|
179
|
+
:param overwrite: whether to overwrite. Default is False.
|
|
180
|
+
"""
|
|
181
|
+
if version == "latest":
|
|
182
|
+
version = latest_commit(dataflow, user)
|
|
183
|
+
if user:
|
|
184
|
+
_track_download(False, user, dataflow, version)
|
|
185
|
+
logger.info(f"pulling dataflow {user}/{dataflow} with version {version}")
|
|
186
|
+
local_file_path = USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow)
|
|
187
|
+
else:
|
|
188
|
+
_track_download(True, None, dataflow, version)
|
|
189
|
+
logger.info(f"pulling official dataflow {dataflow} with version {version}")
|
|
190
|
+
local_file_path = OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow)
|
|
191
|
+
|
|
192
|
+
h_files = [
|
|
193
|
+
"__init__.py",
|
|
194
|
+
"requirements.txt",
|
|
195
|
+
"README.md",
|
|
196
|
+
"valid_configs.jsonl",
|
|
197
|
+
"tags.json",
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
if os.path.exists(local_file_path) and not overwrite:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"Dataflow {user}/{dataflow} with version {version} already exists locally. Not downloading."
|
|
203
|
+
)
|
|
204
|
+
os.makedirs(local_file_path, exist_ok=True)
|
|
205
|
+
for h_file in h_files:
|
|
206
|
+
if user:
|
|
207
|
+
url = BASE_URL.format(commit_ish=version) + f"/user/{user}/{dataflow}/{h_file}"
|
|
208
|
+
else:
|
|
209
|
+
url = BASE_URL.format(commit_ish=version) + f"/dagworks/{dataflow}/{h_file}"
|
|
210
|
+
# response = requests.get(url)
|
|
211
|
+
status_code, text = _get_request(url)
|
|
212
|
+
if status_code == 404:
|
|
213
|
+
raise ValueError(f"Dataflow {user}/{dataflow}/{h_file} not found at {url}.")
|
|
214
|
+
elif status_code != 200:
|
|
215
|
+
raise ValueError(
|
|
216
|
+
f"Dataflow {user}/{dataflow} returned status code {status_code}:\n{text}"
|
|
217
|
+
)
|
|
218
|
+
file_contents = text
|
|
219
|
+
if os.path.exists(os.path.join(local_file_path, h_file)):
|
|
220
|
+
logger.info(f"Warning: overwriting {h_file} in {local_file_path}")
|
|
221
|
+
with open(os.path.join(local_file_path, h_file), "w") as f:
|
|
222
|
+
f.write(file_contents)
|
|
223
|
+
logger.info(f"wrote {h_file} to {local_file_path}")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@_track_function_call
|
|
227
|
+
def import_module(
|
|
228
|
+
dataflow: str, user: str = None, version: str = "latest", overwrite: bool = False
|
|
229
|
+
) -> ModuleType:
|
|
230
|
+
"""Pulls & imports dataflow code from github and returns a module.
|
|
231
|
+
|
|
232
|
+
.. code-block:: python
|
|
233
|
+
|
|
234
|
+
from hamilton import dataflows, driver
|
|
235
|
+
# downloads into ~/.hamilton/dataflows and loads the module -- WARNING: ensure you know what code you're importing!
|
|
236
|
+
# NAME_OF_DATAFLOW = dataflow.import_module("NAME_OF_DATAFLOW") # if using official dataflow
|
|
237
|
+
NAME_OF_DATAFLOW = dataflow.import_module("NAME_OF_DATAFLOW", "NAME_OF_USER")
|
|
238
|
+
dr = (
|
|
239
|
+
driver.Builder()
|
|
240
|
+
.with_config({}) # replace with configuration as appropriate
|
|
241
|
+
.with_modules(NAME_OF_DATAFLOW)
|
|
242
|
+
.build()
|
|
243
|
+
)
|
|
244
|
+
# execute the dataflow, specifying what you want back. Will return a dictionary.
|
|
245
|
+
result = dr.execute(
|
|
246
|
+
[NAME_OF_DATAFLOW.FUNCTION_NAME, ...], # this specifies what you want back
|
|
247
|
+
inputs={...} # pass in inputs as appropriate
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
:param dataflow: the name of the dataflow.
|
|
251
|
+
:param user: Optional. If none it assumes official.
|
|
252
|
+
:param version: the version to get. "latest" will resolve to the most recent commit. \
|
|
253
|
+
Otherwise pass a the commit SHA you want to pull.
|
|
254
|
+
:param overwrite: whether to overwrite the local path. Default is False.
|
|
255
|
+
:return: a Module that you can then pass to Hamilton.
|
|
256
|
+
"""
|
|
257
|
+
if version == "latest":
|
|
258
|
+
version = latest_commit(dataflow, user)
|
|
259
|
+
if user:
|
|
260
|
+
local_file_path = (
|
|
261
|
+
USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow) + "/__init__.py"
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
local_file_path = (
|
|
265
|
+
OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow) + "/__init__.py"
|
|
266
|
+
)
|
|
267
|
+
if not os.path.exists(local_file_path) or overwrite:
|
|
268
|
+
pull_module(dataflow, user, version=version, overwrite=overwrite)
|
|
269
|
+
else:
|
|
270
|
+
logger.info(f"Found {user}/{dataflow} with version {version} locally. Not downloading.")
|
|
271
|
+
|
|
272
|
+
if dataflow in sys.modules:
|
|
273
|
+
logger.info(
|
|
274
|
+
f"Warning: overwriting existing {dataflow} module with version {sys.modules[dataflow].__version__}"
|
|
275
|
+
)
|
|
276
|
+
spec = importlib.util.spec_from_file_location(dataflow, local_file_path)
|
|
277
|
+
module = importlib.util.module_from_spec(spec)
|
|
278
|
+
module.__version__ = version
|
|
279
|
+
sys.modules[dataflow] = module
|
|
280
|
+
spec.loader.exec_module(module)
|
|
281
|
+
return module
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class InspectResult(NamedTuple):
|
|
285
|
+
version: str # git commit sha/package version
|
|
286
|
+
user: str # github user URL
|
|
287
|
+
dataflow: str # dataflow URL
|
|
288
|
+
python_dependencies: list[str] # python dependencies
|
|
289
|
+
configurations: list[str] # configurations for the dataflow stored as a JSON string
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@_track_function_call
|
|
293
|
+
def inspect(dataflow: str, user: str = None, version: str = "latest") -> InspectResult:
|
|
294
|
+
"""Inspects a dataflow for information.
|
|
295
|
+
|
|
296
|
+
This is a helper function to get information about a dataflow that exists locally.
|
|
297
|
+
It does not get more information because we don't want to assume we can import the module.
|
|
298
|
+
|
|
299
|
+
.. code-block:: python
|
|
300
|
+
|
|
301
|
+
from hamilton import dataflows
|
|
302
|
+
|
|
303
|
+
info = dataflows.inspect("text_summarization", "zilto")
|
|
304
|
+
|
|
305
|
+
:param dataflow: the dataflow name.
|
|
306
|
+
:param user: the github name of the user. None for DAGWorks official.
|
|
307
|
+
:param version: the version to inspect. "latest" will resolve to the most recent commit, else pass \
|
|
308
|
+
a commit SHA.
|
|
309
|
+
:return: hamilton.dataflow.InspectResult object that contains version, user URL, dataflow URL, python dependencies, configurations.
|
|
310
|
+
"""
|
|
311
|
+
if version == "latest":
|
|
312
|
+
version = latest_commit(dataflow, user)
|
|
313
|
+
|
|
314
|
+
if user:
|
|
315
|
+
local_file_path = USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow)
|
|
316
|
+
dataflow_url = (
|
|
317
|
+
f"https://github.com/apache/hamilton/tree/{version}/contrib/contrib/user/{user}/{dataflow}",
|
|
318
|
+
)
|
|
319
|
+
user_url = f"https://github.com/{user}"
|
|
320
|
+
else:
|
|
321
|
+
local_file_path = OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow)
|
|
322
|
+
dataflow_url = (
|
|
323
|
+
f"https://github.com/apache/hamilton/tree/{version}/contrib/contrib/dagworks/{dataflow}",
|
|
324
|
+
)
|
|
325
|
+
user_url = None
|
|
326
|
+
if not os.path.exists(local_file_path):
|
|
327
|
+
raise ValueError(
|
|
328
|
+
f"Dataflow {user or 'dagworks'}/{dataflow} with version {version} does not exist locally. Not inspecting."
|
|
329
|
+
)
|
|
330
|
+
# return dictionary of python deps, inputs, nodes, designated outputs, commit hash
|
|
331
|
+
info: dict[str, str | builtins.list[dict] | builtins.list[str]] = {
|
|
332
|
+
"version": version,
|
|
333
|
+
"user": user_url,
|
|
334
|
+
"dataflow": dataflow_url,
|
|
335
|
+
"python_dependencies": [],
|
|
336
|
+
"configurations": [],
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
with open(os.path.join(local_file_path, "requirements.txt"), "r") as f:
|
|
340
|
+
file_contents = [line.strip() for line in f]
|
|
341
|
+
info["python_dependencies"] = file_contents
|
|
342
|
+
|
|
343
|
+
with open(os.path.join(local_file_path, "valid_configs.jsonl"), "r") as f:
|
|
344
|
+
file_contents = [line.strip() for line in f]
|
|
345
|
+
info["configurations"] = file_contents
|
|
346
|
+
return InspectResult(**info)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class InspectModuleResult(NamedTuple):
|
|
350
|
+
version: str # git commit sha/package version
|
|
351
|
+
user: str # github user URL
|
|
352
|
+
dataflow: str # dataflow URL
|
|
353
|
+
python_dependencies: list[str] # python dependencies
|
|
354
|
+
configurations: list[str] # configurations for the dataflow stored as a JSON string
|
|
355
|
+
possible_inputs: list[tuple[str, type]]
|
|
356
|
+
nodes: list[tuple[str, type]]
|
|
357
|
+
designated_outputs: list[tuple[str, type]]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@_track_function_call
|
|
361
|
+
def inspect_module(module: ModuleType) -> InspectModuleResult:
|
|
362
|
+
"""Inspects the import module for information.
|
|
363
|
+
|
|
364
|
+
This does more than `inspect` because the module has been loaded and thus
|
|
365
|
+
we can put it into a Hamilton driver and ask questions of it.
|
|
366
|
+
|
|
367
|
+
.. code-block:: python
|
|
368
|
+
|
|
369
|
+
from hamilton.contrib.user.zilto import text_summarization
|
|
370
|
+
from hamilton import dataflows
|
|
371
|
+
|
|
372
|
+
info = dataflows.inspect_module(text_summarization)
|
|
373
|
+
|
|
374
|
+
:param module: the module with Hamilton code to deeply introspect.
|
|
375
|
+
:return: hamilton.dataflow.InspectModuleResult object.
|
|
376
|
+
"""
|
|
377
|
+
if not module.__file__.startswith(DATAFLOW_FOLDER):
|
|
378
|
+
logger.info("not a downloaded dataflow module.")
|
|
379
|
+
return None
|
|
380
|
+
version = module.__version__
|
|
381
|
+
dataflow = module.__file__.split("/")[-2]
|
|
382
|
+
if "hamilton/contrib/dagworks" in module.__file__:
|
|
383
|
+
user = None
|
|
384
|
+
local_file_path = OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow)
|
|
385
|
+
user_url = None
|
|
386
|
+
dataflow_url = (
|
|
387
|
+
f"https://github.com/apache/hamilton/tree/{version}/contrib/contrib/dagworks/{dataflow}"
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
user = module.__file__.split("/")[-3]
|
|
391
|
+
user_url = f"https://github.com/{user}"
|
|
392
|
+
local_file_path = USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow)
|
|
393
|
+
dataflow_url = f"https://github.com/apache/hamilton/tree/{version}/contrib/contrib/user/{user}/{dataflow}"
|
|
394
|
+
|
|
395
|
+
if not os.path.exists(local_file_path):
|
|
396
|
+
raise ValueError(
|
|
397
|
+
f"Dataflow {user or 'dagworks'}/{dataflow} with version {version} does not exist locally. Not inspecting."
|
|
398
|
+
)
|
|
399
|
+
# return dictionary of python deps, inputs, nodes, designated outputs, commit hash
|
|
400
|
+
info: dict[
|
|
401
|
+
str, str | builtins.list[dict] | builtins.list[str] | builtins.list[tuple[str, type]]
|
|
402
|
+
] = {
|
|
403
|
+
"version": version,
|
|
404
|
+
"user": user_url,
|
|
405
|
+
"dataflow": dataflow_url,
|
|
406
|
+
"python_dependencies": [],
|
|
407
|
+
"possible_inputs": [],
|
|
408
|
+
"nodes": [],
|
|
409
|
+
"designated_outputs": [],
|
|
410
|
+
"configurations": [],
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
with open(os.path.join(local_file_path, "requirements.txt"), "r") as f:
|
|
414
|
+
file_contents = f.readlines()
|
|
415
|
+
info["python_dependencies"] = file_contents
|
|
416
|
+
|
|
417
|
+
with open(os.path.join(local_file_path, "valid_configs.jsonl"), "r") as f:
|
|
418
|
+
file_contents = [json.loads(s) for s in f]
|
|
419
|
+
info["configurations"] = file_contents
|
|
420
|
+
|
|
421
|
+
dr = driver.Driver(info["configurations"][0], module)
|
|
422
|
+
vars = dr.list_available_variables()
|
|
423
|
+
info["possible_inputs"] = [(var.name, var.type) for var in vars if var.is_external_input]
|
|
424
|
+
info["nodes"] = [(var.name, var.type) for var in vars if not var.is_external_input]
|
|
425
|
+
info["designated_outputs"] = [
|
|
426
|
+
(var.name, var.type) for var in vars if var.tags.get("designated_output", False) == "True"
|
|
427
|
+
]
|
|
428
|
+
return InspectModuleResult(**info)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@_track_function_call
|
|
432
|
+
def install_dependencies_string(dataflow: str, user: str = None, version: str = "latest") -> str:
|
|
433
|
+
"""Returns a string for the user to install dependencies.
|
|
434
|
+
|
|
435
|
+
:param dataflow: the name of the dataflow.
|
|
436
|
+
:param user: the github name of the user.
|
|
437
|
+
:param version: the version to inspect. "latest" will resolve to the most recent commit, else pass \
|
|
438
|
+
a commit SHA.
|
|
439
|
+
:return: pip install string to use.
|
|
440
|
+
"""
|
|
441
|
+
if version == "latest":
|
|
442
|
+
version = latest_commit(dataflow, user)
|
|
443
|
+
if user:
|
|
444
|
+
local_file_path = USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow)
|
|
445
|
+
else:
|
|
446
|
+
local_file_path = OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow)
|
|
447
|
+
if not os.path.exists(local_file_path):
|
|
448
|
+
logger.info(
|
|
449
|
+
"Dataflow does not exist locally. Can't provide details on how to install dependencies."
|
|
450
|
+
)
|
|
451
|
+
return ""
|
|
452
|
+
return f"pip install -r {local_file_path}/requirements.txt"
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
from importlib.metadata import PackageNotFoundError
|
|
456
|
+
from importlib.metadata import version as pkg_version
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@_track_function_call
|
|
460
|
+
def are_py_dependencies_satisfied(dataflow, user=None, version="latest"):
|
|
461
|
+
"""Given a commit_ish, user & dataflow, reads the requirements.txt and checks if \
|
|
462
|
+
those dependencies have been satisfied in the currently running python interpreter.
|
|
463
|
+
|
|
464
|
+
Note: this does not handle versions. Just whether the package is installed or not.
|
|
465
|
+
|
|
466
|
+
:param dataflow: the name of the dataflow.
|
|
467
|
+
:param user: the github name of the user. None if DAGWorks official.
|
|
468
|
+
:param version: the version to inspect. "latest" will resolve to the most recent commit, else pass \
|
|
469
|
+
a commit SHA.
|
|
470
|
+
:return: boolean whether the dependencies are satisfied.
|
|
471
|
+
"""
|
|
472
|
+
if version == "latest":
|
|
473
|
+
version = latest_commit(dataflow, user)
|
|
474
|
+
if user:
|
|
475
|
+
requirements_path = (
|
|
476
|
+
USER_PATH.format(commit_ish=version, user=user, dataflow=dataflow) + "/requirements.txt"
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
requirements_path = (
|
|
480
|
+
OFFICIAL_PATH.format(commit_ish=version, dataflow=dataflow) + "/requirements.txt"
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
if not os.path.exists(requirements_path):
|
|
484
|
+
logger.info(f"requirements.txt not found at {requirements_path}")
|
|
485
|
+
return True
|
|
486
|
+
|
|
487
|
+
# Get list of currently installed packages
|
|
488
|
+
with open(requirements_path, "r") as req_file:
|
|
489
|
+
lines = req_file.readlines()
|
|
490
|
+
|
|
491
|
+
for line in lines:
|
|
492
|
+
line = line.strip()
|
|
493
|
+
equals = line.find("=")
|
|
494
|
+
less_than = line.find("<")
|
|
495
|
+
greater_than = line.find(">")
|
|
496
|
+
version_marker = min(equals, less_than, greater_than)
|
|
497
|
+
if version_marker > 0:
|
|
498
|
+
package_name, required_version = (
|
|
499
|
+
line[:version_marker],
|
|
500
|
+
line[version_marker + 1 :],
|
|
501
|
+
)
|
|
502
|
+
else:
|
|
503
|
+
package_name = line
|
|
504
|
+
required_version = None
|
|
505
|
+
required_version # noqa here for now...
|
|
506
|
+
try:
|
|
507
|
+
installed_version = pkg_version(package_name)
|
|
508
|
+
installed_version # noqa here for now..
|
|
509
|
+
except PackageNotFoundError:
|
|
510
|
+
logger.info(f"Package '{package_name}' is not installed.")
|
|
511
|
+
return False
|
|
512
|
+
|
|
513
|
+
# TODO: Check version if specified
|
|
514
|
+
# if required_version and installed_version != required_version:
|
|
515
|
+
# logger.info(
|
|
516
|
+
# f"Package '{package_name}' version mismatch. Required: {required_version}, Installed: {installed_version}")
|
|
517
|
+
# return False
|
|
518
|
+
|
|
519
|
+
logger.info("All requirements are satisfied.")
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@_track_function_call
|
|
524
|
+
def list(version: str = "latest", user: str = None) -> list:
|
|
525
|
+
"""Lists dataflows locally downloaded based on commit_ish and user.
|
|
526
|
+
|
|
527
|
+
:param version: the version to inspect. "latest" will resolve to the most recent commit, else pass \
|
|
528
|
+
a commit SHA.
|
|
529
|
+
:param user: the github name of the user.
|
|
530
|
+
:return: list of tuples of (version, user, dataflow)
|
|
531
|
+
"""
|
|
532
|
+
if version == "latest":
|
|
533
|
+
version = "main"
|
|
534
|
+
if not os.path.exists(DATAFLOW_FOLDER):
|
|
535
|
+
# TODO better error message here.
|
|
536
|
+
logger.info(f"Folder {DATAFLOW_FOLDER} does not exist.")
|
|
537
|
+
return []
|
|
538
|
+
|
|
539
|
+
dataflows = []
|
|
540
|
+
|
|
541
|
+
for ci in os.listdir(DATAFLOW_FOLDER):
|
|
542
|
+
if (
|
|
543
|
+
(version and ci != version)
|
|
544
|
+
or os.path.isfile(os.path.join(DATAFLOW_FOLDER, ci))
|
|
545
|
+
or ci.startswith(".")
|
|
546
|
+
):
|
|
547
|
+
continue
|
|
548
|
+
commit_path = os.path.join(DATAFLOW_FOLDER, ci, "contrib", "hamilton", "contrib", "user")
|
|
549
|
+
for usr in os.listdir(commit_path):
|
|
550
|
+
if (
|
|
551
|
+
(user and usr != user)
|
|
552
|
+
or os.path.isfile(os.path.join(commit_path, usr))
|
|
553
|
+
or usr.startswith(".")
|
|
554
|
+
):
|
|
555
|
+
continue
|
|
556
|
+
user_path = os.path.join(commit_path, usr)
|
|
557
|
+
for df in os.listdir(user_path):
|
|
558
|
+
if os.path.isfile(os.path.join(user_path, df)) or df.startswith("."):
|
|
559
|
+
continue
|
|
560
|
+
dataflows.append((ci, usr, df))
|
|
561
|
+
|
|
562
|
+
# for ci, usr, df in dataflows:
|
|
563
|
+
# logger.info(f"version: {ci}, user: {usr}, dataflow: {df}")
|
|
564
|
+
|
|
565
|
+
return dataflows
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
@_track_function_call
|
|
569
|
+
def find(query: str, version: str = None, user: str = None):
|
|
570
|
+
"""Searches for locally downloaded dataflows based on a query string.
|
|
571
|
+
|
|
572
|
+
:param query: key words to search for.
|
|
573
|
+
:param version: the version to inspect. "latest" will resolve to the most recent commit, else pass \
|
|
574
|
+
a commit SHA.
|
|
575
|
+
:param user: the github name of the user.
|
|
576
|
+
:return: list of tuples of (version, user, dataflow)
|
|
577
|
+
"""
|
|
578
|
+
if not os.path.exists(DATAFLOW_FOLDER):
|
|
579
|
+
logger.info(f"Folder {DATAFLOW_FOLDER} does not exist.")
|
|
580
|
+
return []
|
|
581
|
+
|
|
582
|
+
matches = set()
|
|
583
|
+
|
|
584
|
+
for ci in os.listdir(DATAFLOW_FOLDER):
|
|
585
|
+
if (
|
|
586
|
+
(version and ci != version)
|
|
587
|
+
or os.path.isfile(os.path.join(DATAFLOW_FOLDER, ci))
|
|
588
|
+
or ci.startswith(".")
|
|
589
|
+
):
|
|
590
|
+
continue
|
|
591
|
+
commit_path = os.path.join(DATAFLOW_FOLDER, ci, "contrib", "hamilton", "contrib", "user")
|
|
592
|
+
for usr in os.listdir(commit_path):
|
|
593
|
+
if (
|
|
594
|
+
(user and usr != user)
|
|
595
|
+
or os.path.isfile(os.path.join(commit_path, usr))
|
|
596
|
+
or usr.startswith(".")
|
|
597
|
+
):
|
|
598
|
+
continue
|
|
599
|
+
user_path = os.path.join(commit_path, usr)
|
|
600
|
+
for df in os.listdir(user_path):
|
|
601
|
+
for root, _, files in os.walk(user_path + "/" + df):
|
|
602
|
+
for file in files:
|
|
603
|
+
if not (
|
|
604
|
+
file.startswith(".") or file.endswith(".py") or file.endswith(".md")
|
|
605
|
+
):
|
|
606
|
+
continue
|
|
607
|
+
file_path = os.path.join(root, file)
|
|
608
|
+
with open(file_path, "r") as f:
|
|
609
|
+
# dumb search -- just see if the string is in the file verbatim.
|
|
610
|
+
content = f.read()
|
|
611
|
+
if query in content:
|
|
612
|
+
matches.add((ci, usr, df))
|
|
613
|
+
# TODO: incorporate tags
|
|
614
|
+
return matches
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@_track_function_call
|
|
618
|
+
def copy(
|
|
619
|
+
dataflow: ModuleType,
|
|
620
|
+
destination_path: str,
|
|
621
|
+
overwrite: bool = False,
|
|
622
|
+
renamed_module: str = None,
|
|
623
|
+
):
|
|
624
|
+
"""Copies a dataflow module to the passed in path.
|
|
625
|
+
|
|
626
|
+
.. code-block:: python
|
|
627
|
+
|
|
628
|
+
from hamilton import dataflows
|
|
629
|
+
|
|
630
|
+
# dynamically pull and then copy
|
|
631
|
+
NAME_OF_DATAFLOW = dataflow.import_module("NAME_OF_DATAFLOW", "NAME_OF_USER")
|
|
632
|
+
dataflow.copy(NAME_OF_DATAFLOW, destination_path="PATH_TO_DIRECTORY")
|
|
633
|
+
# copy from the installed library
|
|
634
|
+
from hamilton.contrib.user.NAME_OF_USER import NAME_OF_DATAFLOW
|
|
635
|
+
|
|
636
|
+
dataflow.copy(NAME_OF_DATAFLOW, destination_path="PATH_TO_DIRECTORY")
|
|
637
|
+
|
|
638
|
+
:param dataflow: the module to copy.
|
|
639
|
+
:param destination_path: the path to a directory to place the module in.
|
|
640
|
+
:param overwrite: whether to overwrite the destination. Default is False and raise an error.
|
|
641
|
+
:param renamed_module: whether to rename the copied module. Default is None and will use the original name.
|
|
642
|
+
"""
|
|
643
|
+
# make sure the module exists and has a file to copy
|
|
644
|
+
if not os.path.exists(dataflow.__file__):
|
|
645
|
+
raise ValueError(f"Dataflow {dataflow.__name__} does not exist locally. Not copying.")
|
|
646
|
+
|
|
647
|
+
if renamed_module: # rename the module
|
|
648
|
+
module_name = renamed_module
|
|
649
|
+
else:
|
|
650
|
+
# get the module name
|
|
651
|
+
module_name = dataflow.__name__.split(".")[-1]
|
|
652
|
+
|
|
653
|
+
# append the module name to the destination path
|
|
654
|
+
destination_path = os.path.join(destination_path, module_name)
|
|
655
|
+
# make the directories if they don't exist
|
|
656
|
+
if not os.path.exists(destination_path):
|
|
657
|
+
os.makedirs(destination_path, exist_ok=True)
|
|
658
|
+
|
|
659
|
+
# Note: we'll need to change this if we change how module code is structured.
|
|
660
|
+
file_path = os.path.join(destination_path, "__init__.py")
|
|
661
|
+
# if the file_path exists and overwrite is False, raise an error
|
|
662
|
+
if os.path.exists(file_path) and not overwrite:
|
|
663
|
+
raise ValueError(
|
|
664
|
+
f"Destination {file_path} already exists. Not copying. Use overwrite=True to overwrite."
|
|
665
|
+
)
|
|
666
|
+
# save the module code to the destination path
|
|
667
|
+
shutil.copy(dataflow.__file__, file_path)
|
|
668
|
+
logger.info(f"Successfully copied {dataflow.__name__} to {file_path}.")
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
@_track_function_call
|
|
672
|
+
def init():
|
|
673
|
+
"""Creates a template for someone to add a new flow for
|
|
674
|
+
|
|
675
|
+
Don't want to bite off having to setup a fork,etc. This will
|
|
676
|
+
just create a new directory with template files.
|
|
677
|
+
"""
|
|
678
|
+
# TODO:
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
if __name__ == "__main__":
|
|
683
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
|
684
|
+
_user = "zilto"
|
|
685
|
+
_version = "latest" # or a git commit hash
|
|
686
|
+
_dataflow = "text_summarization"
|
|
687
|
+
_module = import_module(_dataflow, _user, _version, overwrite=True)
|
|
688
|
+
|
|
689
|
+
dr = driver.Driver({}, _module)
|
|
690
|
+
# logger.info(dr.list_available_variables())
|
|
691
|
+
dr.display_all_functions("./dag", {"format": "png", "view": False})
|
|
692
|
+
|
|
693
|
+
logger.info("Listing dataflows ---")
|
|
694
|
+
dfs = list(version=None, user="zilto")
|
|
695
|
+
logger.info(dfs)
|
|
696
|
+
logger.info("Listing chunks ---")
|
|
697
|
+
chunks = find("chunk")
|
|
698
|
+
logger.info(chunks)
|
|
699
|
+
logger.info("Determining python dependencies ---")
|
|
700
|
+
are_py_dependencies_satisfied(_user, _dataflow, _version)
|
|
701
|
+
import pprint
|
|
702
|
+
|
|
703
|
+
logger.info("Inspect module output ---")
|
|
704
|
+
logger.info(pprint.pformat(inspect_module(_module)))
|
|
705
|
+
logger.info("Resolve dataflow commit output ---")
|
|
706
|
+
_version = latest_commit(_dataflow, _user)
|
|
707
|
+
logger.info(_version)
|
|
708
|
+
logger.info("Install dependencies output ---")
|
|
709
|
+
logger.info(install_dependencies_string(_dataflow, _user, _version))
|
|
710
|
+
logger.info("Inspect output ---")
|
|
711
|
+
logger.info(inspect(_dataflow, _user, _version))
|
|
712
|
+
|
|
713
|
+
from hamilton.contrib.user.zilto import text_summarization # noqa:F401
|
|
714
|
+
|
|
715
|
+
copy(
|
|
716
|
+
text_summarization,
|
|
717
|
+
destination_path="~/temp/",
|
|
718
|
+
overwrite=True,
|
|
719
|
+
renamed_module="text_summarization_copy",
|
|
720
|
+
)
|
|
721
|
+
copy(
|
|
722
|
+
_module,
|
|
723
|
+
destination_path="~/temp/",
|
|
724
|
+
overwrite=True,
|
|
725
|
+
renamed_module="text_summarization_copy2",
|
|
726
|
+
)
|