Guilherme Pereira de Freitas
10/22/2024, 9:13 PMGuilherme Pereira de Freitas
10/22/2024, 9:15 PMdr.cache.run_ids() will be an empty list). I have already discovered hamilton.caching.cache_key.create_cache_key and hamilton.caching.stores.sqlite.SQLiteMetadataStore but as you can see, the SQLite metadata store, via this API, does not give me all the info I need to build the correct cache keys (it's missing the dependencies_data_versions info)
[ins] In [81]: cm = hamilton.caching.stores.sqlite.SQLiteMetadataStore('.hamilton_cache')
[ins] In [82]: cm.get_last_run()
Out[82]:
[{'node_name': 'unpack_pull_config_params',
'code_version': 'e12571afc3d1a8c1b1e45a83b51928d400092ce807294425d739e945c9aefc00',
'data_version': 'sZbMajJ4izr9OBqiaGl_oFdxK_3sLLtcd6oDwQ=='},
{'node_name': 'earliest_transaction_date',
'code_version': 'e12571afc3d1a8c1b1e45a83b51928d400092ce807294425d739e945c9aefc00',
'data_version': '-Bglmx2Xj0-HtonjDSQ7uQ=='},
{'node_name': 'latest_transaction_date',
'code_version': 'e12571afc3d1a8c1b1e45a83b51928d400092ce807294425d739e945c9aefc00',
'data_version': 'HrKZKK-9DWjxMxLOqJmQTw=='},
{'node_name': 'run_date',
'code_version': 'e12571afc3d1a8c1b1e45a83b51928d400092ce807294425d739e945c9aefc00',
'data_version': 'hirf8ZYkDNoT7i2WK8vUiQ=='},
{'node_name': 'rcc_transactions',
'code_version': '4d862064d9deb8e0aa8a8fb8e1e578e0c7a62fcbaf4c947015fcc1345ba240e9',
'data_version': 'BgHnGxP--F2Qt5eIpXSnT-gHERzRW9T2Yc0AAg=='}]Guilherme Pereira de Freitas
10/22/2024, 9:16 PMGuilherme Pereira de Freitas
10/22/2024, 9:18 PMStefan Krawczyk
10/22/2024, 9:19 PMGuilherme Pereira de Freitas
10/22/2024, 9:21 PMStefan Krawczyk
10/22/2024, 9:26 PMStefan Krawczyk
10/22/2024, 9:32 PMStefan Krawczyk
10/22/2024, 9:37 PMcm.get_run_ids() should list all runs -> there is a way to log a run_id when the test runs.
2. then cm.get_run(run_id) will give you the data structure above
3. default store is the filesystem — the file by default will be a pickle and the name of the file is the data version value (we will likely change this in future)
4. you now have node name -> data version mapping. So you could manually then do the following:
from hamilton.caching.stores.file import FileResultStore
data_version = ... # from metadatastore result
result_store = FileResultStore(...)
result = result_store.get(data_version)Stefan Krawczyk
10/22/2024, 9:44 PMThierry Jean
10/22/2024, 10:01 PMA -> B; A -> C; B -> C
After the first `.execute()`:
• the MetadataStore will have an entry for A, B, C
• the ResultStore will have an entry for A, B, C
• an entry in the MetadataStore, must minimally store the cache_key and the data_version for each node. They may contain other metadata
◦ as you've found the SQLiteMetadataStore, there's currently a bug. It doesn't actually return the cache_key value. You can still use the data_version for retrieval though. I have fix planned for this!
• to get the dependencies_data_versions, you can use hamilton.caching.cache_key.decode_key(). You will probably find the Internals section of the tutorial notebook informative
When you don't have access to a driver
from hamilton.caching.stores.sqlite import SQLiteMetadataStore
from hamilton.caching.cache_key import decode_key
metadata_store = SQLiteMetadataStore(...)
run_ids = metadata_stores.get_run_ids()
run_id = run_ids[2]
# run_info is a list of {node_name: ..., code_version: ..., data_version: ...}
run_info = metadata_store.get_run(run_id)
Then, you can use the data_version (for the current node or from a dependency via the decoded cache_key)
from hamilton.caching.stores.file import FileResultStore
# select the right entry
node_name = "foo"
node_foo = next(n for n in run_info if n["node_name"] == node_name)
data_version = node_foo["data_version"]
result_store = FileResultStore(...)
result = result_store.get(data_version)Guilherme Pereira de Freitas
10/22/2024, 10:04 PMStefan Krawczyk
10/22/2024, 10:09 PMThierry Jean
10/22/2024, 10:22 PMcache_key. The relevant value is data_version, which can be retrieved from MetadataStore.get_run() and then used with ResultStore.get(data_version)
If you have questions about the cache_key, I'm happy to help too!
random: it's a side-effect and this may change, but currently values for code_version, data_version, and cache_key should be distinctive enough
• code_version is a hash converted to a string (fixed length); it only includes alphanumeric characters
◦ c9e3377d6c5044944bd89eeb7073c730ee8707627c39906b4156c6411f056f00
• data_version uses base64 encoding (compressed, but variable length), so you'll fin distinctive trailing ==
◦ t-BDcMLikFSNdn4piUKy1mBcKPoEsnsYjUNzWg==
• cache_key is in 3 parts (and can be very long): f"{node_name}-{code_version}-{encoding(dependencies_data_versions)}
◦ raw_data-9d727859b9fd883247c3379d4d25a35af4a56df9d9fde20c75c6375dde631c68-eF6zycvPS7UDAAeiAis=Guilherme Pereira de Freitas
10/22/2024, 10:26 PMGuilherme Pereira de Freitas
10/22/2024, 10:27 PMGuilherme Pereira de Freitas
10/22/2024, 10:27 PMGuilherme Pereira de Freitas
10/22/2024, 10:27 PMThierry Jean
10/22/2024, 10:28 PMGuilherme Pereira de Freitas
10/22/2024, 10:28 PMGuilherme Pereira de Freitas
10/22/2024, 10:29 PMStefan Krawczyk
10/22/2024, 10:32 PMGuilherme Pereira de Freitas
10/22/2024, 10:32 PMham_driver outside of that reading in all the modules with dag nodes) :
@pytest.fixture()
def hamilton_fixtures(request, inputs):
"""Returns a dict of hamilton DAG fixtures.
"""
# load the fixtures
hamilton_vars = set(node.name for node in ham_driver.list_available_variables())
requested_fixtures = set(request.fixturenames)
hamilton_fixtures = hamilton_vars.intersection(requested_fixtures)
<http://logger.info|logger.info>("Loading hamilton_fixtures...")
# fixtures is a dictionary
fixtures = ham_driver.execute(final_vars=hamilton_fixtures, inputs=inputs)
logger.debug(f"Non-NULL hamilton fixtures: {[k for k in fixtures if fixtures[k] is not None]}")
logger.debug(fixtures)
<http://logger.info|logger.info>("[DONE] Loading hamilton_fixtures.")
return fixtures
And then in conftest.py
this_module = sys.modules[__name__]
for node in ham_driver.list_available_variables():
def fixture_wrapper(varname=node.name):
@pytest.fixture
def hamfix(hamilton_fixtures):
return hamilton_fixtures.get(varname)
return hamfix
setattr(this_module, node.name, fixture_wrapper(node.name))
So then you can just go write a test_func(node_A, node_B) and jump straight into the asserts for node_A and node_B.Guilherme Pereira de Freitas
10/22/2024, 10:33 PMGuilherme Pereira de Freitas
10/22/2024, 10:34 PMrequest fixture). So we "compute" the fixtures for every test separately, which is inefficient. However, with caching, that is not so bad.Guilherme Pereira de Freitas
10/22/2024, 10:35 PMGuilherme Pereira de Freitas
10/22/2024, 10:36 PMGuilherme Pereira de Freitas
10/22/2024, 10:36 PMGuilherme Pereira de Freitas
10/23/2024, 4:25 AMStefan Krawczyk
10/23/2024, 5:20 AMStefan Krawczyk
10/23/2024, 5:20 AMGuilherme Pereira de Freitas
10/23/2024, 10:02 PMStefan Krawczyk
10/23/2024, 10:59 PM