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.
@@ -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.16.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.16.0"
1054
+ version = "0.17.0"
1055
1055
  source = "registry+https://github.com/rust-lang/crates.io-index"
1056
- checksum = "dbf8199409730f9f76a71d4ee945b2e5eead8f1aece63e4aa907313083e4f1e2"
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.16.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.16.0", features = ["python"] }
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.16.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 parsed.
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
- config: Config,
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: config_path.clone(),
94
- config: Config::from_yaml_file(config_path)?,
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) => parse_xml_slice(b.as_bytes(), &self.config)?,
113
- XmlInput::OwnedBytes(v) => parse_xml_slice(&v, &self.config)?,
114
- XmlInput::File(f) => parse_xml(BufReader::new(XmlReader::File(f)), &self.config)?,
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