Skip to main content

streaming parser using gjson-style path queries over XML

Project description

pygxml

streaming parser using gjson-style path queries over XML. Rust core (quick-xml) + PyO3.

The original GJSON: tidwall/gjson

Installation

pip install pygxml

Usage examples

import pygxml

xml = """<store>
  <book id="b1"><title>XML in a Nutshell</title><price>30</price></book>
  <book id="b2"><title>The Cathedral and the Bazaar</title><price>20</price></book>
  <book id="b3"><title>Programming Rust</title><price>45</price></book>
</store>"""

# Single-shot path query — returns a typed Result.
pygxml.get(xml, "store.book").type_                         # list (3 books)
pygxml.get(xml, "store.book.0.title").to_str()             # 'XML in a Nutshell'
pygxml.get(xml, "store.book.1.@id").to_str()               # 'b2'
pygxml.get(xml, "store.book.#").to_int()                   # 3
[str(r) for r in pygxml.get(xml, "store.book.#.title")]    # ['XML in a Nutshell', ...]

# A bare child name with multiple matches AND a follow-on step is rejected:
# the user must pick `.N` (single) or `.#` (each).
pygxml.get(xml, "store.book.title")                         # ValueError

# Filters
pygxml.get(xml, "store.book.#(price>=30).title").to_str()           # 'XML in a Nutshell'
[str(r) for r in pygxml.get(xml, "store.book.#(price>=30)#.title")] # all matches
pygxml.get(xml, 'store.book.#(@id=="b2").title').to_str()           # 'The Cathedral...'

# Modifiers
pygxml.get(xml, "store.book.#.title|@count").to_int()               # 3

# Result.get(...) — descend into a captured element fragment.
book = pygxml.get(xml, "store.book.0")
book.get("title").to_str()                                # 'XML in a Nutshell'
book.get("@id").to_str()                                  # 'b1'
book.get("price").to_int()                                # 30

# parse(data) — wrap the input as a top-level Result for chained navigation.
r = pygxml.parse(xml)
r.get("store.book.0.title").to_str()                      # 'XML in a Nutshell'
r.get("store.book.#(price>=30)#.title").value             # [Result('XML in a Nutshell'), Result('Programming Rust')]

# get_many — scan the document once and return multiple Results.
title, price = pygxml.get_many(xml, ["store.book.0.title", "store.book.0.price"])

# mmap input — true zero-copy on huge files. parse(mm) keeps the mmap by
# reference, so subsequent .get() calls re-borrow it without copying.
import mmap
with open('huge.xml', 'rb') as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
    title = pygxml.parse(mm).get("store.book.0.title").to_str()

# Namespace prefix-aware match
pygxml.get(atom_xml, "atom:feed.atom:entry.atom:title").to_str()

# Validate well-formedness without raising.
pygxml.validate(xml)                                        # True

API

Module-level functions

Function Description
get(xml, path) Query xml (str) at path; returns Result
get_bytes(xml, path) Query xml (bytes) at path; returns Result
get_buffer(xml, path) Query xml (buffer protocol) at path; returns Result
get_many(xml, paths) Query xml (str) at each path; returns list[Result]
get_many_bytes(xml, paths) Query xml (bytes) at each path; returns list[Result]
get_many_buffer(xml, paths) Query xml (buffer protocol) at each path; returns list[Result]
parse(xml) Parse the entire JSON document into a Result
validate(xml) True if xml is syntactically valid

Result

get and parse return a Result. Result accessors

Properties

Property Description
r.type_ Python type for this value: None, bool, int, float, str, list, dict
r.value Value converted to the corresponding Python type: None / int / float / str / list[Result] / dict[str, Result]

gjson-style methods

Method Description
r.exists() True if the value was found in the XML
r.to_str() String representation (text content for elements, or full XML for dict/list elements)
r.to_int(),r.to_float() Typed coercions; return 0 / 0.0 when empty
r.to_bool() gjson-style boolean coercion (see below); returns False when empty
r.get(path) Sub-query relative to this value
r.get_many(paths) Sub-query at multiple paths; returns list[Result]

Result.to_bool() follows gjson semantics:

  • "1" / "true"True; "0" / "false"False
  • "\"t\"" / "\"T\"" / "\"1\""True; "\"f\"" / "\"F\"" / "\"0\""False
  • "\"true\"" / "\"TRUE\"" / "\"True\""True; "\"false\"" / "\"FALSE\"" / "\"False\""False
  • Any other value: to_int() != 0 (non-numeric strings → False)
  • Non-empty dict or list Result → True; empty Result → False

