fix(cache): use a reproducible function hash for cache_in_file keys

Build the cache_in_file key from an MD5 hash of the wrapped function
instead of inspect.getsource(). Reading the source fails for functions
whose source is not on disk, and the key could go stale when the file
changes after import.

The hash covers what decides the result besides the arguments: the
qualified name, the bytecode with the names of called functions,
attributes and globals, the constants including nested functions, the
defaults and the values captured from an enclosing function. Wrappers made
with functools.wraps are unwrapped. Code objects, functions and sets are
hashed without memory addresses or hash-seed dependent order, so the key
stays the same across restarts and the file cache keeps hitting.

Additional fixes
- Build the key from the arguments bound to the signature, so *args,
  **kwargs and the arguments of wrapped functions are part of it.
- Apply control parameters given at call time to that call only; they no
  longer change the decorator settings for later calls.
- Apply all control parameters of a call, so until_date is no longer
  dropped when force_update is also passed; keep the precedence
  until_datetime > with_ttl > until_date.
- Recreate an unreadable cache file in that call only, instead of
  disabling the cache for the rest of the process.
- Pass force_update in two provider tests that relied on a force_update
  left over from an earlier test.
- Fix "Calback" typo in cache_energy_management_store_callback docstring.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bobby Noelte
2026-11-09 16:16:26 +01:00
co-authored by Claude Opus 5.5
parent 38b07f5a5b
commit 2c58874c52
4 changed files with 482 additions and 72 deletions
+3 -2
View File
@@ -178,11 +178,12 @@ class TestElecPriceAkkudokor:
requests.exceptions.HTTPError if exception else None
)
mock_get.return_value = mock_response
# force_update: bypass the file cache, the request must be made in every case.
if exception:
with pytest.raises(exception):
provider._request_forecast()
provider._request_forecast(force_update=True)
else:
provider._request_forecast()
provider._request_forecast(force_update=True)
@pytest.mark.asyncio