Skip to content

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:

pip install pydexpi[cli]

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 info data/reference_pid.xml

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:

pydexpi convert data/dexpi_training_test_cases/C01V04-VER.EX01.xml -o model.json

Three things to know:

  • --fast does 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.xsd forbids, and the parser preserves source ids verbatim for traceability. The XML writer maps them as it writes — Tank-1 becomes Tank_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 --fast conversions 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:

pydexpi export-graph data/dexpi_2_0_1/synthetic_process.xml -o flow.graphml --level process_flow

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.