polyxml 0.4.0__tar.gz → 0.5.1__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 (27) hide show
  1. {polyxml-0.4.0 → polyxml-0.5.1}/Cargo.lock +4 -4
  2. {polyxml-0.4.0 → polyxml-0.5.1}/Cargo.toml +1 -1
  3. {polyxml-0.4.0 → polyxml-0.5.1}/PKG-INFO +34 -2
  4. polyxml-0.5.1/README.md +34 -0
  5. polyxml-0.5.1/crates/polyxml-python/README.md +34 -0
  6. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-python/tests/test_polyxml.py +63 -7
  7. {polyxml-0.4.0 → polyxml-0.5.1}/pyproject.toml +5 -1
  8. polyxml-0.5.1/python/polyxml/AGENT_GUIDE.md +89 -0
  9. polyxml-0.5.1/python/polyxml/__init__.py +194 -0
  10. polyxml-0.4.0/README.md +0 -5
  11. polyxml-0.4.0/crates/polyxml-python/README.md +0 -5
  12. polyxml-0.4.0/python/polyxml/__init__.py +0 -87
  13. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/Cargo.toml +0 -0
  14. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/README.md +0 -0
  15. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  16. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/converters.rs +0 -0
  17. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/error.rs +0 -0
  18. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/lib.rs +0 -0
  19. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/parser.rs +0 -0
  20. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/schema.rs +0 -0
  21. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/serializer.rs +0 -0
  22. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/src/value.rs +0 -0
  23. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/tests/test_core.rs +0 -0
  24. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-core/tests/test_security.rs +0 -0
  25. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-python/Cargo.toml +0 -0
  26. {polyxml-0.4.0 → polyxml-0.5.1}/crates/polyxml-python/src/lib.rs +0 -0
  27. {polyxml-0.4.0 → polyxml-0.5.1}/python/polyxml/py.typed +0 -0
