mcrotk-cache-utils 0.1.0__tar.gz

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 (25) hide show
  1. mcrotk_cache_utils-0.1.0/LICENSE +21 -0
  2. mcrotk_cache_utils-0.1.0/PKG-INFO +190 -0
  3. mcrotk_cache_utils-0.1.0/README.md +156 -0
  4. mcrotk_cache_utils-0.1.0/pyproject.toml +94 -0
  5. mcrotk_cache_utils-0.1.0/pyproject.toml.orig +85 -0
  6. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/__init__.py +36 -0
  7. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/copying/__init__.py +1 -0
  8. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/copying/copy_handling.py +231 -0
  9. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/copying/instance_copying.py +54 -0
  10. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/__init__.py +1 -0
  11. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/cache_controls.py +82 -0
  12. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/function_caching.py +94 -0
  13. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/method_caching.py +204 -0
  14. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/rng_caching.py +194 -0
  15. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/support/__init__.py +1 -0
  16. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/support/decorator_options.py +55 -0
  17. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/decorators/support/placement_checks.py +61 -0
  18. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/__init__.py +1 -0
  19. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/cache_keys.py +217 -0
  20. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/converters/__init__.py +12 -0
  21. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/converters/arrays.py +47 -0
  22. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/converters/containers.py +45 -0
  23. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/converters/pandas_objects.py +134 -0
  24. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/keys/passthrough.py +169 -0
  25. mcrotk_cache_utils-0.1.0/src/mcrotk_cache_utils/py.typed +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael C. Rotkowitz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcrotk-cache-utils
