xml2arrow 0.16.0__tar.gz → 0.17.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.
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/.github/workflows/CI.yml +14 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/Cargo.lock +3 -3
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/Cargo.toml +2 -2
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/PKG-INFO +14 -1
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/README.md +13 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/pyproject.toml +8 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/python/xml2arrow/_xml2arrow.pyi +18 -4
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/src/lib.rs +18 -10
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/.gitignore +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/LICENSE +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/python/xml2arrow/__init__.py +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/python/xml2arrow/exceptions.py +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/python/xml2arrow/py.typed +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/src/file_like.rs +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/conftest.py +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/test_data/bookstore.xml +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/test_data/bookstore.yaml +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/test_data/stations.xml +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/test_data/stations.yaml +0 -0
- {xml2arrow-0.16.0 → xml2arrow-0.17.0}/tests/test_xml2arrow.py +0 -0
|
@@ -31,6 +31,20 @@ jobs:
|
|
|
31
31
|
- name: Run Ruff format
|
|
32
32
|
run: ruff format python/ --check
|
|
33
33
|
|
|
34
|
+
type-check:
|
|
35
|
+
name: Type Check
|
|
36
|
+
needs: lint
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
- name: Install dependencies
|
|
44
|
+
run: pip install mypy pyarrow
|
|
45
|
+
- name: Run mypy
|
|
46
|
+
run: mypy
|
|
47
|
+
|
|
34
48
|
test:
|
|
35
49
|
name: Test
|
|
36
50
|
needs: lint # Only run tests if linting passes
|
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "_xml2arrow"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.17.0"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"arrow",
|
|
10
10
|
"pyo3",
|
|
@@ -1051,9 +1051,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
|
1051
1051
|
|
|
1052
1052
|
[[package]]
|
|
1053
1053
|
name = "xml2arrow"
|
|
1054
|
-
version = "0.
|
|
1054
|
+
version = "0.17.0"
|
|
1055
1055
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1056
|
-
checksum = "
|
|
1056
|
+
checksum = "b0b0db78456d3e962998f25ccd2020b072aad6ba7240d3b27f2a1ec2f97eec20"
|
|
1057
1057
|
dependencies = [
|
|
1058
1058
|
"arrow",
|
|
1059
1059
|
"atoi",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "_xml2arrow"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.17.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ name = "_xml2arrow"
|
|
|
10
10
|
crate-type = ["cdylib"]
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
xml2arrow = { version = "0.
|
|
13
|
+
xml2arrow = { version = "0.17.0", features = ["python"] }
|
|
14
14
|
arrow = { version = "58.1.0", features = ["pyarrow"] }
|
|
15
15
|
|
|
16
16
|
[dependencies.pyo3]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xml2arrow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -158,6 +158,19 @@ names defined in your config. Because the values are standard PyArrow
|
|
|
158
158
|
`RecordBatch` objects they integrate directly with pandas, Polars, DuckDB,
|
|
159
159
|
and any other tool in the Arrow ecosystem.
|
|
160
160
|
|
|
161
|
+
> **Tip:** Constructing an `XmlToArrowParser` validates the config and compiles
|
|
162
|
+
> its path lookup table once, up front. When processing many files with the same
|
|
163
|
+
> config, build the parser **once** and reuse it across `parse()` calls — this
|
|
164
|
+
> amortizes that fixed setup cost and is noticeably faster than creating a new
|
|
165
|
+
> parser per file, especially for many small documents.
|
|
166
|
+
>
|
|
167
|
+
> ```python
|
|
168
|
+
> parser = XmlToArrowParser("config.yaml") # validate + compile once
|
|
169
|
+
> for path in xml_files:
|
|
170
|
+
> record_batches = parser.parse(path) # reused for every file
|
|
171
|
+
> ...
|
|
172
|
+
> ```
|
|
173
|
+
|
|
161
174
|
## Example
|
|
162
175
|
|
|
163
176
|
This example extracts meteorological station data from a nested XML document into
|
|
@@ -129,6 +129,19 @@ names defined in your config. Because the values are standard PyArrow
|
|
|
129
129
|
`RecordBatch` objects they integrate directly with pandas, Polars, DuckDB,
|
|
130
130
|
and any other tool in the Arrow ecosystem.
|
|
131
131
|
|
|
132
|
+
> **Tip:** Constructing an `XmlToArrowParser` validates the config and compiles
|
|
133
|
+
> its path lookup table once, up front. When processing many files with the same
|
|
134
|
+
> config, build the parser **once** and reuse it across `parse()` calls — this
|
|
135
|
+
> amortizes that fixed setup cost and is noticeably faster than creating a new
|
|
136
|
+
> parser per file, especially for many small documents.
|
|
137
|
+
>
|
|
138
|
+
> ```python
|
|
139
|
+
> parser = XmlToArrowParser("config.yaml") # validate + compile once
|
|
140
|
+
> for path in xml_files:
|
|
141
|
+
> record_batches = parser.parse(path) # reused for every file
|
|
142
|
+
> ...
|
|
143
|
+
> ```
|
|
144
|
+
|
|
132
145
|
## Example
|
|
133
146
|
|
|
134
147
|
This example extracts meteorological station data from a nested XML document into
|
|
@@ -61,6 +61,14 @@ requires-python = ">=3.10"
|
|
|
61
61
|
python-source = "python"
|
|
62
62
|
module-name = "xml2arrow._xml2arrow"
|
|
63
63
|
|
|
64
|
+
[tool.mypy]
|
|
65
|
+
strict = true
|
|
66
|
+
files = ["python/xml2arrow"]
|
|
67
|
+
|
|
68
|
+
[[tool.mypy.overrides]]
|
|
69
|
+
module = ["pyarrow", "pyarrow.*"]
|
|
70
|
+
ignore_missing_imports = true
|
|
71
|
+
|
|
64
72
|
[tool.ruff]
|
|
65
73
|
# Target the minimum Python version supported by your project
|
|
66
74
|
target-version = "py310"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from os import PathLike
|
|
2
|
-
from typing import IO, Any
|
|
2
|
+
from typing import IO, Any, final
|
|
3
3
|
|
|
4
4
|
from pyarrow import RecordBatch
|
|
5
5
|
|
|
6
|
+
@final
|
|
6
7
|
class XmlToArrowParser:
|
|
7
8
|
"""A parser for converting XML files to Arrow tables based on a configuration.
|
|
8
9
|
|
|
@@ -13,19 +14,25 @@ class XmlToArrowParser:
|
|
|
13
14
|
base exception.
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
|
-
def __init__(self, config_path: str | PathLike) -> None:
|
|
17
|
+
def __init__(self, config_path: str | PathLike[str]) -> None:
|
|
17
18
|
"""Initializes the parser with a configuration file path.
|
|
18
19
|
|
|
20
|
+
The configuration is loaded, validated, and compiled here, once. Reuse a
|
|
21
|
+
single parser instance across many files to amortize this setup cost.
|
|
22
|
+
|
|
19
23
|
Args:
|
|
20
24
|
config_path: The path to the YAML configuration file.
|
|
21
25
|
|
|
22
26
|
Raises:
|
|
23
|
-
Xml2ArrowError: If the configuration file cannot be loaded or
|
|
27
|
+
Xml2ArrowError: If the configuration file cannot be loaded, parsed, or
|
|
28
|
+
validated. Because validation happens at construction time, an
|
|
29
|
+
invalid config (e.g. InvalidConfigError) is raised here rather
|
|
30
|
+
than on the first ``parse()`` call.
|
|
24
31
|
"""
|
|
25
32
|
|
|
26
33
|
def parse(
|
|
27
34
|
self,
|
|
28
|
-
source: str | PathLike | bytes | bytearray | IO[Any],
|
|
35
|
+
source: str | PathLike[str] | bytes | bytearray | IO[Any],
|
|
29
36
|
) -> dict[str, RecordBatch]:
|
|
30
37
|
"""Parses an XML source and returns a dictionary of Arrow RecordBatches.
|
|
31
38
|
|
|
@@ -49,5 +56,12 @@ class XmlToArrowParser:
|
|
|
49
56
|
|
|
50
57
|
def __repr__(self) -> str: ...
|
|
51
58
|
|
|
59
|
+
class Xml2ArrowError(Exception): ...
|
|
60
|
+
class XmlParsingError(Xml2ArrowError): ...
|
|
61
|
+
class YamlParsingError(Xml2ArrowError): ...
|
|
62
|
+
class ParseError(Xml2ArrowError): ...
|
|
63
|
+
class UnsupportedConversionError(Xml2ArrowError): ...
|
|
64
|
+
class InvalidConfigError(Xml2ArrowError): ...
|
|
65
|
+
|
|
52
66
|
def _get_version() -> str:
|
|
53
67
|
"""Returns the version of the xml2arrow package."""
|
|
@@ -6,12 +6,12 @@ use pyo3::{
|
|
|
6
6
|
use std::fs::File;
|
|
7
7
|
use std::io::{BufReader, Read};
|
|
8
8
|
use std::path::PathBuf;
|
|
9
|
+
use xml2arrow::Parser;
|
|
9
10
|
use xml2arrow::config::Config;
|
|
10
11
|
use xml2arrow::errors::{
|
|
11
12
|
InvalidConfigError, ParseError, UnsupportedConversionError, Xml2ArrowError, XmlParsingError,
|
|
12
13
|
YamlParsingError,
|
|
13
14
|
};
|
|
14
|
-
use xml2arrow::{parse_xml, parse_xml_slice};
|
|
15
15
|
|
|
16
16
|
mod file_like;
|
|
17
17
|
use file_like::PyBinaryFile;
|
|
@@ -72,10 +72,16 @@ impl Read for XmlReader {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/// A parser for converting XML files to Arrow tables based on a configuration.
|
|
75
|
+
///
|
|
76
|
+
/// The configuration's path trie is compiled once, here, and reused for every
|
|
77
|
+
/// `parse()` call via [`xml2arrow::Parser`]. This matters most when one parser
|
|
78
|
+
/// instance processes many files: the fixed per-document setup cost (config
|
|
79
|
+
/// validation + path-trie construction) is paid a single time rather than on
|
|
80
|
+
/// every parse.
|
|
75
81
|
#[pyclass(name = "XmlToArrowParser")]
|
|
76
82
|
pub struct XmlToArrowParser {
|
|
77
83
|
config_path: PathBuf,
|
|
78
|
-
|
|
84
|
+
parser: Parser,
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
#[pymethods]
|
|
@@ -89,9 +95,13 @@ impl XmlToArrowParser {
|
|
|
89
95
|
/// XmlToArrowParser: A new parser instance.
|
|
90
96
|
#[new]
|
|
91
97
|
pub fn new(config_path: PathBuf) -> PyResult<Self> {
|
|
98
|
+
// Compile the config once here. `Parser::new` also runs config
|
|
99
|
+
// validation, so an invalid config now surfaces at construction time
|
|
100
|
+
// rather than on the first `parse()` call.
|
|
101
|
+
let config = Config::from_yaml_file(config_path.clone())?;
|
|
92
102
|
Ok(XmlToArrowParser {
|
|
93
|
-
config_path
|
|
94
|
-
|
|
103
|
+
config_path,
|
|
104
|
+
parser: Parser::new(&config)?,
|
|
95
105
|
})
|
|
96
106
|
}
|
|
97
107
|
|
|
@@ -109,12 +119,10 @@ impl XmlToArrowParser {
|
|
|
109
119
|
#[pyo3(signature = (source))]
|
|
110
120
|
pub fn parse(&self, py: Python<'_>, source: XmlInput<'_>) -> PyResult<Py<PyAny>> {
|
|
111
121
|
let batches = match source {
|
|
112
|
-
XmlInput::Bytes(b) =>
|
|
113
|
-
XmlInput::OwnedBytes(v) =>
|
|
114
|
-
XmlInput::File(f) =>
|
|
115
|
-
XmlInput::FileLike(f) =>
|
|
116
|
-
parse_xml(BufReader::new(XmlReader::FileLike(f)), &self.config)?
|
|
117
|
-
}
|
|
122
|
+
XmlInput::Bytes(b) => self.parser.parse_slice(b.as_bytes())?,
|
|
123
|
+
XmlInput::OwnedBytes(v) => self.parser.parse_slice(&v)?,
|
|
124
|
+
XmlInput::File(f) => self.parser.parse(BufReader::new(XmlReader::File(f)))?,
|
|
125
|
+
XmlInput::FileLike(f) => self.parser.parse(BufReader::new(XmlReader::FileLike(f)))?,
|
|
118
126
|
};
|
|
119
127
|
let tables = PyDict::new(py);
|
|
120
128
|
for (name, batch) in batches {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|