diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9695d..1c1bf30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [0.4.8] 2026-02-06 +### Added +- `checksum_any` now supports `Enum` instances. + +### Fixed +- Missing global typing imports. + ## [0.4.7] 2026-02-01 ### Modified - Disk cache functions now detect saving backend when custom load/dump functions are provided. diff --git a/CITATION.cff b/CITATION.cff index c9c71de..45e46ea 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -17,5 +17,5 @@ keywords: - tools - utilities license: MIT -version: 0.4.7 -date-released: '2026-02-01' +version: 0.4.8 +date-released: '2026-02-06' diff --git a/src/pythonwrench/__init__.py b/src/pythonwrench/__init__.py index d9c9a81..a9c3109 100644 --- a/src/pythonwrench/__init__.py +++ b/src/pythonwrench/__init__.py @@ -9,7 +9,7 @@ __license__ = "MIT" __maintainer__ = "Étienne Labbé (Labbeti)" __status__ = "Development" -__version__ = "0.4.7" +__version__ = "0.4.8" # Re-import for language servers @@ -165,6 +165,7 @@ BuiltinScalar, DataclassInstance, EllipsisType, + ListOrTuple, NamedTupleInstance, NoneType, SupportsAdd, @@ -172,10 +173,14 @@ SupportsBool, SupportsDiv, SupportsGetitem, + SupportsGetitem2, SupportsGetitemIterLen, + SupportsGetitemIterLen2, SupportsGetitemLen, + SupportsGetitemLen2, SupportsIterLen, SupportsLen, + SupportsMatmul, SupportsMul, SupportsOr, T_BuiltinNumber, diff --git a/src/pythonwrench/cast.py b/src/pythonwrench/cast.py index ee5ff64..04c1c41 100644 --- a/src/pythonwrench/cast.py +++ b/src/pythonwrench/cast.py @@ -182,7 +182,7 @@ def as_builtin(x: Any, **kwargs) -> Any: ... def as_builtin(x: Any, **kwargs) -> Any: - """Convert an object to a sanitized python builtin equivalent. + """Convert an object to a sanitized python builtin equivalent recursively. This function can be used to sanitize data before saving to a JSON, YAML or CSV file. diff --git a/src/pythonwrench/checksum.py b/src/pythonwrench/checksum.py index 82b6e11..61b48e7 100644 --- a/src/pythonwrench/checksum.py +++ b/src/pythonwrench/checksum.py @@ -6,6 +6,7 @@ import struct import zlib from dataclasses import asdict +from enum import Enum from functools import lru_cache from pathlib import Path from types import FunctionType, MethodType @@ -191,6 +192,11 @@ def checksum_dict(x: dict, **kwargs) -> int: return _checksum_mapping(x, **kwargs) +@register_checksum_fn(Enum) +def checksum_enum(x: Enum, **kwargs) -> int: + return _checksum_iterable((x.__class__, x.name, x.value), **kwargs) + + @register_checksum_fn((list, tuple)) def checksum_list_tuple(x: Union[list, tuple], **kwargs) -> int: return _checksum_iterable(x, **kwargs) diff --git a/src/pythonwrench/disk_cache.py b/src/pythonwrench/disk_cache.py index 9b748a8..4e87bfc 100644 --- a/src/pythonwrench/disk_cache.py +++ b/src/pythonwrench/disk_cache.py @@ -12,6 +12,7 @@ Any, Callable, Dict, + Iterable, Literal, Optional, Tuple, @@ -37,6 +38,8 @@ SavingBackend = Literal["csv", "json", "pickle"] StoreMode = Literal["outputs_only", "outputs_metadata", "outputs_metadata_inputs"] +_DEFAULT_CACHE_STORE_MODE: StoreMode = "outputs_metadata" + class _CacheMeta(TypedDict): datetime: str @@ -63,10 +66,29 @@ def disk_cache_decorator( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Literal["custom"], cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Callable[[Any, Path], Any], cache_load_fn: Callable[[Path], Any], cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode, +) -> Callable[[Callable[P, T]], Callable[P, T]]: ... + + +@overload +def disk_cache_decorator( + fn: None = None, + *, + cache_dpath: Union[str, Path, None] = None, + cache_force: bool = False, + cache_verbose: int = 0, + cache_checksum_fn: ChecksumFn = checksum_any, + cache_saving_backend: SavingBackend, + cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, + cache_dump_fn: None = None, + cache_load_fn: None = None, + cache_enable: bool = True, + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable[[Callable[P, T]], Callable[P, T]]: ... @@ -80,10 +102,11 @@ def disk_cache_decorator( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable[[Callable[P, T]], Callable[P, T]]: ... @@ -97,10 +120,11 @@ def disk_cache_decorator( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Literal["custom"], cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Callable[[Any, Path], Any], cache_load_fn: Callable[[Path], Any], cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable[P, T]: ... @@ -114,10 +138,11 @@ def disk_cache_decorator( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable[P, T]: ... @@ -130,10 +155,11 @@ def disk_cache_decorator( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable: """Decorator to store function output in a cache file. @@ -169,6 +195,7 @@ def disk_cache_decorator( cache_checksum_fn=cache_checksum_fn, cache_saving_backend=cache_saving_backend, cache_fname_fmt=cache_fname_fmt, + cache_fname_fmt_args=cache_fname_fmt_args, cache_dump_fn=cache_dump_fn, cache_load_fn=cache_load_fn, cache_enable=cache_enable, @@ -190,10 +217,30 @@ def disk_cache_call( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Literal["custom"], cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", - cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, - cache_load_fn: Optional[Callable[[Path], Any]] = None, + cache_fname_fmt_args: Optional[Iterable[str]] = None, + cache_dump_fn: Callable[[Any, Path], Any], + cache_load_fn: Callable[[Path], Any], + cache_enable: bool = True, + cache_store_mode: StoreMode, + **kwargs, +) -> T: ... + + +@overload +def disk_cache_call( + fn: Callable[..., T], + *args, + cache_dpath: Union[str, Path, None] = None, + cache_force: bool = False, + cache_verbose: int = 0, + cache_checksum_fn: ChecksumFn = checksum_any, + cache_saving_backend: SavingBackend, + cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, + cache_dump_fn: None = None, + cache_load_fn: None = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, **kwargs, ) -> T: ... @@ -208,10 +255,11 @@ def disk_cache_call( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, **kwargs, ) -> T: ... @@ -225,10 +273,11 @@ def disk_cache_call( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, **kwargs, ) -> T: r"""Call function and store output in a cache file. @@ -266,6 +315,7 @@ def disk_cache_call( cache_checksum_fn=cache_checksum_fn, cache_saving_backend=cache_saving_backend, cache_fname_fmt=cache_fname_fmt, + cache_fname_fmt_args=cache_fname_fmt_args, cache_dump_fn=cache_dump_fn, cache_load_fn=cache_load_fn, cache_enable=cache_enable, @@ -282,10 +332,11 @@ def _disk_cache_impl( cache_checksum_fn: ChecksumFn = checksum_any, cache_saving_backend: Union[SavingBackend, Literal["custom", "auto"]] = "auto", cache_fname_fmt: Union[str, Callable[..., str]] = "{fn_name}_{csum}{suffix}", + cache_fname_fmt_args: Optional[Iterable[str]] = None, cache_dump_fn: Optional[Callable[[Any, Path], Any]] = None, cache_load_fn: Optional[Callable[[Path], Any]] = None, cache_enable: bool = True, - cache_store_mode: StoreMode = "outputs_metadata", + cache_store_mode: StoreMode = _DEFAULT_CACHE_STORE_MODE, ) -> Callable[[Callable[P, T]], Callable[P, T]]: # for backward compatibility if cache_fname_fmt is None: @@ -368,15 +419,30 @@ def _disk_cache_impl_fn(fn: Callable[P, T]) -> Callable[P, T]: @wraps(fn) def _disk_cache_wrapper(*args: P.args, **kwargs: P.kwargs) -> T: checksum_args = fn, args, kwargs - csum = cache_checksum_fn(checksum_args) - inputs = dict(zip(argnames, args)) - inputs.update(kwargs) - cache_fname = cache_fname_fmt( - fn_name=fn_name, - csum=csum, - suffix=suffix, - **inputs, - ) + + kwds = {} + + if cache_fname_fmt_args is None or "fn_name" in cache_fname_fmt_args: + kwds["fn_name"] = fn_name + + if cache_fname_fmt_args is None or "suffix" in cache_fname_fmt_args: + kwds["suffix"] = suffix + + if cache_fname_fmt_args is None or "csum" in cache_fname_fmt_args: + csum = cache_checksum_fn(checksum_args) + kwds["checksum"] = csum + kwds["csum"] = csum + else: + csum = None + + inputs_kwds = { + argname: argval + for argname, argval in zip(argnames, args) + if cache_fname_fmt_args is None or argname in cache_fname_fmt_args + } + kwds.update(inputs_kwds) + + cache_fname = cache_fname_fmt(**kwds) cache_fpath = cache_fn_dpath.joinpath(cache_fname) if not cache_enable: diff --git a/tests/test_checksum.py b/tests/test_checksum.py index 88cc730..171d34c 100644 --- a/tests/test_checksum.py +++ b/tests/test_checksum.py @@ -57,6 +57,23 @@ def test_sets(self) -> None: assert s1 == s2 assert checksum_any(s1) == checksum_any(s2) + def test_enums(self) -> None: + from enum import Enum + + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + ROUGE = RED + + c1 = Color.RED + c2 = Color.GREEN + assert c1 != c2 + assert checksum_any(c1) != checksum_any(c2) + + assert Color.RED == Color.ROUGE + assert checksum_any(Color.RED) == checksum_any(Color.ROUGE) + if __name__ == "__main__": unittest.main()