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,140 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
import inspect
|
|
19
|
+
import shutil
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from typing import override
|
|
25
|
+
except ImportError:
|
|
26
|
+
override = lambda x: x # noqa E731
|
|
27
|
+
|
|
28
|
+
from hamilton.io.data_adapters import DataLoader, DataSaver
|
|
29
|
+
|
|
30
|
+
from .base import ResultStore, StoredResult
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FileResultStore(ResultStore):
|
|
34
|
+
def __init__(self, path: str, create_dir: bool = True) -> None:
|
|
35
|
+
self.path = Path(path)
|
|
36
|
+
self.create_dir = create_dir
|
|
37
|
+
|
|
38
|
+
if self.create_dir:
|
|
39
|
+
self.path.mkdir(exist_ok=True, parents=True)
|
|
40
|
+
|
|
41
|
+
def __getstate__(self) -> dict:
|
|
42
|
+
"""Serialize the `__init__` kwargs to pass in Parallelizable branches
|
|
43
|
+
when using multiprocessing.
|
|
44
|
+
"""
|
|
45
|
+
return {"path": str(self.path)}
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _write_result(file_path: Path, stored_result: StoredResult) -> None:
|
|
49
|
+
file_path.write_bytes(stored_result.save())
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _load_result_from_path(path: Path) -> StoredResult | None:
|
|
53
|
+
try:
|
|
54
|
+
data = path.read_bytes()
|
|
55
|
+
return StoredResult.load(data)
|
|
56
|
+
except FileNotFoundError:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def _path_from_data_version(self, data_version: str) -> Path:
|
|
60
|
+
return self.path.joinpath(data_version)
|
|
61
|
+
|
|
62
|
+
def _materialized_path(self, data_version: str, saver_cls: DataSaver) -> Path:
|
|
63
|
+
# TODO allow a more flexible mechanism to specify file path extension
|
|
64
|
+
return self._path_from_data_version(data_version).with_suffix(f".{saver_cls.name()}")
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def exists(self, data_version: str) -> bool:
|
|
68
|
+
result_path = self._path_from_data_version(data_version)
|
|
69
|
+
return result_path.exists()
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
def set(
|
|
73
|
+
self,
|
|
74
|
+
data_version: str,
|
|
75
|
+
result: Any,
|
|
76
|
+
saver_cls: DataSaver | None = None,
|
|
77
|
+
loader_cls: DataLoader | None = None,
|
|
78
|
+
) -> None:
|
|
79
|
+
# != operator on boolean is XOR
|
|
80
|
+
if bool(saver_cls is not None) != bool(loader_cls is not None):
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"Must pass both `saver` and `loader` or neither. Currently received: "
|
|
83
|
+
f"`saver`: `{saver_cls}`; `loader`: `{loader_cls}`"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if saver_cls is not None:
|
|
87
|
+
# materialized_path
|
|
88
|
+
materialized_path = self._materialized_path(data_version, saver_cls)
|
|
89
|
+
saver_argspec = inspect.getfullargspec(saver_cls.__init__)
|
|
90
|
+
loader_argspec = inspect.getfullargspec(loader_cls.__init__)
|
|
91
|
+
if "file" in saver_argspec.args:
|
|
92
|
+
saver = saver_cls(file=str(materialized_path.absolute()))
|
|
93
|
+
elif "path" in saver_argspec.args:
|
|
94
|
+
saver = saver_cls(path=str(materialized_path.absolute()))
|
|
95
|
+
else:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Saver [{saver_cls.name()}] must have either `file` or `path` as an argument."
|
|
98
|
+
)
|
|
99
|
+
if "file" in loader_argspec.args:
|
|
100
|
+
loader = loader_cls(file=str(materialized_path.absolute()))
|
|
101
|
+
elif "path" in loader_argspec.args:
|
|
102
|
+
loader = loader_cls(path=str(materialized_path.absolute()))
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Loader [{loader_cls.name()}] must have either `file` or `path` as an argument."
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
saver = None
|
|
109
|
+
loader = None
|
|
110
|
+
|
|
111
|
+
self.path.mkdir(exist_ok=True)
|
|
112
|
+
result_path = self._path_from_data_version(data_version)
|
|
113
|
+
stored_result = StoredResult.new(value=result, saver=saver, loader=loader)
|
|
114
|
+
self._write_result(result_path, stored_result)
|
|
115
|
+
|
|
116
|
+
@override
|
|
117
|
+
def get(self, data_version: str) -> Any | None:
|
|
118
|
+
result_path = self._path_from_data_version(data_version)
|
|
119
|
+
stored_result = self._load_result_from_path(result_path)
|
|
120
|
+
|
|
121
|
+
if stored_result is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
return stored_result.value
|
|
125
|
+
|
|
126
|
+
@override
|
|
127
|
+
def delete(self, data_version: str) -> None:
|
|
128
|
+
result_path = self._path_from_data_version(data_version)
|
|
129
|
+
result_path.unlink(missing_ok=True)
|
|
130
|
+
|
|
131
|
+
@override
|
|
132
|
+
def delete_all(self) -> None:
|
|
133
|
+
shutil.rmtree(self.path)
|
|
134
|
+
self.path.mkdir(exist_ok=True)
|
|
135
|
+
|
|
136
|
+
def delete_expired(self) -> None:
|
|
137
|
+
for file_path in self.path.iterdir():
|
|
138
|
+
stored_result = self._load_result_from_path(file_path)
|
|
139
|
+
if stored_result and stored_result.expired:
|
|
140
|
+
file_path.unlink(missing_ok=True)
|
|
@@ -0,0 +1,297 @@
|
|
|
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 collections.abc import Sequence
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from typing import override
|
|
23
|
+
except ImportError:
|
|
24
|
+
override = lambda x: x # noqa E731
|
|
25
|
+
|
|
26
|
+
from hamilton.caching.cache_key import decode_key
|
|
27
|
+
|
|
28
|
+
from .base import MetadataStore, ResultStore, StoredResult
|
|
29
|
+
from .file import FileResultStore
|
|
30
|
+
from .sqlite import SQLiteMetadataStore
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InMemoryMetadataStore(MetadataStore):
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._data_versions: dict[str, str] = {} # {cache_key: data_version}
|
|
36
|
+
self._cache_keys_by_run: dict[str, list[str]] = {} # {run_id: [cache_key]}
|
|
37
|
+
self._run_ids: list[str] = []
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def __len__(self) -> int:
|
|
41
|
+
"""Number of unique ``cache_key`` values."""
|
|
42
|
+
return len(self._data_versions.keys())
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def exists(self, cache_key: str) -> bool:
|
|
46
|
+
"""Indicate if ``cache_key`` exists and it can retrieve a ``data_version``."""
|
|
47
|
+
return cache_key in self._data_versions.keys()
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
def initialize(self, run_id: str) -> None:
|
|
51
|
+
"""Set up and log the beginning of the run."""
|
|
52
|
+
self._cache_keys_by_run[run_id] = []
|
|
53
|
+
self._run_ids.append(run_id)
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def set(self, cache_key: str, data_version: str, run_id: str, **kwargs) -> Any | None:
|
|
57
|
+
"""Set the ``data_version`` for ``cache_key`` and associate it with the ``run_id``."""
|
|
58
|
+
self._data_versions[cache_key] = data_version
|
|
59
|
+
self._cache_keys_by_run[run_id].append(cache_key)
|
|
60
|
+
|
|
61
|
+
@override
|
|
62
|
+
def get(self, cache_key: str) -> str | None:
|
|
63
|
+
"""Retrieve the ``data_version`` for ``cache_key``."""
|
|
64
|
+
return self._data_versions.get(cache_key, None)
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def delete(self, cache_key: str) -> None:
|
|
68
|
+
"""Delete the ``data_version`` for ``cache_key``."""
|
|
69
|
+
del self._data_versions[cache_key]
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
def delete_all(self) -> None:
|
|
73
|
+
"""Delete all stored metadata."""
|
|
74
|
+
self._data_versions.clear()
|
|
75
|
+
|
|
76
|
+
def persist_to(self, metadata_store: MetadataStore | None = None) -> None:
|
|
77
|
+
"""Persist in-memory metadata using another MetadataStore implementation.
|
|
78
|
+
|
|
79
|
+
:param metadata_store: MetadataStore implementation to use for persistence.
|
|
80
|
+
If None, a SQLiteMetadataStore is created with the default path "./.hamilton_cache".
|
|
81
|
+
|
|
82
|
+
.. code-block:: python
|
|
83
|
+
|
|
84
|
+
from hamilton import driver
|
|
85
|
+
from hamilton.caching.stores.sqlite import SQLiteMetadataStore
|
|
86
|
+
from hamilton.caching.stores.memory import InMemoryMetadataStore
|
|
87
|
+
import my_dataflow
|
|
88
|
+
|
|
89
|
+
dr = (
|
|
90
|
+
driver.Builder()
|
|
91
|
+
.with_modules(my_dataflow)
|
|
92
|
+
.with_cache(metadata_store=InMemoryMetadataStore())
|
|
93
|
+
.build()
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# execute the Driver several time. This will populate the in-memory metadata store
|
|
97
|
+
dr.execute(...)
|
|
98
|
+
|
|
99
|
+
# persist to disk in-memory metadata
|
|
100
|
+
dr.cache.metadata_store.persist_to(SQLiteMetadataStore(path="./.hamilton_cache"))
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
if metadata_store is None:
|
|
104
|
+
metadata_store = SQLiteMetadataStore(path="./.hamilton_cache")
|
|
105
|
+
|
|
106
|
+
for run_id in self._run_ids:
|
|
107
|
+
metadata_store.initialize(run_id)
|
|
108
|
+
|
|
109
|
+
for run_id, cache_keys in self._cache_keys_by_run.items():
|
|
110
|
+
for cache_key in cache_keys:
|
|
111
|
+
data_version = self._data_versions[cache_key]
|
|
112
|
+
metadata_store.set(
|
|
113
|
+
cache_key=cache_key,
|
|
114
|
+
data_version=data_version,
|
|
115
|
+
run_id=run_id,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def load_from(cls, metadata_store: MetadataStore) -> "InMemoryMetadataStore":
|
|
120
|
+
"""Load in-memory metadata from another MetadataStore instance.
|
|
121
|
+
|
|
122
|
+
:param metadata_store: MetadataStore instance to load from.
|
|
123
|
+
:return: InMemoryMetadataStore copy of the ``metadata_store``.
|
|
124
|
+
|
|
125
|
+
.. code-block:: python
|
|
126
|
+
|
|
127
|
+
from hamilton import driver
|
|
128
|
+
from hamilton.caching.stores.sqlite import SQLiteMetadataStore
|
|
129
|
+
from hamilton.caching.stores.memory import InMemoryMetadataStore
|
|
130
|
+
import my_dataflow
|
|
131
|
+
|
|
132
|
+
sqlite_metadata_store = SQLiteMetadataStore(path="./.hamilton_cache")
|
|
133
|
+
in_memory_metadata_store = InMemoryMetadataStore.load_from(sqlite_metadata_store)
|
|
134
|
+
|
|
135
|
+
# create the Driver with the in-memory metadata store
|
|
136
|
+
dr = (
|
|
137
|
+
driver.Builder()
|
|
138
|
+
.with_modules(my_dataflow)
|
|
139
|
+
.with_cache(metadata_store=in_memory_metadata_store)
|
|
140
|
+
.build()
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
in_memory_metadata_store = InMemoryMetadataStore()
|
|
145
|
+
|
|
146
|
+
for run_id in metadata_store.get_run_ids():
|
|
147
|
+
in_memory_metadata_store.initialize(run_id)
|
|
148
|
+
|
|
149
|
+
for node_metadata in metadata_store.get_run(run_id):
|
|
150
|
+
in_memory_metadata_store.set(
|
|
151
|
+
cache_key=node_metadata["cache_key"],
|
|
152
|
+
data_version=node_metadata["data_version"],
|
|
153
|
+
run_id=run_id,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return in_memory_metadata_store
|
|
157
|
+
|
|
158
|
+
@override
|
|
159
|
+
def get_run_ids(self) -> list[str]:
|
|
160
|
+
"""Return a list of all ``run_id`` values stored."""
|
|
161
|
+
return self._run_ids
|
|
162
|
+
|
|
163
|
+
@override
|
|
164
|
+
def get_run(self, run_id: str) -> list[dict[str, str]]:
|
|
165
|
+
"""Return a list of node metadata associated with a run."""
|
|
166
|
+
if self._cache_keys_by_run.get(run_id, None) is None:
|
|
167
|
+
raise IndexError(f"Run ID not found: {run_id}")
|
|
168
|
+
|
|
169
|
+
nodes_metadata = []
|
|
170
|
+
for cache_key in self._cache_keys_by_run[run_id]:
|
|
171
|
+
decoded_key = decode_key(cache_key)
|
|
172
|
+
nodes_metadata.append(
|
|
173
|
+
dict(
|
|
174
|
+
cache_key=cache_key,
|
|
175
|
+
data_version=self._data_versions[cache_key],
|
|
176
|
+
node_name=decoded_key["node_name"],
|
|
177
|
+
code_version=decoded_key["code_version"],
|
|
178
|
+
dependencies_data_versions=decoded_key["dependencies_data_versions"],
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return nodes_metadata
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class InMemoryResultStore(ResultStore):
|
|
186
|
+
def __init__(self, persist_on_exit: bool = False) -> None:
|
|
187
|
+
self._results: dict[str, StoredResult] = {} # {data_version: result}
|
|
188
|
+
|
|
189
|
+
@override
|
|
190
|
+
def exists(self, data_version: str) -> bool:
|
|
191
|
+
return data_version in self._results.keys()
|
|
192
|
+
|
|
193
|
+
# TODO handle materialization
|
|
194
|
+
@override
|
|
195
|
+
def set(self, data_version: str, result: Any, **kwargs) -> None:
|
|
196
|
+
self._results[data_version] = StoredResult.new(value=result)
|
|
197
|
+
|
|
198
|
+
@override
|
|
199
|
+
def get(self, data_version: str) -> Any | None:
|
|
200
|
+
stored_result = self._results.get(data_version, None)
|
|
201
|
+
if stored_result is None:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
return stored_result.value
|
|
205
|
+
|
|
206
|
+
@override
|
|
207
|
+
def delete(self, data_version: str) -> None:
|
|
208
|
+
del self._results[data_version]
|
|
209
|
+
|
|
210
|
+
@override
|
|
211
|
+
def delete_all(self) -> None:
|
|
212
|
+
self._results.clear()
|
|
213
|
+
|
|
214
|
+
def delete_expired(self) -> None:
|
|
215
|
+
to_delete = [
|
|
216
|
+
data_version
|
|
217
|
+
for data_version, stored_result in self._results.items()
|
|
218
|
+
if stored_result.expired
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
# first collect keys then delete because you can delete from dictionary
|
|
222
|
+
# as you iterate through it
|
|
223
|
+
for data_version in to_delete:
|
|
224
|
+
self.delete(data_version)
|
|
225
|
+
|
|
226
|
+
def persist_to(self, result_store: ResultStore | None = None) -> None:
|
|
227
|
+
"""Persist in-memory results using another ``ResultStore`` implementation.
|
|
228
|
+
|
|
229
|
+
:param result_store: ResultStore implementation to use for persistence.
|
|
230
|
+
If None, a FileResultStore is created with the default path "./.hamilton_cache".
|
|
231
|
+
"""
|
|
232
|
+
if result_store is None:
|
|
233
|
+
result_store = FileResultStore(path="./.hamilton_cache")
|
|
234
|
+
|
|
235
|
+
for data_version, stored_result in self._results.items():
|
|
236
|
+
result_store.set(data_version, stored_result.value)
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def load_from(
|
|
240
|
+
cls,
|
|
241
|
+
result_store: ResultStore,
|
|
242
|
+
metadata_store: MetadataStore | None = None,
|
|
243
|
+
data_versions: Sequence[str] | None = None,
|
|
244
|
+
) -> "InMemoryResultStore":
|
|
245
|
+
"""Load in-memory results from another ResultStore instance.
|
|
246
|
+
|
|
247
|
+
Since result stores do not store an index of their keys, you must provide a
|
|
248
|
+
``MetadataStore`` instance or a list of ``data_version`` for which results
|
|
249
|
+
should be loaded in memory.
|
|
250
|
+
|
|
251
|
+
:param result_store: ``ResultStore`` instance to load results from.
|
|
252
|
+
:param metadata_store: ``MetadataStore`` instance from which all ``data_version`` are retrieved.
|
|
253
|
+
:return: InMemoryResultStore copy of the ``result_store``.
|
|
254
|
+
|
|
255
|
+
.. code-block:: python
|
|
256
|
+
|
|
257
|
+
from hamilton import driver
|
|
258
|
+
from hamilton.caching.stores.sqlite import SQLiteMetadataStore
|
|
259
|
+
from hamilton.caching.stores.memory import InMemoryMetadataStore
|
|
260
|
+
import my_dataflow
|
|
261
|
+
|
|
262
|
+
sqlite_metadata_store = SQLiteMetadataStore(path="./.hamilton_cache")
|
|
263
|
+
in_memory_metadata_store = InMemoryMetadataStore.load_from(sqlite_metadata_store)
|
|
264
|
+
|
|
265
|
+
# create the Driver with the in-memory metadata store
|
|
266
|
+
dr = (
|
|
267
|
+
driver.Builder()
|
|
268
|
+
.with_modules(my_dataflow)
|
|
269
|
+
.with_cache(metadata_store=in_memory_metadata_store)
|
|
270
|
+
.build()
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
"""
|
|
275
|
+
if metadata_store is None and data_versions is None:
|
|
276
|
+
raise ValueError(
|
|
277
|
+
"A `metadata_store` or `data_versions` must be provided to load results."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
in_memory_result_store = InMemoryResultStore()
|
|
281
|
+
|
|
282
|
+
data_versions_to_retrieve = set()
|
|
283
|
+
if data_versions is not None:
|
|
284
|
+
data_versions_to_retrieve.update(data_versions)
|
|
285
|
+
|
|
286
|
+
if metadata_store is not None:
|
|
287
|
+
for run_id in metadata_store.get_run_ids():
|
|
288
|
+
for node_metadata in metadata_store.get_run(run_id):
|
|
289
|
+
data_versions_to_retrieve.add(node_metadata["data_version"])
|
|
290
|
+
|
|
291
|
+
for data_version in data_versions_to_retrieve:
|
|
292
|
+
# TODO disambiguate "result is None" from the sentinel value when `data_version`
|
|
293
|
+
# is not found in `result_store`.
|
|
294
|
+
result = result_store.get(data_version)
|
|
295
|
+
in_memory_result_store.set(data_version, result)
|
|
296
|
+
|
|
297
|
+
return in_memory_result_store
|