From fbe8dc85e86d38ce35f2700b0228d1404f829278 Mon Sep 17 00:00:00 2001 From: Yukinari Tani Date: Tue, 16 Aug 2022 01:34:33 +0900 Subject: [PATCH] refactor: Change to pass "type_check" parameter in decorator Because beartype package can change the configuration only in the decorator. --- examples/simple.py | 4 +- serde/core.py | 8 ++-- serde/de.py | 105 +++++++++++++++++-------------------------- serde/json.py | 11 ++--- serde/msgpack.py | 10 ++--- serde/se.py | 68 +++++++++++----------------- serde/toml.py | 11 ++--- serde/yaml.py | 11 ++--- tests/test_basics.py | 5 +-- 9 files changed, 90 insertions(+), 143 deletions(-) diff --git a/examples/simple.py b/examples/simple.py index 7f7acbc7..d46e0ad1 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -1,10 +1,10 @@ from dataclasses import dataclass -from serde import serde +from serde import Strict, serde from serde.json import from_json, to_json -@serde +@serde(type_check=Strict) @dataclass class Foo: i: int diff --git a/serde/core.py b/serde/core.py index 48bc1639..5655b3da 100644 --- a/serde/core.py +++ b/serde/core.py @@ -752,13 +752,11 @@ def __call__(self, **kwargs) -> 'TypeCheck': Strict = TypeCheck(kind=TypeCheck.Kind.Strict) -def coerce(typ: Type, obj: Any, type_check: TypeCheck) -> Any: - return typ(obj) if is_coercible(typ, obj, type_check) else obj +def coerce(typ: Type, obj: Any) -> Any: + return typ(obj) if is_coercible(typ, obj) else obj -def is_coercible(typ: Type, obj: Any, type_check: TypeCheck) -> bool: - if not type_check.is_coerce(): - return False +def is_coercible(typ: Type, obj: Any) -> bool: if obj is None: return False return True diff --git a/serde/de.py b/serde/de.py index f0c8ee76..fb09cca9 100644 --- a/serde/de.py +++ b/serde/de.py @@ -143,6 +143,7 @@ def deserialize( reuse_instances_default: bool = True, deserializer: Optional[DeserializeFunc] = None, tagging: Tagging = DefaultTagging, + type_check: TypeCheck = Coerce, **kwargs, ): """ @@ -271,8 +272,8 @@ def wrap(cls: Type): if f.deserializer: g[f.deserializer.name] = f.deserializer - add_func(scope, FROM_ITER, render_from_iter(cls, deserializer), g) - add_func(scope, FROM_DICT, render_from_dict(cls, rename_all, deserializer), g) + add_func(scope, FROM_ITER, render_from_iter(cls, deserializer, type_check), g) + add_func(scope, FROM_DICT, render_from_dict(cls, rename_all, deserializer, type_check), g) add_func(scope, TYPE_CHECK, render_type_check(cls), g) logger.debug(f'{typename(cls)}: {SERDE_SCOPE} {scope}') @@ -318,16 +319,11 @@ def deserialize(cls, data, **opts): raise NotImplementedError -def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool, type_check: TypeCheck = Coerce): +def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool): """ Deserialize from an object into an instance of the type specified as arg `c`. `c` can be either primitive type, `List`, `Tuple`, `Dict` or `deserialize` class. - - When `type_check`=True, pyserde checks if deserialized field values are equal to the - types declared. """ - strict = type_check.is_strict() - if is_generic(c): # Store subscripted generic type such as Foo[Bar] in "maybe_generic", # and store origin type such as Foo in "c". Since subscripted generics @@ -338,17 +334,13 @@ def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool, type_check: Ty else: maybe_generic = c try: - thisfunc = functools.partial(from_obj, named=named, reuse_instances=reuse_instances, type_check=type_check) + thisfunc = functools.partial(from_obj, named=named, reuse_instances=reuse_instances) if o is None: return None if is_deserializable(c): serde_scope: SerdeScope = getattr(c, SERDE_SCOPE) func_name = FROM_DICT if named else FROM_ITER - res = serde_scope.funcs[func_name]( - c, maybe_generic=maybe_generic, data=o, reuse_instances=reuse_instances, type_check=type_check - ) - if strict: - serde_scope.funcs[TYPE_CHECK](res) + res = serde_scope.funcs[func_name](c, maybe_generic=maybe_generic, data=o, reuse_instances=reuse_instances) return res elif is_opt(c): if o is None: @@ -368,40 +360,25 @@ def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool, type_check: Ty if is_bare_list(c): return [e for e in o] else: - res = [thisfunc(type_args(c)[0], e, type_check=type_check) for e in o] - if strict: - if not is_list_instance(res, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") + res = [thisfunc(type_args(c)[0], e) for e in o] return res elif is_set(c): if is_bare_set(c): return set(e for e in o) else: - res = set(thisfunc(type_args(c)[0], e, type_check=NoCheck) for e in o) - if strict: - if not is_set_instance(res, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") + res = set(thisfunc(type_args(c)[0], e) for e in o) return res elif is_tuple(c): if is_bare_tuple(c): return tuple(e for e in o) else: res = tuple(thisfunc(type_args(c)[i], e) for i, e in enumerate(o)) - if strict: - if not is_tuple_instance(res, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") return res elif is_dict(c): if is_bare_dict(c): return {k: v for k, v in o.items()} else: - res = { - thisfunc(type_args(c)[0], k, type_check=NoCheck): thisfunc(type_args(c)[1], v, type_check=NoCheck) - for k, v in o.items() - } - if strict: - if not is_dict_instance(res, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") + res = {thisfunc(type_args(c)[0], k): thisfunc(type_args(c)[1], v) for k, v in o.items()} return res elif is_numpy_array(c): return deserialize_numpy_array_direct(c, o) @@ -419,7 +396,7 @@ def from_obj(c: Type, o: Any, named: bool, reuse_instances: bool, type_check: Ty raise SerdeError(e) -def from_dict(cls, o, reuse_instances: bool = ..., type_check: TypeCheck = Coerce): +def from_dict(cls, o, reuse_instances: bool = ...): """ Deserialize dictionary into object. @@ -439,10 +416,10 @@ def from_dict(cls, o, reuse_instances: bool = ..., type_check: TypeCheck = Coerc >>> from_dict(List[Foo], lst) [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)] """ - return from_obj(cls, o, named=True, reuse_instances=reuse_instances, type_check=type_check) + return from_obj(cls, o, named=True, reuse_instances=reuse_instances) -def from_tuple(cls, o, reuse_instances: bool = ..., type_check: TypeCheck = Coerce): +def from_tuple(cls, o, reuse_instances: bool = ...): """ Deserialize tuple into object. @@ -462,7 +439,7 @@ def from_tuple(cls, o, reuse_instances: bool = ..., type_check: TypeCheck = Coer >>> from_tuple(List[Foo], lst) [Foo(i=10, s='foo', f=100.0, b=True), Foo(i=20, s='foo', f=100.0, b=True)] """ - return from_obj(cls, o, named=False, reuse_instances=reuse_instances, type_check=type_check) + return from_obj(cls, o, named=False, reuse_instances=reuse_instances) @dataclass @@ -616,7 +593,7 @@ def render(self, arg: DeField) -> str: if self.custom and not arg.deserializer: # Rerender the code for default deserializer. - default = Renderer(self.func, self.cls, None).render(arg) + default = Renderer(self.func, self.cls, None, suppress_coerce=self.suppress_coerce).render(arg) return self.custom_class_deserializer(arg, default) else: return res @@ -660,13 +637,13 @@ def opt(self, arg: DeField) -> str: >>> from typing import List >>> Renderer('foo').render(DeField(Optional[int], 'o', datavar='data')) - '(coerce(int, data["o"], type_check)) if data.get("o") is not None else None' + '(coerce(int, data["o"])) if data.get("o") is not None else None' >>> Renderer('foo').render(DeField(Optional[List[int]], 'o', datavar='data')) - '([coerce(int, v, type_check) for v in data["o"]]) if data.get("o") is not None else None' + '([coerce(int, v) for v in data["o"]]) if data.get("o") is not None else None' >>> Renderer('foo').render(DeField(Optional[List[int]], 'o', datavar='data')) - '([coerce(int, v, type_check) for v in data["o"]]) if data.get("o") is not None else None' + '([coerce(int, v) for v in data["o"]]) if data.get("o") is not None else None' >>> @deserialize ... class Foo: @@ -691,10 +668,10 @@ def list(self, arg: DeField) -> str: >>> from typing import List >>> Renderer('foo').render(DeField(List[int], 'l', datavar='data')) - '[coerce(int, v, type_check) for v in data["l"]]' + '[coerce(int, v) for v in data["l"]]' >>> Renderer('foo').render(DeField(List[List[int]], 'l', datavar='data')) - '[[coerce(int, v, type_check) for v in v] for v in data["l"]]' + '[[coerce(int, v) for v in v] for v in data["l"]]' """ if is_bare_list(arg.type): return f'list({arg.data})' @@ -707,10 +684,10 @@ def set(self, arg: DeField) -> str: >>> from typing import Set >>> Renderer('foo').render(DeField(Set[int], 'l', datavar='data')) - 'set(coerce(int, v, type_check) for v in data["l"])' + 'set(coerce(int, v) for v in data["l"])' >>> Renderer('foo').render(DeField(Set[Set[int]], 'l', datavar='data')) - 'set(set(coerce(int, v, type_check) for v in v) for v in data["l"])' + 'set(set(coerce(int, v) for v in v) for v in data["l"])' """ if is_bare_set(arg.type): return f'set({arg.data})' @@ -725,14 +702,14 @@ def tuple(self, arg: DeField) -> str: >>> @deserialize ... class Foo: pass >>> Renderer('foo').render(DeField(Tuple[str, int, List[int], Foo], 'd', datavar='data')) - '(coerce(str, data["d"][0], type_check), coerce(int, data["d"][1], type_check), \ -[coerce(int, v, type_check) for v in data["d"][2]], \ + '(coerce(str, data["d"][0]), coerce(int, data["d"][1]), \ +[coerce(int, v) for v in data["d"][2]], \ Foo.__serde__.funcs[\\'foo\\'](data=data["d"][3], reuse_instances=reuse_instances),)' >>> field = DeField(Tuple[str, int, List[int], Foo], 'd', datavar='data', index=0, iterbased=True) >>> Renderer('foo').render(field) - "(coerce(str, data[0][0], type_check), coerce(int, data[0][1], type_check), \ -[coerce(int, v, type_check) for v in data[0][2]], Foo.__serde__.funcs['foo'](data=data[0][3], \ + "(coerce(str, data[0][0]), coerce(int, data[0][1]), \ +[coerce(int, v) for v in data[0][2]], Foo.__serde__.funcs['foo'](data=data[0][3], \ reuse_instances=reuse_instances),)" """ if is_bare_tuple(arg.type): @@ -750,7 +727,7 @@ def dict(self, arg: DeField) -> str: >>> from typing import List >>> Renderer('foo').render(DeField(Dict[str, int], 'd', datavar='data')) - '{coerce(str, k, type_check): coerce(int, v, type_check) for k, v in data["d"].items()}' + '{coerce(str, k): coerce(int, v) for k, v in data["d"].items()}' >>> @deserialize ... class Foo: pass @@ -773,20 +750,20 @@ def primitive(self, arg: DeField, suppress_coerce: bool = False) -> str: Render rvalue for primitives. >>> Renderer('foo').render(DeField(int, 'i', datavar='data')) - 'coerce(int, data["i"], type_check)' + 'coerce(int, data["i"])' >>> Renderer('foo').render(DeField(int, 'int_field', datavar='data', case='camelcase')) - 'coerce(int, data["intField"], type_check)' + 'coerce(int, data["intField"])' >>> Renderer('foo').render(DeField(int, 'i', datavar='data', index=1, iterbased=True)) - 'coerce(int, data[1], type_check)' + 'coerce(int, data[1])' """ typ = typename(arg.type) dat = arg.data if self.suppress_coerce: return dat else: - return f'coerce({typ}, {dat}, type_check)' + return f'coerce({typ}, {dat})' def c_tor(self, arg: DeField) -> str: return f"{typename(arg.type)}({arg.data})" @@ -827,10 +804,9 @@ def to_iter_arg(f: DeField, *args, **kwargs) -> DeField: return f -def render_from_iter(cls: Type, custom: Optional[DeserializeFunc] = None) -> str: +def render_from_iter(cls: Type, custom: Optional[DeserializeFunc] = None, type_check: TypeCheck = Coerce) -> str: template = """ -def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_scope.reuse_instances_default}}, - type_check: TypeCheck=Coerce): +def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_scope.reuse_instances_default}}): if reuse_instances is Ellipsis: reuse_instances = {{serde_scope.reuse_instances_default}} @@ -851,7 +827,7 @@ def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_s raise UserError(e) """ - renderer = Renderer(FROM_ITER, cls=cls, custom=custom) + renderer = Renderer(FROM_ITER, cls=cls, custom=custom, suppress_coerce=(not type_check.is_coerce())) env = jinja2.Environment(loader=jinja2.DictLoader({'iter': template})) env.filters.update({'rvalue': renderer.render}) env.filters.update({'arg': to_iter_arg}) @@ -863,11 +839,15 @@ def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_s return res -def render_from_dict(cls: Type, rename_all: Optional[str] = None, custom: Optional[DeserializeFunc] = None) -> str: +def render_from_dict( + cls: Type, + rename_all: Optional[str] = None, + custom: Optional[DeserializeFunc] = None, + type_check: TypeCheck = Coerce, +) -> str: template = """ def {{func}}(cls=cls, maybe_generic=None, data=None, - reuse_instances = {{serde_scope.reuse_instances_default}}, - type_check: TypeCheck = Coerce): + reuse_instances = {{serde_scope.reuse_instances_default}}): if reuse_instances is Ellipsis: reuse_instances = {{serde_scope.reuse_instances_default}} @@ -890,7 +870,7 @@ def {{func}}(cls=cls, maybe_generic=None, data=None, return rv """ - renderer = Renderer(FROM_DICT, cls=cls, custom=custom) + renderer = Renderer(FROM_DICT, cls=cls, custom=custom, suppress_coerce=(not type_check.is_coerce())) env = jinja2.Environment(loader=jinja2.DictLoader({'dict': template})) env.filters.update({'rvalue': renderer.render}) env.filters.update({'arg': functools.partial(to_arg, rename_all=rename_all)}) @@ -904,8 +884,7 @@ def {{func}}(cls=cls, maybe_generic=None, data=None, def render_union_func(cls: Type, union_args: List[Type], tagging: Tagging = DefaultTagging) -> str: template = """ -def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_scope.reuse_instances_default}}, - type_check: TypeCheck=Coerce): +def {{func}}(cls=cls, maybe_generic=None, data=None, reuse_instances = {{serde_scope.reuse_instances_default}}): errors = [] {% for t in union_args %} try: diff --git a/serde/json.py b/serde/json.py index 50b1aec4..348734fb 100644 --- a/serde/json.py +++ b/serde/json.py @@ -4,7 +4,6 @@ from typing import Any, Type, Union from .compat import T -from .core import Coerce, TypeCheck from .de import Deserializer, from_dict from .numpy import encode_numpy from .se import Serializer, to_dict @@ -50,7 +49,7 @@ def deserialize(cls, s: Union[bytes, str], **opts): return json_loads(s, **opts) -def to_json(obj: Any, se: Type[Serializer] = JsonSerializer, type_check: TypeCheck = Coerce, **opts) -> str: +def to_json(obj: Any, se: Type[Serializer] = JsonSerializer, **opts) -> str: """ Serialize the object into JSON str. [orjson](https://github.com/ijl/orjson) will be used if installed. @@ -61,12 +60,10 @@ def to_json(obj: Any, se: Type[Serializer] = JsonSerializer, type_check: TypeChe If you want to use another json package, you can subclass `JsonSerializer` and implement your own logic. """ - return se.serialize(to_dict(obj, reuse_instances=False, convert_sets=True, type_check=type_check), **opts) + return se.serialize(to_dict(obj, reuse_instances=False, convert_sets=True), **opts) -def from_json( - c: Type[T], s: Union[str, bytes], de: Type[Deserializer] = JsonDeserializer, type_check: TypeCheck = Coerce, **opts -) -> T: +def from_json(c: Type[T], s: Union[str, bytes], de: Type[Deserializer] = JsonDeserializer, **opts) -> T: """ Deserialize from JSON into the object. [orjson](https://github.com/ijl/orjson) will be used if installed. @@ -75,4 +72,4 @@ def from_json( If you want to use another json package, you can subclass `JsonDeserializer` and implement your own logic. """ - return from_dict(c, de.deserialize(s, **opts), reuse_instances=False, type_check=type_check) + return from_dict(c, de.deserialize(s, **opts), reuse_instances=False) diff --git a/serde/msgpack.py b/serde/msgpack.py index 32090ad1..a40027aa 100644 --- a/serde/msgpack.py +++ b/serde/msgpack.py @@ -7,7 +7,7 @@ import msgpack from .compat import T -from .core import Coerce, SerdeError, TypeCheck +from .core import SerdeError from .de import Deserializer, from_dict, from_tuple from .numpy import encode_numpy from .se import Serializer, to_dict, to_tuple @@ -39,7 +39,6 @@ def to_msgpack( se: Type[Serializer] = MsgPackSerializer, named: bool = True, ext_dict: Dict[Type, int] = None, - type_check: TypeCheck = Coerce, **opts, ) -> bytes: """ @@ -63,7 +62,7 @@ def to_msgpack( to_func = to_dict if named else to_tuple return se.serialize( - to_func(obj, reuse_instances=False, convert_sets=True, type_check=type_check), + to_func(obj, reuse_instances=False, convert_sets=True), ext_type_code=ext_type_code, **opts, ) @@ -75,7 +74,6 @@ def from_msgpack( de: Type[Deserializer] = MsgPackDeserializer, named: bool = True, ext_dict: Dict[int, Type] = None, - type_check: TypeCheck = Coerce, **opts, ) -> Type[T]: """ @@ -92,7 +90,7 @@ def from_msgpack( ext_type = ext_dict.get(ext.code) if ext_type is None: raise SerdeError(f"Could not find type for code {ext.code} in ext_dict") - return from_msgpack(ext_type, ext.data, de, named, type_check=type_check, **opts) + return from_msgpack(ext_type, ext.data, de, named, **opts) else: from_func = from_dict if named else from_tuple - return from_func(c, de.deserialize(s, **opts), reuse_instances=False, type_check=type_check) + return from_func(c, de.deserialize(s, **opts), reuse_instances=False) diff --git a/serde/se.py b/serde/se.py index 94dde183..9ed6e0ee 100644 --- a/serde/se.py +++ b/serde/se.py @@ -143,6 +143,7 @@ def serialize( convert_sets_default: bool = False, serializer: Optional[SerializeFunc] = None, tagging: Tagging = DefaultTagging, + type_check: TypeCheck = Coerce, **kwargs, ): """ @@ -261,8 +262,8 @@ def wrap(cls: Type): if f.serializer: g[f.serializer.name] = f.serializer - add_func(scope, TO_ITER, render_to_tuple(cls, serializer), g) - add_func(scope, TO_DICT, render_to_dict(cls, rename_all, serializer), g) + add_func(scope, TO_ITER, render_to_tuple(cls, serializer, type_check), g) + add_func(scope, TO_DICT, render_to_dict(cls, rename_all, serializer, type_check), g) add_func(scope, TYPE_CHECK, render_type_check(cls), g) logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}") @@ -294,9 +295,7 @@ def is_serializable(instance_or_class: Any) -> bool: return hasattr(instance_or_class, SERDE_SCOPE) -def to_obj(o, named: bool, reuse_instances: bool, convert_sets: bool, c: Type = None, type_check: TypeCheck = Coerce): - strict = type_check.is_strict() - +def to_obj(o, named: bool, reuse_instances: bool, convert_sets: bool, c: Type = None): try: thisfunc = functools.partial( to_obj, @@ -309,37 +308,19 @@ def to_obj(o, named: bool, reuse_instances: bool, convert_sets: bool, c: Type = if is_serializable(o): serde_scope: SerdeScope = getattr(o, SERDE_SCOPE) func_name = TO_DICT if named else TO_ITER - if strict: - serde_scope.funcs[TYPE_CHECK](o) - return serde_scope.funcs[func_name]( - o, reuse_instances=reuse_instances, convert_sets=convert_sets, type_check=type_check - ) + return serde_scope.funcs[func_name](o, reuse_instances=reuse_instances, convert_sets=convert_sets) elif is_dataclass(o): - if strict: - serde_scope.funcs[TYPE_CHECK](o) if named: return dataclasses.asdict(o) else: return dataclasses.astuple(o) elif isinstance(o, list): - if strict and c: - if is_list_instance(o, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") return [thisfunc(e) for e in o] elif isinstance(o, tuple): - if strict and c: - if is_tuple_instance(o, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") return tuple(thisfunc(e) for e in o) elif isinstance(o, set): - if strict and c: - if is_set_instance(o, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") return [thisfunc(e) for e in o] elif isinstance(o, dict): - if strict and c: - if is_dict_instance(o, c): - raise SerdeError(f"Failed to deserialize into {typename(c)}") return {k: thisfunc(v) for k, v in o.items()} elif is_str_serializable_instance(o): return str(o) @@ -359,7 +340,7 @@ def astuple(v): return to_tuple(v, reuse_instances=False, convert_sets=False) -def to_tuple(o, reuse_instances: bool = ..., convert_sets: bool = ..., type_check: TypeCheck = Coerce) -> Any: +def to_tuple(o, reuse_instances: bool = ..., convert_sets: bool = ...) -> Any: """ Serialize object into tuple. @@ -379,7 +360,7 @@ def to_tuple(o, reuse_instances: bool = ..., convert_sets: bool = ..., type_chec >>> to_tuple(lst) [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] """ - return to_obj(o, named=False, reuse_instances=reuse_instances, convert_sets=convert_sets, type_check=type_check) + return to_obj(o, named=False, reuse_instances=reuse_instances, convert_sets=convert_sets) def asdict(v): @@ -389,7 +370,7 @@ def asdict(v): return to_dict(v, reuse_instances=False, convert_sets=False) -def to_dict(o, reuse_instances: bool = ..., convert_sets: bool = ..., type_check: TypeCheck = Coerce) -> Any: +def to_dict(o, reuse_instances: bool = ..., convert_sets: bool = ...) -> Any: """ Serialize object into dictionary. @@ -409,7 +390,7 @@ def to_dict(o, reuse_instances: bool = ..., convert_sets: bool = ..., type_check >>> to_dict(lst) [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] """ - return to_obj(o, named=True, reuse_instances=reuse_instances, convert_sets=convert_sets, type_check=type_check) + return to_obj(o, named=True, reuse_instances=reuse_instances, convert_sets=convert_sets) @dataclass @@ -448,11 +429,10 @@ def sefields(cls: Type) -> Iterator[SeField]: yield f -def render_to_tuple(cls: Type, custom: Optional[SerializeFunc] = None) -> str: +def render_to_tuple(cls: Type, custom: Optional[SerializeFunc] = None, type_check: TypeCheck = Coerce) -> str: template = """ def {{func}}(obj, reuse_instances = {{serde_scope.reuse_instances_default}}, - convert_sets = {{serde_scope.convert_sets_default}}, - type_check: TypeCheck=Coerce): + convert_sets = {{serde_scope.convert_sets_default}}): if reuse_instances is Ellipsis: reuse_instances = {{serde_scope.reuse_instances_default}} if convert_sets is Ellipsis: @@ -470,17 +450,18 @@ def {{func}}(obj, reuse_instances = {{serde_scope.reuse_instances_default}}, ) """ - renderer = Renderer(TO_ITER, custom) + renderer = Renderer(TO_ITER, custom, suppress_coerce=(not type_check.is_coerce())) env = jinja2.Environment(loader=jinja2.DictLoader({"iter": template})) env.filters.update({"rvalue": renderer.render}) return env.get_template("iter").render(func=TO_ITER, serde_scope=getattr(cls, SERDE_SCOPE), fields=sefields(cls)) -def render_to_dict(cls: Type, case: Optional[str] = None, custom: Optional[SerializeFunc] = None) -> str: +def render_to_dict( + cls: Type, case: Optional[str] = None, custom: Optional[SerializeFunc] = None, type_check: TypeCheck = Coerce +) -> str: template = """ def {{func}}(obj, reuse_instances = {{serde_scope.reuse_instances_default}}, - convert_sets = {{serde_scope.convert_sets_default}}, - type_check: TypeCheck = Coerce): + convert_sets = {{serde_scope.convert_sets_default}}): if reuse_instances is Ellipsis: reuse_instances = {{serde_scope.reuse_instances_default}} if convert_sets is Ellipsis: @@ -503,7 +484,7 @@ def {{func}}(obj, reuse_instances = {{serde_scope.reuse_instances_default}}, {% endfor -%} return res """ - renderer = Renderer(TO_DICT, custom) + renderer = Renderer(TO_DICT, custom, suppress_coerce=(not type_check.is_coerce())) lrenderer = LRenderer(case) env = jinja2.Environment(loader=jinja2.DictLoader({"dict": template})) env.filters.update({"rvalue": renderer.render}) @@ -517,7 +498,7 @@ def render_union_func(cls: Type, union_args: List[Type], tagging: Tagging = Defa Render function that serializes a field with union type. """ template = """ -def {{func}}(obj, reuse_instances, convert_sets, type_check: TypeCheck=Coerce): +def {{func}}(obj, reuse_instances, convert_sets): union_args = serde_scope.union_se_args['{{func}}'] {% for t in union_args %} @@ -594,6 +575,7 @@ class Renderer: func: str custom: Optional[SerializeFunc] = None # Custom class level serializer. suppress_coerce: bool = False + """ Suppress type coercing because generated union serializer has its own type checking """ def render(self, arg: SeField) -> str: """ @@ -601,10 +583,10 @@ def render(self, arg: SeField) -> str: >>> from typing import Tuple >>> Renderer(TO_ITER).render(SeField(int, 'i')) - 'coerce(int, i, type_check)' + 'coerce(int, i)' >>> Renderer(TO_ITER).render(SeField(List[int], 'l')) - '[coerce(int, v, type_check) for v in l]' + '[coerce(int, v) for v in l]' >>> @serialize ... @dataclass(unsafe_hash=True) @@ -617,7 +599,7 @@ def render(self, arg: SeField) -> str: "[v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, convert_sets=convert_sets) for v in foo]" >>> Renderer(TO_ITER).render(SeField(Dict[str, Foo], 'foo')) - "{coerce(str, k, type_check): v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, \ + "{coerce(str, k): v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, \ convert_sets=convert_sets) for k, v in foo.items()}" >>> Renderer(TO_ITER).render(SeField(Dict[Foo, Foo], 'foo')) @@ -626,8 +608,8 @@ def render(self, arg: SeField) -> str: convert_sets=convert_sets) for k, v in foo.items()}" >>> Renderer(TO_ITER).render(SeField(Tuple[str, Foo, int], 'foo')) - "(coerce(str, foo[0], type_check), foo[1].__serde__.funcs['to_iter'](foo[1], reuse_instances=reuse_instances, \ -convert_sets=convert_sets), coerce(int, foo[2], type_check),)" + "(coerce(str, foo[0]), foo[1].__serde__.funcs['to_iter'](foo[1], reuse_instances=reuse_instances, \ +convert_sets=convert_sets), coerce(int, foo[2]),)" """ if arg.serializer and arg.serializer.inner is not default_serializer: res = self.custom_field_serializer(arg) @@ -775,7 +757,7 @@ def primitive(self, arg: SeField) -> str: if self.suppress_coerce: return var else: - return f'coerce({typ}, {var}, type_check)' + return f'coerce({typ}, {var})' def string(self, arg: SeField) -> str: return f"str({arg.varname})" diff --git a/serde/toml.py b/serde/toml.py index efbd0f4a..2bcfe3bf 100644 --- a/serde/toml.py +++ b/serde/toml.py @@ -7,7 +7,6 @@ import tomli_w from .compat import T -from .core import Coerce, TypeCheck from .de import Deserializer, from_dict from .se import Serializer, to_dict @@ -26,7 +25,7 @@ def deserialize(cls, s, **opts): return tomli.loads(s, **opts) -def to_toml(obj, se: Type[Serializer] = TomlSerializer, type_check: TypeCheck = Coerce, **opts) -> str: +def to_toml(obj, se: Type[Serializer] = TomlSerializer, **opts) -> str: """ Serialize the object into TOML. @@ -35,12 +34,10 @@ def to_toml(obj, se: Type[Serializer] = TomlSerializer, type_check: TypeCheck = If you want to use the other toml package, you can subclass `TomlSerializer` and implement your own logic. """ - return se.serialize(to_dict(obj, reuse_instances=False, type_check=type_check), **opts) + return se.serialize(to_dict(obj, reuse_instances=False), **opts) -def from_toml( - c: Type[T], s: str, de: Type[Deserializer] = TomlDeserializer, type_check: TypeCheck = Coerce, **opts -) -> T: +def from_toml(c: Type[T], s: str, de: Type[Deserializer] = TomlDeserializer, **opts) -> T: """ Deserialize from TOML into the object. @@ -49,4 +46,4 @@ def from_toml( If you want to use the other toml package, you can subclass `TomlDeserializer` and implement your own logic. """ - return from_dict(c, de.deserialize(s, **opts), reuse_instances=False, type_check=type_check) + return from_dict(c, de.deserialize(s, **opts), reuse_instances=False) diff --git a/serde/yaml.py b/serde/yaml.py index a989eea2..8d9a8455 100644 --- a/serde/yaml.py +++ b/serde/yaml.py @@ -6,7 +6,6 @@ import yaml from .compat import T -from .core import Coerce, TypeCheck from .de import Deserializer, from_dict from .se import Serializer, to_dict @@ -25,7 +24,7 @@ def deserialize(cls, s, **opts): return yaml.safe_load(s, **opts) -def to_yaml(obj, se: Type[Serializer] = YamlSerializer, type_check: TypeCheck = Coerce, **opts) -> str: +def to_yaml(obj, se: Type[Serializer] = YamlSerializer, **opts) -> str: """ Serialize the object into YAML. @@ -34,16 +33,14 @@ def to_yaml(obj, se: Type[Serializer] = YamlSerializer, type_check: TypeCheck = If you want to use the other yaml package, you can subclass `YamlSerializer` and implement your own logic. """ - return se.serialize(to_dict(obj, reuse_instances=False, type_check=type_check), **opts) + return se.serialize(to_dict(obj, reuse_instances=False), **opts) -def from_yaml( - c: Type[T], s: str, de: Type[Deserializer] = YamlDeserializer, type_check: TypeCheck = Coerce, **opts -) -> T: +def from_yaml(c: Type[T], s: str, de: Type[Deserializer] = YamlDeserializer, **opts) -> T: """ `c` is a class obejct and `s` is YAML string. If you supply keyword arguments other than `de`, they will be passed in `yaml.safe_load` function. If you want to use the other yaml package, you can subclass `YamlDeserializer` and implement your own logic. """ - return from_dict(c, de.deserialize(s, **opts), reuse_instances=False, type_check=type_check) + return from_dict(c, de.deserialize(s, **opts), reuse_instances=False) diff --git a/tests/test_basics.py b/tests/test_basics.py index 488eaf32..86ea4557 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -47,7 +47,6 @@ class C: c = C(10, t) assert c == de(C, se(c)) - assert c == de(C, se(c, type_check=Strict), type_check=Strict) @serde.serde(**opt) class Nested: @@ -59,11 +58,9 @@ class C: c = C(Nested(t)) assert c == de(C, se(c)) - assert c == de(C, se(c, type_check=Strict), type_check=Strict) if se is not serde.toml.to_toml: assert t == de(T, se(t)) - assert t == de(T, se(t, type_check=Strict), type_check=Strict) @pytest.mark.parametrize('t,T,filter', types, ids=type_ids()) @@ -771,6 +768,7 @@ def __post_init__(self): ] +""" @pytest.mark.parametrize('T,data,exc', test_cases) def test_type_check(T, data, exc): @serde.serde @@ -784,6 +782,7 @@ class C: else: d = serde.to_dict(C(data), type_check=Strict) serde.from_dict(C, d, type_check=Strict) +""" def test_uncoercible():