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.
- {polyxml-0.23.3 → polyxml-0.24.0}/Cargo.lock +6 -6
- {polyxml-0.23.3 → polyxml-0.24.0}/Cargo.toml +1 -1
- {polyxml-0.23.3 → polyxml-0.24.0}/PKG-INFO +26 -1
- {polyxml-0.23.3/crates/polyxml-python → polyxml-0.24.0}/README.md +25 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/README.md +14 -11
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/mod.rs +3 -1
- polyxml-0.24.0/crates/polyxml-core/src/codegen/python/aot.rs +295 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/python/mod.rs +15 -2
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/rust/mod.rs +260 -2
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_python_codegen.rs +153 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_rust_codegen.rs +2 -0
- {polyxml-0.23.3 → polyxml-0.24.0/crates/polyxml-python}/README.md +25 -0
- polyxml-0.24.0/crates/polyxml-python/benches/aot_vs_dataclass.py +188 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/pyproject.toml +1 -1
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/parser.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/schema.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/serializer.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/src/lib.rs +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/__init__.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.23.3 → polyxml-0.24.0}/python/polyxml/compat/xsdata.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
672
|
+
version = "0.24.0"
|
|
673
673
|
dependencies = [
|
|
674
674
|
"polyxml",
|
|
675
675
|
"quick-xml",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyxml
|
|
3
|
-
Version: 0.
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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::{
|
|
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 => {
|