Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/cachew/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,13 +848,10 @@ def cached_items():
yield from func(*args, **kwargs)


from .legacy import NTBinder

__all__ = [
'cachew',
'CachewException',
'SourceHash',
'HashFunction',
'get_logger',
'NTBinder', # NOTE: we need to keep this here for now because it's used in promnesia
]
17 changes: 17 additions & 0 deletions src/cachew/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import sqlalchemy
from sqlalchemy import Column

from .pytest import parametrize
from .utils import CachewException


Expand Down Expand Up @@ -470,3 +471,19 @@ def types(ts):

for p in PRIMITIVE_TYPES:
assert p in Values.__args__ # type: ignore


@parametrize(
('tp', 'val'),
[
(int, 22),
(bool, False),
(Optional[str], 'abacaba'),
(Union[str, int], 1),
],
)
def test_ntbinder_primitive(tp, val) -> None:
b = NTBinder.make(tp, name='x')
row = b.to_row(val)
vv = b.from_row(list(row))
assert vv == val
14 changes: 10 additions & 4 deletions src/cachew/marshall/cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def load(self, dct):

@dataclass(**SLOTS)
class SUnion(Schema):
# it's a bit faster to cache indixes here, gives about 15% speedup
# it's a bit faster to cache indices here, gives about 15% speedup
args: tuple[tuple[int, Schema], ...]

def dump(self, obj):
Expand Down Expand Up @@ -442,18 +442,24 @@ def test_serialize_and_deserialize() -> None:
helper('aaa', Optional[str])
helper('aaa', Union[str, None])
helper(None, Union[str, None])
if sys.version_info[:2] >= (3, 10):
helper('aaa', str | None)

# lists
helper([1, 2, 3], List[int])
# lists/tuples/sequences
helper([1, 2, 3], List[int])
helper([1, 2, 3], Sequence[int], expected=(1, 2, 3))
helper((1, 2, 3), Sequence[int])
helper((1, 2, 3), Tuple[int, int, int])
helper((1, 2, 3), Tuple[int, int, int])
if sys.version_info[:2] >= (3, 9):
# TODO test with from __future__ import annotations..
helper([1, 2, 3], list[int])
helper((1, 2, 3), tuple[int, int, int])

# dicts
helper({'a': 'aa', 'b': 'bb'}, Dict[str, str])
helper({'a': None, 'b': 'bb'}, Dict[str, Optional[str]])
if sys.version_info[:2] >= (3, 9):
helper({'a': 'aa', 'b': 'bb'}, dict[str, str])

