From f62733d998c2f0c6ef8f07bbed2bbb8817deea09 Mon Sep 17 00:00:00 2001 From: voidZXL Date: Wed, 24 Jan 2024 22:31:16 +0800 Subject: [PATCH] fix dataclass convert issue --- docs/en/README.md | 14 +++++++------- docs/mkdocs.en.yml | 1 + docs/mkdocs.zh.yml | 1 + docs/zh/README.md | 14 +++++++------- tests/test_cls.py | 10 ++++++++++ utype/__init__.py | 2 +- utype/parser/cls.py | 10 ++++++++++ 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index 40e5613..6970631 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -25,7 +25,7 @@ utype is a data type declaration and parsing library based on Python type annota Currently, Python does not have the mechanism to guarantee types at runtime, so when we write a function, we often need to perform type assertion and constraint checking on parameters before we can start writing the actual logic. such as ```python -def login(username, password): +def signup(username, password): import re if not isinstance(username, str) \ or not re.match('[0-9a-zA-Z]{3,20}', username): @@ -43,18 +43,18 @@ However, if we can declare all types and constraints in the parameters, enforce from utype.types import Annotated # compat 3.7+ @utype.parse - def login( + def signup( username: Annotated[str, utype.Param(regex='[0-9a-zA-Z]{3,20}')], password: Annotated[str, utype.Param(min_length=6)] ): # # you can directly start coding return username, password - print(login('alice', 123456)) + print(signup('alice', 123456)) ('alice', '123456') try: - login('@invalid', 123456) + signup('@invalid', 123456) except utype.exc.ParseError as e: print(e) """ @@ -68,18 +68,18 @@ However, if we can declare all types and constraints in the parameters, enforce import utype @utype.parse - def login( + def signup( username: str = utype.Param(regex='[0-9a-zA-Z]{3,20}'), password: str = utype.Param(min_length=6) ): # # you can directly start coding return username, password - print(login('alice', 123456)) + print(signup('alice', 123456)) ('alice', '123456') try: - login('@invalid', 123456) + signup('@invalid', 123456) except utype.exc.ParseError as e: print(e) """ diff --git a/docs/mkdocs.en.yml b/docs/mkdocs.en.yml index 5152790..20ba19e 100644 --- a/docs/mkdocs.en.yml +++ b/docs/mkdocs.en.yml @@ -28,6 +28,7 @@ theme: - toc.follow - navigation.tracking - navigation.top + - content.code.copy repo_name: utilmeta/utype repo_url: https://github.com/utilmeta/utype diff --git a/docs/mkdocs.zh.yml b/docs/mkdocs.zh.yml index a0f40cf..4b2f1dc 100644 --- a/docs/mkdocs.zh.yml +++ b/docs/mkdocs.zh.yml @@ -27,6 +27,7 @@ theme: - toc.follow - navigation.tracking - navigation.top + - content.code.copy repo_name: utilmeta/utype repo_url: https://github.com/utilmeta/utype diff --git a/docs/zh/README.md b/docs/zh/README.md index 85a8cee..d3ee71f 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -26,7 +26,7 @@ utype 是一个基于 Python 类型注解的数据类型声明与解析库,能 目前 Python 没有在运行时解析类型与校验约束的机制,所以当我们编写一个函数时,往往需要先对参数进行类型断言和约束校验等操作,然后才能开始编写真正的逻辑,否则很可能会在运行时发生异常错误,如 ```python -def login(username, password): +def signup(username, password): import re if not isinstance(username, str) \ or not re.match('[0-9a-zA-Z]{3,20}', username): @@ -44,18 +44,18 @@ def login(username, password): from utype.types import Annotated @utype.parse - def login( + def signup( username: Annotated[str, utype.Param(regex='[0-9a-zA-Z]{3,20}')], password: Annotated[str, utype.Param(min_length=6)] ): # 你可以直接开始编写逻辑了 return username, password - print(login('alice', 123456)) + print(signup('alice', 123456)) ('alice', '123456') try: - login('@invalid', 123456) + signup('@invalid', 123456) except utype.exc.ParseError as e: print(e) """ @@ -69,18 +69,18 @@ def login(username, password): import utype @utype.parse - def login( + def signup( username: str = utype.Param(regex='[0-9a-zA-Z]{3,20}'), password: str = utype.Param(min_length=6) ): # 你可以直接开始编写逻辑了 return username, password - print(login('alice', 123456)) + print(signup('alice', 123456)) ('alice', '123456') try: - login('@invalid', 123456) + signup('@invalid', 123456) except utype.exc.ParseError as e: print(e) """ diff --git a/tests/test_cls.py b/tests/test_cls.py index 460b4b9..fe6c058 100644 --- a/tests/test_cls.py +++ b/tests/test_cls.py @@ -996,3 +996,13 @@ class LogicalUser(DataClass): assert res == {'oneOf': [{'type': 'object', 'properties': {'name': {'type': 'string', 'maxLength': 10}, 'age': {'type': 'integer'}}, 'required': ['name', 'age']}, {'type': 'array', 'prefixItems': [{'type': 'string'}, {'type': 'integer'}]}]} + + def test_pass(self): + class A(Schema): + a: str = utype.Field(no_output=True) + + class B(Schema): + a_or: A + + b = B(a_or=[A(a=3)]) + assert b.a_or.a == '3' diff --git a/utype/__init__.py b/utype/__init__.py index 7118d95..ba8e7be 100644 --- a/utype/__init__.py +++ b/utype/__init__.py @@ -12,7 +12,7 @@ register_transformer = TypeTransformer.registry.register -VERSION = (0, 4, 0, None) +VERSION = (0, 4, 1, None) def _get_version(): diff --git a/utype/parser/cls.py b/utype/parser/cls.py index 6f05bb6..b7d59cc 100644 --- a/utype/parser/cls.py +++ b/utype/parser/cls.py @@ -574,8 +574,18 @@ def init_dataclass( detector=lambda cls: isinstance(getattr(cls, "__parser__", None), ClassParser), ) def transform_dataclass(transformer: TypeTransformer, data, cls): + if isinstance(data, (list, tuple)) and not transformer.options.no_explicit_cast: + if data: + if transformer.options.no_data_loss and len(data) > 1: + raise TypeError + data = data[0] + # otherwise the data will become dict then fill the dataclass + if type(data) == cls: + return data + if transformer.options.allow_subclasses: if isinstance(data, cls): # subclass return data + return init_dataclass(cls, data, context=transformer.context)