Skip to content

Query API

The Query API provides a developer-friendly interface for querying pyDEXPI models. It sits on top of the existing object model, toolkit, and NetworkX graph functionality — it does not replace any existing parsing or graph logic.

Quick Start

from pydexpi.loaders import DexpiXmlSerializer

model = DexpiXmlSerializer.read_dexpi_xml("my_plant.xml")

# Access the query engine via model.q or use convenience methods directly
pump = model.get_by_tag("P-101")
tanks = model.find_all(type="Tank")
connections = model.find_connections("P-101")
paths = model.find_paths("TK-101", "P-201", max_depth=8)

API Reference

Object Lookup

get_by_id(object_id)

Look up a single object by its unique ID. Raises ObjectNotFoundError if no object has the given ID.

obj = model.get_by_id("some-uuid-string")

find_by_tag(tag, *, type=None)

Find all objects with a given tag. Returns a list (possibly empty). Tags are resolved by pydexpi.toolkits.display_name.get_tag, the same resolution behind display names: tagName for equipment, subTagName for nozzles, lineNumber for piping network systems, pipingComponentName for valves, identifier for the objects of a DEXPI 2.0 process model (process steps, ports, streams and other process connections, materials), and so on. A process object's free-text label is never a tag.

Instrumentation functions also have their printed tag (e.g. PI-4712.01) indexed as an alias. Their canonical tag remains the bare number, which may also name a loop and other instruments. The optional type filter matches concrete and DEXPI parent class names, case-sensitively, just as find_all does. Unknown types return no matches.

pumps = model.find_by_tag("P-101")  # list of matching objects

get_by_tag(tag, *, type=None)

Get exactly one object by tag. Raises ObjectNotFoundError if no match, or AmbiguousTagError if multiple objects share the tag. Process identifiers are often not unique -- a BPMN export numbers its streams and its material states from "1" alike -- so on a process model use find_by_tag when a tag may repeat.

pump = model.get_by_tag("P-101")

Filter before checking uniqueness to distinguish a loop from its instrument:

from pydexpi.query import QueryEngine

engine = QueryEngine(model)
instrument = engine.get_by_tag("4712.01", type="ProcessInstrumentationFunction")
loop = engine.get_by_tag("4712.01", type="InstrumentationLoopFunction")
instrument = engine.get_by_tag("PI-4712.01")

Multiple instruments can share a number, and even a printed tag. Such duplicates remain ambiguous; no instrument or loop is preferred implicitly. The resolver also accepts printed tags, preserving its existing precedence of object IDs over tags. MCP's find_by_tag and get_by_tag tools accept the same type filter and aliases.

find_all(type=None, has_graphic=None)

Find all objects in the model, optionally filtered by type name and/or whether they have a graphical representation.

# All objects
everything = model.find_all()

# Filter by type (matches class name and parent classes)
pumps = model.find_all(type="Pump")          # includes CentrifugalPump, ReciprocatingPump, etc.
tanks = model.find_all(type="Tank")

# Filter by graphic presence
with_graphics = model.find_all(has_graphic=True)

# Combined filters
graphic_pumps = model.find_all(type="Pump", has_graphic=True)

Connectivity Queries

neighbors(ref)

Get the immediate graph neighbors of an object. The ref parameter accepts an object instance, an ID string, or a tag string.

result = model.neighbors("P-101")
print(result.source)     # the pump object
print(result.neighbors)  # list of connected objects

find_connections(ref)

Alias for neighbors().

result = model.find_connections("P-101")

find_paths(source_ref, target_ref, max_depth=10, max_paths=10)

Find paths between two objects in the process graph. Parameters are bounded to avoid expensive traversals.

result = model.find_paths("TK-101", "P-201", max_depth=8)
print(len(result))        # number of paths found
for path in result.paths:
    print([obj.id for obj in path])

Inspection

describe(ref)

Describe one object: its type and tag, the data attributes that carry a value, its composition owners (nearest first), the piping segment and system it belongs to, and its own piping nodes with whether each is connected.