3
+ Version: 0.1.0
4
+ Summary: Caching decorators for Python methods and functions: a cache per instance, easy omission or passthrough of arguments such as RNGs, safe copying.
5
+ Keywords: cache,caching,memoization,decorator,cachetools,numpy,random number generator,dataclass
6
+ Author: Michael C. Rotkowitz
7
+ Author-email: Michael C. Rotkowitz <mcrotk@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Dist: cachetools>=8
24
+ Requires-Dist: numpy>=1.23.2 ; extra == 'numpy'
25
+ Requires-Dist: pandas>=1.5.0 ; extra == 'pandas'
26
+ Requires-Python: >=3.11
27
+ Project-URL: Homepage, https://github.com/mcrotk/mcrotk_cache_utils
28
+ Project-URL: Documentation, https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md
29
+ Project-URL: Changelog, https://github.com/mcrotk/mcrotk_cache_utils/releases
30
+ Project-URL: Issues, https://github.com/mcrotk/mcrotk_cache_utils/issues
31
+ Provides-Extra: numpy
32
+ Provides-Extra: pandas
33
+ Description-Content-Type: text/markdown
34
+
35
+ # mcrotk-cache-utils
36
+
37
+ Caching decorators for Python methods, built on [cachetools](https://github.com/tkem/cachetools): a cache for each instance, with nothing to set up, and simple options to leave an argument out of the key or pass it through to the result. Functions, static methods and class methods get the same handling of keys and arguments.
38
+
39
+ ## What it's for
40
+
41
+ The standard tools (`functools.cache` and `lru_cache`, and cachetools) leave several common cases awkward or impossible:
42
+
43
+ - **A cache for each instance**
44
+ - `functools.cache` and `lru_cache` keep one cache for the whole class, keyed on `self`. Instances must be hashable (a regular dataclass isn't), and every instance stays in memory as long as the cache does.
45
+ - `cachetools.cachedmethod` needs each instance to create and hold its own cache: on a dataclass, a field to keep out of `==` and `repr`, and on a frozen dataclass, a workaround to set it.
46
+ - A cache held by the instance is shared by `copy.copy`, so a copy that's then changed gets the original's results. With `cachetools.cachedmethod`, a copy made after a call even runs the method on the original.
47
+ - **Choosing what goes in the key**, for methods and functions alike
48
+ - Arguments are keyed as passed, so `f(1)` and `f(x=1)` are separate entries.
49
+ - Leaving an argument out of the key (e.g., a `verbose` flag) isn't an option in `functools`. In cachetools it means writing a key function that repeats the method's signature, including its defaults, handles the argument whether it's passed by position or by name, and has to be kept in step whenever the method changes.
50
+ - Arguments returned with the result (a state, a counter) can't simply be left out of the key: a hit would return the value stored by an earlier call, not the one passed in this call.
51
+ - Random number generators: the recommended practice ([SPEC 7](https://scientific-python.org/specs/spec-0007/)) is to pass a numpy `Generator` (or a seed) into each function that needs random numbers, which advances it in place. As part of a key, a generator is compared by identity, not by its state, so calls with different generators never share a cached result.
52
+ - **Unhashable arguments:** numpy arrays, lists and dicts can't be used at all.
53
+
54
+ What this library does:
55
+
56
+ - **[A cache per instance](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#caching-methods)**, with nothing to declare. `@cached_method` creates it on first use, outside the fields, and it goes with the instance. Instances needn't be hashable, and `==`, `repr` and frozen dataclasses are unaffected.
57
+ - **[Keys from the bound arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-keys).** `f(1)` and `f(x=1)` share an entry, with defaults filled in.
58
+ - **[Omitting arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-keys)** with one option: `omit="verbose"`, by name or position, however the argument is passed. Use it for arguments that don't affect the result, or whose effect shouldn't produce separate cache entries.
59
+ - **[Passthrough](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#passing-arguments-through)** for any argument returned with the result. On a hit, the stored result comes back with the value passed in this call, not the one stored with it.
60
+ - **[Random number generators](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#random-number-generators):** a common case of omission, and the original motivation for this library, with its own decorator. `@rng_omit_cached_method` leaves the generator out of the key, so a hit draws nothing. `@rng_passthrough_cached_method` caches methods written to also return the generator. On a hit, it returns the generator passed in this call in place of the stored one.
61
+ - **[Unhashable arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#unhashable-arguments).** numpy arrays (keyed by their contents), lists, dicts and sets work automatically. Converters handle anything else, with ready-made ones for pandas objects.
62
+ - **[Safe copies](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#copying-and-pickling).** `copy.copy` and `copy.deepcopy` give the copy its own cache, empty or copied, and its cached methods run on the copy.
63
+ - **[Functions too](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#functions-static-methods-and-class-methods).** `@cached_function` and function versions of the RNG decorators, with the same keys and options, also for static and class methods.
64
+ - **[cachetools underneath](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-types-sizes-and-statistics):** its cache types (`LRUCache`, `TTLCache`, ...), locks and hit/miss statistics.
65
+ - **[Typed](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#type-checking).** Decorated methods and functions keep their signatures for pyright, mypy and editors.
66
+
67
+ ## Installation
68
+
69
+ Not on PyPI yet; until it is, install from GitHub:
70
+
71
+ ```sh
72
+ pip install "mcrotk-cache-utils @ git+https://github.com/mcrotk/mcrotk_cache_utils"
73
+ pip install "mcrotk-cache-utils[numpy] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # for the RNG passthrough decorators
74
+ pip install "mcrotk-cache-utils[pandas] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # for the pandas key helpers
75
+ pip install "mcrotk-cache-utils[numpy,pandas] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # both
76
+ ```
77
+
78
+ Requires Python 3.11+ and cachetools 8+ (installed with it); numpy 1.23.2+ and pandas 1.5+ only for the features that use them.
79
+
80
+ ## Examples
81
+
82
+ ### Omitting arguments
83
+
84
+ ```python
85
+ from dataclasses import dataclass
86
+
87
+ from mcrotk_cache_utils import cached_method
88
+
89
+
90
+ @dataclass
91
+ class Portfolio:
92
+ weights: list[float]
93
+
94
+ @cached_method(omit="verbose")
95
+ def risk(self, horizon: int, verbose: bool = False) -> float:
96
+ if verbose:
97
+ print("computing...")
98
+ return sum(w * w for w in self.weights) * horizon
99
+
100
+
101
+ p = Portfolio([0.5, 0.5])
102
+ p.risk(10) # computed
103
+ p.risk(horizon=10) # cached: the same call
104
+ p.risk(10, verbose=True) # cached: verbose isn't part of the key
105
+ Portfolio([0.5, 0.5]).risk(10) # computed: another instance, its own cache
106
+ ```
107
+
108
+ ### Omitting a random number generator
109
+
110
+ A special case of omission, common enough to get its own decorator: `@rng_omit_cached_method` is `@cached_method(omit="rng")`.
111
+
112
+ ```python
113
+ import numpy as np
114
+
115
+ from mcrotk_cache_utils import rng_omit_cached_method
116
+
117
+
118
+ class Simulation:
119
+ def __init__(self, n_paths: int):
120
+ self.n_paths = n_paths
121
+
122
+ @rng_omit_cached_method
123
+ def mean_path(self, horizon: int, rng=None) -> np.ndarray:
124
+ rng = np.random.default_rng(rng)
125
+ paths = rng.normal(size=(self.n_paths, horizon)).cumsum(axis=1)
126
+ return paths.mean(axis=0)
127
+
128
+
129
+ sim = Simulation(10_000)
130
+ rng = np.random.default_rng(0) # one generator, made once and passed to every call
131
+ short = sim.mean_path(10, rng) # computed: the draws advance rng
132
+ long = sim.mean_path(250, rng) # computed: continues the same stream
133
+ short = sim.mean_path(10, rng) # cached: nothing is drawn, rng is unchanged
134
+ ```
135
+
136
+ ### Passing arguments through
137
+
138
+ For an argument returned with the result. Here, a count of evaluations, which a hit shouldn't add to:
139
+
140
+ ```python
141
+ from dataclasses import dataclass
142
+
143
+ from mcrotk_cache_utils import cached_method
144
+
145
+
146
+ @dataclass
147
+ class Model:
148
+ data: list[float]
149
+
150
+ @cached_method(passthrough={"n_evals": -1})
151
+ def loss(self, center: float, n_evals: int = 0) -> tuple[float, int]:
152
+ value = sum((x - center) ** 2 for x in self.data) # stands in for an expensive computation
153
+ return value, n_evals + 1
154
+
155
+
156
+ model = Model([1.0, 2.0, 4.0])
157
+ loss, n_evals = model.loss(2.0) # computed: n_evals == 1
158
+ loss, n_evals = model.loss(2.5, n_evals) # computed: n_evals == 2
159
+ loss, n_evals = model.loss(2.0, n_evals) # cached: n_evals == 2, as passed in
160
+ ```
161
+
162
+ Left in the key, `n_evals` would make every call a miss; with `omit=`, the last call would return the stored `n_evals == 1`, setting the count back. With `passthrough=`, the cached result is used without resetting the counter.
163
+
164
+ ### Array arguments, and functions
165
+
166
+ `@cached_function` for functions; numpy arrays are keyed by their contents:
167
+
168
+ ```python
169
+ import numpy as np
170
+
171
+ from mcrotk_cache_utils import cached_function
172
+
173
+
174
+ @cached_function
175
+ def normalized(values: np.ndarray) -> np.ndarray:
176
+ return values / values.sum()
177
+
178
+
179
+ normalized(np.arange(5.0)) # computed
180
+ normalized(np.arange(5.0)) # cached: an equal array
181
+ ```
182
+
183
+ ## Documentation
184
+
185
+ - **[User guide](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md)**: how each part works, with examples and caveats.
186
+ - **[API reference](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/reference.md)**: every function and option.
187
+
188
+ ## License
189
+
190
+ Released under the MIT License; see [LICENSE](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/LICENSE) for the full text.
@@ -0,0 +1,156 @@
1
+ # mcrotk-cache-utils
2
+
3
+ Caching decorators for Python methods, built on [cachetools](https://github.com/tkem/cachetools): a cache for each instance, with nothing to set up, and simple options to leave an argument out of the key or pass it through to the result. Functions, static methods and class methods get the same handling of keys and arguments.
4
+
5
+ ## What it's for
6
+
7
+ The standard tools (`functools.cache` and `lru_cache`, and cachetools) leave several common cases awkward or impossible:
8
+
9
+ - **A cache for each instance**
10
+ - `functools.cache` and `lru_cache` keep one cache for the whole class, keyed on `self`. Instances must be hashable (a regular dataclass isn't), and every instance stays in memory as long as the cache does.
11
+ - `cachetools.cachedmethod` needs each instance to create and hold its own cache: on a dataclass, a field to keep out of `==` and `repr`, and on a frozen dataclass, a workaround to set it.
12
+ - A cache held by the instance is shared by `copy.copy`, so a copy that's then changed gets the original's results. With `cachetools.cachedmethod`, a copy made after a call even runs the method on the original.
13
+ - **Choosing what goes in the key**, for methods and functions alike
14
+ - Arguments are keyed as passed, so `f(1)` and `f(x=1)` are separate entries.
15
+ - Leaving an argument out of the key (e.g., a `verbose` flag) isn't an option in `functools`. In cachetools it means writing a key function that repeats the method's signature, including its defaults, handles the argument whether it's passed by position or by name, and has to be kept in step whenever the method changes.
16
+ - Arguments returned with the result (a state, a counter) can't simply be left out of the key: a hit would return the value stored by an earlier call, not the one passed in this call.
17
+ - Random number generators: the recommended practice ([SPEC 7](https://scientific-python.org/specs/spec-0007/)) is to pass a numpy `Generator` (or a seed) into each function that needs random numbers, which advances it in place. As part of a key, a generator is compared by identity, not by its state, so calls with different generators never share a cached result.
18
+ - **Unhashable arguments:** numpy arrays, lists and dicts can't be used at all.
19
+
20
+ What this library does:
21
+
22
+ - **[A cache per instance](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#caching-methods)**, with nothing to declare. `@cached_method` creates it on first use, outside the fields, and it goes with the instance. Instances needn't be hashable, and `==`, `repr` and frozen dataclasses are unaffected.
23
+ - **[Keys from the bound arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-keys).** `f(1)` and `f(x=1)` share an entry, with defaults filled in.
24
+ - **[Omitting arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-keys)** with one option: `omit="verbose"`, by name or position, however the argument is passed. Use it for arguments that don't affect the result, or whose effect shouldn't produce separate cache entries.
25
+ - **[Passthrough](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#passing-arguments-through)** for any argument returned with the result. On a hit, the stored result comes back with the value passed in this call, not the one stored with it.
26
+ - **[Random number generators](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#random-number-generators):** a common case of omission, and the original motivation for this library, with its own decorator. `@rng_omit_cached_method` leaves the generator out of the key, so a hit draws nothing. `@rng_passthrough_cached_method` caches methods written to also return the generator. On a hit, it returns the generator passed in this call in place of the stored one.
27
+ - **[Unhashable arguments](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#unhashable-arguments).** numpy arrays (keyed by their contents), lists, dicts and sets work automatically. Converters handle anything else, with ready-made ones for pandas objects.
28
+ - **[Safe copies](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#copying-and-pickling).** `copy.copy` and `copy.deepcopy` give the copy its own cache, empty or copied, and its cached methods run on the copy.
29
+ - **[Functions too](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#functions-static-methods-and-class-methods).** `@cached_function` and function versions of the RNG decorators, with the same keys and options, also for static and class methods.
30
+ - **[cachetools underneath](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#cache-types-sizes-and-statistics):** its cache types (`LRUCache`, `TTLCache`, ...), locks and hit/miss statistics.
31
+ - **[Typed](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md#type-checking).** Decorated methods and functions keep their signatures for pyright, mypy and editors.
32
+
33
+ ## Installation
34
+
35
+ Not on PyPI yet; until it is, install from GitHub:
36
+
37
+ ```sh
38
+ pip install "mcrotk-cache-utils @ git+https://github.com/mcrotk/mcrotk_cache_utils"
39
+ pip install "mcrotk-cache-utils[numpy] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # for the RNG passthrough decorators
40
+ pip install "mcrotk-cache-utils[pandas] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # for the pandas key helpers
41
+ pip install "mcrotk-cache-utils[numpy,pandas] @ git+https://github.com/mcrotk/mcrotk_cache_utils" # both
42
+ ```
43
+
44
+ Requires Python 3.11+ and cachetools 8+ (installed with it); numpy 1.23.2+ and pandas 1.5+ only for the features that use them.
45
+
46
+ ## Examples
47
+
48
+ ### Omitting arguments
49
+
50
+ ```python
51
+ from dataclasses import dataclass
52
+
53
+ from mcrotk_cache_utils import cached_method
54
+
55
+
56
+ @dataclass
57
+ class Portfolio:
58
+ weights: list[float]
59
+
60
+ @cached_method(omit="verbose")
61
+ def risk(self, horizon: int, verbose: bool = False) -> float:
62
+ if verbose:
63
+ print("computing...")
64
+ return sum(w * w for w in self.weights) * horizon
65
+
66
+
67
+ p = Portfolio([0.5, 0.5])
68
+ p.risk(10) # computed
69
+ p.risk(horizon=10) # cached: the same call
70
+ p.risk(10, verbose=True) # cached: verbose isn't part of the key
71
+ Portfolio([0.5, 0.5]).risk(10) # computed: another instance, its own cache
72
+ ```
73
+
74
+ ### Omitting a random number generator
75
+
76
+ A special case of omission, common enough to get its own decorator: `@rng_omit_cached_method` is `@cached_method(omit="rng")`.
77
+
78
+ ```python
79
+ import numpy as np
80
+
81
+ from mcrotk_cache_utils import rng_omit_cached_method
82
+
83
+
84
+ class Simulation:
85
+ def __init__(self, n_paths: int):
86
+ self.n_paths = n_paths
87
+
88
+ @rng_omit_cached_method
89
+ def mean_path(self, horizon: int, rng=None) -> np.ndarray:
90
+ rng = np.random.default_rng(rng)
91
+ paths = rng.normal(size=(self.n_paths, horizon)).cumsum(axis=1)
92
+ return paths.mean(axis=0)
93
+
94
+
95
+ sim = Simulation(10_000)
96
+ rng = np.random.default_rng(0) # one generator, made once and passed to every call
97
+ short = sim.mean_path(10, rng) # computed: the draws advance rng
98
+ long = sim.mean_path(250, rng) # computed: continues the same stream
99
+ short = sim.mean_path(10, rng) # cached: nothing is drawn, rng is unchanged
100
+ ```
101
+
102
+ ### Passing arguments through
103
+
104
+ For an argument returned with the result. Here, a count of evaluations, which a hit shouldn't add to:
105
+
106
+ ```python
107
+ from dataclasses import dataclass
108
+
109
+ from mcrotk_cache_utils import cached_method
110
+
111
+
112
+ @dataclass
113
+ class Model:
114
+ data: list[float]
115
+
116
+ @cached_method(passthrough={"n_evals": -1})
117
+ def loss(self, center: float, n_evals: int = 0) -> tuple[float, int]:
118
+ value = sum((x - center) ** 2 for x in self.data) # stands in for an expensive computation
119
+ return value, n_evals + 1
120
+
121
+
122
+ model = Model([1.0, 2.0, 4.0])
123
+ loss, n_evals = model.loss(2.0) # computed: n_evals == 1
124
+ loss, n_evals = model.loss(2.5, n_evals) # computed: n_evals == 2
125
+ loss, n_evals = model.loss(2.0, n_evals) # cached: n_evals == 2, as passed in
126
+ ```
127
+
128
+ Left in the key, `n_evals` would make every call a miss; with `omit=`, the last call would return the stored `n_evals == 1`, setting the count back. With `passthrough=`, the cached result is used without resetting the counter.
129
+
130
+ ### Array arguments, and functions
131
+
132
+ `@cached_function` for functions; numpy arrays are keyed by their contents:
133
+
134
+ ```python
135
+ import numpy as np
136
+
137
+ from mcrotk_cache_utils import cached_function
138
+
139
+
140
+ @cached_function
141
+ def normalized(values: np.ndarray) -> np.ndarray:
142
+ return values / values.sum()
143
+
144
+
145
+ normalized(np.arange(5.0)) # computed
146
+ normalized(np.arange(5.0)) # cached: an equal array
147
+ ```
148
+
149
+ ## Documentation
150
+
151
+ - **[User guide](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md)**: how each part works, with examples and caveats.
152
+ - **[API reference](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/reference.md)**: every function and option.
153
+
154
+ ## License
155
+
156
+ Released under the MIT License; see [LICENSE](https://github.com/mcrotk/mcrotk_cache_utils/blob/main/LICENSE) for the full text.
@@ -0,0 +1,94 @@
1
+ [project]
2
+ name = "mcrotk-cache-utils"
3
+ version = "0.1.0"
4
+ description = "Caching decorators for Python methods and functions: a cache per instance, easy omission or passthrough of arguments such as RNGs, safe copying."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ keywords = [
10
+ "cache",
11
+ "caching",
12
+ "memoization",
13
+ "decorator",
14
+ "cachetools",
15
+ "numpy",
16
+ "random number generator",
17
+ "dataclass",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Science/Research",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "Programming Language :: Python :: 3.15",
31
+ "Topic :: Software Development :: Libraries :: Python Modules",
32
+ "Typing :: Typed",
33
+ ]
34
+ dependencies = ["cachetools>=8"]
35
+
36
+ [[project.authors]]
37
+ name = "Michael C. Rotkowitz"
38
+ email = "mcrotk@gmail.com"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/mcrotk/mcrotk_cache_utils"
42
+ Documentation = "https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md"
43
+ Changelog = "https://github.com/mcrotk/mcrotk_cache_utils/releases"
44
+ Issues = "https://github.com/mcrotk/mcrotk_cache_utils/issues"
45
+
46
+ [project.optional-dependencies]
47
+ numpy = ["numpy>=1.23.2"]
48
+ pandas = ["pandas>=1.5.0"]
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.13.7,<0.14.0"]
52
+ build-backend = "uv_build"
53
+
54
+ [dependency-groups]
55
+ dev = [
56
+ "mypy",
57
+ "numpy",
58
+ "pandas",
59
+ "pyright>=1.1.414",
60
+ "pytest>=9.2.0",
61
+ "ruff>=0.17.4",
62
+ ]
63
+
64
+ [tool.pytest.ini_options]
65
+ testpaths = ["tests"]
66
+
67
+ [tool.ruff]
68
+ line-length = 120
69
+ target-version = "py311"
70
+
71
+ [tool.ruff.lint]
72
+ select = [
73
+ "E",
74
+ "W",
75
+ "F",
76
+ "I",
77
+ "UP",
78
+ "B",
79
+ ]
80
+
81
+ [tool.mypy]
82
+ files = ["tests/test_typing.py"]
83
+ python_version = "3.11"
84
+ check_untyped_defs = true
85
+ warn_unused_ignores = true
86
+ follow_imports = "silent"
87
+
88
+ [tool.pyright]
89
+ include = [
90
+ "src",
91
+ "tests",
92
+ ]
93
+ pythonVersion = "3.11"
94
+ typeCheckingMode = "standard"
@@ -0,0 +1,85 @@
1
+ [project]
2
+ name = "mcrotk-cache-utils"
3
+ version = "0.1.0"
4
+ description = "Caching decorators for Python methods and functions: a cache per instance, easy omission or passthrough of arguments such as RNGs, safe copying."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Michael C. Rotkowitz", email = "mcrotk@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["cache", "caching", "memoization", "decorator", "cachetools", "numpy", "random number generator", "dataclass"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: Science/Research",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Programming Language :: Python :: 3.15",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "cachetools>=8",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/mcrotk/mcrotk_cache_utils"
34
+ Documentation = "https://github.com/mcrotk/mcrotk_cache_utils/blob/main/docs/guide.md"
35
+ Changelog = "https://github.com/mcrotk/mcrotk_cache_utils/releases"
36
+ Issues = "https://github.com/mcrotk/mcrotk_cache_utils/issues"
37
+
38
+ [project.optional-dependencies]
39
+ numpy = [
40
+ # first numpy release with Python 3.11 wheels (default_rng itself dates from 1.17)
41
+ "numpy>=1.23.2",
42
+ ]
43
+ pandas = [
44
+ # first pandas release with Python 3.11 wheels
45
+ "pandas>=1.5.0",
46
+ ]
47
+
48
+ [build-system]
49
+ requires = ["uv_build>=0.13.7,<0.14.0"]
50
+ build-backend = "uv_build"
51
+
52
+ [dependency-groups]
53
+ dev = [
54
+ "mypy",
55
+ "numpy",
56
+ "pandas",
57
+ "pyright>=1.1.414",
58
+ "pytest>=9.2.0",
59
+ "ruff>=0.17.4",
60
+ ]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests"]
64
+
65
+ [tool.ruff]
66
+ line-length = 120
67
+ target-version = "py311"
68
+
69
+ [tool.ruff.lint]
70
+ # pycodestyle, pyflakes, import sorting, pyupgrade, bugbear
71
+ select = ["E", "W", "F", "I", "UP", "B"]
72
+
73
+ [tool.mypy]
74
+ # Only the typing tests: they check what users' code sees (follow_imports=silent keeps mypy's
75
+ # findings in the library's own source out of the report; pyright checks that).
76
+ files = ["tests/test_typing.py"]
77
+ python_version = "3.11"
78
+ check_untyped_defs = true
79
+ warn_unused_ignores = true
80
+ follow_imports = "silent"
81
+
82
+ [tool.pyright]
83
+ include = ["src", "tests"]
84
+ pythonVersion = "3.11"
85
+ typeCheckingMode = "standard"
@@ -0,0 +1,36 @@
1
+ """Caching decorators for methods and functions, built on cachetools.
2
+
3
+ Per-instance caches for methods, argument omission, passthrough (e.g. for RNGs), key converters, and
4
+ key helpers for arrays, containers and pandas objects.
5
+ """
6
+
7
+ from .copying.instance_copying import CacheCopyWarning, copy_instance
8
+ from .decorators.cache_controls import CacheControls, cache_controls, clear_caches
9
+ from .decorators.function_caching import cached_function
10
+ from .decorators.method_caching import cached_method
11
+ from .decorators.rng_caching import (
12
+ rng_omit_cached_function,
13
+ rng_omit_cached_method,
14
+ rng_passthrough_cached_function,
15
+ rng_passthrough_cached_method,
16
+ )
17
+ from .keys.converters import array_key, container_key, dataframe_key, index_key, series_key
18
+
19
+ __all__ = [
20
+ "CacheControls",
21
+ "CacheCopyWarning",
22
+ "array_key",
23
+ "cache_controls",
24
+ "cached_function",
25
+ "cached_method",
26
+ "clear_caches",
27
+ "container_key",
28
+ "copy_instance",
29
+ "dataframe_key",
30
+ "index_key",
31
+ "rng_omit_cached_function",
32
+ "rng_omit_cached_method",
33
+ "rng_passthrough_cached_function",
34
+ "rng_passthrough_cached_method",
35
+ "series_key",
36
+ ]
@@ -0,0 +1 @@
1
+ """Copying instances with cached methods: ``copy_instance``, and the copy handling ``cached_method`` installs."""