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.
Files changed (26) hide show
  1. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/PKG-INFO +6 -6
  2. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/README.md +5 -5
  3. msgspec_xml-0.3.0/msgspec_xml/__init__.py +16 -0
  4. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/decoder.py +1 -1
  5. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/encoder.py +5 -2
  6. msgspec_xml-0.3.0/msgspec_xml/metadata.py +74 -0
  7. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pylock.toml +111 -111
  8. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pyproject.toml +13 -17
  9. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-03.xml +1 -1
  10. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/test_metadata.py +12 -12
  11. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/test_roundtrip.py +11 -8
  12. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/uv.lock +115 -115
  13. msgspec_xml-0.2.0/msgspec-xml.zip +0 -0
  14. msgspec_xml-0.2.0/msgspec_xml/__init__.py +0 -6
  15. msgspec_xml-0.2.0/msgspec_xml/metadata.py +0 -43
  16. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.gitattributes +0 -0
  17. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.gitignore +0 -0
  18. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/.pre-commit-config.yaml +0 -0
  19. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/LICENSE +0 -0
  20. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/msgspec_xml/introspection.py +0 -0
  21. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/pdm.toml +0 -0
  22. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/renovate.json +0 -0
  23. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/__init__.py +0 -0
  24. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-01.xml +0 -0
  25. {msgspec_xml-0.2.0 → msgspec_xml-0.3.0}/tests/resources/test-02.xml +0 -0
  26. {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.2.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 XML, decode, encode
56
+ from msgspec_xml import decode, encode, Attribute, Value, Wrapper
57
57
 
58
58
 
59
59
  class Price(Struct):
60
- currency: Annotated[str, XML(attr=True)]
61
- cost: Annotated[Decimal, XML(text=True)]
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], XML(wrapper="Prices", tag="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 XML, decode, encode
29
+ from msgspec_xml import decode, encode, Attribute, Value, Wrapper
30
30
 
31
31
 
32
32
  class Price(Struct):
33
- currency: Annotated[str, XML(attr=True)]
34
- cost: Annotated[Decimal, XML(text=True)]
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], XML(wrapper="Prices", tag="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.text:
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.text:
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
- return str(to_builtins(value))
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.")