Skip to content

Commit

Permalink
v0.5.5, optmize forwardref annotation, add formats for date transform
Browse files Browse the repository at this point in the history
  • Loading branch information
voidZXL committed Sep 26, 2024
1 parent 7631162 commit cae830e
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 13 deletions.
36 changes: 36 additions & 0 deletions tests/test_future.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

import pytest # noqa
from utype import Schema, Field


class TestSchema(Schema):
a: int
base: TestSchema | None


class MyField(Field):
my_prop = 'my_value'


class MySchema(Schema):
__field__ = MyField


class TestGeneratedSchema(Schema):
key: MySchema


class TestFuture:
def test_recursive(self):
t = TestSchema(a=1, base={'a': 2, 'base': None})
assert t.base.a == 2
assert t.base.base is None

def test_generate(self):
assert isinstance(TestGeneratedSchema.__parser__.fields['key'].field, MyField)
# class MySchema2(Schema):
# __field__ = MyField
#
# class TestGeneratedSchema2(Schema):
# key: MySchema2
12 changes: 12 additions & 0 deletions tests/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ def trans_my(trans, d, t):
True, True
),
("2022-01-02", datetime(2022, 1, 2), True, True),
("2022/01/20", datetime(2022, 1, 20), True, True),
("2022/1/02", datetime(2022, 1, 2), True, True),
# ('10:20:30', datetime(1900, 1, 1, 10, 20, 30), True, True), # no standard behaviour
(dt.date(), datetime(2022, 1, 2), True, True),
(dt.timestamp(), dt.astimezone(timezone.utc), True, True),
Expand Down Expand Up @@ -311,6 +313,16 @@ def trans_my(trans, d, t):
],
date: [
("2020-02-20", date(2020, 2, 20), True, True),
("2020/02/20", date(2020, 2, 20), True, True),
("2020/2/20", date(2020, 2, 20), True, True),
("20/02/2020", date(2020, 2, 20), True, True),
("20/2/2020", date(2020, 2, 20), True, True),
("02/20/2020", date(2020, 2, 20), True, True),
("20-02-2020", date(2020, 2, 20), True, True),
("20 Feb 2020", date(2020, 2, 20), True, True),
("20 February 2020", date(2020, 2, 20), True, True),
("Thursday, 20 February 2020", date(2020, 2, 20), True, True),
("Thu, 20 Feb 2020", date(2020, 2, 20), True, True),
(b"2020-02-20", date(2020, 2, 20), True, True),
(dt.timestamp(), dt.date(), True, False),
(int(dt.timestamp() * 1000), dt.date(), True, False),
Expand Down
2 changes: 1 addition & 1 deletion utype/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
register_transformer = TypeTransformer.registry.register


VERSION = (0, 5, 3, None)
VERSION = (0, 5, 5, None)


def _get_version():
Expand Down
1 change: 1 addition & 0 deletions utype/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(self, obj, options: Options = None):
self.obj = obj
self.init_kwargs = {"options": options}
self.options: Options = self.options_cls.generate_from(options)
self.output_options: Optional[Options] = None

self.forward_refs: Dict[
str, Tuple[ForwardRef, dict]
Expand Down
19 changes: 18 additions & 1 deletion utype/parser/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from uuid import UUID

from ..utils import exceptions as exc
from ..utils.compat import Literal, get_args, is_final, is_annotated
from ..utils.compat import Literal, get_args, is_final, is_annotated, ForwardRef
from ..utils.datastructures import unprovided
from ..utils.functional import copy_value, get_name, multi
from .options import Options, RuntimeContext
Expand Down Expand Up @@ -1095,7 +1095,24 @@ def generate(
prop = None
output_type = None
dependencies = None

if isinstance(annotation, str):
annotation = ForwardRef(annotation)

if isinstance(annotation, ForwardRef):
# try to evaluate now, if failed, evaluate in the further
from .rule import register_forward_ref
annotation = register_forward_ref(
annotation=annotation,
global_vars=global_vars,
forward_refs=forward_refs,
forward_key=attname,
force_clear=force_clear_refs,
evaluate_only=True
)

field = cls.get_field(annotation, default, **kwargs)

output_field = None
field_kwargs = dict()

Expand Down
6 changes: 4 additions & 2 deletions utype/parser/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def register_forward_ref(
global_vars: Dict[str, Any] = None,
forward_refs: Dict[str, Tuple[ForwardRef, dict]] = None,
forward_key: str = None,
force_clear: bool = False
force_clear: bool = False,
evaluate_only: bool = False,
):

if not isinstance(annotation, ForwardRef):
Expand All @@ -72,7 +73,8 @@ def register_forward_ref(
if force_clear:
ref.__forward_evaluated__ = False
ref.__forward_value__ = None

if evaluate_only:
return annotation
if not evaluated:
if isinstance(forward_refs, dict):
# class A:
Expand Down
12 changes: 9 additions & 3 deletions utype/specs/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,19 @@ def generate_for_dataclass(self, t):
data = {"type": "object"}
required = []
properties = {}
options = parser.options

if self.output:
# handle output
if parser.output_options:
options = parser.output_options

for name, field in parser.fields.items():
value = self.generate_for_field(field, options=parser.options)
value = self.generate_for_field(field, options=options)
if value is None:
continue
properties[name] = value
if field.is_required(parser.options or self.options):
if field.is_required(options or self.options):
# will count options.ignore_required in
required.append(name)
elif self.output:
Expand All @@ -381,7 +387,7 @@ def generate_for_dataclass(self, t):
data.update(properties=properties)
if required:
data.update(required=required)
addition = parser.options.addition
addition = options.addition
if addition is not None:
if isinstance(addition, type):
data.update(additionalProperties=self.generate_for_type(addition))
Expand Down
30 changes: 24 additions & 6 deletions utype/utils/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ class TypeTransformer:

# ----- preferences
# can be override
DATE_FORMATS = [
"%Y-%m-%d",
"%d %b %Y",
"%d %B %Y",
"%Y/%m/%d",
"%d/%m/%Y",
"%m/%d/%Y",
"%d-%m-%Y",
"%A, %d %B %Y",
"%a, %d %b %Y",
]
DATETIME_FORMATS = [
DateFormat.DATETIME,
DateFormat.DATETIME_DF,
Expand All @@ -56,9 +67,11 @@ class TypeTransformer:
DateFormat.DATETIME_HTTP,
DateFormat.DATETIME_PS,
DateFormat.DATETIME_GMT,
DateFormat.DATE,
DateFormat.DATE_HM

# Date formats
"%Y-%m-%d %H:%M"
]

EPOCH = datetime(1970, 1, 1)
MS_WATERSHED = int(2e10)
ARRAY_SEPARATORS = (",", ";")
Expand Down Expand Up @@ -485,14 +498,14 @@ def to_date(self, data, t: Type[date] = date) -> date:
elif isinstance(data, t):
return data

dt = self.to_datetime(data, datetime)
dt = self.to_datetime(data, datetime, date_first=True)
if self.no_data_loss:
if dt.time() != time(0, 0):
raise ValueError(f"Invalid date: {data}, got time part: {dt.time()}")
return dt.date()

@registry.register(datetime)
def to_datetime(self, data, t: Type[datetime] = datetime) -> datetime:
def to_datetime(self, data, t: Type[datetime] = datetime, date_first: bool = False) -> datetime:
if isinstance(data, t):
return data
elif isinstance(data, date):
Expand All @@ -512,7 +525,12 @@ def to_datetime(self, data, t: Type[datetime] = datetime) -> datetime:
is_utc = "GMT" in data or 'UTC' in data or data.endswith("Z") and "T" in data
data = data.replace('GMT', '').replace('UTC', '').replace('TZD', '').rstrip('Z').strip()

for f in self.DATETIME_FORMATS:
if date_first:
formats = self.DATE_FORMATS + self.DATETIME_FORMATS
else:
formats = self.DATETIME_FORMATS + self.DATE_FORMATS

for f in formats:
try:
val = t.strptime(data, f)
if is_utc:
Expand All @@ -522,7 +540,7 @@ def to_datetime(self, data, t: Type[datetime] = datetime) -> datetime:
continue

if '+' in str(data):
for f in self.DATETIME_FORMATS:
for f in formats:
try:
# val = t.strptime(data, f + ' %z')
val = t.strptime(data, f + (' %z' if ' +' in str(data) else '%z'))
Expand Down

0 comments on commit cae830e

Please sign in to comment.