Export Architecture¶
Single source of truth: edit this file only. The published docs include it from
docs/source/guides/exports.mdvia MyST{include}.
Formats and Engines¶
Each format has its own {format}_{engine}.py file. The dispatcher in
export/__init__.py picks the engine automatically.
Format |
Function |
Engines |
Selection |
|---|---|---|---|
CIM XML |
|
|
|
N-Quads |
|
polars (lazy plan, ~4x), pandas |
|
CSV |
|
polars, pandas |
by input DataFrame type |
Excel |
|
pandas |
polars input converted to pandas first |
NetworkX |
|
pandas |
polars input converted to pandas first |
CIM XML Engines¶
Two engines with automatic fallback, mirroring the parser engine setup:
Engine |
File |
Requires |
Speed (RealGrid 1.14M rows) |
|---|---|---|---|
|
|
lxml + pandas (core), always works |
9.3 s |
|
|
+ C++ build + pyarrow |
0.8 s (11.5x) |
Fallback order: cython_pugixml -> python_lxml
Both engines expose the same interface:
generate_xml(instance_data, rdf_map, namespace_map, class_KEY, export_undefined, comment, debug)
returning {"filename": str, "file": bytes} for one instance. They produce
data-identical XML (verified by an engine-equivalence test); only whitespace
formatting differs.
Engine aliases: performance / pugixml -> cython_pugixml, lxml / pandas -> python_lxml
Call Sequence (CIM XML)¶
data.export_to_cimxml(rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1)
|
'-> export.export_to_cimxml(data, engine="auto")
|
|-> get_cimxml_engine("auto")
| try cython_pugixml -> ImportError (not compiled)
| fall back python_lxml -> always works
|
|-> data.groupby("INSTANCE_ID") # one XML document per instance
|
|-> for each instance:
| '-> engine.generate_xml(instance, rdf_map, ...)
| |
| |-> cimxml_utils.resolve_instance_config()
| | |-> file_name from 'label' (source filename) or new UUID
| | |-> profile sub-schema via Model.messageType / Model.profile
| | '-> namespace map: given > instance NamespaceMap > schema
| |
| | python_lxml cython_pugixml
| | ----------- --------------
| | lxml ElementMaker Arrow string arrays (32-bit offsets)
| | per-row Python loop C++ loop over Arrow buffers
| | etree.tostring() pugixml DOM -> serialize
| | | |
| | v v
| '-> {"filename": ..., "file": xml bytes}
|
'-> package per export_type:
|-> XML_PER_INSTANCE -> one BytesIO per XML
|-> XML_PER_INSTANCE_ZIP_PER_ALL -> all XMLs in one ZIP
'-> XML_PER_INSTANCE_ZIP_PER_XML -> each XML in its own ZIP (default)
File Layout¶
triplets/export/
|-- __init__.py # dispatchers: export_to_cimxml() + engine registry,
| # export_to_csv(), export_to_nquads(), ExportType
|-- cimxml_utils.py # shared per-instance config resolution (both cimxml engines)
|-- cimxml_pandas.py # generate_xml() via lxml (default)
|-- cimxml_pugixml.py # generate_xml() via compiled extension
|-- cimxml_cython_pugixml.pyx # Arrow -> pugixml C++ -> XML bytes
|-- csv_pandas.py # CSV via pandas
|-- csv_polars.py # CSV via polars
|-- nquads_utils.py # shared N-Quads formatting helpers
|-- nquads_pandas.py # N-Quads via pandas
|-- nquads_polars.py # N-Quads via polars
|-- excel_pandas.py # Excel via openpyxl
'-- networkx_pandas.py # NetworkX graph
Usage¶
import pandas
import triplets
from triplets.export_schema import schemas
data = pandas.read_RDF(["grid.zip"])
# CIM XML — auto picks the fastest available engine
files = data.export_to_cimxml(
rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1,
export_type="xml_per_instance",
export_to_memory=True,
)
# explicit engine selection
files = data.export_to_cimxml(rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1, engine="python_lxml")
files = data.export_to_cimxml(rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1, engine="cython_pugixml")
# N-Quads (fast input for SPARQL engines like qlever); schema enables enum namespaces
data.export_to_nquads("grid.nq", rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1)
# other formats
data.export_to_csv(export_to_memory=True)
data.export_to_excel(export_to_memory=True)
graph = data.export_to_networkx()
# accessor namespace (pandas / polars)
data.triplets.export_to_cimxml(rdf_map=schemas.ENTSOE_CGMES_3_0_0_552_ED1)
import duckdb
con = duckdb.connect()
con.read_rdf(["grid.zip"])
con.export_to_nquads("grid.nq")
con.triplets.export_to_csv(export_to_memory=True)
Building the Cython Engine¶
The same build produces both compiled extensions (parser and CIM XML export):
pixi install -e build
pixi run build-cython-pugixml-arrow
Or manually:
python setup_cython_parser.py build_ext --inplace
Naming Convention¶
Engine files follow {format}_{engine}.py:
format: what is produced (
cimxml,csv,nquads,excel,networkx)engine: what does the work (
pandas,polars,pugixml)
Shared format helpers live in {format}_utils.py.