@@ -395,7 +395,7 @@ dependencies = [
395
395
 
396
396
  [[package]]
397
397
  name = "polyxml"
398
- version = "0.4.0"
398
+ version = "0.5.1"
399
399
  dependencies = [
400
400
  "criterion",
401
401
  "lexical-core",
@@ -408,14 +408,14 @@ dependencies = [
408
408
 
409
409
  [[package]]
410
410
  name = "polyxml-c"
411
- version = "0.4.0"
411
+ version = "0.5.1"
412
412
  dependencies = [
413
413
  "polyxml",
414
414
  ]
415
415
 
416
416
  [[package]]
417
417
  name = "polyxml-js"
418
- version = "0.4.0"
418
+ version = "0.5.1"
419
419
  dependencies = [
420
420
  "napi",
421
421
  "napi-build",
@@ -425,7 +425,7 @@ dependencies = [
425
425
 
426
426
  [[package]]
427
427
  name = "polyxml-python"
428
- version = "0.4.0"
428
+ version = "0.5.1"
429
429
  dependencies = [
430
430
  "lexical-core",
431
431
  "polyxml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.4.0"
6
+ version = "0.5.1"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -16,7 +16,10 @@ Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
16
16
  Requires-Dist: pytest-cov>=5.0.0 ; extra == 'dev'
17
17
  Requires-Dist: pydantic>=2.7.0 ; extra == 'dev'
18
18
  Requires-Dist: ruff>=0.4.0 ; extra == 'dev'
19
+ Requires-Dist: msgspec>=0.18.0 ; extra == 'dev'
20
+ Requires-Dist: msgspec>=0.18.0 ; extra == 'msgpack'
19
21
  Provides-Extra: dev
22
+ Provides-Extra: msgpack
20
23
  Summary: High-performance, polyglot XML data-binding engine built in Rust
21
24
  Author-email: Bailey Nguyen <bailey.tan.nguyen@gmail.com>
22
25
  License: MIT
@@ -31,5 +34,34 @@ Project-URL: Repository, https://github.com/nth-bailey/PolyXML
31
34
 
32
35
  High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
33
36
 
34
- Powered by polyxml-core written in Rust and PyO3 (abi3-py312).
37
+ Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
38
+
39
+ ## Features
40
+
41
+ - **⚡ Blazing Fast**: 16x faster deserialization and 38x faster serialization than standard Python tools.
42
+ - **🌊 Streaming `iterparse()`**: Parse multi-gigabyte XML files with O(1) constant memory (<5 MB RAM).
43
+ - **📦 Zero-GIL Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`), up to 12.4x faster than pickle.
44
+ - **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
45
+
46
+ ## Quickstart
47
+
48
+ ```python
49
+ from dataclasses import dataclass, field
50
+ import polyxml
51
+
52
+ @dataclass
53
+ class Item:
54
+ id: int = field(metadata={"type": "Attribute"})
55
+ name: str = field(metadata={"type": "Element"})
56
+
57
+ # 1. XML Deserialization
58
+ item = polyxml.deserialize(b'<Item id="1"><name>Gadget</name></Item>', Item)
59
+
60
+ # 2. XML Serialization
61
+ xml = polyxml.serialize(item, indent=2)
62
+
63
+ # 3. High-Speed Binary Serialization (Key-Value databases / IPC)
64
+ blob = polyxml.dumps_binary(item)
65
+ restored = polyxml.loads_binary(blob, Item)
66
+ ```
35
67
 
@@ -0,0 +1,34 @@
1
+ # PolyXML Python Bindings
2
+
3
+ High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
4
+
5
+ Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
6
+
7
+ ## Features
8
+
9
+ - **⚡ Blazing Fast**: 16x faster deserialization and 38x faster serialization than standard Python tools.
10
+ - **🌊 Streaming `iterparse()`**: Parse multi-gigabyte XML files with O(1) constant memory (<5 MB RAM).
11
+ - **📦 Zero-GIL Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`), up to 12.4x faster than pickle.
12
+ - **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
13
+
14
+ ## Quickstart
15
+
16
+ ```python
17
+ from dataclasses import dataclass, field
18
+ import polyxml
19
+
20
+ @dataclass
21
+ class Item:
22
+ id: int = field(metadata={"type": "Attribute"})
23
+ name: str = field(metadata={"type": "Element"})
24
+
25
+ # 1. XML Deserialization
26
+ item = polyxml.deserialize(b'<Item id="1"><name>Gadget</name></Item>', Item)
27
+
28
+ # 2. XML Serialization
29
+ xml = polyxml.serialize(item, indent=2)
30
+
31
+ # 3. High-Speed Binary Serialization (Key-Value databases / IPC)
32
+ blob = polyxml.dumps_binary(item)
33
+ restored = polyxml.loads_binary(blob, Item)
34
+ ```
@@ -0,0 +1,34 @@
1
+ # PolyXML Python Bindings
2
+
3
+ High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
4
+
5
+ Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
6
+
7
+ ## Features
8
+
9
+ - **⚡ Blazing Fast**: 16x faster deserialization and 38x faster serialization than standard Python tools.
10
+ - **🌊 Streaming `iterparse()`**: Parse multi-gigabyte XML files with O(1) constant memory (<5 MB RAM).
11
+ - **📦 Zero-GIL Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`), up to 12.4x faster than pickle.
12
+ - **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
13
+
14
+ ## Quickstart
15
+
16
+ ```python
17
+ from dataclasses import dataclass, field
18
+ import polyxml
19
+
20
+ @dataclass
21
+ class Item:
22
+ id: int = field(metadata={"type": "Attribute"})
23
+ name: str = field(metadata={"type": "Element"})
24
+
25
+ # 1. XML Deserialization
26
+ item = polyxml.deserialize(b'<Item id="1"><name>Gadget</name></Item>', Item)
27
+
28
+ # 2. XML Serialization
29
+ xml = polyxml.serialize(item, indent=2)
30
+
31
+ # 3. High-Speed Binary Serialization (Key-Value databases / IPC)
32
+ blob = polyxml.dumps_binary(item)
33
+ restored = polyxml.loads_binary(blob, Item)
34
+ ```
@@ -12,7 +12,6 @@ try:
12
12
  except ImportError:
13
13
 
14
14
  class XmlDate:
15
-
16
15
  def __init__(self, val: str):
17
16
  self.val = val
18
17
 
@@ -27,7 +26,6 @@ except ImportError:
27
26
  return self.val
28
27
 
29
28
  class XmlDateTime:
30
-
31
29
  def __init__(self, val: str):
32
30
  self.val = val
33
31
 
@@ -42,7 +40,6 @@ except ImportError:
42
40
  return self.val
43
41
 
44
42
  class XmlTime:
45
-
46
43
  def __init__(self, val: str):
47
44
  self.val = val
48
45
 
@@ -57,7 +54,6 @@ except ImportError:
57
54
  return self.val
58
55
 
59
56
  class XmlDuration:
60
-
61
57
  def __init__(self, val: str):
62
58
  self.val = val
63
59
 
@@ -67,6 +63,7 @@ except ImportError:
67
63
  def __str__(self):
68
64
  return self.val
69
65
 
66
+
70
67
  import polyxml
71
68
 
72
69
 
@@ -333,9 +330,7 @@ def test_to_bytes_invalid_type():
333
330
 
334
331
  @dataclass
335
332
  class UnionWrapper:
336
- child: None | ChildNode = field(
337
- default=None, metadata={"type": "Element", "name": "child"}
338
- )
333
+ child: None | ChildNode = field(default=None, metadata={"type": "Element", "name": "child"})
339
334
  count: int | None = field(default=None, metadata={"type": "Element"})
340
335
 
341
336
 
@@ -346,3 +341,64 @@ def test_pep604_union_nested_dataclass():
346
341
  assert res.child.tag == "Payload"
347
342
  assert res.count == 42
348
343
 
344
+
345
+ def test_binary_serialization_dataclass():
346
+ item = SimpleItem(id=42, name="TestItem", price=19.99, active=True)
347
+ payload = polyxml.dumps_binary(item)
348
+ assert isinstance(payload, bytes)
349
+ assert len(payload) > 0
350
+
351
+ decoded = polyxml.loads_binary(payload, SimpleItem)
352
+ assert isinstance(decoded, SimpleItem)
353
+ assert decoded.id == 42
354
+ assert decoded.name == "TestItem"
355
+ assert decoded.price == 19.99
356
+ assert decoded.active is True
357
+
358
+
359
+ def test_binary_serialization_decimal():
360
+ @dataclass
361
+ class DecimalItem:
362
+ val: Decimal
363
+
364
+ item = DecimalItem(val=Decimal("19.99"))
365
+ payload = polyxml.dumps_binary(item)
366
+ decoded = polyxml.loads_binary(payload, DecimalItem)
367
+ assert decoded.val == Decimal("19.99")
368
+
369
+
370
+ def test_binary_serialization_untyped():
371
+ data = {"name": "Alice", "score": 100}
372
+ payload = polyxml.dumps_binary(data)
373
+ decoded = polyxml.loads_binary(payload)
374
+ assert decoded == data
375
+
376
+
377
+ def test_binary_serialization_xmldate_and_path():
378
+ @dataclass
379
+ class ExtraItem:
380
+ date: XmlDate
381
+ path: pathlib.Path
382
+
383
+ orig = ExtraItem(date=XmlDate("2026-09-12"), path=pathlib.Path("/tmp/test.xml"))
384
+ payload = polyxml.dumps_binary(orig)
385
+ decoded = polyxml.loads_binary(payload, ExtraItem)
386
+ assert decoded.date == XmlDate("2026-09-12")
387
+ assert decoded.path == pathlib.Path("/tmp/test.xml")
388
+
389
+
390
+ def test_binary_serialization_unsupported_type():
391
+ class CustomObject:
392
+ pass
393
+
394
+ with pytest.raises(NotImplementedError, match="PolyXML binary serializer cannot serialize"):
395
+ polyxml.dumps_binary(CustomObject())
396
+
397
+
398
+ def test_binary_deserialization_unsupported_type():
399
+ class UnhandledClass:
400
+ pass
401
+
402
+ payload = polyxml.dumps_binary({"a": 1})
403
+ with pytest.raises(NotImplementedError, match="PolyXML binary deserializer cannot deserialize"):
404
+ polyxml.loads_binary(payload, UnhandledClass)
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.4.0"
7
+ version = "0.5.1"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
@@ -28,11 +28,15 @@ classifiers = [
28
28
  dependencies = []
29
29
 
30
30
  [project.optional-dependencies]
31
+ msgpack = [
32
+ "msgspec>=0.18.0",
33
+ ]
31
34
  dev = [
32
35
  "pytest>=8.0.0",
33
36
  "pytest-cov>=5.0.0",
34
37
  "pydantic>=2.7.0",
35
38
  "ruff>=0.4.0",
39
+ "msgspec>=0.18.0",
36
40
  ]
37
41
 
38
42
  [project.urls]
@@ -0,0 +1,89 @@
1
+ # PolyXML AI Agent Guide
2
+
3
+ Quick-reference instructions for AI coding assistants (Claude, Cursor, Copilot, Antigravity, etc.) using `polyxml` in Python projects.
4
+
5
+ ---
6
+
7
+ ## 1. Overview & Capabilities
8
+
9
+ `polyxml` is an ultra-fast, native XML data-binding engine written in pure Rust (using `quick-xml` and `lexical-core`) with stable ABI (`abi3-py312`) Python bindings. It provides bidirectional deserialization and serialization for Python 3.12+ `dataclasses` and Pydantic v2 models.
10
+
11
+ ---
12
+
13
+ ## 2. Common Recipes
14
+
15
+ ### A. Deserializing XML (Strings, Bytes, or Files)
16
+ ```python
17
+ from dataclasses import dataclass, field
18
+ import pathlib
19
+ import polyxml
20
+
21
+
22
+ @dataclass
23
+ class Item:
24
+ id: int = field(metadata={"type": "Attribute"})
25
+ name: str = field(metadata={"type": "Element"})
26
+ price: float = field(metadata={"type": "Element"})
27
+
28
+
29
+ # 1. From raw XML string:
30
+ item = polyxml.deserialize('<Item id="1"><name>Bolt</name><price>0.99</price></Item>', Item)
31
+
32
+ # 2. From raw bytes:
33
+ item = polyxml.deserialize(b"<Item ...>...</Item>", Item)
34
+
35
+ # 3. From pathlib.Path:
36
+ item = polyxml.deserialize(pathlib.Path("item.xml"), Item)
37
+ ```
38
+
39
+ ### B. Streaming Massive XML Files with O(1) Constant Memory
40
+ For large datasets (hundreds of MBs or GBs such as transit timetables or feeds), do **NOT** load the entire XML tree at once. Use `iterparse`:
41
+
42
+ ```python
43
+ import polyxml
44
+ from my_models import VehicleJourney
45
+
46
+ # Streams elements one-by-one with zero intermediate DOM allocations
47
+ for journey in polyxml.iterparse("large_transit_dump.xml", VehicleJourney, tag="VehicleJourney"):
48
+ process_journey(journey)
49
+ ```
50
+
51
+ ### C. Pydantic v2 Models
52
+ PolyXML natively deserializes into Pydantic v2 `BaseModel` classes with the same API:
53
+
54
+ ```python
55
+ from pydantic import BaseModel, Field
56
+ import polyxml
57
+
58
+
59
+ class StopPoint(BaseModel):
60
+ id: str = Field(json_schema_extra={"type": "Attribute"})
61
+ name: str
62
+
63
+
64
+ stop = polyxml.deserialize(
65
+ '<StopPoint id="SP1"><name>Central Station</name></StopPoint>', StopPoint
66
+ )
67
+ ```
68
+
69
+ ### D. Serializing to XML
70
+ ```python
71
+ import polyxml
72
+
73
+ # Serialize model to UTF-8 XML bytes
74
+ xml_bytes = polyxml.serialize(item, indent=2)
75
+
76
+ # If string is required:
77
+ xml_string = xml_bytes.decode("utf-8")
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 3. Common Pitfalls & Quick Fixes
83
+
84
+ - **Pitfall**: Attempting `polyxml.XmlParser()`
85
+ **Fix**: PolyXML uses module-level functional entrypoints: `polyxml.deserialize(...)` and `polyxml.iterparse(...)`.
86
+ - **Pitfall**: Passing file handles or paths without reading
87
+ **Fix**: `polyxml.deserialize(...)` accepts strings, byte buffers, `pathlib.Path`, or `IO` streams directly.
88
+ - **Pitfall**: High memory usage on massive XML files
89
+ **Fix**: Switch from `polyxml.deserialize` to `polyxml.iterparse(source, ModelClass, tag="TagName")`.
@@ -0,0 +1,194 @@
1
+ """PolyXML: High-performance, polyglot native XML data-binding engine.
2
+
3
+ QUICKSTART FOR AI AGENTS & DEVELOPERS:
4
+
5
+ 1. Deserialize XML (string, bytes, or file Path) into a typed Dataclass or Pydantic model:
6
+ >>> import polyxml
7
+ >>> from dataclasses import dataclass, field
8
+ >>>
9
+ >>> @dataclass
10
+ >>> class User:
11
+ ... name: str = field(metadata={"type": "Element"})
12
+ >>>
13
+ >>> user = polyxml.deserialize("<User><name>Alice</name></User>", User)
14
+ >>> user.name
15
+ 'Alice'
16
+
17
+ 2. Stream large XML files with O(1) constant memory (Streaming / Facet Extraction):
18
+ >>> for user in polyxml.iterparse("massive_users.xml", User, tag="User"):
19
+ ... process(user)
20
+
21
+ 3. Serialize a model instance back into XML bytes:
22
+ >>> xml_bytes = polyxml.serialize(user, indent=2)
23
+
24
+ See `AGENT_GUIDE.md` or https://nth-bailey.github.io/PolyXML/ for complete documentation.
25
+ """
26
+
27
+ import pathlib
28
+ from collections.abc import Iterator
29
+ from decimal import Decimal
30
+ from typing import IO
31
+
32
+ from polyxml._polyxml import ( # type: ignore[import-not-found]
33
+ deserialize as _deserialize,
34
+ )
35
+ from polyxml._polyxml import (
36
+ iterparse as _iterparse,
37
+ )
38
+ from polyxml._polyxml import (
39
+ serialize as _serialize,
40
+ )
41
+ from polyxml._polyxml import (
42
+ version as _version,
43
+ )
44
+
45
+ __version__: str = _version()
46
+
47
+
48
+ def _to_bytes(source: bytes | str | pathlib.Path | IO[bytes] | IO[str]) -> bytes:
49
+ if isinstance(source, bytes):
50
+ return source
51
+ if isinstance(source, str):
52
+ if source.lstrip().startswith("<"):
53
+ return source.encode("utf-8")
54
+ path = pathlib.Path(source)
55
+ if path.is_file():
56
+ return path.read_bytes()
57
+ return source.encode("utf-8")
58
+ if isinstance(source, pathlib.Path):
59
+ return source.read_bytes()
60
+ if hasattr(source, "read"):
61
+ data = source.read()
62
+ return data.encode("utf-8") if isinstance(data, str) else data
63
+ raise TypeError(
64
+ f"Unsupported XML source type: {type(source).__name__}. "
65
+ "Expected bytes, str (XML string or file path), pathlib.Path, or a binary/text IO stream."
66
+ )
67
+
68
+
69
+ def deserialize[T](
70
+ source: bytes | str | pathlib.Path | IO[bytes] | IO[str], target_type: type[T]
71
+ ) -> T:
72
+ """Deserialize XML bytes, string, file path, or stream into a typed model.
73
+
74
+ Args:
75
+ source: XML content as raw bytes, string, Path, or file stream.
76
+ target_type: The target dataclass or model class.
77
+
78
+ Returns:
79
+ The deserialized model instance.
80
+ """
81
+ return _deserialize(_to_bytes(source), target_type)
82
+
83
+
84
+ def iterparse[T](
85
+ source: bytes | str | pathlib.Path | IO[bytes] | IO[str],
86
+ target_type: type[T],
87
+ tag: str | None = None,
88
+ ) -> Iterator[T]:
89
+ """Stream and deserialize XML elements one-by-one with O(1) constant memory.
90
+
91
+ Args:
92
+ source: XML content as raw bytes, string, Path, or file stream.
93
+ target_type: The target dataclass or model class for each element.
94
+ tag: Optional XML element tag name to match. Defaults to the model schema name.
95
+
96
+ Yields:
97
+ Deserialized model instances as they are streamed.
98
+ """
99
+ return _iterparse(_to_bytes(source), target_type, tag)
100
+
101
+
102
+ def serialize(obj: object, *, indent: int | None = None) -> bytes:
103
+ """Serialize a strongly-typed model instance back into XML bytes.
104
+
105
+ Args:
106
+ obj: Python dataclass or Pydantic model instance.
107
+ indent: Optional indentation size in spaces for pretty-printing.
108
+
109
+ Returns:
110
+ UTF-8 encoded XML bytes representing the model instance.
111
+ """
112
+ return _serialize(obj, indent=indent)
113
+
114
+
115
+ try:
116
+ import msgspec
117
+
118
+ _HAS_MSGSPEC = True
119
+ except ImportError: # pragma: no cover
120
+ msgspec = None # type: ignore[assignment]
121
+ _HAS_MSGSPEC = False
122
+
123
+
124
+ def _enc_hook(obj: object) -> str:
125
+ if isinstance(obj, pathlib.Path | Decimal):
126
+ return str(obj)
127
+ if hasattr(obj, "from_string") and hasattr(obj, "val"):
128
+ return str(obj)
129
+ raise NotImplementedError(
130
+ f"PolyXML binary serializer cannot serialize object of type {type(obj).__name__}"
131
+ )
132
+
133
+
134
+ def _dec_hook(target_type: type[object], obj: object) -> object:
135
+ if target_type is pathlib.Path:
136
+ return pathlib.Path(str(obj))
137
+ if hasattr(target_type, "from_string"):
138
+ return target_type.from_string(str(obj))
139
+ raise NotImplementedError(
140
+ f"PolyXML binary deserializer cannot deserialize object into type {target_type.__name__}"
141
+ )
142
+
143
+
144
+ def dumps_binary(obj: object) -> bytes:
145
+ """Serialize a model or dataclass into a high-throughput binary MessagePack buffer.
146
+
147
+ Args:
148
+ obj: Python dataclass, Pydantic model, or object.
149
+
150
+ Returns:
151
+ Compact binary MessagePack bytes.
152
+
153
+ Raises:
154
+ RuntimeError: If msgspec is not installed.
155
+ """
156
+ if not _HAS_MSGSPEC: # pragma: no cover
157
+ raise RuntimeError(
158
+ "PolyXML binary serialization requires 'msgspec'. "
159
+ "Install it via: pip install 'polyxml[msgpack]' or pip install msgspec"
160
+ )
161
+ return msgspec.msgpack.encode(obj, enc_hook=_enc_hook)
162
+
163
+
164
+ def loads_binary[T](data: bytes, target_type: type[T] | None = None) -> T | object:
165
+ """Deserialize binary MessagePack bytes into a strongly-typed model or object.
166
+
167
+ Args:
168
+ data: Binary MessagePack bytes to decode.
169
+ target_type: Optional target dataclass or model class. If omitted, returns dynamic structure.
170
+
171
+ Returns:
172
+ The deserialized model instance or object.
173
+
174
+ Raises:
175
+ RuntimeError: If msgspec is not installed.
176
+ """
177
+ if not _HAS_MSGSPEC: # pragma: no cover
178
+ raise RuntimeError(
179
+ "PolyXML binary deserialization requires 'msgspec'. "
180
+ "Install it via: pip install 'polyxml[msgpack]' or pip install msgspec"
181
+ )
182
+ if target_type is not None:
183
+ return msgspec.msgpack.decode(data, type=target_type, dec_hook=_dec_hook)
184
+ return msgspec.msgpack.decode(data, dec_hook=_dec_hook)
185
+
186
+
187
+ __all__ = [
188
+ "__version__",
189
+ "deserialize",
190
+ "dumps_binary",
191
+ "iterparse",
192
+ "loads_binary",
193
+ "serialize",
194
+ ]
polyxml-0.4.0/README.md DELETED
@@ -1,5 +0,0 @@
1
- # PolyXML Python Bindings
2
-
3
- High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
4
-
5
- Powered by polyxml-core written in Rust and PyO3 (abi3-py312).
@@ -1,5 +0,0 @@
1
- # PolyXML Python Bindings
2
-
3
- High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
4
-
5
- Powered by polyxml-core written in Rust and PyO3 (abi3-py312).
@@ -1,87 +0,0 @@
1
- """PolyXML: High-performance, polyglot XML data-binding engine."""
2
-
3
- import pathlib
4
- from collections.abc import Iterator
5
- from typing import IO
6
-
7
- from polyxml._polyxml import ( # type: ignore[import-not-found]
8
- deserialize as _deserialize,
9
- )
10
- from polyxml._polyxml import (
11
- iterparse as _iterparse,
12
- )
13
- from polyxml._polyxml import (
14
- serialize as _serialize,
15
- )
16
- from polyxml._polyxml import (
17
- version as _version,
18
- )
19
-
20
- __version__: str = _version()
21
-
22
-
23
- def _to_bytes(source: bytes | str | pathlib.Path | IO[bytes] | IO[str]) -> bytes:
24
- if isinstance(source, bytes):
25
- return source
26
- if isinstance(source, str):
27
- if source.lstrip().startswith("<"):
28
- return source.encode("utf-8")
29
- path = pathlib.Path(source)
30
- if path.is_file():
31
- return path.read_bytes()
32
- return source.encode("utf-8")
33
- if isinstance(source, pathlib.Path):
34
- return source.read_bytes()
35
- if hasattr(source, "read"):
36
- data = source.read()
37
- return data.encode("utf-8") if isinstance(data, str) else data
38
- raise TypeError(f"Unsupported XML source type: {type(source)}")
39
-
40
-
41
- def deserialize[T](
42
- source: bytes | str | pathlib.Path | IO[bytes] | IO[str], target_type: type[T]
43
- ) -> T:
44
- """Deserialize XML bytes, string, file path, or stream into a typed model.
45
-
46
- Args:
47
- source: XML content as raw bytes, string, Path, or file stream.
48
- target_type: The target dataclass or model class.
49
-
50
- Returns:
51
- The deserialized model instance.
52
- """
53
- return _deserialize(_to_bytes(source), target_type)
54
-
55
-
56
- def iterparse[T](
57
- source: bytes | str | pathlib.Path | IO[bytes] | IO[str],
58
- target_type: type[T],
59
- tag: str | None = None,
60
- ) -> Iterator[T]:
61
- """Stream and deserialize XML elements one-by-one with O(1) constant memory.
62
-
63
- Args:
64
- source: XML content as raw bytes, string, Path, or file stream.
65
- target_type: The target dataclass or model class for each element.
66
- tag: Optional XML element tag name to match. Defaults to the model schema name.
67
-
68
- Yields:
69
- Deserialized model instances as they are streamed.
70
- """
71
- return _iterparse(_to_bytes(source), target_type, tag)
72
-
73
-
74
- def serialize(obj: object, *, indent: int | None = None) -> bytes:
75
- """Serialize a strongly-typed model instance back into XML bytes.
76
-
77
- Args:
78
- obj: Python dataclass or Pydantic model instance.
79
- indent: Optional indentation size in spaces for pretty-printing.
80
-
81
- Returns:
82
- UTF-8 encoded XML bytes representing the model instance.
83
- """
84
- return _serialize(obj, indent=indent)
85
-
86
-
87
- __all__ = ["__version__", "deserialize", "iterparse", "serialize"]
File without changes