# compounds of simple types
helper(['1', 2, '3'], List[Union[str, int]])
Expand Down
20 changes: 20 additions & 0 deletions src/cachew/pytest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Helpers to prevent depending on pytest in runtime
"""

import sys
import typing

under_pytest = 'pytest' in sys.modules

if typing.TYPE_CHECKING or under_pytest:
import pytest

parametrize = pytest.mark.parametrize
else:

def parametrize(*_args, **_kwargs):
def wrapper(f):
return f

return wrapper
3 changes: 2 additions & 1 deletion src/cachew/tests/marshall.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import pytest
import pytz

from ..legacy import NTBinder
from ..marshall.cachew import CachewMarshall
from ..marshall.common import Json
from .utils import (
Expand Down Expand Up @@ -45,6 +44,8 @@ def do_test(*, test_name: str, Type, factory, count: int, impl: Impl = 'cachew')
to_json = marshall.dump
from_json = marshall.load
elif impl == 'legacy':
from ..legacy import NTBinder

# NOTE: legacy binder emits a tuple which can be inserted directly into the database
# so 'json dump' and 'json load' should really be disregarded for this flavor
# if you're comparing with <other> implementation, you should compare
Expand Down
97 changes: 45 additions & 52 deletions src/cachew/tests/test_cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from .. import (
Backend,
CachewException,
NTBinder,
cachew,
callable_name,
get_logger,
Expand Down Expand Up @@ -80,22 +79,6 @@ def restore_settings():
setattr(settings, k, v)


# fmt: off
@pytest.mark.parametrize(('tp', 'val'), [
(int, 22),
(bool, False),
(Optional[str], 'abacaba'),
(Union[str, int], 1),
])
# fmt: on
def test_ntbinder_primitive(tp, val) -> None:
# TODO good candidate for property tests...
b = NTBinder.make(tp, name='x')
row = b.to_row(val)
vv = b.from_row(list(row))
assert vv == val


class UUU(NamedTuple):
xx: int
yy: int
Expand Down Expand Up @@ -582,12 +565,14 @@ def make_people_data(count: int) -> Iterator[Person]:
)


class Breaky(NamedTuple):
job_title: int
job: Optional[Job]
def test_unique_columns(tmp_path: Path) -> None:
# TODO remove this test? it's for legacy stuff..
from ..legacy import NTBinder

class Breaky(NamedTuple):
job_title: int
job: Optional[Job]

def test_unique(tmp_path: Path) -> None:
assert [c.name for c in NTBinder.make(Breaky).columns] == [
'job_title',
'_job_is_null',
Expand Down Expand Up @@ -698,46 +683,53 @@ def fun() -> Iterable[Dates]:
# fmt: off
@dataclass
class AllTypes:
an_int : int
a_bool : bool
a_float: float
a_str : str
a_dt : datetime
a_date : date
a_json : Dict[str, Any]
a_list : List[Any]
an_exc : Exception
a_str : str
an_int : int
a_float : float
a_bool : bool
a_dt : datetime
a_date : date
a_dict : Dict[str, Any]
a_list : List[Any]
a_tuple : Tuple[float, str]
an_exc : Exception
an_opt : Optional[str]
# fmt: on

# TODO test new style list/tuple/union/optional
# TODO support vararg tuples?


def test_types(tmp_path: Path) -> None:
tz = pytz.timezone('Europe/Berlin')
# fmt: off
obj = AllTypes(
an_int =1123,
a_bool =True,
a_float=3.131,
a_str ='abac',
a_dt =datetime.now(tz=tz),
a_date =datetime.now().replace(year=2000).date(),
a_json ={'a': True, 'x': {'whatever': 3.14}},
a_list =['aba', 123, None],
an_exc =RuntimeError('error!', 123),
a_str = 'abac',
an_int = 1123,
a_float = 3.131,
a_bool = True,
a_dt = datetime.now(tz=tz),
a_date = datetime.now().replace(year=2000).date(),
a_dict = {'a': True, 'x': {'whatever': 3.14}},
a_list = ['aba', 123, None],
a_tuple = (1.23, '3.2.1'),
an_exc = RuntimeError('error!', 123),
an_opt = 'hello',
)
# fmt: on

@cachew(tmp_path)
def get() -> Iterator[AllTypes]:
yield obj

def H(t: AllTypes):
def helper(t: AllTypes):
# Exceptions can't be directly compared.. so this kinda helps
d = asdict(t)
d['an_exc'] = d['an_exc'].args
return d

assert H(one(get())) == H(obj)
assert H(one(get())) == H(obj)
assert helper(one(get())) == helper(obj)
assert helper(one(get())) == helper(obj)


# TODO if I do perf tests, look at this https://docs.sqlalchemy.org/en/13/_modules/examples/performance/large_resultsets.html
Expand Down Expand Up @@ -786,24 +778,25 @@ class O(NamedTuple):
x: int


def test_default_arguments(tmp_path: Path) -> None:
class HackHash:
def __init__(self, x: int) -> None:
self.x = x
class _HackHash:
def __init__(self, x: int) -> None:
self.x = x

def __repr__(self):
return repr(self.x)
def __repr__(self):
return repr(self.x)

hh = HackHash(1)

def test_default_arguments(tmp_path: Path) -> None:
hh = _HackHash(1)

calls = 0

def orig(a: int, param: HackHash = hh) -> Iterator[O]:
def orig(a: int, param: _HackHash = hh) -> Iterator[O]:
yield O(hh.x)
nonlocal calls
calls += 1

def depends_on(a: int, param: HackHash) -> str:
def depends_on(a: int, param: _HackHash) -> str:
# hmm. in principle this should be str according to typing
# on practice though we always convert hash to str, so maybe type should be changed to Any?
return (a, param.x) # type: ignore[return-value]
Expand All @@ -820,7 +813,7 @@ def depends_on(a: int, param: HackHash) -> str:
assert calls == 2

# should be ok with explicitly passing
assert list(fun(123, param=HackHash(2))) == [O(2)]
assert list(fun(123, param=_HackHash(2))) == [O(2)]
assert calls == 2

# we don't have to handle the default param in the default hash key
Expand Down