d = q.describe("GlobeValve1")
print(d.dexpi_type, d.segment.id, d.system.id)     # GlobeValve PipingNetworkSegment19 PipingNetworkSystem7
print(d.tag_name, d.display_name)                   # the tag, and the caption get_display_name gives
print([type(o).__name__ for o in d.owners])         # ['PipingNetworkSegment', 'PipingNetworkSystem', 'PlantModel']
print([(n.node.id, n.connected) for n in d.nodes])  # [('PipingNode51', True), ('PipingNode52', True)]
print(d.attributes)                                 # data attributes; None and empty values left out

A node counts as connected when a piping connection starts or ends at it, or a segment starts or ends at it from outside the segment. A segment that starts at one of its own items (an off-page connector, say) names that item's node without connecting anything to it.

free_ports(ref)

The piping nodes of an object that nothing connects to yet, each with the item that owns it. For equipment the ports are its nozzles' nodes; for anything with nodes of its own (a nozzle, a valve, an off-page connector) they are those nodes.

for port in q.free_ports("TK-101"):
    print(port.item.id, port.node.id)

Both are cached with the index; call invalidate() after mutating the model.

Sheet / Layout Queries

find_on_sheet(sheet_name)

Find all conceptual objects represented on a named diagram sheet.

objects = model.find_on_sheet("Sheet-1")

find_graphics_for(ref)

Get the graphical representations (RepresentationGroups) for a conceptual object.

graphics = model.find_graphics_for("P-101")
for g in graphics:
    print(g.id)

find_semantic_for_graphic(graphic_id)

Reverse lookup: given a graphic representation ID, find the conceptual object it represents.

obj = model.find_semantic_for_graphic("graphic-uuid")

Cache Management

model.q.invalidate()

Clear all cached indexes and graphs. Call this after mutating the model to ensure queries reflect the current state.

model.q.invalidate()

Architecture

Module Structure

pydexpi/query/
├── __init__.py    # Public API exports
├── engine.py      # QueryEngine — main public interface
├── index.py       # ModelIndex — cached lookup indexes
├── resolver.py    # Resolver — flexible reference resolution
├── results.py     # Typed result objects (ConnectionResult, PathResult, etc.)
└── errors.py      # Custom exceptions (ObjectNotFoundError, AmbiguousTagError)

Design Principles

  1. Layer above, not replacement: The Query API reuses existing model_toolkit.get_all_instances_in_model() for traversal and GraphLoader.dexpi_to_graph() for NetworkX graph construction.

  2. Lazy initialization: Indexes and the graph are built on first access and cached. This avoids unnecessary work if only a subset of query methods is used.

  3. Explicit ambiguity: get_* methods raise clear exceptions when results are ambiguous. find_* methods return lists, making multiplicity visible.

  4. Flexible references: Most methods accept an object instance, ID string, or tag string. The resolver tries ID lookup first, then tag lookup, preferring exact matches.

  5. Bounded queries: Path queries support max_depth and max_paths to prevent expensive traversals on large models.

Reuse of Existing Logic

Query API component Existing code reused
Instance traversal model_toolkit.get_all_instances_in_model()
Graph construction GraphLoader.dexpi_to_graph()
Path finding networkx.all_simple_paths() on undirected view
Tag access display_name.get_tag()
Graphics mapping Diagram / RepresentationGroup tree walking

Custom Exceptions

  • QueryError — base class for all query errors
  • ObjectNotFoundError — raised when a lookup yields no results
  • AmbiguousTagError — raised when get_by_tag() finds multiple objects with the same tag

Result Types

  • QueryResult — iterable list wrapper with .first, .ids, len(), and bool() support
  • ConnectionResult — wraps a source object and its list of neighbors
  • PathResult — wraps source, target, and a list of paths (each path is a list of objects)
  • ObjectDescription — the result of describe(): type, tag, display name, attributes, owners, segment, system and NodeStates
  • NodeState — a piping node and whether it is connected
  • FreePort — an unconnected piping node and the item that owns it