msgspec-xml 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/PKG-INFO +6 -6
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/README.md +5 -5
- msgspec_xml-0.3.0/msgspec_xml/__init__.py +16 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/decoder.py +1 -1
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/encoder.py +5 -2
- msgspec_xml-0.3.0/msgspec_xml/metadata.py +74 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pylock.toml +111 -111
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pyproject.toml +13 -17
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-03.xml +1 -1
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/test_metadata.py +12 -12
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/test_roundtrip.py +11 -8
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/uv.lock +115 -115
- msgspec_xml-0.2.0/msgspec-xml.zip +0 -0
- msgspec_xml-0.2.0/msgspec_xml/__init__.py +0 -6
- msgspec_xml-0.2.0/msgspec_xml/metadata.py +0 -43
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.gitattributes +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.gitignore +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.pre-commit-config.yaml +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/LICENSE +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/introspection.py +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pdm.toml +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/renovate.json +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/__init__.py +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-01.xml +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-02.xml +0 -0
- {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-04.xml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: msgspec-xml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: XML Serialization/Deserialization using msgspec
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/msgspec-xml
|
|
6
6
|
Project-URL: Issues, https://codefloe.com/buriedincode/msgspec-xml/issues
|
|
@@ -53,17 +53,17 @@ from decimal import Decimal
|
|
|
53
53
|
from typing import Annotated
|
|
54
54
|
|
|
55
55
|
from msgspec import Struct
|
|
56
|
-
from msgspec_xml import
|
|
56
|
+
from msgspec_xml import decode, encode, Attribute, Value, Wrapper
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class Price(Struct):
|
|
60
|
-
currency: Annotated[str,
|
|
61
|
-
cost: Annotated[Decimal,
|
|
60
|
+
currency: Annotated[str, Attribute()]
|
|
61
|
+
cost: Annotated[Decimal, Value()]
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class Book(Struct, rename="pascal"):
|
|
65
|
-
title: str
|
|
66
|
-
prices: Annotated[list[Price],
|
|
65
|
+
title: str # Implied Element class
|
|
66
|
+
prices: Annotated[list[Price], Wrapper(wrapper="Prices", tag="Price")]
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
book = Book(
|
|
@@ -26,17 +26,17 @@ from decimal import Decimal
|
|
|
26
26
|
from typing import Annotated
|
|
27
27
|
|
|
28
28
|
from msgspec import Struct
|
|
29
|
-
from msgspec_xml import
|
|
29
|
+
from msgspec_xml import decode, encode, Attribute, Value, Wrapper
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Price(Struct):
|
|
33
|
-
currency: Annotated[str,
|
|
34
|
-
cost: Annotated[Decimal,
|
|
33
|
+
currency: Annotated[str, Attribute()]
|
|
34
|
+
cost: Annotated[Decimal, Value()]
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class Book(Struct, rename="pascal"):
|
|
38
|
-
title: str
|
|
39
|
-
prices: Annotated[list[Price],
|
|
38
|
+
title: str # Implied Element class
|
|
39
|
+
prices: Annotated[list[Price], Wrapper(wrapper="Prices", tag="Price")]
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
book = Book(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"XML",
|
|
3
|
+
"Attribute",
|
|
4
|
+
"Decoder",
|
|
5
|
+
"Element",
|
|
6
|
+
"Encoder",
|
|
7
|
+
"Value",
|
|
8
|
+
"Wrapper",
|
|
9
|
+
"decode",
|
|
10
|
+
"encode",
|
|
11
|
+
]
|
|
12
|
+
__version__ = "0.3.0"
|
|
13
|
+
|
|
14
|
+
from msgspec_xml.decoder import Decoder, decode
|
|
15
|
+
from msgspec_xml.encoder import Encoder, encode
|
|
16
|
+
from msgspec_xml.metadata import XML, Attribute, Element, Value, Wrapper
|
|
@@ -53,7 +53,7 @@ def _decode_field(
|
|
|
53
53
|
if xml.attr:
|
|
54
54
|
name = attribute_name(field=field, info=info, namespaces=namespaces)
|
|
55
55
|
return element.attrib.get(name, _MISSING)
|
|
56
|
-
if xml.
|
|
56
|
+
if xml.value:
|
|
57
57
|
return (element.text or "").strip()
|
|
58
58
|
if info.item is not None:
|
|
59
59
|
return _decode_list(element=element, field=field, info=info, namespaces=namespaces)
|
|
@@ -76,7 +76,7 @@ def _encode_field(
|
|
|
76
76
|
if xml.attr:
|
|
77
77
|
name = attribute_name(field=field, info=info, namespaces=namespaces)
|
|
78
78
|
element.set(name, _stringify(value=value))
|
|
79
|
-
elif xml.
|
|
79
|
+
elif xml.value:
|
|
80
80
|
element.text = _stringify(value)
|
|
81
81
|
elif info.item is not None:
|
|
82
82
|
_encode_list(element=element, field=field, info=info, value=value, namespaces=namespaces) # ty: ignore[invalid-argument-type]
|
|
@@ -106,4 +106,7 @@ def _encode_list(
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
def _stringify(value: object) -> str:
|
|
109
|
-
|
|
109
|
+
value = to_builtins(value)
|
|
110
|
+
if isinstance(value, bool):
|
|
111
|
+
return "true" if value else "false"
|
|
112
|
+
return str(value)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
__all__ = ["XML", "Attribute", "Element", "Value", "Wrapper"]
|
|
2
|
+
|
|
3
|
+
from msgspec import Struct
|
|
4
|
+
from msgspec.structs import force_setattr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class XML(Struct, frozen=True, forbid_unknown_fields=True):
|
|
8
|
+
tag: str | None = None
|
|
9
|
+
wrapper: str | None = None
|
|
10
|
+
attr: bool = False
|
|
11
|
+
value: bool = False
|
|
12
|
+
ns: str | None = None
|
|
13
|
+
|
|
14
|
+
def __post_init__(self) -> None:
|
|
15
|
+
if self.attr and self.value:
|
|
16
|
+
raise ValueError("A field cannot be both an XML attribute and element value.")
|
|
17
|
+
if self.wrapper is not None and self.attr:
|
|
18
|
+
raise ValueError("Wrapper elements cannot be used on XML attributes.")
|
|
19
|
+
if self.wrapper is not None and self.value:
|
|
20
|
+
raise ValueError("Wrapper elements cannot be used on XML value.")
|
|
21
|
+
if self.ns is not None and self.value:
|
|
22
|
+
raise ValueError("Namespaces cannot be used on XML value.")
|
|
23
|
+
if self.tag == "":
|
|
24
|
+
raise ValueError("tag cannot be empty.")
|
|
25
|
+
if self.wrapper == "":
|
|
26
|
+
raise ValueError("wrapper cannot be empty.")
|
|
27
|
+
if self.ns == "":
|
|
28
|
+
raise ValueError("ns cannot be empty.")
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def is_element(self) -> bool:
|
|
32
|
+
return not self.attr and not self.value
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def is_attribute(self) -> bool:
|
|
36
|
+
return self.attr
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def is_value(self) -> bool:
|
|
40
|
+
return self.value
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_wrapped(self) -> bool:
|
|
44
|
+
return self.wrapper is not None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Element(XML, frozen=True, forbid_unknown_fields=True):
|
|
48
|
+
def __post_init__(self) -> None:
|
|
49
|
+
super().__post_init__()
|
|
50
|
+
if self.attr:
|
|
51
|
+
raise TypeError("Element does not support 'attr'; use Attribute instead.")
|
|
52
|
+
if self.value:
|
|
53
|
+
raise TypeError("Element does not support 'text'; use Value instead.")
|
|
54
|
+
if self.wrapper:
|
|
55
|
+
raise TypeError("Element does not support 'wrapper'; use Wrapper instead.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Attribute(XML, frozen=True, forbid_unknown_fields=True):
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
force_setattr(self, "attr", True)
|
|
61
|
+
super().__post_init__()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Value(XML, frozen=True, forbid_unknown_fields=True):
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
force_setattr(self, "value", True)
|
|
67
|
+
super().__post_init__()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Wrapper(XML, frozen=True, forbid_unknown_fields=True):
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
super().__post_init__()
|
|
73
|
+
if self.wrapper is None:
|
|
74
|
+
raise ValueError("Wrapper requires the 'wrapper' field to be set.")
|