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,22 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ from hamilton.plugins.h_experiments.hook import ExperimentTracker
19
+
20
+ __all__ = [
21
+ "ExperimentTracker",
22
+ ]
@@ -0,0 +1,62 @@
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 argparse
19
+ import os
20
+ from pathlib import Path
21
+
22
+ from hamilton.plugins.h_experiments.cache import JsonCache
23
+
24
+
25
+ def main():
26
+ try:
27
+ import fastapi # noqa: F401
28
+ import fastui # noqa: F401
29
+ import uvicorn
30
+ except ModuleNotFoundError as e:
31
+ raise ModuleNotFoundError(
32
+ "Some dependencies are missing. Make sure to `pip install sf-hamilton[experiments]`"
33
+ ) from e
34
+ parser = argparse.ArgumentParser(prog="hamilton-experiments")
35
+ parser.description = "Hamilton Experiment Server launcher"
36
+
37
+ parser.add_argument(
38
+ "path",
39
+ metavar="path",
40
+ type=str,
41
+ default="./experiments",
42
+ nargs="?",
43
+ help="Set HAMILTON_EXPERIMENTS_PATH environment variable",
44
+ )
45
+ parser.add_argument("--host", default="127.0.0.1", type=str, help="Bind to this address")
46
+ parser.add_argument("--port", default=8123, type=int, help="Bind to this port")
47
+
48
+ args = parser.parse_args()
49
+
50
+ try:
51
+ JsonCache(args.path)
52
+ except Exception as e:
53
+ raise ValueError(f"Server failed to launch. No metadata cache found at {args.path}") from e
54
+
55
+ # set environment variable that FastAPI will use
56
+ os.environ["HAMILTON_EXPERIMENTS_PATH"] = str(Path(args.path).resolve())
57
+
58
+ uvicorn.run("hamilton.plugins.h_experiments.server:app", host=args.host, port=args.port)
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
@@ -0,0 +1,39 @@
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 dbm
19
+
20
+
21
+ class JsonCache:
22
+ def __init__(self, cache_path: str):
23
+ self.cache_path = f"{cache_path}/json_cache"
24
+
25
+ def keys(self) -> list:
26
+ with dbm.open(self.cache_path, "r") as db:
27
+ return list(db.keys())
28
+
29
+ def write(self, data: str, id_: str) -> None:
30
+ with dbm.open(self.cache_path, "c") as db:
31
+ db[id_] = data
32
+
33
+ def read(self, id_: str) -> str:
34
+ with dbm.open(self.cache_path, "r") as db:
35
+ return db[id_].decode()
36
+
37
+ def delete(self, id_: str) -> None:
38
+ with dbm.open(self.cache_path, "r") as db:
39
+ del db[id_]
@@ -0,0 +1,68 @@
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 datetime
19
+ from typing import Any
20
+
21
+ from pydantic import BaseModel, create_model, model_validator
22
+
23
+
24
+ def model_from_values(name: str, specs: dict[str, Any]) -> dict[str, tuple[type, ...]]:
25
+ return create_model(name, **{str(k): (type(v), ...) for k, v in specs.items()})
26
+
27
+
28
+ class NodeMaterializer(BaseModel):
29
+ source_nodes: list[str]
30
+ path: str
31
+ sink: str
32
+ data_saver: str
33
+
34
+
35
+ def denormalize_node_list(nodes: list) -> dict:
36
+ denorm = dict()
37
+ for node in nodes:
38
+ name = node["name"]
39
+ del node["name"]
40
+ denorm[name] = node
41
+ return denorm
42
+
43
+
44
+ class RunMetadata(BaseModel):
45
+ experiment: str
46
+ run_id: str
47
+ run_dir: str
48
+ success: bool
49
+ date_completed: datetime.datetime
50
+ graph_hash: str
51
+ modules: list[str]
52
+ config: dict
53
+ inputs: dict
54
+ overrides: dict
55
+ materialized: list[NodeMaterializer]
56
+ graph_version: int | None = None
57
+
58
+ @model_validator(mode="before")
59
+ def pre_root(cls, v: dict[str, Any]):
60
+ dt = datetime.datetime.fromisoformat(v["date_completed"])
61
+ res = datetime.timedelta(seconds=1)
62
+ nsecs = dt.hour * 3600 + dt.minute * 60 + dt.second + dt.microsecond * 1e-6
63
+ delta = nsecs % res.seconds
64
+ v["date_completed"] = dt - datetime.timedelta(seconds=delta)
65
+
66
+ for field in ["inputs", "overrides"]:
67
+ v[field] = denormalize_node_list(v[field])
68
+ return v
@@ -0,0 +1,219 @@
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 datetime
19
+ import hashlib
20
+ import inspect
21
+ import json
22
+ import logging
23
+ import os
24
+ import string
25
+ import uuid
26
+ from dataclasses import asdict, dataclass
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ from hamilton import graph_types, lifecycle
31
+ from hamilton.plugins.h_experiments.cache import JsonCache
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ def validate_string_input(user_input):
37
+ """Validate the experiment name will make a valid directory name"""
38
+ allowed = set(string.ascii_letters + string.digits + "_" + "-")
39
+ for char in user_input:
40
+ if char not in allowed:
41
+ raise ValueError(f"`{char}` from `{user_input}` is an invalid character.")
42
+
43
+
44
+ def _get_default_input(node) -> Any:
45
+ """Get default input node value from originating function signature"""
46
+ param_name = node.name
47
+ origin_function = node.originating_functions[0]
48
+ param = inspect.signature(origin_function).parameters[param_name]
49
+ return None if param.default is inspect._empty else param.default
50
+
51
+
52
+ def json_encoder(obj: Any):
53
+ """convert non JSON-serializable objects to a serializable format
54
+
55
+ set[T] -> list[T]
56
+ else -> dict[type: str, byte_hash: str]
57
+ """
58
+ if isinstance(obj, set):
59
+ serialized = list(obj)
60
+ else:
61
+ obj_hash = hashlib.sha256()
62
+ obj_hash.update(obj)
63
+ serialized = dict(
64
+ dtype=type(obj).__name__,
65
+ obj_hash=obj_hash.hexdigest(),
66
+ )
67
+ return serialized
68
+
69
+
70
+ @dataclass
71
+ class NodeImplementation:
72
+ name: str
73
+ source_code: str
74
+
75
+
76
+ @dataclass
77
+ class NodeInput:
78
+ name: str
79
+ value: Any
80
+ default_value: Any | None
81
+
82
+
83
+ @dataclass
84
+ class NodeOverride:
85
+ name: str
86
+ value: Any
87
+
88
+
89
+ @dataclass
90
+ class NodeMaterializer:
91
+ source_nodes: list[str]
92
+ path: str
93
+ sink: str
94
+ data_saver: str
95
+
96
+
97
+ @dataclass
98
+ class RunMetadata:
99
+ """Metadata about an Hamilton to store in cache"""
100
+
101
+ experiment: str
102
+ run_id: str
103
+ run_dir: str
104
+ success: bool
105
+ date_completed: datetime.datetime
106
+ graph_hash: str
107
+ modules: list[str]
108
+ config: dict
109
+ inputs: list[NodeInput]
110
+ overrides: list[NodeOverride]
111
+ materialized: list[NodeMaterializer]
112
+
113
+
114
+ class ExperimentTracker(
115
+ lifecycle.NodeExecutionHook,
116
+ lifecycle.GraphExecutionHook,
117
+ lifecycle.GraphConstructionHook,
118
+ ):
119
+ def __init__(self, experiment_name: str, base_directory: str = "./experiments"):
120
+ validate_string_input(experiment_name)
121
+
122
+ self.experiment_name = experiment_name
123
+ self.cache = JsonCache(cache_path=base_directory)
124
+ self.run_id = str(uuid.uuid4())
125
+
126
+ self.init_directory = Path.cwd()
127
+ self.run_directory = (
128
+ Path(base_directory).resolve().joinpath(self.experiment_name, self.run_id)
129
+ )
130
+ self.run_directory.mkdir(exist_ok=True, parents=True)
131
+
132
+ self.graph_hash: str = ""
133
+ self.modules: set[str] = set()
134
+ self.config = dict()
135
+ self.inputs: list[NodeInput] = list()
136
+ self.overrides: list[NodeOverride] = list()
137
+ self.materializers: list[NodeMaterializer] = list()
138
+
139
+ def run_after_graph_construction(self, *, config: dict[str, Any], **kwargs):
140
+ """Store the Driver config before creating the graph"""
141
+ self.config = config
142
+
143
+ def run_before_graph_execution(
144
+ self,
145
+ *,
146
+ graph: graph_types.HamiltonGraph,
147
+ inputs: dict[str, Any],
148
+ overrides: dict[str, Any],
149
+ **kwargs,
150
+ ):
151
+ """Store execution metadata: graph hash, inputs, overrides"""
152
+ self.graph_hash = graph.version
153
+
154
+ for node in graph.nodes:
155
+ if node.tags.get("module"):
156
+ self.modules.add(node.tags["module"])
157
+
158
+ # filter out config nodes
159
+ elif node.is_external_input and node.originating_functions:
160
+ self.inputs.append(
161
+ NodeInput(
162
+ name=node.name,
163
+ value=inputs.get(node.name),
164
+ default_value=_get_default_input(node),
165
+ )
166
+ )
167
+
168
+ if overrides:
169
+ self.overrides = [NodeOverride(name=k, value=v) for k, v in overrides.items()]
170
+
171
+ def run_before_node_execution(self, *args, node_tags: dict, **kwargs):
172
+ """Move to run directory before executing materializer"""
173
+ if node_tags.get("hamilton.data_saver") is True:
174
+ os.chdir(self.run_directory) # before materialization
175
+
176
+ def run_after_node_execution(
177
+ self, *, node_name: str, node_tags: dict, node_kwargs: dict, result: Any, **kwargs
178
+ ):
179
+ """Move back to init directory after executing materializer.
180
+ Then, save materialization metadata
181
+ """
182
+ if node_tags.get("hamilton.data_saver") is True:
183
+ if "path" in result:
184
+ path = result["path"]
185
+ elif "file_metadata" in result:
186
+ path = result["file_metadata"]["path"]
187
+ else:
188
+ logger.warning(
189
+ f"Materialization result from node={node_name} has no recordable path: {result}. Materializer must have either "
190
+ f"'path' or 'file_metadata' keys."
191
+ )
192
+ self.materializers.append(
193
+ NodeMaterializer(
194
+ source_nodes=list(node_kwargs.keys()),
195
+ path=str(Path(path).resolve()),
196
+ sink=node_tags["hamilton.data_saver.sink"],
197
+ data_saver=node_tags["hamilton.data_saver.classname"],
198
+ )
199
+ )
200
+ os.chdir(self.init_directory) # after materialization
201
+
202
+ def run_after_graph_execution(self, *, success: bool, **kwargs):
203
+ """Encode run metadata as JSON and store in cache"""
204
+ run_data = dict(
205
+ experiment=self.experiment_name,
206
+ run_id=self.run_id,
207
+ run_dir=str(self.run_directory),
208
+ date_completed=datetime.datetime.now().isoformat(),
209
+ success=success,
210
+ graph_hash=self.graph_hash,
211
+ modules=list(self.modules),
212
+ config=self.config,
213
+ inputs=[] if len(self.inputs) == 0 else [asdict(i) for i in self.inputs],
214
+ overrides=[] if len(self.overrides) == 0 else [asdict(o) for o in self.overrides],
215
+ materialized=[asdict(m) for m in self.materializers],
216
+ )
217
+
218
+ run_json_string = json.dumps(run_data, default=str, sort_keys=True)
219
+ self.cache.write(run_json_string, self.run_id)