Result.get(path) only descends into element items — scalar items (attributes, #text, counts, modifier aggregates like @sum) have no children, so .get(...) against them yields an empty Result.

Pythonic methods

Syntax Description
str(v),repr(v) dict: <Result type=dict, keys=[...]>; list: <Result type=list, value=[...]>; others: str(v.value)
int(v) 64-bit Integer
float(v) 64-bit float
bool(v) Equivalent to bool(v.value)False for null/false/0/""/[]/{}
len(v) Chars for String; element count for list/dict elements
v[key] Subscript access
key in v Key membership for dict; string match for list
iter(v) Lazy iterator: chars for str; Results for list; keys for dict
v.keys() Lazy KeysView of dict keys (raises TypeError for non-dict)
v.values() Lazy ValuesView of dict values (raises TypeError for non-dict)
v.items() Lazy ItemsView of (key, Result) pairs (raises TypeError for non-dict)
r == "x", r == ["a", "b"], r == other_result Equality with str/list/Result

Path syntax

syntax meaning
a.b.c Descend into child elements (local-name match, ignores namespaces)
a.0, a.1 N-th same-named sibling
a.# Count of same-named siblings
a.#.b Project b over all same-named siblings
*, ? Wildcards in element name
@name Attribute reference
#text Explicit text content
\., \@ Escape
a.#(expr) Filter, first match. expr ::= path op value
a.#(expr)# Filter, all matches
path | @modifier Apply modifier (@reverse, @first, @last, @count, @sort, @sort_n, @unique/@uniq, @flatten, @tostr, @sum, @avg/@mean, @min, @max)
prefix:local Prefix-aware match — qualified-name literal compare (matches <atom:title>, not <rss:title>)
a.**.b Descendant: match every b at any depth under a (XPath // equivalent)

A bare child name (no .N/.#/filter) is implicit: at the terminal position it returns every match (a list-shaped Result), but at a non-terminal position the engine raises ValueError if more than one element matches. To chain past a multi-match step, pick one (.0, .1, …) or project explicitly with .#. Single-match elements (e.g., a unique root element) chain transparently.

Result.get(path) follows the same rule: it requires the receiver to hold at most one element. To process every element of a multi-match Result, iterate (for item in result: item.get(...)).

Filter operators: == = != < <= > >= % (glob) !% (negative glob). Filter values: "string", number, true/false, or bare unquoted text.

Inputs

bytes, str, mmap.mmap, bytearray, memoryview — anything implementing the buffer protocol. bytes and mmap are zero-copy on the way in; str is copied once to UTF-8.

pygxml.parse(data) keeps a reference to the input object instead of copying it. Two consequences worth knowing:

  • For mmap input, do not close the mmap while a Result derived from it is still in use — re-borrows during .get() / .str() will fault.
  • For bytearray and other mutable buffers, mutations after parse(...) are observed by subsequent Result accesses.

pygxml.get(data, path) does not retain the input; captured element fragments inside the returned Result are owned copies.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygxml-0.0.2.tar.gz (66.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pygxml-0.0.2-cp311-abi3-win_arm64.whl (423.1 kB view details)

Uploaded CPython 3.11+Windows ARM64

pygxml-0.0.2-cp311-abi3-win_amd64.whl (440.4 kB view details)

Uploaded CPython 3.11+Windows x86-64

pygxml-0.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (566.9 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ x86-64

pygxml-0.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (560.5 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

pygxml-0.0.2-cp311-abi3-macosx_11_0_arm64.whl (526.7 kB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

pygxml-0.0.2-cp311-abi3-macosx_10_12_x86_64.whl (539.9 kB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

Details for the file pygxml-0.0.2.tar.gz.

File metadata

  • Download URL: pygxml-0.0.2.tar.gz
  • Upload date:
  • Size: 66.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pygxml-0.0.2.tar.gz
Algorithm Hash digest
SHA256 a8e99da7ed7678ab06d031db4c10a3bcdfc3bd97eb15b27eff0cf9336ec1b243
MD5 4477d93c063f52b72a83874802d26b2b
BLAKE2b-256 1288c707e6e084bb42d2f4c0e6081c60b7b1069e597b8fa0922ab45171d23ffb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2.tar.gz:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-win_arm64.whl.

File metadata

  • Download URL: pygxml-0.0.2-cp311-abi3-win_arm64.whl
  • Upload date:
  • Size: 423.1 kB
  • Tags: CPython 3.11+, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 bc3a6cee426d9181282e14efcc107130635931035cbaecb9fc601dd6d7473870
MD5 f528c69b09309e8f377d15abb5931519
BLAKE2b-256 3222b4b0496a05a382c6c80ecf4ad1cbb88a54cbfee8ddd9182d9b5dbddacc3e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-win_arm64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-win_amd64.whl.

File metadata

  • Download URL: pygxml-0.0.2-cp311-abi3-win_amd64.whl
  • Upload date:
  • Size: 440.4 kB
  • Tags: CPython 3.11+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 b3a28e1066d8f899ac0191700794fc73e3089d114973d6a3af905b8e9639bd3d
MD5 e9525f0c0acf163615dab58ec751f5cd
BLAKE2b-256 bfcc253dcefc1d179eb81a0b6c05bebfae74e1d7013b8e585f1744c127072120

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-win_amd64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7bfac3c87cff1cfed83fa6fa31b2cb6153b0b9473dee5c7832ee1e048ecc8056
MD5 729a85a13d8f10640e3c85ce85205d24
BLAKE2b-256 5f26c2c0ab7d2be905d0fac2facec68c7ad0d4a6400aec135fae8cbc863ceac2

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 9f0d1cb84f7622fd1da33ba3845e5a9a2043b804f996593867d1a700ab2e5fa8
MD5 84aa3d1d265772cd3392a973975fdb78
BLAKE2b-256 e517c2add0d16736ebb6586298ad531a25ac9f055d04144620e7dd7f267417e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c8c5dcb24461cd299f84a677c32f264481999cc9bf2cf37e38e7a76334937a3f
MD5 ae494c6a94f1da979b96f9cd744a0a2c
BLAKE2b-256 d3f4f70a443391bc36b0b519b30d18a91ee19e54245a21c412b176fcd1dd01dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygxml-0.0.2-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pygxml-0.0.2-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 bfbfc35ea22407221ac150ee26456c2ae1592f0444867b9afa3f3c1ec6486ac9
MD5 40ec00098ba930d16f1ab7cb83d617b4
BLAKE2b-256 e45085cbb89e8a38e977932a1f15383119a43f1efec365ddda06f817d3e33212

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygxml-0.0.2-cp311-abi3-macosx_10_12_x86_64.whl:

Publisher: publish.yml on minefuto/pygxml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page