Command-line interface¶
pyDEXPI ships a pydexpi command that wraps the most common workflows so
you can use them without writing Python.
Installation¶
The CLI requires the optional cli extra:
After installation the pydexpi command is available in your terminal.
Run pydexpi --help for an overview.
An installed extension can add commands of its own. The CLI finds such plug-ins through the
entry-point groups pydexpi.cli_plugins (a function register(app) adding commands to the Typer
app) and pydexpi.patch_writers (what diff --patch prints with); without a patch writer,
diff --patch exits 2. A plug-in that fails to import is skipped with a logged warning.
Commands¶
pydexpi info¶
Print a summary of a DEXPI model — equipment, piping, and instrument counts plus
tag names. A process model gets its process steps and its process connections,
in all and by kind (streams, energy flows, information flows). Counts cover the
whole model, not only its top level, and are the same ones the MCP server's
get_model_summary reports: both come from
pydexpi.toolkits.model_summary.summarize_model.
pydexpi validate¶
Load one or more DEXPI XML or JSON files and run every validation rule
(pydexpi.validation.validate) on each. Each finding is one line,
file:line: level rule [ids]: message: what the loader could not represent
(with its line), then each rule violation with the ids it affects and, for XML,
the line the first one is written on. That form is meant for an agent or an
editor to jump to.
pydexpi validate pid/*.xml # exit 1 on a MANDATORY issue or a loader error
pydexpi validate --fail-on recommended P-01.xml
pydexpi validate --json P-01.xml # one report per file, machine-readable
The exit code is 0 when every file is clean at --fail-on (default
mandatory), and 1 when a file has a finding at or above it, a loader entry at
ERROR or CRITICAL, or cannot be read at all (malformed XML or JSON is reported
with its line, not raised). A missing file or an unsupported extension exits 2.
Lenient loading: --lenient¶
Every command that loads a model — info, validate, convert, svg,
export-table, export-graph, diff (both inputs) and git-projection, and the
plug-ins' commands — takes --lenient, which loads with strict=False: an object
that omits an attribute DEXPI makes mandatory is kept instead of discarded.
DEXPI 2.0 process exports usually need it: the CO2 terminal and Tennessee
Eastman examples omit the mandatory Identifier on 78 % and 92 % of the objects
whose class requires it. A strict load builds 146 objects from the CO2 terminal,
against 959 under --lenient (15 %).
It also decides what happens to a DEXPI 2.0 document that DEXPI 2.0.1 cannot
represent without copying or dropping data (see DEXPI 2.0 input below). A
strict load refuses it: the command prints why, listing everything involved, and
exits 1. --lenient converts it and records every copy and drop. Tennessee
Eastman is one: ten of its Compositions are claimed by no MaterialStateType,
and 2.0.1 has no other owner for them, so it loads only under --lenient, with
843 objects.
The loader records each omission, but the CLI does not print the entries: under
--lenient it writes one line to stderr counting them by level, so stdout stays
the command's own output. To read the entries, load the file in Python with
load_xml_with_report or load_json_with_report from pydexpi.loaders.public.
A strict load writes the same line, without the --lenient: prefix, whenever
the loader recorded an entry at error or critical: that is data the document
holds and the loaded model does not, for instance an object of an unknown class
or a part of a JSON document that is not in the shape the format expects. The
command still runs on what was loaded and exits 0. A document that loads with no
such entry prints nothing.
--lenient: the loader recorded 284 issue(s) for TennesseeEastman-vpd-enriched.xml (error: 169, info: 24, warning: 91); pydexpi.loaders.public.load_xml_with_report returns them.
pydexpi info "data/dexpi_process_examples/Tennessee Eastman - Semantum export/TennesseeEastman-vpd-enriched.xml" --lenient
pydexpi convert process.xml -o process.json --lenient
It applies to DEXPI XML and JSON input; Proteus input has no such setting and
ignores it. convert --fast does not build a model, so it is unaffected.
The info entries are what reading a DEXPI 2.0 document as 2.0.1 renamed and
moved; Tennessee Eastman's ten dropped Compositions are ten of the errors, each
carrying the dropped XML (ErrorRegistryEntry.content).
pydexpi convert¶
Convert a DEXPI or Proteus file to DEXPI XML or JSON. The XML is DEXPI 2.0.1, the version of
pyDEXPI's classes (pydexpi.DEXPI_SPEC_VERSION).
# XML to JSON
pydexpi convert data/reference_pid.xml -o model.json
# JSON to XML
pydexpi convert model.json -o model.xml
# Explicit format flag (suffix is normalised automatically)
pydexpi convert data/reference_pid.xml -o output --format json
If --format is omitted the target format is inferred from the output
extension, or defaults to the other format (XML input produces JSON,
JSON input produces XML).
JSON output declares its DEXPI version, as XML output does in its <Import>s: a
root "dexpiVersion": "2.0.1". It is JSON a strict parser reads, on both routes:
a NaN or an infinity the model holds is written as the string "NaN",
"Infinity" or "-Infinity", and read back as the number. Input in the older
spelling, Python's bare NaN, is read the same.
DEXPI 2.0 input¶
A DEXPI 2.0 document -- XML whose <Import>s name models/2.0.0/, or JSON
without a "dexpiVersion", which is all JSON pyDEXPI wrote before -- is read as
DEXPI 2.0.1 and written as 2.0.1: properties 2.0.1 renamed are renamed, and the
model-level Compositions and MaterialStateTypes 2.0.1 removed move into the
owners that name them. What cannot be converted without copying or dropping
data is refused without --lenient (above).
--fast refuses a DEXPI 2.0 document, naming the version it found, and writes
nothing: it rewrites a document without converting it, so its output would
declare 2.0.1 over 2.0 content. Convert without --fast.
--fast cannot convert reference_pid.xml: the document has <Import>s naming DEXPI 2.0, so it is DEXPI 2.0, and --fast rewrites a document without converting it -- its output would declare DEXPI 2.0.1 over DEXPI 2.0 content. Convert it without --fast, which reads it as DEXPI 2.0.1.
Proteus input¶
Proteus XML is detected automatically and parsed with ProteusSerializer, so the same
command performs a Proteus to DEXPI 2.0.1 conversion:
Three things to know:
--fastdoes not apply to Proteus input. The direct converter translates between the two tree-reference formats, and Proteus is not one of them, so the validated route is used and the command says so.- XML output renames ids the schema rejects. Proteus ids conventionally contain a
hyphen, which the id pattern in
DEXPI_XML_Schema.xsdforbids, and the parser preserves source ids verbatim for traceability. The XML writer maps them as it writes —Tank-1becomesTank_1, references follow, and an id that was already legal is untouched — so the ids in the file satisfy the pattern. This is a property of the XML format, so it applies to--fastconversions too, and JSON output keeps the source spelling: convert to JSON if you need the upstream id verbatim. The loaded model keeps its own ids either way.
With the ids mapped, every one of the 35 vendored training cases exports schema-valid —
the last three needed a fix as well,
where one InstrumentationNodePosition was composed into two RepresentationGroups and so
written twice. Your own files are not the corpus, so validate the output when it matters:
DexpiXmlSerializer.validate_xml_schema(path, xsd).
- Conversion is not a guarantee of fidelity. Loading Proteus can drop content that
pyDEXPI has no class for — CustomEquipment and CustomPipingComponent are the known
case —
and the CLI prints only a count of the loader's error registry, when it holds an error. To
read the entries, load through a
ProteusSerializer instance and query that same instance, since load_xml_file builds a
serializer internally and returns only the model:
from pathlib import Path
from pydexpi.loaders.error_registry import ErrorLevels
from pydexpi.loaders.proteus_serializer import ProteusSerializer
serializer = ProteusSerializer()
model = serializer.load_from_bytes(Path("proteus.xml").read_bytes())
errors = serializer.get_loading_errors().get_errors([ErrorLevels.ERROR, ErrorLevels.CRITICAL])
pydexpi svg¶
Render a P&ID diagram to SVG.
pydexpi svg data/reference_pid.xml -o diagram.svg
# Pretty mode — thinner lines, A3 scaling
pydexpi svg data/reference_pid.xml -o pretty.svg --pretty
pydexpi export-table¶
Export equipment, line, instrument, piping-component, or valve tables.
# Default: all lists as Excel
pydexpi export-table data/reference_pid.xml -o tables
# Specific list, CSV format
pydexpi export-table data/reference_pid.xml -o equip --list equipment --format csv
# Multiple lists
pydexpi export-table data/reference_pid.xml -o partial --list equipment --list line
# JSON format
pydexpi export-table data/reference_pid.xml -o tables --format json
# German sheet names, headers, and title-block labels
pydexpi export-table data/reference_pid.xml -o tabellen --language de
# Skip the industrial styling (data header back in row 1)
pydexpi export-table data/reference_pid.xml -o tables --plain
Supported formats: xlsx (default), csv, json.
Available lists: all (default), equipment, line, instrument,
piping component, valve.
Languages (--language): en (default), de, nl, zh — translates sheet
names, fixed column headers, title-block labels, and a curated set of common
attribute labels; everything else falls back to English.
Excel output is styled as an industrial register document by default: each
sheet carries a title block built from the model's metadata (project, drawing
number, revision, creator, …), a styled and frozen header row with autofilter,
and landscape fit-to-width print setup. The workbook file properties (title,
author, subject) are filled from the same metadata. When reading a styled
sheet programmatically, compute the header offset with
pydexpi.exports.table_style.data_header_row(...), or pass --plain.
The combined JSON export carries the metadata under a top-level "metadata"
key. CSV output is always plain.
pydexpi export-graph¶
Export the plant graph at a chosen abstraction level.
# Default: complete graph as GraphML
pydexpi export-graph data/reference_pid.xml -o graph.graphml
# Process-level graph
pydexpi export-graph data/reference_pid.xml -o process.graphml --level process
# Conceptual-level graph as JSON
pydexpi export-graph data/reference_pid.xml -o concept.json --level conceptual --format json
Supported levels: complete (default), process, conceptual for a P&ID, and process_flow for
a DEXPI process model: one node per process step, step detail, instrumentation activity and
instrumentation system (a control loop, over the activities it composes), and
one edge per stream, energy or information flow, keyed by the connection's id (the GraphML edge
id, or the node-link key). Port delegations and what a measurement measures are edges of kind
delegation and measures:
See Graph levels.
Supported formats: graphml (default), json (NetworkX node-link).
pydexpi diff¶
Compare two model states by stable id — revisions of one model, for example kept as JSON.
pydexpi diff old.json new.json # human-readable summary
pydexpi diff old.json new.json --json # the DiffResult, machine-readable
pydexpi diff old.json new.json --semantic-only # leave out graphics and metadata
pydexpi diff old.json new.json --exit-code # exit 1 if they differ, like git diff
Lists DEXPI declares ordered -- z-order, a vector's values, a template's component list -- are
always compared by position; --order also reports reorders of a segment's flow order.
--exit-code's status compares every ordered list, flow order included, with or without
--order, and what is printed stays the diff you asked for. Two independent exports of a
third-party file re-mint many ids on every load, and compare as mostly added and removed.
--patch hands both models to an installed patch writer, and exits 2 when none is installed.
pydexpi git-projection¶
Print a stable, human-readable text of a model, designed for review in Git: a small change gives a small diff, and the same model gives the same text however it was serialized.
pydexpi git-projection plant.xml # XML exports: trust only the file's own ids
pydexpi git-projection plant.json --id-policy all # models kept as JSON with stable ids
pydexpi git-projection plant.xml --no-graphics -o plant.projection.txt
To see projections instead of raw JSON in git diff, register it as a textconv driver:
# .gitattributes
*.dexpi.json diff=dexpi
# once per clone
git config diff.dexpi.textconv "pydexpi git-projection --id-policy all"
The projection is a review view only. It is not read back, and a textual diff of it is never an
executable change: load both revisions and use pydexpi diff.
Error handling¶
| Situation | Behaviour |
|---|---|
| File not found | Non-zero exit code with clear error message |
| Unsupported extension | Non-zero exit code listing valid formats |
| Model has no diagram data | Non-zero exit code (svg command only) |
Limitations¶
The CLI is a thin wrapper around existing pyDEXPI APIs. It does not:
- Support streaming or incremental processing — the full model is loaded into memory.
- Cover every possible option of the underlying APIs — advanced use cases should use the Python library directly.