Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rnag committed Jan 19, 2025
1 parent 739393d commit 6a2497b
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 31 deletions.
51 changes: 21 additions & 30 deletions dataclass_wizard/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ..utils.typing_compat import get_origin_v2


if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from ..bases import META


Expand Down Expand Up @@ -201,7 +201,7 @@ def _wrap_inner(self, extras,
def __str__(self):
return getattr(self, '_wrapped', '')

def __repr__(self):
def __repr__(self): # pragma: no cover
items = ', '.join([f'{v}={getattr(self, v)!r}'
for v in self.__slots__
if not v.startswith('_')])
Expand Down Expand Up @@ -229,39 +229,30 @@ class PatternBase:
'tz_info',
'_repr')

def __init__(self, base, patterns=None, tzname=None):
def __init__(self, base, patterns=None, tz_info=None):
self.base = base
if patterns is not None:
self.patterns = patterns
if tzname is not None:
self.tz_info = ZoneInfo(tzname)
if tz_info is not None:
self.tz_info = tz_info

def with_tz(self, tz_info: tzinfo) -> Self:
def with_tz(self, tz_info: tzinfo) -> Self: # pragma: no cover
self.tz_info = tz_info
return self

def __getitem__(self, patterns):
if (tz_info := getattr(self, 'tz_info', None)) is not None:
if tz_info is ...: # expect time zone as first argument
tz_info = patterns[0]
if isinstance(tz_info, str):
tz_info = ZoneInfo(tz_info)

pb = PatternBase(
self.base,
patterns[1:],
)
else:
pb = PatternBase(
self.base,
(patterns,) if patterns.__class__ is str else patterns,
)

return pb.with_tz(tz_info)
if (tz_info := getattr(self, 'tz_info', None)) is ...:
# expect time zone as first argument
tz_info, *patterns = patterns
if isinstance(tz_info, str):
tz_info = ZoneInfo(tz_info)
else:
patterns = (patterns, ) if patterns.__class__ is str else patterns

return PatternBase(
self.base,
(patterns, ) if patterns.__class__ is str else patterns,
patterns,
tz_info,
)

__call__ = __getitem__
Expand Down Expand Up @@ -437,9 +428,9 @@ def __repr__(self):

Pattern = PatternBase(...)

UTCPattern = PatternBase(...).with_tz(UTC)
UTCPattern = PatternBase(..., tz_info=UTC)

AwarePattern = PatternBase(...).with_tz(...)
AwarePattern = PatternBase(..., tz_info=...)

# noinspection PyTypeChecker
DatePattern = PatternBase(date)
Expand All @@ -451,16 +442,16 @@ def __repr__(self):
DateTimePattern = PatternBase(datetime)

# noinspection PyTypeChecker
UTCTimePattern = PatternBase(time).with_tz(UTC)
UTCTimePattern = PatternBase(time, tz_info=UTC)

# noinspection PyTypeChecker
UTCDateTimePattern = PatternBase(datetime).with_tz(UTC)
UTCDateTimePattern = PatternBase(datetime, tz_info=UTC)

# noinspection PyTypeChecker
AwareTimePattern = PatternBase(time).with_tz(...)
AwareTimePattern = PatternBase(time, tz_info=...)

# noinspection PyTypeChecker
AwareDateTimePattern = PatternBase(datetime).with_tz(...)
AwareDateTimePattern = PatternBase(datetime, tz_info=...)

# Instances of Field are only ever created from within this module,
# and only from the field() function, although Field instances are
Expand Down
4 changes: 3 additions & 1 deletion dataclass_wizard/v1/models.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ class PatternBase:

tz_info: tzinfo | Ellipsis

def __init__(self, base, patterns=None, tzname=None): ...
def __init__(self, base: type[DT],
patterns: tuple[str, ...] = None,
tz_info: tzinfo | Ellipsis | None = None): ...

def with_tz(self, tz_info: tzinfo | Ellipsis) -> Self: ...

Expand Down
64 changes: 64 additions & 0 deletions tests/unit/v1/test_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
List, Optional, Union, Tuple, Dict, NamedTuple, DefaultDict,
Set, FrozenSet, Annotated, Literal, Sequence, MutableSequence, Collection
)
from zoneinfo import ZoneInfo

import pytest

Expand Down Expand Up @@ -770,6 +771,69 @@ class MyClass:
log.debug('Error details: %r', e.value)


def test_aware_and_utc_date_times_with_custom_pattern():
"""
Time and datetime objects with a custom date string
format, where the objects are timezone-aware or in UTC.
"""
class MyTime(time, metaclass=create_strict_eq):
def print_hour(self):
print(self.hour)

@dataclass
class Example(JSONPyWizard):
class _(JSONPyWizard.Meta):
v1 = True

my_dt1: Annotated[AwareDateTimePattern['Asia/Tokyo', '%m-%Y-%H:%M-%Z'], Alias('key')]
my_dt2: UTCDateTimePattern['%Y-%m-%d %H']
my_time1: UTCTimePattern['%H:%M:%S']
my_time2: Annotated[list[MyTime], AwarePattern['US/Hawaii', '%H:%M-%Z']]

d = {'key': '10-2020-15:30-UTC',
'my_dt2': '2010-5-7 8',
'my_time1': '17:10:05',
'my_time2': ['21:45-UTC']}
ex = Example.from_dict(d)

# noinspection PyTypeChecker
expected = Example(
my_dt1=datetime(2020, 10, 1, 15, 30, tzinfo=ZoneInfo('Asia/Tokyo')),
my_dt2=datetime(2010, 5, 7, 8, 0, tzinfo=ZoneInfo('UTC')),
my_time1=time(17, 10, 5, tzinfo=ZoneInfo('UTC')),
my_time2=[
MyTime(21, 45, tzinfo=ZoneInfo('US/Hawaii')),
])

assert ex == expected

assert ex.to_dict() == {
'key': '2020-10-01T15:30:00+09:00',
'my_dt2': '2010-05-07T08:00:00Z',
'my_time1': '17:10:05Z',
'my_time2': ['21:45:00']}

ex = Example.from_dict(ex.to_dict())
ex = Example.from_dict(ex.to_dict())

assert ex == expected

# De-serializing using `timestamp()`

d = {'key': expected.my_dt1.timestamp(),
'my_dt2': int(expected.my_dt2.timestamp()),
'my_time1': '17:10:05',
'my_time2': ['21:45-UTC']}

assert Example.from_dict(d) == expected

# ParseError: `time` doesn't have `fromtimestamp()`,
# so an integer input should raise an error.
d['my_time1'] = 123
with pytest.raises(ParseError):
_ = Example.from_dict(d)


def test_tag_field_is_used_in_load_process():
"""
Confirm that the `_TAG` field is used when de-serializing to a dataclass
Expand Down

0 comments on commit 6a2497b

Please sign in to comment.