Skip to content

Commit

Permalink
tests: add a proper test checking for from __future__ import annotati…
Browse files Browse the repository at this point in the history
…ons handling
  • Loading branch information
karlicoss committed Aug 28, 2024
1 parent 1586fca commit 250f648
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 16 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,18 @@ and compares it against the previously stored hash value.



* automatic schema inference: [1](src/cachew/tests/test_cachew.py#L388), [2](src/cachew/tests/test_cachew.py#L402)
* automatic schema inference: [1](src/cachew/tests/test_cachew.py#L390), [2](src/cachew/tests/test_cachew.py#L404)
* supported types:

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

See [tests.test_types](src/cachew/tests/test_cachew.py#L714), [tests.test_primitive](src/cachew/tests/test_cachew.py#L748), [tests.test_dates](src/cachew/tests/test_cachew.py#L668), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1144)
* [@dataclass and NamedTuple](src/cachew/tests/test_cachew.py#L630)
* [Optional](src/cachew/tests/test_cachew.py#L532) types
* [Union](src/cachew/tests/test_cachew.py#L854) types
* [nested datatypes](src/cachew/tests/test_cachew.py#L448)
See [tests.test_types](src/cachew/tests/test_cachew.py#L713), [tests.test_primitive](src/cachew/tests/test_cachew.py#L747), [tests.test_dates](src/cachew/tests/test_cachew.py#L667), [tests.test_exceptions](src/cachew/tests/test_cachew.py#L1145)
* [@dataclass and NamedTuple](src/cachew/tests/test_cachew.py#L632)
* [Optional](src/cachew/tests/test_cachew.py#L534) types
* [Union](src/cachew/tests/test_cachew.py#L853) types
* [nested datatypes](src/cachew/tests/test_cachew.py#L450)

* detects [datatype schema changes](src/cachew/tests/test_cachew.py#L478) and discards old data automatically
* detects [datatype schema changes](src/cachew/tests/test_cachew.py#L480) and discards old data automatically


# Performance
Expand All @@ -170,15 +170,15 @@ You can also use [extensive unit tests](src/cachew/tests/test_cachew.py) as a re

Some useful (but optional) arguments of `@cachew` decorator:

* `cache_path` can be a directory, or a callable that [returns a path](src/cachew/tests/test_cachew.py#L425) and depends on function's arguments.
* `cache_path` can be a directory, or a callable that [returns a path](src/cachew/tests/test_cachew.py#L427) and depends on function's arguments.

By default, `settings.DEFAULT_CACHEW_DIR` is used.

* `depends_on` is a function which determines whether your inputs have changed, and the cache needs to be invalidated.

By default it just uses string representation of the arguments, you can also specify a custom callable.

For instance, it can be used to [discard cache](src/cachew/tests/test_cachew.py#L120) if the input file was modified.
For instance, it can be used to [discard cache](src/cachew/tests/test_cachew.py#L122) if the input file was modified.

* `cls` is the type that would be serialized.

Expand Down
106 changes: 99 additions & 7 deletions src/cachew/tests/test_cachew.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import hashlib
import inspect
import os
import string
import sys
import textwrap
import time
import timeit
from concurrent.futures import ProcessPoolExecutor
Expand Down Expand Up @@ -638,10 +640,7 @@ def get_dataclasses() -> Iterator[Test]:

def test_inner_class(tmp_path: Path) -> None:
# NOTE: this doesn't work at the moment if from __future__ import annotations is used in client code (e.g. on top of this test)
# because then annotations end up as strings and we can't eval it as we don't have access to a class defined inside function
# keeping this test just to keep track of whether this is fixed at some point
# possibly relevant:
# - https://peps.python.org/pep-0563/#keeping-the-ability-to-use-function-local-state-when-defining-annotations
# see test_future_annotations for more info

@dataclass
class InnerDataclass:
Expand Down Expand Up @@ -1014,13 +1013,14 @@ def orig2():


@pytest.mark.parametrize('throw', [False, True])
def test_future_annotations(*, tmp_path: Path, throw: bool) -> None:
def test_bad_annotation(*, tmp_path: Path, throw: bool) -> None:
"""
this will work in runtime without cachew if from __future__ import annotations is used
so should work with cachew decorator as well
"""
src = tmp_path / 'src.py'
src.write_text(f'''
src.write_text(
f'''
from __future__ import annotations
from cachew import settings, cachew
Expand All @@ -1032,7 +1032,8 @@ def fun() -> BadType:
return 0
fun()
'''.lstrip())
'''.lstrip()
)

ctx = pytest.raises(Exception) if throw else nullcontext()
with ctx:
Expand Down Expand Up @@ -1453,3 +1454,94 @@ def fun_multiple() -> Iterable[int]:

assert (tmp_path / callable_name(fun_single)).exists()
assert (tmp_path / callable_name(fun_multiple)).exists()


@pytest.mark.parametrize('use_future_annotations', [False, True])
@pytest.mark.parametrize('local', [False, True])
@pytest.mark.parametrize('throw', [False, True])
def test_future_annotations(
*,
use_future_annotations: bool,
local: bool,
throw: bool,
tmp_path: Path,
) -> None:
"""
Checks handling of postponed evaluation of annotations (from __future__ import annotations)
"""

if sys.version_info[:2] <= (3, 8):
pytest.skip("too annoying to adjust for 3.8 and it's EOL soon anyway")

# NOTE: to avoid weird interactions with existing interpreter in which pytest is running
# , we compose a program and running in python directly instead
# (also not sure if it's even possible to tweak postponed annotations without doing that)

if use_future_annotations and local and throw:
# when annotation is local (like inner class), then they end up as strings
# so we can't eval it as we don't have access to a class defined inside function
# keeping this test just to keep track of whether this is fixed at some point
# possibly relevant:
# - https://peps.python.org/pep-0563/#keeping-the-ability-to-use-function-local-state-when-defining-annotations
pytest.skip("local aliases/classses don't work with from __future__ import annotations")

_PREAMBLE = f'''
from pathlib import Path
import tempfile
from cachew import cachew, settings
settings.THROW_ON_ERROR = {throw}
temp_dir = tempfile.TemporaryDirectory()
td = Path(temp_dir.name)
'''

_TEST = '''
T = int
@cachew(td)
def fun() -> list[T]:
print("called")
return [1, 2]
assert list(fun()) == [1, 2]
assert list(fun()) == [1, 2]
'''

if use_future_annotations:
code = '''
from __future__ import annotations
'''
else:
code = ''

code += _PREAMBLE

if local:
code += f'''
def test() -> None:
{textwrap.indent(_TEST, prefix=" ")}
test()
'''
else:
code += _TEST

run_py = tmp_path / 'run.py'
run_py.write_text(code)

cache_dir = tmp_path / 'cache'
cache_dir.mkdir()

res = check_output(
[sys.executable, run_py],
env={'TMPDIR': str(cache_dir), **os.environ},
text=True,
)
called = int(res.count('called'))
if use_future_annotations and local and not throw:
# cachew fails to set up, so no caching but at least it works otherwise
assert called == 2
else:
assert called == 1

0 comments on commit 250f648

Please sign in to comment.