Skip to content

Commit 2abd77b

Browse files
committed
fix handling of undefined type annotations when from __future__ import annotations is on
1 parent 65140ba commit 2abd77b

File tree

4 files changed

+37
-6
lines changed

4 files changed

+37
-6
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Cachew gives the best of two worlds and makes it both **easy and efficient**. Th
128128
- first your objects get [converted](src/cachew/marshall/cachew.py#L34) into a simpler JSON-like representation
129129
- after that, they are mapped into byte blobs via [`orjson`](https://github.com/ijl/orjson).
130130

131-
When the function is called, cachew [computes the hash of your function's arguments ](src/cachew/__init__.py:#L511)
131+
When the function is called, cachew [computes the hash of your function's arguments ](src/cachew/__init__.py:#L589)
132132
and compares it against the previously stored hash value.
133133

134134
- If they match, it would deserialize and yield whatever is stored in the cache database
@@ -145,7 +145,7 @@ and compares it against the previously stored hash value.
145145

146146
* primitive: `str`, `int`, `float`, `bool`, `datetime`, `date`, `Exception`
147147

148-
See [tests.test_types](src/cachew/tests/test_cachew.py#L697), [tests.test_primitive](src/cachew/tests/test_cachew.py#L731), [tests.test_dates](src/cachew/tests/test_cachew.py#L651), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1101)
148+
See [tests.test_types](src/cachew/tests/test_cachew.py#L697), [tests.test_primitive](src/cachew/tests/test_cachew.py#L731), [tests.test_dates](src/cachew/tests/test_cachew.py#L651), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1127)
149149
* [@dataclass and NamedTuple](src/cachew/tests/test_cachew.py#L613)
150150
* [Optional](src/cachew/tests/test_cachew.py#L515) types
151151
* [Union](src/cachew/tests/test_cachew.py#L837) types
@@ -165,7 +165,7 @@ You can find some of my performance tests in [benchmarks/](benchmarks) dir, and
165165

166166

167167
# Using
168-
See [docstring](src/cachew/__init__.py#L290) for up-to-date documentation on parameters and return types.
168+
See [docstring](src/cachew/__init__.py#L296) for up-to-date documentation on parameters and return types.
169169
You can also use [extensive unit tests](src/cachew/tests/test_cachew.py) as a reference.
170170

171171
Some useful (but optional) arguments of `@cachew` decorator:
@@ -271,7 +271,7 @@ Now you can use `@mcachew` in place of `@cachew`, and be certain things don't br
271271
## Settings
272272

273273

274-
[cachew.settings](src/cachew/__init__.py#L66) exposes some parameters that allow you to control `cachew` behaviour:
274+
[cachew.settings](src/cachew/__init__.py#L67) exposes some parameters that allow you to control `cachew` behaviour:
275275
- `ENABLE`: set to `False` if you want to disable caching for without removing the decorators (useful for testing and debugging).
276276
You can also use [cachew.extra.disabled_cachew](src/cachew/extra.py#L21) context manager to do it temporarily.
277277
- `DEFAULT_CACHEW_DIR`: override to set a different base directory. The default is the "user cache directory" (see [appdirs docs](https://github.com/ActiveState/appdirs#some-example-output)).

src/cachew/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,12 @@ def infer_return_type(func) -> Union[Failure, Inferred]:
213213
>>> infer_return_type(unsupported_list)
214214
"can't infer type from typing.List[cachew.Custom]: can't cache <class 'cachew.Custom'>"
215215
"""
216-
hints = get_type_hints(func)
216+
try:
217+
hints = get_type_hints(func)
218+
except Exception as ne:
219+
# get_type_hints might fail if types are forward defined or missing
220+
# see test_future_annotation for an example
221+
return str(ne)
217222
rtype = hints.get('return', None)
218223
if rtype is None:
219224
return f"no return type annotation on {func}"

src/cachew/logging_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def get_enlighten():
214214
return Mock()
215215

216216
try:
217-
import enlighten # type: ignore[import]
217+
import enlighten # type: ignore[import-untyped]
218218
except ModuleNotFoundError:
219219
warnings.warn("You might want to 'pip install enlighten' for a nice progress bar")
220220

src/cachew/tests/test_cachew.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,32 @@ def orig2():
996996
assert list(fun()) == [123]
997997

998998

999+
@pytest.mark.parametrize('throw', [False, True])
1000+
def test_future_annotations(tmp_path: Path, throw: bool) -> None:
1001+
"""
1002+
this will work in runtime without cachew if from __future__ import annotations is used
1003+
so should work with cachew decorator as well
1004+
"""
1005+
src = tmp_path / 'src.py'
1006+
src.write_text(f'''
1007+
from __future__ import annotations
1008+
1009+
from cachew import settings, cachew
1010+
settings.THROW_ON_ERROR = {throw}
1011+
1012+
@cachew
1013+
def fun() -> BadType:
1014+
print("called!")
1015+
return 0
1016+
1017+
fun()
1018+
'''.lstrip())
1019+
1020+
ctx = pytest.raises(Exception) if throw else nullcontext()
1021+
with ctx:
1022+
assert check_output([sys.executable, src], text=True).strip() == "called!"
1023+
1024+
9991025
def test_recursive_simple(tmp_path: Path) -> None:
10001026
d0 = 0
10011027
d1 = 1000

0 commit comments

Comments
 (0)