From 1f42a6a5990664494049d4f97810cb0bcccc76df Mon Sep 17 00:00:00 2001 From: Chris Tsou Date: Sun, 16 Jun 2024 18:06:59 +0300 Subject: [PATCH] revert: Add back support for optional lists (#1053) --- docs/models/types.md | 2 + tests/fixtures/books/fixtures.py | 64 +++++++++++----------- tests/formats/dataclass/cases/attribute.py | 3 +- tests/formats/dataclass/cases/element.py | 6 +- xsdata/formats/dataclass/typing.py | 28 ++++++++++ 5 files changed, 69 insertions(+), 34 deletions(-) diff --git a/docs/models/types.md b/docs/models/types.md index 2b3522658..172d4e0e0 100644 --- a/docs/models/types.md +++ b/docs/models/types.md @@ -25,6 +25,7 @@ collections are also supported. | Case | Example | | -------------- | --------------------------------------------------------------------------------- | | List | `value: List[str] = field(default_factory=list)` | +| Optional List | `value: Optional[List[str]] = field(default=None)` | | List Union | `value: List[Union[str, int]] = field(default_factory=list)` | | Tokens List | `value: List[str] = field(default_factory=list, metadata={"tokens": True})` | | List of Tokens | `value: List[List[str]] = field(default_factory=list, metadata={"tokens": True})` | @@ -36,6 +37,7 @@ collections are also supported. | Case | Example | | --------------- | ---------------------------------------------------------------------------------------------- | | Tuple | `value: Tuple[str, ...] = field(default_factory=tuple)` | +| Optional Tuple | `value: Optional[Tuple[str, ...]] = field(default=None)` | | Tuple Union | `value: Tuple[Union[str, int], ...] = field(default_factory=tuple)` | | Tokens Tuple | `value: Tuple[str, ...] = field(default_factory=tuple, metadata={"tokens": True})` | | Tuple of Tokens | `value: Tuple[Tuple[str, ...], ...] = field(default_factory=tuple, metadata={"tokens": True})` | diff --git a/tests/fixtures/books/fixtures.py b/tests/fixtures/books/fixtures.py index 1146dbac5..8eeea7939 100644 --- a/tests/fixtures/books/fixtures.py +++ b/tests/fixtures/books/fixtures.py @@ -1,4 +1,4 @@ -from tests.fixtures.books import Books, BookForm +from tests.fixtures.books import BookForm, Books from xsdata.models.datatype import XmlDate books = Books( @@ -24,37 +24,37 @@ ] ) events = [ - ('start-ns', 'brk', 'urn:books'), - ('start', '{urn:books}books', {}, {'brk': 'urn:books'}), - ('start', 'book', {'id': 'bk001', 'lang': 'en'}, {'brk': 'urn:books'}), - ('start', 'author', {}, {'brk': 'urn:books'}), - ('end', 'author', 'Hightower, Kim', '\n '), - ('start', 'title', {}, {'brk': 'urn:books'}), - ('end', 'title', 'The First Book', '\n '), - ('start', 'genre', {}, {'brk': 'urn:books'}), - ('end', 'genre', 'Fiction', '\n '), - ('start', 'price', {}, {'brk': 'urn:books'}), - ('end', 'price', '44.95', '\n '), - ('start', 'pub_date', {}, {'brk': 'urn:books'}), - ('end', 'pub_date', '2000-10-01', '\n '), - ('start', 'review', {}, {'brk': 'urn:books'}), - ('end', 'review', 'An amazing story of nothing.', '\n '), - ('end', 'book', '\n ', '\n '), - ('start', 'book', {'id': 'bk002', 'lang': 'en'}, {'brk': 'urn:books'}), - ('start', 'author', {}, {'brk': 'urn:books'}), - ('end', 'author', 'Nagata, Suanne', '\n '), - ('start', 'title', {}, {'brk': 'urn:books'}), - ('end', 'title', 'Becoming Somebody', '\n '), - ('start', 'genre', {}, {'brk': 'urn:books'}), - ('end', 'genre', 'Biography', '\n '), - ('start', 'price', {}, {'brk': 'urn:books'}), - ('end', 'price', '33.95', '\n '), - ('start', 'pub_date', {}, {'brk': 'urn:books'}), - ('end', 'pub_date', '2001-01-10', '\n '), - ('start', 'review', {}, {'brk': 'urn:books'}), - ('end', 'review', 'A masterpiece of the fine art of gossiping.', '\n '), - ('end', 'book', '\n ', '\n'), - ('end', '{urn:books}books', '\n ', None) + ("start-ns", "brk", "urn:books"), + ("start", "{urn:books}books", {}, {"brk": "urn:books"}), + ("start", "book", {"id": "bk001", "lang": "en"}, {"brk": "urn:books"}), + ("start", "author", {}, {"brk": "urn:books"}), + ("end", "author", "Hightower, Kim", "\n "), + ("start", "title", {}, {"brk": "urn:books"}), + ("end", "title", "The First Book", "\n "), + ("start", "genre", {}, {"brk": "urn:books"}), + ("end", "genre", "Fiction", "\n "), + ("start", "price", {}, {"brk": "urn:books"}), + ("end", "price", "44.95", "\n "), + ("start", "pub_date", {}, {"brk": "urn:books"}), + ("end", "pub_date", "2000-10-01", "\n "), + ("start", "review", {}, {"brk": "urn:books"}), + ("end", "review", "An amazing story of nothing.", "\n "), + ("end", "book", "\n ", "\n "), + ("start", "book", {"id": "bk002", "lang": "en"}, {"brk": "urn:books"}), + ("start", "author", {}, {"brk": "urn:books"}), + ("end", "author", "Nagata, Suanne", "\n "), + ("start", "title", {}, {"brk": "urn:books"}), + ("end", "title", "Becoming Somebody", "\n "), + ("start", "genre", {}, {"brk": "urn:books"}), + ("end", "genre", "Biography", "\n "), + ("start", "price", {}, {"brk": "urn:books"}), + ("end", "price", "33.95", "\n "), + ("start", "pub_date", {}, {"brk": "urn:books"}), + ("end", "pub_date", "2001-01-10", "\n "), + ("start", "review", {}, {"brk": "urn:books"}), + ("end", "review", "A masterpiece of the fine art of gossiping.", "\n "), + ("end", "book", "\n ", "\n"), + ("end", "{urn:books}books", "\n ", None), ] events_default_ns = [ diff --git a/tests/formats/dataclass/cases/attribute.py b/tests/formats/dataclass/cases/attribute.py index c249f0f99..e4d75a583 100644 --- a/tests/formats/dataclass/cases/attribute.py +++ b/tests/formats/dataclass/cases/attribute.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Set, Tuple, Union +from typing import Dict, List, Literal, Optional, Set, Tuple, Union from tests.formats.dataclass.cases import PY39, PY310 from xsdata.models.enums import Mode @@ -14,6 +14,7 @@ (Tuple[int, ...], ((int,), None, tuple)), (List[int], ((int,), None, list)), (List[Union[str, int]], ((str, int), None, list)), + (Optional[List[Union[str, int]]], ((str, int), None, list)), ] not_tokens = [ diff --git a/tests/formats/dataclass/cases/element.py b/tests/formats/dataclass/cases/element.py index 4a94c5098..24310ab58 100644 --- a/tests/formats/dataclass/cases/element.py +++ b/tests/formats/dataclass/cases/element.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple, Union from tests.formats.dataclass.cases import PY39 @@ -7,10 +7,13 @@ (Dict[str, int], False), (Tuple[str, str], False), (List[str], ((str,), None, list)), + (Optional[List[str]], ((str,), None, list)), (Tuple[str, ...], ((str,), None, tuple)), (List[List[str]], ((str,), list, list)), + (Optional[List[List[Union[str, int]]]], ((str, int), list, list)), (List[Tuple[str, ...]], ((str,), list, tuple)), (Tuple[List[str], ...], ((str,), tuple, list)), + (Optional[Tuple[List[str], ...]], ((str,), tuple, list)), ] not_tokens = [ @@ -23,6 +26,7 @@ (str, ((str,), None, None)), (List[str], ((str,), list, None)), (List[Union[str, int]], ((str, int), list, None)), + (Optional[List[Union[str, int]]], ((str, int), list, None)), (Tuple[str, ...], ((str,), tuple, None)), ] diff --git a/xsdata/formats/dataclass/typing.py b/xsdata/formats/dataclass/typing.py index 3782e246d..95d3548bc 100644 --- a/xsdata/formats/dataclass/typing.py +++ b/xsdata/formats/dataclass/typing.py @@ -90,6 +90,29 @@ def analyze_token_args(origin: Any, args: Tuple[Any, ...]) -> Tuple[Any]: raise TypeError +def analyze_optional_origin( + origin: Any, args: Tuple[Any, ...], types: Tuple[Any, ...] +) -> Tuple[Any, ...]: + """Analyze optional type annotations. + + Remove the NoneType, adjust and return the origin, args and types. + + Args + origin: The annotation origin + args: The annotation arguments + types: The annotation types + + Returns: + The old or new origin args and types. + """ + if origin in UNION_TYPES: + new_args = filter_none_type(args) + if len(new_args) == 1: + return get_origin(new_args[0]), get_args(new_args[0]), new_args + + return origin, args, types + + def filter_none_type(args: Tuple[Any, ...]) -> Tuple[Any, ...]: return tuple(arg for arg in args if arg is not NONE_TYPE) @@ -105,11 +128,14 @@ def evaluate_text(annotation: Any, tokens: bool = False) -> Result: def evaluate_attribute(annotation: Any, tokens: bool = False) -> Result: """Validate annotations for a xml attribute.""" + types = (annotation,) origin = get_origin(annotation) args = get_args(annotation) tokens_factory = None if tokens: + origin, args, types = analyze_optional_origin(origin, args, types) + args = analyze_token_args(origin, args) tokens_factory = origin origin = get_origin(args[0]) @@ -161,6 +187,8 @@ def evaluate_element(annotation: Any, tokens: bool = False) -> Result: args = get_args(annotation) tokens_factory = factory = None + origin, args, types = analyze_optional_origin(origin, args, types) + if tokens: args = analyze_token_args(origin, args)