polyxml 0.23.3__tar.gz → 0.24.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 (64) hide show
  1. {polyxml-0.23.3 → polyxml-0.24.0}/Cargo.lock +6 -6
  2. {polyxml-0.23.3 → polyxml-0.24.0}/Cargo.toml +1 -1
  3. {polyxml-0.23.3 → polyxml-0.24.0}/PKG-INFO +26 -1
  4. {polyxml-0.23.3/crates/polyxml-python → polyxml-0.24.0}/README.md +25 -0
  5. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/README.md +14 -11
  6. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/mod.rs +3 -1
  7. polyxml-0.24.0/crates/polyxml-core/src/codegen/python/aot.rs +295 -0
  8. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/python/mod.rs +15 -2
  9. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/rust/mod.rs +260 -2
  10. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_python_codegen.rs +153 -0
  11. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_rust_codegen.rs +2 -0
  12. {polyxml-0.23.3 → polyxml-0.24.0/crates/polyxml-python}/README.md +25 -0
  13. polyxml-0.24.0/crates/polyxml-python/benches/aot_vs_dataclass.py +188 -0
  14. {polyxml-0.23.3 → polyxml-0.24.0}/pyproject.toml +1 -1
  15. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/Cargo.toml +0 -0
  16. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  17. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  18. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  19. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  20. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  21. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  22. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  23. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  24. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  25. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  26. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/converters.rs +0 -0
  27. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/error.rs +0 -0
  28. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
  29. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  30. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/json.rs +0 -0
  31. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/lib.rs +0 -0
  32. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/parser.rs +0 -0
  33. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/schema.rs +0 -0
  34. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  35. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/serializer.rs +0 -0
  36. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  37. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/value.rs +0 -0
  38. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  39. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  40. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  41. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  42. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  43. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  44. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  45. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  46. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  47. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  48. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  49. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  50. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  51. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  52. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  53. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/Cargo.toml +0 -0
  54. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/src/lib.rs +0 -0
  55. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  56. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  57. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_json.py +0 -0
  58. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  59. {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  60. {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  61. {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/__init__.py +0 -0
  62. {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/compat/__init__.py +0 -0
  63. {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/compat/xsdata.py +0 -0
  64. {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.23.3"
611
+ version = "0.24.0"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -628,14 +628,14 @@ dependencies = [
628
628
 
629
629
  [[package]]
630
630
  name = "polyxml-c"
631
- version = "0.23.3"
631
+ version = "0.24.0"
632
632
  dependencies = [
633
633
  "polyxml",
634
634
  ]
635
635
 
636
636
  [[package]]
637
637
  name = "polyxml-cli"
638
- version = "0.23.3"
638
+ version = "0.24.0"
639
639
  dependencies = [
640
640
  "clap",
641
641
  "glob",
@@ -649,7 +649,7 @@ dependencies = [
649
649
 
650
650
  [[package]]
651
651
  name = "polyxml-js"
652
- version = "0.23.3"
652
+ version = "0.24.0"
653
653
  dependencies = [
654
654
  "napi",
655
655
  "napi-build",
@@ -659,7 +659,7 @@ dependencies = [
659
659
 
660
660
  [[package]]
661
661
  name = "polyxml-python"
662
- version = "0.23.3"
662
+ version = "0.24.0"
663
663
  dependencies = [
664
664
  "lexical-core",
665
665
  "polyxml",
@@ -669,7 +669,7 @@ dependencies = [
669
669
 
670
670
  [[package]]
671
671
  name = "polyxml-wasm"
672
- version = "0.23.3"
672
+ version = "0.24.0"
673
673
  dependencies = [
674
674
  "polyxml",
675
675
  "quick-xml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.23.3"
6
+ version = "0.24.0"
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.23.3
3
+ Version: 0.24.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -31,12 +31,35 @@ Project-URL: Homepage, https://github.com/polyxml/PolyXML
31
31
  Project-URL: Issues, https://github.com/polyxml/PolyXML/issues
32
32
  Project-URL: Repository, https://github.com/polyxml/PolyXML
33
33
 
34
+ <p align="center">
35
+ <a href="https://github.com/polyxml/PolyXML">
36
+ <img src="https://raw.githubusercontent.com/polyxml/PolyXML/main/docs/assets/brand/logo_polyxml_banner.png" alt="PolyXML" width="800">
37
+ </a>
38
+ </p>
39
+
34
40
  # PolyXML Python Bindings
35
41
 
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/polyxml/"><img src="https://img.shields.io/pypi/v/polyxml.svg?logo=pypi&label=PyPI" alt="PyPI"></a>
44
+ <a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.12%20%7C%203.13%20%7C%203.14%20%7C%203.15-3776AB.svg?logo=python&logoColor=white" alt="Python: 3.12+"></a>
45
+ <a href="https://polyxml.github.io/PolyXML/languages/python/"><img src="https://img.shields.io/badge/docs-zensical-blue.svg" alt="Documentation"></a>
46
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
47
+ </p>
48
+
36
49
  High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
37
50
 
38
51
  Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
39
52
 
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install polyxml
59
+ ```
60
+
61
+ ---
62
+
40
63
  ## Features
41
64
 
42
65
  - **⚡ Blazing Fast**: 16x faster deserialization and 38x faster serialization than standard Python tools.
@@ -44,6 +67,8 @@ Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
44
67
  - **📦 Zero-GIL Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`), up to 12.4x faster than pickle.
45
68
  - **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
46
69
 
70
+ ---
71
+
47
72
  ## Quickstart
48
73
 
49
74
  ```python
@@ -1,9 +1,32 @@
1
+ <p align="center">
2
+ <a href="https://github.com/polyxml/PolyXML">
3
+ <img src="https://raw.githubusercontent.com/polyxml/PolyXML/main/docs/assets/brand/logo_polyxml_banner.png" alt="PolyXML" width="800">
4
+ </a>
5
+ </p>
6
+
1
7
  # PolyXML Python Bindings
2
8
 
9
+ <p align="center">
10
+ <a href="https://pypi.org/project/polyxml/"><img src="https://img.shields.io/pypi/v/polyxml.svg?logo=pypi&label=PyPI" alt="PyPI"></a>
11
+ <a href="https://www.python.org"><img src="https://img.shields.io/badge/Python-3.12%20%7C%203.13%20%7C%203.14%20%7C%203.15-3776AB.svg?logo=python&logoColor=white" alt="Python: 3.12+"></a>
12
+ <a href="https://polyxml.github.io/PolyXML/languages/python/"><img src="https://img.shields.io/badge/docs-zensical-blue.svg" alt="Documentation"></a>
13
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
14
+ </p>
15
+
3
16
  High-performance native XML data-binding engine for Python dataclasses and Pydantic models.
4
17
 
5
18
  Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
6
19
 
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install polyxml
26
+ ```
27
+
28
+ ---
29
+
7
30
  ## Features
8
31
 
9
32
  - **⚡ Blazing Fast**: 16x faster deserialization and 38x faster serialization than standard Python tools.
@@ -11,6 +34,8 @@ Powered by `polyxml-core` written in Rust and PyO3 (`abi3-py312`).
11
34
  - **📦 Zero-GIL Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`), up to 12.4x faster than pickle.
12
35
  - **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
13
36
 
37
+ ---
38
+
14
39
  ## Quickstart
15
40
 
16
41
  ```python
@@ -1,8 +1,17 @@
1
+ <p align="center">
2
+ <a href="https://github.com/polyxml/PolyXML">
3
+ <img src="https://raw.githubusercontent.com/polyxml/PolyXML/main/docs/assets/brand/logo_polyxml_banner.png" alt="PolyXML" width="800">
4
+ </a>
5
+ </p>
6
+
1
7
  # polyxml
2
8
 
3
- [![Crates.io](https://img.shields.io/crates/v/polyxml.svg)](https://crates.io/crates/polyxml)
4
- [![Docs.rs](https://docs.rs/polyxml/badge.svg)](https://docs.rs/polyxml)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/polyxml/PolyXML/blob/main/LICENSE)
9
+ <p align="center">
10
+ <a href="https://crates.io/crates/polyxml"><img src="https://img.shields.io/crates/v/polyxml.svg?logo=rust&label=crates.io" alt="Crates.io"></a>
11
+ <a href="https://docs.rs/polyxml"><img src="https://docs.rs/polyxml/badge.svg" alt="Docs.rs"></a>
12
+ <a href="https://polyxml.github.io/PolyXML/"><img src="https://img.shields.io/badge/docs-zensical-blue.svg" alt="Documentation"></a>
13
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
14
+ </p>
6
15
 
7
16
  **PolyXML** is a high-performance streaming XML data-binding engine and polyglot schema compiler built in Rust. It serves as the foundational native core powering the `polyxml` CLI and language bindings across Python, C/C++, Java, TypeScript, Go, and C#.
8
17
 
@@ -23,19 +32,13 @@
23
32
 
24
33
  ## Installation
25
34
 
26
- Add `polyxml` to your `Cargo.toml`:
27
-
28
- ```toml
29
- [dependencies]
30
- polyxml = "0.1.0"
31
- ```
32
-
33
- Or run:
35
+ Add `polyxml` to your Rust project:
34
36
 
35
37
  ```bash
36
38
  cargo add polyxml
37
39
  ```
38
40
 
41
+
39
42
  ---
40
43
 
41
44
  ## Quickstart
@@ -18,7 +18,9 @@ pub use cpp::{CppBackend, CppCodegen, CppMode, CppOptions};
18
18
  pub use csharp::{CSharpCodegen, CSharpOptions, CSharpRecordKind};
19
19
  pub use go::{GoBackend, GoCodegen, GoOptions};
20
20
  pub use java::{JavaBackend, JavaCodegen, JavaOptions};
21
- pub use python::{PythonBackend, PythonCodegen, PythonOptions};
21
+ pub use python::{
22
+ PythonAotCodegen, PythonAotCrate, PythonAotOptions, PythonBackend, PythonCodegen, PythonOptions,
23
+ };
22
24
  pub use rust::{RustCodegen, RustOptions};
23
25
  pub use typescript::{TypeScriptBackend, TypeScriptCodegen, TypeScriptOptions};
24
26
 
@@ -0,0 +1,295 @@
1
+ //! Ahead-of-Time Python PyO3 native extension codegen.
2
+
3
+ use std::collections::{BTreeSet, HashSet};
4
+ use std::fmt::Write as FmtWrite;
5
+
6
+ use heck::AsPascalCase;
7
+ use serde::{Deserialize, Serialize};
8
+
9
+ use crate::codegen::python::{to_field_identifier, PythonLanguageContext};
10
+ use crate::codegen::rust::{RustCodegen, RustOptions};
11
+ use crate::codegen::{
12
+ build_type_name_map, flatten_fields, lookup_type_name, set_type_name_map, LanguageContext,
13
+ };
14
+ use crate::ir::{EnumDef, FieldDef, QName, SchemaIR, StructDef, TypeDef, TypeRef};
15
+
16
+ /// Options configuring Ahead-of-Time Python PyO3 extension compilation.
17
+ #[derive(Debug, Clone, Serialize, Deserialize)]
18
+ pub struct PythonAotOptions {
19
+ pub module_name: String,
20
+ pub custom_header: Option<String>,
21
+ }
22
+
23
+ impl Default for PythonAotOptions {
24
+ fn default() -> Self {
25
+ Self {
26
+ module_name: "models".to_string(),
27
+ custom_header: None,
28
+ }
29
+ }
30
+ }
31
+
32
+ /// Represents the emitted multi-file PyO3 native extension crate.
33
+ pub struct PythonAotCrate {
34
+ pub module_name: String,
35
+ pub cargo_toml: String,
36
+ pub pyproject_toml: String,
37
+ pub lib_rs: String,
38
+ pub pyi_stub: String,
39
+ pub readme: String,
40
+ }
41
+
42
+ /// Generator for native Python PyO3 C-extension crates.
43
+ pub struct PythonAotCodegen {
44
+ options: PythonAotOptions,
45
+ context: PythonLanguageContext,
46
+ }
47
+
48
+ fn type_ident(q: &QName) -> String {
49
+ lookup_type_name(q, || AsPascalCase(&q.local).to_string())
50
+ }
51
+
52
+ impl PythonAotCodegen {
53
+ pub fn new(options: PythonAotOptions) -> Self {
54
+ Self {
55
+ options,
56
+ context: PythonLanguageContext,
57
+ }
58
+ }
59
+
60
+ /// Generates a turnkey, standalone PyO3 native extension crate.
61
+ pub fn generate_crate(&self, ir: &SchemaIR) -> PythonAotCrate {
62
+ let module_name = &self.options.module_name;
63
+
64
+ // 1. Cargo.toml
65
+ let cargo_toml = format!(
66
+ r#"[package]
67
+ name = "{module_name}"
68
+ version = "0.1.0"
69
+ edition = "2021"
70
+
71
+ [lib]
72
+ name = "{module_name}"
73
+ crate-type = ["cdylib"]
74
+
75
+ [dependencies]
76
+ pyo3 = {{ version = "0.29", features = ["abi3-py312", "extension-module"] }}
77
+ serde = {{ version = "1.0", features = ["derive"] }}
78
+ serde_json = "1.0"
79
+ quick-xml = {{ version = "0.42", features = ["serialize"] }}
80
+ "#
81
+ );
82
+
83
+ // 2. pyproject.toml
84
+ let pyproject_toml = format!(
85
+ r#"[build-system]
86
+ requires = ["maturin>=1.5,<2.0"]
87
+ build-backend = "maturin"
88
+
89
+ [project]
90
+ name = "{module_name}"
91
+ version = "0.1.0"
92
+ description = "PolyXML Python AOT native PyO3 extension module"
93
+ requires-python = ">=3.12"
94
+ dependencies = []
95
+
96
+ [tool.maturin]
97
+ module-name = "{module_name}"
98
+ features = ["pyo3/extension-module"]
99
+ "#
100
+ );
101
+
102
+ // 3. README.md
103
+ let readme = format!(
104
+ r#"# {module_name} (PolyXML Python AOT Extension)
105
+
106
+ High-performance native Python extension module generated by PolyXML, compiled with PyO3 and Rust.
107
+
108
+ ## Building and Installing
109
+
110
+ ```bash
111
+ # In active virtual environment:
112
+ maturin develop --release
113
+
114
+ # Or build wheel:
115
+ maturin build --release
116
+ ```
117
+
118
+ ## Quick Start
119
+
120
+ ```python
121
+ import {module_name}
122
+
123
+ # Create instance
124
+ obj = {module_name}.<ModelName>()
125
+
126
+ # Serialize / Deserialize XML
127
+ xml_data = obj.to_xml()
128
+ restored = {module_name}.<ModelName>.from_xml(xml_data)
129
+
130
+ # Serialize / Deserialize JSON
131
+ json_data = obj.to_json()
132
+ restored_json = {module_name}.<ModelName>.from_json(json_data)
133
+ ```
134
+ "#
135
+ );
136
+
137
+ // 4. Rust source (src/lib.rs)
138
+ let rust_opts = RustOptions {
139
+ zero_copy: false,
140
+ derive_serde: true,
141
+ derive_default: true,
142
+ emit_polyxml_attrs: false,
143
+ emit_root_aliases: true,
144
+ emit_codecs: true,
145
+ emit_rkyv: false,
146
+ phf: false,
147
+ pyo3: true,
148
+ pyo3_module_name: Some(module_name.clone()),
149
+ custom_header: self.options.custom_header.clone(),
150
+ };
151
+ let rust_codegen = RustCodegen::new(rust_opts);
152
+ let lib_rs = rust_codegen.generate_module(ir);
153
+
154
+ // 5. Python Type Stub (.pyi)
155
+ let pyi_stub = self.generate_pyi(ir);
156
+
157
+ PythonAotCrate {
158
+ module_name: module_name.clone(),
159
+ cargo_toml,
160
+ pyproject_toml,
161
+ lib_rs,
162
+ pyi_stub,
163
+ readme,
164
+ }
165
+ }
166
+
167
+ /// Generates PEP-561 compliant `.pyi` type stubs with docstrings and type hints.
168
+ pub fn generate_pyi(&self, ir: &SchemaIR) -> String {
169
+ set_type_name_map(build_type_name_map(ir, |local| {
170
+ AsPascalCase(local).to_string()
171
+ }));
172
+
173
+ let mut out = String::new();
174
+ out.push_str("# @generated by PolyXML Compiler (Python AOT PyO3 Extension)\n");
175
+ out.push_str("from __future__ import annotations\n");
176
+ out.push_str("import enum\n");
177
+ out.push_str("import typing\n\n");
178
+
179
+ for td in ir.types.values() {
180
+ match td {
181
+ TypeDef::Enum(e) => self.emit_pyi_enum(&mut out, e),
182
+ TypeDef::Struct(s) => self.emit_pyi_struct(&mut out, s, ir),
183
+ TypeDef::Simple(s) => {
184
+ let type_name = type_ident(&s.qname);
185
+ let base_type = self.context.map_type_ref(&s.base_type);
186
+ let _ = writeln!(out, "type {} = {}\n", type_name, base_type);
187
+ }
188
+ TypeDef::Union(_) => {}
189
+ }
190
+ }
191
+
192
+ // Emit root element aliases
193
+ let mut declared_names = BTreeSet::new();
194
+ for td in ir.types.values() {
195
+ declared_names.insert(type_ident(td.qname()));
196
+ }
197
+ for element in ir.elements.values() {
198
+ let el_name = AsPascalCase(&element.qname.local).to_string();
199
+ if !declared_names.contains(&el_name) {
200
+ let target_type = self.context.map_type_ref(&element.type_ref);
201
+ if el_name != target_type {
202
+ let _ = writeln!(out, "type {} = {}", el_name, target_type);
203
+ declared_names.insert(el_name);
204
+ }
205
+ }
206
+ }
207
+
208
+ out
209
+ }
210
+
211
+ fn emit_pyi_enum(&self, out: &mut String, e: &EnumDef) {
212
+ let enum_name = type_ident(&e.qname);
213
+ if let Some(ref doc) = e.documentation {
214
+ let _ = writeln!(out, "\"\"\"{}\"\"\"", doc.trim());
215
+ }
216
+ let _ = writeln!(out, "class {}(enum.IntEnum):", enum_name);
217
+ for v in &e.variants {
218
+ let var_name = AsPascalCase(&v.name).to_string();
219
+ let _ = writeln!(out, " {}: int", var_name);
220
+ }
221
+ out.push('\n');
222
+ }
223
+
224
+ fn emit_pyi_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
225
+ let struct_name = type_ident(&s.qname);
226
+ let _ = writeln!(out, "class {}:", struct_name);
227
+ if let Some(ref doc) = s.documentation {
228
+ let _ = writeln!(out, " \"\"\"{}\"\"\"", doc.trim());
229
+ }
230
+
231
+ let fields = flatten_fields(s, ir);
232
+ let mut seen_fields = HashSet::new();
233
+
234
+ // Field signatures for type checking
235
+ for field in &fields {
236
+ let field_name = self.unique_py_field_name(&field.name, &mut seen_fields);
237
+ let py_type = self.field_python_type(field);
238
+ let _ = writeln!(out, " {}: {}", field_name, py_type);
239
+ }
240
+
241
+ // __init__ constructor
242
+ seen_fields.clear();
243
+ out.push_str("\n def __init__(\n self,\n");
244
+ for field in &fields {
245
+ let field_name = self.unique_py_field_name(&field.name, &mut seen_fields);
246
+ let py_type = self.field_python_type(field);
247
+ let _ = writeln!(out, " {}: {} | None = None,", field_name, py_type);
248
+ }
249
+ out.push_str(" ) -> None: ...\n\n");
250
+
251
+ // Codecs
252
+ out.push_str(" def to_xml(self) -> str: ...\n");
253
+ let _ = writeln!(
254
+ out,
255
+ " @staticmethod\n def from_xml(xml: str) -> {}: ...",
256
+ struct_name
257
+ );
258
+ out.push_str(" def to_json(self) -> str: ...\n");
259
+ let _ = writeln!(
260
+ out,
261
+ " @staticmethod\n def from_json(json_str: str) -> {}: ...\n",
262
+ struct_name
263
+ );
264
+ }
265
+
266
+ fn unique_py_field_name(&self, name: &str, seen: &mut HashSet<String>) -> String {
267
+ let base = to_field_identifier(name);
268
+ let mut candidate = base.clone();
269
+ let mut counter = 1;
270
+ while seen.contains(&candidate) {
271
+ counter += 1;
272
+ candidate = format!("{}_{}", base, counter);
273
+ }
274
+ seen.insert(candidate.clone());
275
+ candidate
276
+ }
277
+
278
+ fn field_python_type(&self, field: &FieldDef) -> String {
279
+ let is_list = field.cardinality.is_list() || field.type_ref.is_list();
280
+ let is_optional = field.cardinality.is_optional() || field.nillable;
281
+ let inner_ref = match &field.type_ref {
282
+ TypeRef::List(inner) => inner.as_ref(),
283
+ other => other,
284
+ };
285
+ let inner_type = self.context.map_type_ref(inner_ref);
286
+
287
+ if is_list {
288
+ format!("list[{}]", inner_type)
289
+ } else if is_optional {
290
+ format!("{} | None", inner_type)
291
+ } else {
292
+ inner_type
293
+ }
294
+ }
295
+ }
@@ -28,12 +28,16 @@ const PATTERN_VALIDATOR_HELPER: &str = r#"def _polyxml_patterns(*patterns: str):
28
28
  return _validate
29
29
  "#;
30
30
 
31
+ pub mod aot;
32
+ pub use aot::{PythonAotCodegen, PythonAotCrate, PythonAotOptions};
33
+
31
34
  /// Target backend for Python code emission.
32
35
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
33
36
  pub enum PythonBackend {
34
37
  #[default]
35
38
  Dataclass,
36
39
  Pydantic,
40
+ Aot,
37
41
  }
38
42
 
39
43
  impl PythonBackend {
@@ -41,6 +45,7 @@ impl PythonBackend {
41
45
  match s.to_lowercase().trim() {
42
46
  "dataclass" | "dataclasses" | "std" | "stdlib" => Some(Self::Dataclass),
43
47
  "pydantic" | "pydantic_v2" | "pydantic-v2" | "pydantic2" => Some(Self::Pydantic),
48
+ "aot" | "native" | "pyo3" => Some(Self::Aot),
44
49
  _ => None,
45
50
  }
46
51
  }
@@ -223,6 +228,14 @@ impl PythonCodegen {
223
228
  }
224
229
 
225
230
  pub fn generate_module(&self, ir: &SchemaIR) -> String {
231
+ if self.options.backend == PythonBackend::Aot {
232
+ let aot = PythonAotCodegen::new(PythonAotOptions {
233
+ module_name: "models".to_string(),
234
+ custom_header: self.options.custom_header.clone(),
235
+ });
236
+ return aot.generate_crate(ir).lib_rs;
237
+ }
238
+
226
239
  set_type_name_map(build_type_name_map(ir, |local| {
227
240
  AsPascalCase(local).to_string()
228
241
  }));
@@ -517,7 +530,7 @@ impl PythonCodegen {
517
530
  .map(type_ident);
518
531
 
519
532
  match self.options.backend {
520
- PythonBackend::Dataclass => {
533
+ PythonBackend::Dataclass | PythonBackend::Aot => {
521
534
  let slots_arg = if self.options.slots {
522
535
  "slots=True"
523
536
  } else {
@@ -657,7 +670,7 @@ impl PythonCodegen {
657
670
  let meta_dict = self.build_field_metadata(field);
658
671
 
659
672
  let (field_type, field_call) = match self.options.backend {
660
- PythonBackend::Dataclass => {
673
+ PythonBackend::Dataclass | PythonBackend::Aot => {
661
674
  self.format_dataclass_field(field, is_list, &inner_type, &meta_dict)
662
675
  }
663
676
  PythonBackend::Pydantic => {