API Reference¶
Python framework for Piping and Instrumentation Diagrams (P&IDs) using the DEXPI information model
Core Components¶
Loaders¶
Load DEXPI models from various formats.
DexpiXmlLoader¶
Load DEXPI XML files into pyDEXPI models.
pydexpi.loaders.DexpiXmlSerializer ¶
DexpiXmlSerializer(id_canonicalization: bool = False, export_metadata: DexpiXmlModelMetadata | None = None, indent: int = 2, strict: bool = True)
Bases: AbstractTreeReferenceSerializer[DexpiXmlDeserializationElement, DexpiXmlSerializationElement]
Serializer for the DEXPI XML tree-reference format.
Carriage returns in string data attributes are written as and survive
the round trip unchanged. The remaining C0 control characters -- everything below
U+0020 except tab, line feed and carriage return -- have no representation in
XML 1.0, as a literal or as a character reference, so export raises ValueError
naming the attribute and the offending code point rather than writing a file that
cannot be read back. JsonSerializer has no such restriction.
Initialize a new XML serializer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id_canonicalization
|
bool
|
Whether to perform ID canonicalization for reference attributes during serialization. If True, list attributes will be sorted by ID. Default is False. |
False
|
export_metadata
|
DexpiXmlModelMetadata | None
|
Metadata for the DEXPI XML model document. If not provided, default metadata with Core, Plant, and Process imports will be used. |
None
|
indent
|
int
|
Number of spaces to use for indentation in the output XML. Default is 2. |
2
|
strict
|
bool
|
Whether an object omitting a mandatory DEXPI attribute is discarded on load. Pass
False to load such objects anyway, recording each omission as an error - needed for
real process files, which routinely omit the mandatory |
True
|
validate_xml_schema
classmethod
¶
Validate the XML file against the DEXPI XML Schema.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xml_path
|
Path
|
Path to the XML file to validate. |
required |
xsd_path
|
Path
|
Path to the XSD file to validate against. |
required |
Raises:
| Type | Description |
|---|---|
DocumentInvalid
|
If the XML file does not conform to the DEXPI XML Schema. |
JsonSerializer¶
Serialize and deserialize DEXPI models to/from JSON format.
pydexpi.loaders.JsonSerializer ¶
JsonSerializer(id_canonicalization: bool = False, indent: int | None = 4, explicit: bool = False, strict: bool = True, declare_version: bool = True, non_finite: NonFiniteSpelling = 'strings')
Bases: AbstractTreeReferenceSerializer[JsonDeserializationElement, JsonSerializationElement]
JSON implementation of the AbstractTreeReferenceSerializer for DEXPI models.
This class provides JSON-specific implementations of the loader and serializer element factories, enabling serialization and deserialization of DEXPI models to/from JSON format.
Initialize a new JSON serializer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id_canonicalization
|
bool
|
Whether to perform ID canonicalization for reference attributes during serialization. If True, list attributes will be sorted by ID. Default is False. |
False
|
indent
|
int
|
The number of spaces to use for indentation in the JSON output. Default is 4. |
4
|
explicit
|
bool
|
Whether to include null values in the JSON output. Default is False. |
False
|
strict
|
bool
|
Whether an object omitting a mandatory DEXPI attribute is discarded on load. Pass
False to load such objects anyway, recording each omission as an error -- as for
|
True
|
declare_version
|
bool
|
Whether the documents written declare the DEXPI version, |
True
|
non_finite
|
('strings', 'constants')
|
How a NaN or an infinity the model holds is written. |
"strings"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
PickleSerializer¶
Serialize and deserialize DEXPI models to/from Pickle format.
pydexpi.loaders.PickleSerializer ¶
Bases: Serializer
Save a DEXPI model as a pickle, and load one these classes wrote.
A pickle is Python-specific persistence, tied to the pyDEXPI classes that wrote it: it is not
a format for exchange, and not one for keeping a model across pyDEXPI or DEXPI versions.
Nothing converts it, so load refuses a pickle written by other classes rather than return a
model that fails later (pydexpi.loaders.pickle_stamp).
Only load a pickle you trust. Unpickling runs code the file names, so a pickle from somewhere else can do anything your Python process can. The check for other classes is no protection against that: it reads what the file says about itself.
load ¶
Load a DEXPI model from a pickle file written by these pyDEXPI classes.
Only for a file you trust: unpickling runs code the file names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dir_path
|
Path
|
Directory where the pickle file is stored. |
required |
filename
|
str
|
Filename of the pickle file. |
required |
Returns:
| Type | Description |
|---|---|
DexpiModel
|
Loaded DEXPI model. |
Raises:
| Type | Description |
|---|---|
IncompatiblePickleError
|
If the pickle was written by other classes: its stamp names another DEXPI version, or a class that is gone or whose fields changed; or, for a pickle without a stamp (one an earlier pyDEXPI wrote), an object in it does not hold the fields of its class. Read the model from its DEXPI XML or JSON source instead, and pickle it again. |
save ¶
Saves a DEXPI model to pickle.
The file is a plain pickle of model, which pickle.load reads as it is, followed by
a stamp naming the DEXPI version, the pyDEXPI version and the classes it holds, which
load checks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PyDexpiBaseModel
|
DEXPI model that should be saved. |
required |
dir_path
|
Path
|
Directory where the DEXPI model should be saved. |
required |
filename
|
str
|
Filename for the saved DEXPI model. |
required |
load raises this for a pickle written by other pyDEXPI classes (see
Pickle Format):
pydexpi.loaders.IncompatiblePickleError ¶
Bases: ValueError
A pickle cannot be read back by these classes: it was written by other ones.
A pickle is tied to the classes that wrote it and is never converted. Read the model from its DEXPI XML or JSON source, which pyDEXPI converts from an earlier DEXPI version, and pickle it again.
GraphLoader¶
Convert DEXPI models to NetworkX graphs and manipulate graph structures.
pydexpi.loaders.GraphLoader ¶
Bases: AbstractGraphLoader
Loads a DEXPI model instance into a NetworkX MultiDiGraph.
dexpi_to_graph ¶
Parse a pyDEXPI model into a NetworkX graph representation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The pyDEXPI model to be parsed into a NetworkX graph. |
required |
Returns:
| Type | Description |
|---|---|
MultiDiGraph
|
The parsed NetworkX graph representation of the pyDEXPI model. |
graph_to_dexpi ¶
Parsing the example graph back to pyDEXPI is not supported yet.
FormatConverter¶
Convert directly between tree-reference-backed formats (DEXPI XML, JSON) without building a pyDEXPI model. Faster than loading and re-saving, but skips pydantic validation and error reporting.
pydexpi.loaders.tree_ref_base.FormatConverter ¶
FormatConverter(serialization_element_factory: Callable[[], OutputSerializationElement], dt_serialization_element_factory: Callable[[], OutputDataTypeSerializationElement] | None = None)
Convert between tree-reference-backed serialization formats directly.
This utility uses the SerializationElement and DeserializationElement interfaces to
transform pyDEXPI tree-reference-backed formats without direct parsing to pyDEXPI models.
Attribute order is preserved as found in the source. Unlike TreeReferenceExporter, this
converter has no id_canonicalization option and never sorts list-valued attributes, so
output ordering follows the input rather than a canonical form.
Since the conversion bypasses pyDEXPI models, it also bypasses pydantic validation and the
loader's ErrorRegistry. A DEXPI class name that is unknown to pyDEXPI raises an
AttributeError and aborts the conversion instead of being logged and skipped. So does a
source element that left part of its structure out (StructureReportingElement): the
loader would register that part with its content, and here there is no registry to hold
it, so converting on would drop it without a word. It raises a ValueError naming the
part.
Attributes:
| Name | Type | Description |
|---|---|---|
serialization_element_factory |
Callable[[], OutputSerializationElement]
|
Factory function to create new instances of the target serialization element type. |
dt_serialization_element_factory |
Callable[[], OutputDataTypeSerializationElement]
|
Factory function to create new instances of the target data type serialization element type. |
Initialize the format converter with the given serialization element factories.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serialization_element_factory
|
Callable[[], OutputSerializationElement]
|
Factory function to create new instances of the target serialization element type. |
required |
dt_serialization_element_factory
|
Callable[[], OutputDataTypeSerializationElement]
|
Factory function to create new instances of the target data type serialization element type.
If not provided, the |
None
|
Source code in pydexpi/loaders/tree_ref_base/format_converter.py
convert_deser_to_ser ¶
Convert a deserialization element to a serialization element.
The concrete serialization element type produced is determined by the
serialization_element_factory provided during initialization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deser_element
|
DeserializationElement
|
The deserialization element to convert. |
required |
Returns:
| Type | Description |
|---|---|
OutputSerializationElement
|
The converted serialization element. |
Source code in pydexpi/loaders/tree_ref_base/format_converter.py
dt_deser_element_to_dt_ser_element ¶
dt_deser_element_to_dt_ser_element(dt_deser_element: DataTypeDeserializationElement | DeserializerDataFieldType) -> OutputDataTypeSerializationElement | SerializerDataFieldType
Convert a data type deserialization element to a data type serialization element.
The concrete serialization element type produced is determined by the
dt_serialization_element_factory provided during initialization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dt_deser_element
|
DataTypeDeserializationElement | DeserializerDataFieldType
|
The data type deserialization element to convert. |
required |
Returns:
| Type | Description |
|---|---|
OutputDataTypeSerializationElement | SerializerDataFieldType
|
The converted data type serialization element. |
Source code in pydexpi/loaders/tree_ref_base/format_converter.py
Error Registry¶
Problems encountered while loading a document, including where in the source they came from. See Loading Errors for how to read them.
pydexpi.loaders.error_registry.ErrorRegistryEntry
dataclass
¶
ErrorRegistryEntry(message: str, level: ErrorLevels, object_id: str | None = None, exception: Exception | None = None, element: str | None = None, xpath: str | None = None, line: int | None = None, column: int | None = None, content: str | None = None)
Data class representing an error entry in the error registry.
The element, xpath, line and column fields locate the error in the source document. They
are populated on a best-effort basis: a loader fills in whatever its format can provide, and
leaves the rest as None. Errors that do not originate from a specific element of the source
document (loader-level errors, for instance) legitimately leave all four unset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable error message |
required |
level
|
ErrorLevels
|
Severity level of the error |
required |
object_id
|
str or None
|
pyDEXPI object ID associated with the error if available, by default None |
None
|
exception
|
Exception or None
|
The exception that caused the error, if applicable, by default None |
None
|
element
|
str or None
|
Name of the offending element, e.g. the XML tag "Nozzle" or a DEXPI class name. This is a name, never an element object, by default None |
None
|
xpath
|
str or None
|
Path of the offending element within the source document, e.g. "/PlantModel/Equipment[2]/Nozzle[1]" for XML or a JSON Pointer for JSON. This is a structural path into the exact document that was parsed: it is position dependent and is not a stable identifier across edits of that document, by default None |
None
|
line
|
int or None
|
1-based line number in the source document, by default None |
None
|
column
|
int or None
|
1-based column number in the source document. Only available for well-formedness errors, as XML parsers do not report a column per element, by default None |
None
|
content
|
str or None
|
What the entry is about, when the load does not keep it, serialized in the document's
format so the data stays recoverable from the report: an object dropped because DEXPI
2.0.1 gives it no owner, a value the 2.0.1 model cannot hold, or repeated |
None
|
pydexpi.loaders.error_registry.SourceLocation
dataclass
¶
SourceLocation(element: str | None = None, xpath: str | None = None, line: int | None = None, column: int | None = None)
Where in a source document an error originated.
This is the same information as the location fields of ErrorRegistryEntry, bundled up so that
format-specific readout logic can return it in one piece. Helpers that derive it from concrete
documents live in pydexpi.loaders.source_location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
str or None
|
Name of the offending element, e.g. the XML tag "Nozzle" or a DEXPI class name. This is always a name, never an element object, by default None |
None
|
xpath
|
str or None
|
Path of the offending element within the source document, by default None |
None
|
line
|
int or None
|
1-based line number in the source document, by default None |
None
|
column
|
int or None
|
1-based column number in the source document, by default None |
None
|
pydexpi.loaders.error_registry.ErrorRegistry ¶
Registry for collecting and managing errors during serialization.
This class provides a centralized way to collect, store, and retrieve errors that occur during the parsing and serialization process.
Initialize an empty error registry.
get_errors ¶
Retrieve all registered errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
levels
|
list[ErrorLevels] or None
|
If provided, filter errors by their severity level. If None, return all errors. Defaults to None. |
None
|
Returns:
| Type | Description |
|---|---|
list[ErrorRegistryEntry]
|
List of all error entries in the registry |
register_error ¶
register_error(message: str, level: ErrorLevels | Literal['info', 'warning', 'error', 'critical'], object_id: str | None = None, exception: Exception | None = None, element: str | None = None, xpath: str | None = None, line: int | None = None, column: int | None = None, content: str | None = None) -> None
Convenience method to create and register an error entry.
This method allows for quick registration of errors by providing the necessary information directly as parameters. It creates an ErrorRegistryEntry and registers it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable error message |
required |
level
|
ErrorLevels or str
|
Severity level of the error. A string is converted to the matching |
required |
object_id
|
str or None
|
pyDEXPI object ID associated with the error if available, by default None |
None
|
exception
|
Exception or None
|
The exception that caused the error, if applicable, by default None |
None
|
element
|
str or None
|
Name of the offending element in the source document, by default None |
None
|
xpath
|
str or None
|
Path of the offending element within the source document, by default None |
None
|
line
|
int or None
|
1-based line number in the source document, by default None |
None
|
column
|
int or None
|
1-based column number in the source document, by default None |
None
|
content
|
str or None
|
What was dropped, when the load does not keep it, serialized as it stood when dropped (for a DEXPI 2.0 document, after its conversion to 2.0.1), by default None |
None
|
Toolkits¶
Utilities for working with DEXPI models programmatically.
Model Toolkit¶
pydexpi.toolkits.model_toolkit ¶
Engineering Model Toolkit
This module contains functions and tools that concern overall of an overall engineering model/conceptual model.
EngineeringModel ¶
PlantModel ¶
Bases: ConceptualModel
PyDexpiBaseModel ¶
Bases: ABC, BaseModel
Base Model for the classes created for the dexpi data model, containing some overarching functionality
_detach_from_composition_parents ¶
_detach_from_composition_parents(all_instances: list[PyDexpiBaseModel], dexpi_objects: Iterable[PyDexpiBaseModel]) -> None
Detach each of dexpi_objects from whichever instance composes it.
Shared by remove_object_from_model and remove_children_from_model so the two cannot drift. Matching is identity-based, and each object is detached at most once: the scan returns as soon as every object has been accounted for, which for a single object reproduces the "stop at the first composition parent" behaviour that remove_object_from_model documents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
all_instances
|
list[PyDexpiBaseModel]
|
The model's instances, already materialized by the caller so that a batch costs one traversal rather than one per object. |
required |
dexpi_objects
|
Iterable[PyDexpiBaseModel]
|
The objects to detach from their composition parents. |
required |
clone_model ¶
Creates a deep copy of the model and generates new IDs for all instances in the model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
ModelT
|
The model to be cloned. |
required |
Returns:
| Type | Description |
|---|---|
ModelT
|
A deep copy of the model with new IDs for all instances. |
combine_engineering_models ¶
combine_engineering_models(models: list[EngineeringModel], engineering_model_kwargs: dict | None = None, plant_model_kwargs: dict | None = None) -> EngineeringModel
This function combines the contents of a list of engineering models and returns a new model with its contents.
The combination is performed on all list attributes of the plant model. The data attributes of the engineering model and the plant model can be passed in via kwargs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
list[EngineeringModel]
|
The list of engineering models to be combined. |
required |
engineering_model_kwargs
|
dict
|
The keyword arguments for the engineering model. This can be used to set data attributes of the engineering model. By default, an empty dict is used. |
None
|
plant_model_kwargs
|
dict
|
The keyword arguments for the plant model. This can be used to set data attributes of the plant model. By default, an empty dict is used. |
None
|
Returns:
| Type | Description |
|---|---|
EngineeringModel
|
The combined engineering model. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Combining diagram information is not yet implemented. If any model has diagram or shapeCatalog attributes that are not None, Error is raised. |
get_all_instances_in_model ¶
get_all_instances_in_model(the_model: PyDexpiBaseModel, dexpi_classes: tuple[PyDexpiBaseModel] | None = None) -> list[PyDexpiBaseModel]
Recursively discover and collect all instances of specified classes within a model.
This function traverses the attributes of the provided model object, recursively inspecting its attributes and sub-attributes to find all instances of the specified classes. The function returns a list of all discovered instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
PyDexpiBaseModel
|
The root model object to inspect. |
required |
dexpi_classes
|
class or tuple of classes
|
A tuple of class types to discover within the model. Only instances of these classes will be collected. If None, all objects are collected. |
None
|
Returns:
| Type | Description |
|---|---|
list
|
A list of all discovered instances of the specified classes within the model. |
get_instances_by_ids ¶
get_instances_by_ids(the_model: PyDexpiBaseModel, ids: Iterable[str]) -> dict[str, PyDexpiBaseModel]
Resolve DEXPI ids to the live objects carrying them, in one traversal.
An id that no object in the model carries is simply absent from the result. That is the useful answer rather than an error: the caller is typically working from a record of what a change touched, and an object it removed is legitimately gone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
PyDexpiBaseModel
|
The model to search. Its whole composition tree is walked once. |
required |
ids
|
Iterable[str]
|
The ids to look for. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, PyDexpiBaseModel]
|
The requested ids that were found, mapped to their objects. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a requested id is carried by more than one object. The request is
genuinely ambiguous, and returning whichever one happened to win would be
silent: |
get_instances_with_attribute ¶
get_instances_with_attribute(the_model: PyDexpiBaseModel, attribute_name: str, target_value: Any = _ANY_VALUE, dexpi_classes: tuple[PyDexpiBaseModel] | None = None) -> list[PyDexpiBaseModel]
Retrieve all subinstances in a engineering model with a specific attribute.
The engineering model is searched recursively through compositional attributes of all levels. The attribute name is specified as a string. If the target_value is not specified, any value is permitted. If the target_value is specified, only instances with that value are returned. The dexpi_classes parameter can be used to specify the classes to be considered. If None, all classes are considered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
PyDexpiBaseModel
|
The model to be searched. |
required |
attribute_name
|
str
|
The name of the attribute to be identified. |
required |
target_value
|
Any
|
The value this attribute is supposed to have. If not specified any value is permitted. |
_ANY_VALUE
|
dexpi_classes
|
tuple
|
Can specify the classes if only certain class types are to be considered. If None, all are considered, by default None. |
None
|
Returns:
| Type | Description |
|---|---|
list
|
List of discovered instances that have the attribute specified. |
get_instances_with_condition ¶
get_instances_with_condition(the_model: PyDexpiBaseModel, condition: Callable[[PyDexpiBaseModel], bool], dexpi_classes: tuple[PyDexpiBaseModel] | None = None) -> list[PyDexpiBaseModel]
Retrieve all subinstances in a engineering model that satisfy a specific condition.
Recursively searches through the model's attributes and sub-attributes to find all instances that satisfy the given condition. The condition is specified as a callable that takes a PyDexpiBaseModel instance as an argument and returns a boolean value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
PyDexpiBaseModel
|
The model to be searched. |
required |
condition
|
Callable[[PyDexpiBaseModel], bool]
|
The condition that the instances must satisfy. |
required |
dexpi_classes
|
tuple
|
Can specify the classes if only certain class types are to be considered. If None, all are considered, by default None. |
None
|
Returns:
| Type | Description |
|---|---|
list
|
List of discovered instances that satisfy the condition. |
get_objects_referencing ¶
get_objects_referencing(dexpi_model: EngineeringModel, dexpi_objects: Iterable[PyDexpiBaseModel]) -> list[tuple[PyDexpiBaseModel, str]]
Find every reference attribute in the model that points at one of dexpi_objects.
Returns (holder, attribute_name) pairs: the object holding the reference and the attribute it holds it in. A holder appears once per matching attribute.
DEXPI references are one-directional -- an ActuatingSystem points at the valve
it operates, the valve points at nothing -- so answering "what refers to this?"
means scanning. The model is traversed exactly once regardless of how many
objects are passed, and matching is identity-based (is), never by id
field or equality, so distinct objects sharing an id do not alias. Only
objects reachable from dexpi_model through composition are visited.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to search. |
required |
dexpi_objects
|
Iterable[PyDexpiBaseModel]
|
The objects to find references to. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[PyDexpiBaseModel, str]]
|
(referencing object, reference attribute name) pairs, in traversal order, and for one holder in the order its class declares the attributes. The order is deterministic, so a caller can record what it clears in it. |
import_model_contents_into_model ¶
import_model_contents_into_model(target_model: EngineeringModel, import_models: list[EngineeringModel]) -> None
Imports the contents of passed models into target model. Conceptually similar to combine_dexpi_models, but the target_model is manipulated in place and its other, non-list attributes are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_model
|
EngineeringModel
|
The model that is manipulated in place and that the other models are imported into. |
required |
import_models
|
list[EngineeringModel]
|
The models that are to be imported to the target. |
required |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Combining diagram information is not yet implemented. If any model has diagram or shapeCatalog attributes that are not None, Error is raised. |
object_is_in_model ¶
Checks if the given dexpi_object is in some way a member of the given dexpi_model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the object. |
required |
dexpi_object
|
PyDexpiBaseModel
|
The object to be searched for in the model. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if object is found in the model, False otherwise |
reassign_ids_in_model ¶
Reassigns new IDs to all instances in the model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_model
|
PyDexpiBaseModel
|
The model in which to reassign IDs. |
required |
remove_children_from_model ¶
Removes all children of the given dexpi_object from the given dexpi_model.
The children are removed as one batch: the model is traversed a constant number of times regardless of how many children there are. Calling remove_object_from_model per child instead costs two full-model traversals each, a multiplicative cost on a large model.
Batching also makes the outcome independent of iteration order. Removing the children one at a time cleared references held by a later child to an earlier one but not the reverse, because the earlier child was already detached and so no longer reachable when the later one was processed. References between the removed children are now cleared symmetrically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the children. |
required |
dexpi_object
|
PyDexpiBaseModel
|
The object whose children are to be removed from the model. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any child is not found in the model. |
remove_external_references ¶
Removes all references to objects outside of the given object.
This is done by searching through the object's reference attributes to find any references to objects that are not part of the object's composition tree. These references are then removed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object
|
PyDexpiBaseModel
|
The object whose external references are to be removed. |
required |
remove_object_from_model ¶
Removes the given dexpi_object from the given dexpi_model.
This is done by searching through the model's compositional and reference attributes to find the object and remove it. This ensures that the object is removed from both the model's compositional tree and the cross-references.
Assumptions
Assumes that the object is in the model's composition tree only once, as it should for a well-formed model. If the object is found in multiple places, only the first occurrence will be removed. Scanning stops as soon as the composition parent is found; in a malformed model with the object in multiple locations, remaining occurrences are left intact.
Note that any references from the removed object to other objects in the model are not removed. This can be done with remove_references_to_object, if desired.
Note also that this covers the generic case. However, for some specific cases, such as removing a pipe in a segment, this can lead to an invalid model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the object. |
required |
dexpi_object
|
PyDexpiBaseModel
|
The object to be removed from the model. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the object is not found in the model. |
remove_references_to_children ¶
remove_references_to_children(dexpi_model: EngineeringModel, dexpi_object: PyDexpiBaseModel) -> None
Removes all references to the children of the given dexpi_object from the given dexpi_model.
This is done by searching through the model's reference attributes to find the children and remove them. This ensures that the children are removed from the cross-references. References to dexpi_object itself are kept.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the references. |
required |
dexpi_object
|
PyDexpiBaseModel
|
The object whose children's references are to be removed from the model. |
required |
remove_references_to_object ¶
Removes all references to the given dexpi_object from the given dexpi_model.
This is done by searching through the model's reference attributes to find the object and remove it. This ensures that the object is removed from the cross-references. References to the compositional descendants of dexpi_object are removed as well, in the same single model traversal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the references. |
required |
dexpi_object
|
PyDexpiBaseModel
|
The object whose references are to be removed from the model. |
required |
remove_references_to_objects ¶
remove_references_to_objects(dexpi_model: EngineeringModel, dexpi_objects: Iterable[PyDexpiBaseModel]) -> None
Removes all references to any of the given dexpi_objects from the given dexpi_model.
The model is traversed exactly once, regardless of how many objects are passed,
and every reference attribute is checked against the batch with an O(1)
membership test. Matching is identity-based (is), never by id field or
equality, so distinct objects that happen to share an id do not alias.
Scalar references to a matched object are set to None; this raises a pydantic ValidationError if the reference attribute is required. List references are filtered. Attributes without a match are left untouched (no reassignment, so no validation pass). Only objects reachable from dexpi_model through composition are visited: references held by already-detached subtrees are not cleared.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the references. |
required |
dexpi_objects
|
Iterable[PyDexpiBaseModel]
|
The objects whose references are to be removed from the model. |
required |
replace_object_in_model ¶
replace_object_in_model(dexpi_model: EngineeringModel, old_object: PyDexpiBaseModel, new_object: PyDexpiBaseModel, replace_obj_references: bool = True) -> None
Replaces the given old_object with the new_object in the given dexpi_model.
This is done by searching through the model's compositional and reference attributes to find the old object and replace it with the new object. This ensures that the object is replaced in both the model's compositional tree and the cross-references.
Assumptions
Assumes that the old object is in the model's composition tree only once, as it should for a well-formed model. If the old object is found in multiple places, only the first occurrence will be replaced.
Note that any references from the old object to other objects in the model are not removed or updated. These can be removed with remove_references_to_object, if desired.
Note also that any nested references are not updated, as not to assume that the new object has the same nested structure as the old object. Instead, these references are removed. Nested references need to be updated manually on children of the new object, if desired.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to be searched for the old object. |
required |
old_object
|
PyDexpiBaseModel
|
The object to be replaced in the model. |
required |
new_object
|
PyDexpiBaseModel
|
The object to replace the old object with. |
required |
replace_obj_references
|
bool
|
If True, the new object will be assigned the old object's references. If False, the new object will not be assigned the old object's references. By default, True. |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the old object is not found in the model. |
resolve_class_by_name ¶
Return the class named class_name from module, validated as a subclass
of base_class. Raises ValueError on failure.
Safe against arbitrary code execution: getattr only returns names already present in the module namespace, and the issubclass check restricts the result to the expected DEXPI class hierarchy.
Equipment Toolkit¶
pydexpi.toolkits.equipment_toolkit ¶
Utility functions for DEXPI equipment classes.
InternalSlot
dataclass
¶
InternalSlot(declared: tuple[type[PyDexpiBaseModel], ...], accepts: tuple[type[PyDexpiBaseModel], ...], is_list: bool)
One composition attribute that holds equipment internals, as :func:internal_slots reads it.
Attributes:
| Name | Type | Description |
|---|---|---|
declared |
tuple[type[PyDexpiBaseModel], ...]
|
The classes the generated annotation names, such as |
accepts |
tuple[type[PyDexpiBaseModel], ...]
|
Every equipment internal class the attribute takes: the declared classes and their
subclasses, |
is_list |
bool
|
Whether the attribute is a list ( |
Nozzle ¶
Bases: ConceptualObject, ActuatingElectricalLocation, SensingLocation, PipingNodeOwner, PipingSourceItem, PipingTargetItem
A projecting short piece of pipe that is attached to another (hollow) body.
ProcessEquipment ¶
PyDexpiBaseModel ¶
Bases: ABC, BaseModel
Base Model for the classes created for the dexpi data model, containing some overarching functionality
TaggedPlantItem ¶
Bases: ConceptualObject, TechnicalItem
A fully tagged item in a plant.
_equipment_internal_classes
cached
¶
:func:get_equipment_internal_classes, computed once: the generated classes are fixed.
_internal_slots
cached
¶
:func:internal_slots, computed once per class. Never hand this dict out: it is shared.
_is_list ¶
Whether an annotation is a list, or a union holding one.
_model_classes ¶
The model classes an annotation names, through X | None and list[X].
get_composition_attributes_from_class ¶
get_composition_attributes_from_class(dexpi_class: type[PyDexpiBaseModel | PyDexpiDataTypeBaseModel]) -> set[str]
Return names of composition attributes for a DEXPI class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_class
|
type[PyDexpiBaseModel | PyDexpiDataTypeBaseModel]
|
The class to inspect. |
required |
Returns:
| Type | Description |
|---|---|
set[str]
|
Set of field names categorized as composition attributes. |
get_equipment_internal_classes ¶
Return all equipment internal component classes.
Equipment internals are classes that:
- Belong to the package Plant.ProcessEquipment
- Do NOT inherit from
TaggedPlantItem - Exclude
Nozzleexplicitly. - Do NOT include superclasses of
ProcessEquipment
The list holds ColumnSection and Vent, which classes outside it derive from: the
top-level item TaggedColumnSection and the piping component VentLine. An
isinstance test against the list is True for both. Ask :func:is_equipment_internal
whether an object is an internal.
Returns:
| Type | Description |
|---|---|
list[type[PyDexpiBaseModel]]
|
Sorted list of equipment internal component class objects; a new list on every call. |
get_inheritance_from_dexpi_class ¶
Get inheritance chain from a PyDexpiBaseModel class.
This method extracts the inheritance hierarchy from a PyDexpiBaseModel class, returning the class types in the inheritance chain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
type[PyDexpiBaseModel]
|
A PyDexpiBaseModel class (not an instance) |
required |
Returns:
| Type | Description |
|---|---|
list[type]
|
List of class types in the inheritance chain, excluding base classes like PyDexpiBaseModel, ABC, and BaseModel |
internal_slots ¶
The composition attributes of owner_cls that hold equipment internals, in field order.
Read from the resolved annotations: generated fields carry string forward references such as
'Chamber | None', which model_fields leaves unresolved, so they go through
typing.get_type_hints. Reference attributes never count -- Vessel.columnSections
points at TaggedColumnSections, while ProcessColumn.columnSections composes its
SubTaggedColumnSections.
Across the generated classes that is 25 attributes on 105 owner classes, 17 of them lists,
and each pair of an owner class and an internal class that fit at all fit exactly one
attribute, so :func:slot_for needs no trial assignment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
owner_cls
|
type[PyDexpiBaseModel]
|
A generated class: equipment, or an internal that holds internals itself, such as
|
required |
Returns:
| Type | Description |
|---|---|
dict[str, InternalSlot]
|
Field name to slot; empty for a class that holds no internals. A new dict on every call; the reading behind it is done once per class. |
is_equipment_internal ¶
Whether an object, or a class, is an equipment internal: a chamber, an impeller, ...
An instance of exactly one of the classes :func:get_equipment_internal_classes lists, by
membership and not issubclass. That list is every such class of
Plant.ProcessEquipment, so a subclass outside it comes from elsewhere, and two do, each
a different thing that a subclass test would take for an internal:
TaggedColumnSectionderives fromColumnSectionand fromTaggedPlantItem. It is a top-level item intaggedPlantItems.VentLinederives fromVentand fromPipingComponent. It is an item of a piping segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj_or_cls
|
object
|
A model object, or a class. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True for the 41 internals of the corpus (26 chambers, the column internals arrangements, the SubTaggedColumnSections, ...), False for a TaggedColumnSection, a VentLine, a nozzle, equipment and anything else. |
slot_for ¶
The composition attribute of owner_cls that takes an internal_cls, or None.
A function of the two classes alone: no owner class has two attributes that take the same
internal class (see :func:internal_slots).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
owner_cls
|
type[PyDexpiBaseModel]
|
The class of the object the internal would go into. |
required |
internal_cls
|
type[PyDexpiBaseModel]
|
An equipment internal class. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The field name, such as |
Piping Toolkit¶
pydexpi.toolkits.piping_toolkit ¶
Dexpi Piping Toolkit
This module contains functions and tools for the main piping concepts of a dexpi model, mainly reading and checking piping network segments.
DexpiConnectionException ¶
Bases: Exception
Exception for dexpi piping, connection-related errors.
Raised if a connection or reconnection is attempted that is invalid or may cause corrupt segments
DexpiCorruptPipingSegmentException ¶
Bases: Exception
Exception raised if a segment is encountered that violates piping network segment convention.
EngineeringModel ¶
PipingConnectionConvention ¶
Bases: Enum
Convention enumeration for creating connections for higher level piping tools where explicit assignment of source and target nodes/items becomes impractical.
Attributes:
| Name | Type | Description |
|---|---|---|
IN_NODE_0_OUT_NODE_1 |
int = 0
|
Node with index 0 is used as main inlet, node with index one is main outlet |
USE_ITEMS |
int = 1
|
Use direct assignments to items |
PipingTraversalException ¶
Bases: Exception
Raised if an error occurs during traversal of connected piping components/items/segments.
PipingValidityCode ¶
Bases: Enum
Enumeration of validity codes for piping related objects.
Attributes:
| Name | Type | Description |
|---|---|---|
VALID |
int = 0
|
|
INTERNAL_VIOLATION |
int = 1
|
Violation code for an internal violation in a pipingNetworkSegment. Examples are unconnected, loose pipes or items, interrupted segments. |
CONNECTION_CONVENTION_VIOLATION |
int = 2
|
Violation code if the connection convention regarding a pipingNetworkSegment and its pipingConnections is violated |
MULTIPLE_NODE_REFERENCE_VIOLATION |
int = 3
|
Violation of the convention that a node can only be referenced once |
PlantModel ¶
Bases: ConceptualModel
PyDexpiBaseModel ¶
Bases: ABC, BaseModel
Base Model for the classes created for the dexpi data model, containing some overarching functionality
SegmentEnds ¶
Bases: NamedTuple
Where a segment's items and connections start and end; see :func:segment_ends.
_flow_links ¶
A segment's members, and what each one leads to inside the segment. Read-only.
The members are its items then its connections, as listed. An item leads to the
connections of the segment that leave it (sourceItem); a connection leads to
its targetItem when that is one of the segment's items, and to nothing when
it ends outside the segment or nowhere. Identity throughout, never equality.
The one definition :func:segment_chain and :func:segment_ends share.
_items_in_flow_order ¶
segment's own items in :func:segment_chain order, without its connections.
_run_continuation ¶
_run_continuation(segments: list[PipingNetworkSegment], segment: PipingNetworkSegment) -> piping.PipingNetworkSegment | None
The segment that continues segment's run, or None at the line's end.
The continuation starts on the item and the node object segment ends on.
Raises PipingTraversalException when that cannot be decided (see
:func:run_items).
connect_piping_connection ¶
connect_piping_connection(connector_item: PipingSourceItem | PipingTargetItem, piping_connection: PipingConnection | None = None, piping_segment: PipingNetworkSegment | None = None, connector_node: PipingNode | None = None, as_source: bool = False, force_reconnect: bool = False) -> None
Connects the pipe to the target connector object.
If the connector object is a PipingNode, the connection is implemented via
node connection. Otherwise, a connection via Source or TargetItem is
attempted. Raises an error if the piping connection is already connected
unless force_reconnect is True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connector_item
|
Union[PipingSourceItem, PipingTargetItem]
|
The item to which the pipe will be connected. |
required |
piping_connection
|
PipingConnection
|
The piping connection to be reconnected. If not passed, then reconnection only performed on the segment |
None
|
piping_segment
|
PipingNetworkSegment
|
Applies the reconnection to the piping segment too if passed along. |
None
|
connector_node
|
PipingNode | None
|
Optionally, the node of connector_item to be connected to. Must be a member of connector_item |
None
|
as_source
|
bool
|
If True, the connection is established as source, else as target. Defaults to False. |
False
|
force_reconnect
|
bool
|
If False, raises an exception if the piping connection is already connected. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
None
|
This function modifies the state of the piping connection but does not return any value. |
Raises:
| Type | Description |
|---|---|
ConnectionException
|
If the piping connection is already connected, and |
ValueError
|
If the piping_connection and piping_segment are found to be faulty or do not fit together, or if the optional connector_node is not associated to the connector_item |
connect_piping_network_segment ¶
connect_piping_network_segment(piping_segment: PipingNetworkSegment, connector_item: PipingSourceItem | PipingTargetItem, connector_node_index: int | None = None, as_source=False, force_reconnect=False) -> None
Connects a piping network segment by inferring the pipe to reconnect.
This is based on the assumption that a valid piping network segment has at most one unconnected incoming and at most one unconnected outflowing connection. Note that a valid piping network segment may end on a segment item like a valve, in which case there are no outgoing flow connections (same for input). In this case, a ValueError is raised. An Exception is also raised if the piping network segment is corrupted (i.e., has more than one inflowing or outflowing connection).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
piping_segment
|
PipingNetworkSegment
|
Piping segment to be reconnected. |
required |
connector_item
|
Union[PipingSourceItem, PipingTargetItem]
|
The connector object to which the segment will be connected. It can be either a PipingNode, PipingSourceItem, or PipingTargetItem. |
required |
connector_node_index
|
int | None
|
Optionally, the index of the node of connector_item to be connected to, by default None |
None
|
as_source
|
bool
|
If True, the connection is established as source, else as target. Defaults to False. |
False
|
force_reconnect
|
bool
|
If False, raises an exception if the piping connection is already connected. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
None
|
This function modifies the state of the piping segment and the connection but does not return any value. |
Raises:
| Type | Description |
|---|---|
DexpiConnectionException
|
If the piping connection is already connected, and force_reconnect is False. |
ValueError
|
If the piping network segment either has no unconnected output connection (or input connection if as_source) or has more than one such connections, in which case it is corrupted. |
DexpiCorruptPipingSegmentException
|
If the segment to be connected is found to be corrupt |
find_final_connection ¶
find_final_connection(the_segment: PipingNetworkSegment, as_source: bool = False) -> piping.PipingConnection
Finds the first or last connection object in the_segment. It does so by comparing the source and target objects of the segment and the connections. Note that in a valid segment, these should be the first or final connection. However, this method includes some validity checks, so it is recommended to use this instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_segment
|
pipingNetworkSegment
|
The segment whose final connection is to befound. |
required |
as_source
|
bool
|
If the first connection should be found, else last, by default False. |
False
|
Returns:
| Type | Description |
|---|---|
pipingConnection
|
The first or last connection. |
Raises:
| Type | Description |
|---|---|
DexpiCorruptPipingSegmentException
|
If no connection with the same source and target info as the segment is found. |
get_objects_referencing ¶
get_objects_referencing(dexpi_model: EngineeringModel, dexpi_objects: Iterable[PyDexpiBaseModel]) -> list[tuple[PyDexpiBaseModel, str]]
Find every reference attribute in the model that points at one of dexpi_objects.
Returns (holder, attribute_name) pairs: the object holding the reference and the attribute it holds it in. A holder appears once per matching attribute.
DEXPI references are one-directional -- an ActuatingSystem points at the valve
it operates, the valve points at nothing -- so answering "what refers to this?"
means scanning. The model is traversed exactly once regardless of how many
objects are passed, and matching is identity-based (is), never by id
field or equality, so distinct objects sharing an id do not alias. Only
objects reachable from dexpi_model through composition are visited.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dexpi_model
|
EngineeringModel
|
The model to search. |
required |
dexpi_objects
|
Iterable[PyDexpiBaseModel]
|
The objects to find references to. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[PyDexpiBaseModel, str]]
|
(referencing object, reference attribute name) pairs, in traversal order, and for one holder in the order its class declares the attributes. The order is deterministic, so a caller can record what it clears in it. |
get_segment_from_item ¶
get_segment_from_item(conceptual_model: PlantModel, item: PipingNetworkSegmentItem) -> piping.PipingNetworkSegment
Retrieves the piping network segment associated with a given item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conceptual_model
|
PlantModel
|
The conceptual model containing the piping network systems. |
required |
item
|
PipingNetworkSegmentItem
|
The item for which to retrieve the associated segment. |
required |
Returns:
| Type | Description |
|---|---|
PipingNetworkSegment
|
The piping network segment associated with the given item. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the item is not part of any piping network segment. |
get_unconnected_piping_segments ¶
get_unconnected_piping_segments(conceptual_model: PlantModel, as_source=False) -> list[piping.PipingNetworkSegment]
Returns a list of unconnected piping segments ending in a connection in the conceptual model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conceptual_model
|
ConceptualModel
|
The model to be analyzed. |
required |
as_source
|
bool
|
Whether to look for unconnected sources or targets in piping segments. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
list[PipingNetworkSegment]
|
The list of identified unconnected segments. |
node_holders ¶
node_holders(model: EngineeringModel, node: PipingNode, *, excluding: Iterable[tuple[PyDexpiBaseModel, str]] = ()) -> list[tuple[PyDexpiBaseModel, str]]
Every connection or segment end that names node, as (holder, attribute) pairs.
This is the one answer to "is this port taken": the MCP discovery tools report a node as free exactly when it returns nothing. A node is one physical port, so a pipe attaches to it at most once; a segment's own end counts too, as a run ending on the port has it.
Only sourceNode/targetNode references count -- a node's own drawing
refers to it as well.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
EngineeringModel
|
The model to search. |
required |
node
|
PipingNode
|
The node to check. |
required |
excluding
|
Iterable[tuple[PyDexpiBaseModel, str]]
|
|
()
|
Returns:
| Type | Description |
|---|---|
list[tuple[PyDexpiBaseModel, str]]
|
The holders, in model traversal order. Empty when the node is free. |
piping_network_segment_validity_check ¶
piping_network_segment_validity_check(the_segment: PipingNetworkSegment) -> tuple[PipingValidityCode, str]
Performs a consistency check on the pipingNetworkSegment.
Returns an appropriate PipingValidityCode Enum type. Internally, this is done by going along all items and connections to see if they form a valid chain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_segment
|
pipingNetworkSegment
|
The piping network segment to be checked for consistency. |
required |
Returns:
| Type | Description |
|---|---|
(PipingValidityCode, str)
|
An Enum type indicating the validity status of the piping network segment. msg contains the corresponding error message. |
run_items ¶
run_items(model: EngineeringModel, start: PipingNetworkSegment | PipingNetworkSegmentItem, *, across_segments: bool = True) -> list[piping.PipingNetworkSegmentItem]
The items along a line, in flow order, from start to the line's end. Read-only.
Within a segment the order is :func:segment_chain's: source to target. At
the segment's end the walk carries on into the segment that starts on the
same item and the same node object the previous one ends on, and only
that one. That is how DEXPI encodes a run split at an item: at a tee the
trunk ends on the tee's run outlet and the continuation starts on that same
node, while a branch leaves from another node of the tee -- so the walk
follows the run and never the branch. The same holds where a run was split at
any other item (a valve, a reducer). The continuation may lie in another
piping network system.
The walk stops where nothing continues the line: an open end, a nozzle, or an
item no segment starts from on that node (a branch joining another line ends
there too). That stop needs an item that can be a branch point. At an item with
no more than two nodes, a segment leaving from another node continues the line,
so the walk raises there rather than return part of the line as the whole; an
item with more nodes is taken as a possible branch point and the walk stops.
Items outside every segment, such as the nozzles at a line's two
ends, are not listed. The walk only goes downstream; it never steps back
upstream of start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
EngineeringModel
|
The model holding the line. |
required |
start
|
PipingNetworkSegment | PipingNetworkSegmentItem
|
Where the walk starts. A segment starts at its first item; an item, which
must be one of a segment's |
required |
across_segments
|
bool
|
Carry on into continuation segments, through tees along the run.
|
True
|
Returns:
| Type | Description |
|---|---|
list[PipingNetworkSegmentItem]
|
The items in flow order, each once. Empty for a segment without items that nothing continues. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
DexpiCorruptPipingSegmentException
|
If a segment on the way is not one linear chain (see :func: |
PipingTraversalException
|
If the run is ambiguous: two segments start on the node a segment ends on, a segment ends at an item without naming the node while segments start from that item, a segment starting from that item names no node, or the walk comes back to a segment or item it already passed (a cycle). Also when segments leave the item a segment ends at, none starts on the node it ends on, and the item has no more than two nodes: the line goes on there, but the model names two nodes for the junction, so the walk refuses rather than return the line cut short. |
segment_chain ¶
segment_chain(segment: PipingNetworkSegment) -> list[piping.PipingNetworkSegmentItem | piping.PipingConnection]
One segment's items and connections, interleaved in flow order. Read-only.
The order runs from the segment's source to its target and is derived from
the connections' sourceItem / targetItem, never from list order, so
an imported segment whose lists are shuffled still yields its chain. Every
PipingConnection counts (a DirectPipingConnection as much as a
Pipe), and both endpoint forms are understood: a segment may start or end
at one of its own items, or with a connection to an item outside it (or to
none). The start is the single element nothing inside the segment leads to.
The segment is left as it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment
|
PipingNetworkSegment
|
The segment to walk. |
required |
Returns:
| Type | Description |
|---|---|
list[PipingNetworkSegmentItem | PipingConnection]
|
The segment's members in flow order. Empty for an empty segment. |
Raises:
| Type | Description |
|---|---|
DexpiCorruptPipingSegmentException
|
When the segment is not one linear chain: an element listed twice, an item with two outgoing connections, an element with two predecessors, a cycle, or elements the walk from the start never reaches. A corrupt segment is refused rather than guessed at. |
segment_ends ¶
The elements a segment starts and ends at, read from its connections alone. Read-only.
A start is an element nothing inside the segment leads to: one of its items
that none of its connections enters, or one of its connections whose
sourceItem is not an item of the segment (an item outside it, or none). An
end is the converse: an item none of its connections leaves, or a connection
whose targetItem is not an item of the segment. These are the starts and
ends :func:segment_chain looks for, but nothing here requires the segment to
be one linear chain: a segment that branches has several ends, one that joins
several starts, and a closed loop none. List order is never consulted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment
|
PipingNetworkSegment
|
The segment to read. |
required |
Returns:
| Type | Description |
|---|---|
SegmentEnds
|
|
segment_ends_in_connection ¶
Checks if a segment ends in a connection, as opposed to an item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
the_segment
|
PipingNetworkSegment
|
The segment to be evaluated |
required |
as_source
|
bool
|
Whether the segments source should be checked, by default False |
False
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the segment ends in a connection. |
segments_are_connected ¶
Checks if two segments are connected by comparing their source and target items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segment_a
|
PipingNetworkSegment
|
The first segment to be checked. |
required |
segment_b
|
PipingNetworkSegment
|
The second segment to be checked. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the segments are connected, False otherwise. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the two segments are the same, as they cannot be connected to themselves. |
sort_connected_items_and_connections ¶
sort_connected_items_and_connections(items: list[PipingNetworkSegmentItem], connections: list[PipingConnection]) -> tuple[list[piping.PipingNetworkSegmentItem], list[piping.PipingConnection]]
Sorts the items and connections of a piping network segment in the order of occurrence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[PipingNetworkSegmentItem]
|
The list of items to be sorted. |
required |
connections
|
list[PipingConnection]
|
The list of connections to be sorted. |
required |
Returns:
| Type | Description |
|---|---|
tuple[list[PipingNetworkSegmentItem], list[PipingConnection]]
|
A tuple containing the sorted items and connections. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the segment is not validly connected, such as having multiple starting points or disconnected elements. |
traverse_items_and_connections ¶
traverse_items_and_connections(all_items: list[PipingNetworkSegmentItem], all_connections: list[PipingConnection], start_element: PipingNetworkSegmentItem | PipingConnection, end_condition: PipingNetworkSegmentItem | PipingConnection | Callable[[PipingNetworkSegmentItem | PipingConnection], bool], reverse=False) -> list[piping.PipingNetworkSegmentItem | piping.PipingConnection]
Traverses piping items and connections until the end condition is met.
The traversal is done in a depth-first manner, starting from the specified start element. Branching decisions are inferred from the order of the connections in the list of all connections. Branches that start with connections that are higher in the list are traversed first. The traversal can be done in reverse order by setting the reverse parameter to True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
all_items
|
list[PipingNetworkSegmentItem]
|
List of all items in the piping network segment. |
required |
all_connections
|
list[PipingConnection]
|
List of all connections in the piping network segment. |
required |
start_element
|
PipingNetworkSegmentItem | PipingConnection
|
The element from which to start the traversal. |
required |
end_condition
|
piping.PipingNetworkSegmentItem|piping.PipingConnection|
|
The condition that determines when to stop the traversal. This can be a specific element to be encountered or a callable function for more complex end conditions. |
required |
reverse
|
bool
|
Whether to traverse in reverse order. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
list[PipingNetworkSegmentItem | PipingConnection]
|
A list of traversed elements. |
Raises:
| Type | Description |
|---|---|
PipingTraversalException
|
If a circle is encountered or if the traversal cannot continue due to invalid connections. |
Display Name Toolkit¶
Derive human-readable captions for DEXPI objects. Applied as the
displayName property of every graph export: nodes are captioned
"{Humanised class} ({tag})", while relationships formed by collapsing a node
carry a concise engineer-facing term such as Measurement or Pipe (47121).
The tag is type-specific -- tagName for equipment, identifier for the
objects of a DEXPI 2.0 process model -- and a process object's free-text
label can caption it but is never its tag.
pydexpi.toolkits.display_name ¶
Derive human-readable display names for pyDEXPI objects.
Graph exports expose the original DEXPI data attributes, but none of them is a consistent caption suitable for graph visualisation. Neo4j Browser, for example, picks a different property per node -- sometimes a tag, sometimes an object id, sometimes an unhelpful serialised value.
This module provides :func:get_display_name, a type-aware resolver that
produces a short, deterministic caption for any PyDexpiBaseModel. It is
applied once in the shared graph-building layer
(:meth:pydexpi.loaders.graph_loader.GraphLoader._add_nodes), so every
exporter -- GraphML, Neo4j CSV, LLM context, the CLI -- receives the same
displayName value.
The resulting format is "{Humanised class name} ({identifier})"::
Centrifugal Pump (P4711)
Ball Valve (73KH12)
Nozzle (N1)
Piping Network Segment (S1)
Pipe (Pipe1)
Direct Piping Connection # no identifier available
The class name stays in the caption because it is the most reliable piece of
information available: many DEXPI objects carry no tag at all. The concrete
class also remains available separately as the label node attribute and as
the Neo4j node label.
Relationship captions
:class:~pydexpi.loaders.graph_loader.GraphAbstractor collapses a handful of
classes into relationships when it builds the process and conceptual graphs.
A collapsed edge inherits the node's attributes, so without further work it
would be captioned with the same class-name caption -- Measuring Line
Function (MeasuringLineFunction1) where an engineer wants Measurement.
:func:get_relationship_display_name supplies the engineer-facing term for
those classes::
Measurement # MeasuringLineFunction
Signal # SignalConveyingFunction
Actuation # OperatedValveReference
Direct connection # DirectPipingConnection
Pipe (47121) # Pipe, identified by its line number
The wording has to exist as data on the edge because the Neo4j Browser resolves a caption as a single property name: a stylesheet template mixing literal text with a placeholder renders as nothing at all.
Extending the mapping
For nodes, add an entry to :data:DISPLAY_NAME_RESOLVERS. Entries are matched
with isinstance in order, so more specific classes must be listed before
their base classes. For collapsed relationships, add an entry to
:data:RELATIONSHIP_DISPLAY_NAMES.
Examples:
>>> from pydexpi.pydexpi_classes.plant.piping import BallValve
>>> get_display_name(BallValve(pipingComponentName="73KH12"))
'Ball Valve (73KH12)'
CAPTION_FALLBACK_ATTRIBUTES
module-attribute
¶
DISPLAY_NAME_RESOLVERS
module-attribute
¶
DISPLAY_NAME_RESOLVERS: tuple[tuple[type, tuple[str, ...]], ...] = ((Nozzle, ('subTagName',)), (PipingNetworkSegment, ('segmentNumber',)), (PipingNetworkSystem, ('lineNumber',)), (PipingComponent, ('pipingComponentName', 'pipingComponentNumber')), (PipeOffPageConnector, ('pipeConnectorNumber',)), (SignalOffPageConnector, ('signalConnectorNumber',)), (InstrumentationLoopFunction, ('instrumentationLoopFunctionNumber',)), (ProcessInstrumentationFunction, ('processInstrumentationFunctionNumber',)), (ProcessSignalGeneratingFunction, ('processSignalGeneratingFunctionNumber',)), (SignalConveyingFunction, ('signalPointNumber',)), (MeasuringSystem, ('measuringSystemNumber',)), (ActuatingElectricalSystem, ('actuatingElectricalSystemNumber',)), (ActuatingSystem, ('actuatingSystemNumber',)), (ActuatingElectricalFunction, ('actuatingElectricalFunctionNumber',)), (ActuatingFunction, ('actuatingFunctionNumber',)), (TaggedPlantItem, ('tagName',)), (ProcessStep, ('identifier',)), (Port, ('identifier',)), (ProcessConnection, ('identifier',)), (ProcessStepDetail, ('identifier',)), (MaterialTemplate, ('identifier',)), (MaterialState, ('identifier',)), (MaterialStateType, ('identifier',)), (PureMaterialComponent, ('identifier', 'iUPAC_identifier', 'chEBI_identifier')), (CustomMaterialComponent, ('identifier', 'projectReference')), (MaterialComponent, ('identifier',)), (InstrumentationActivity, ('identifier',)), (InstrumentationSystemActivity, ('identifier',)))
GENERIC_TAG_ATTRIBUTES
module-attribute
¶
GENERIC_TAG_ATTRIBUTES: tuple[str, ...] = ('tagName', 'subTagName', 'pipingComponentName', 'pipingComponentNumber', 'segmentNumber', 'lineNumber', 'pipeConnectorNumber')
RELATIONSHIP_DISPLAY_NAMES
module-attribute
¶
RELATIONSHIP_DISPLAY_NAMES: dict[str, tuple[str, tuple[str, ...]]] = {'Pipe': ('Pipe', ('lineNumber',)), 'DirectPipingConnection': ('Direct connection', ()), 'MeasuringLineFunction': ('Measurement', ()), 'SignalConveyingFunction': ('Signal', ()), 'OperatedValveReference': ('Actuation', ())}
_CAMEL_SPLIT
module-attribute
¶
__all__
module-attribute
¶
__all__ = ['CAPTION_FALLBACK_ATTRIBUTES', 'DISPLAY_NAME_RESOLVERS', 'GENERIC_TAG_ATTRIBUTES', 'RELATIONSHIP_DISPLAY_NAMES', 'combine_name_and_tag', 'get_display_name', 'get_relationship_display_name', 'get_tag', 'humanize_class_name', 'referenced_object_name']
ActuatingElectricalFunction ¶
Bases: ConceptualObject, SignalConveyingFunctionTarget, TechnicalItem
An actuation setting electrical function. It covers all types of electrical consumers, e.g., motors and heaters.
ActuatingElectricalSystem ¶
Bases: ConceptualObject, TechnicalItem
An assembly of artefacts that is designed to fulfill an ActuatingElectricalFunction.
ActuatingFunction ¶
Bases: ConceptualObject, SignalConveyingFunctionSource, SignalConveyingFunctionTarget, TechnicalItem
A function for acting control structures relating to the process.
ActuatingSystem ¶
Bases: ConceptualObject, TechnicalItem
An assembly of artefacts that is designed to fulfill an ActuatingFunction.
CustomMaterialComponent ¶
Bases: MaterialComponent
A CustomMaterialComponent defines a custom chemical compound used to specify the chemical composition of a material. These compounds are usually pseudocomponents that are used to simulate the thermodynamic properties of complex mixtures of organic compounds with high molecular weight.
InstrumentationActivity ¶
Bases: ConceptualObject
This is the base type for control and instrumentation functions that are specified as part of the process design.
InstrumentationLoopFunction ¶
Bases: ConceptualObject, TechnicalItem
An identified collection of related ProcessInstrumentationFunctions that interact for a known purpose.
InstrumentationSystemActivity ¶
Bases: ConceptualObject
An InstrumentationSystemActivity is a collection of related InstrumentationActivity instances that together perform a specified function. Thus a MeasuringProcessVariable and a ControllingProcessVariable activity can be grouped into a single InstrumentationSystemActivity that represents the control loop.
MaterialComponent ¶
Bases: ConceptualObject
This is the base type to chemical components in the model. It is parent class for PureMaterialComponent and CustomMaterialComponent.
MaterialState ¶
Bases: ConceptualObject
This data structure contains the properties for a Stream. It contains one or more MaterialState objects: one for the total properties and then one for each of the phases present.
MaterialStateType ¶
Bases: ConceptualObject
This data structure represents the properties of a material or a single material phase. The MaterialState data structure for a Stream is built up of one or more of these data structures: one for the total properties and then one for each of the phases present.
MaterialTemplate ¶
Bases: ConceptualObject
This data structure defines the structure of Stream objects that correspond to a specific type of material. This structure contains information about the thermodynamic model used to represent this type of material. It contains the ListOfMaterialComponents: pure and pseudo components used to represent the system. It also determines the legend for a stream table when the model is used to represent a Process Flow Diagram.
MeasuringSystem ¶
Bases: ConceptualObject, TechnicalItem
An assembly of artefacts that is designed to measure and transmit a property in order to fulfill one or more ProcessSignalGeneratingFunctions.
Nozzle ¶
Bases: ConceptualObject, ActuatingElectricalLocation, SensingLocation, PipingNodeOwner, PipingSourceItem, PipingTargetItem
A projecting short piece of pipe that is attached to another (hollow) body.
PipeOffPageConnector ¶
Bases: ConceptualObject, PipingNetworkSegmentItem, PipingNodeOwner
A connector that indicates that a piping network segment is continued elsewhere, either on the same PID or on another PID. Graphically, it is usually represented as an arrow.
PipingComponent ¶
Bases: ConceptualObject, SensingLocation, PipingNetworkSegmentItem, PipingNodeOwner, PipingSourceItem, PipingTargetItem
A piping component
PipingNetworkSegment ¶
Bases: ConceptualObject, ActuatingElectricalLocation, SensingLocation
PipingNetworkSystem ¶
Bases: ConceptualObject, TechnicalItem
Port ¶
Bases: ConceptualObject
A port represents a transfer of material, energy or information into or out of a process step. It provides the anchor point for connecting two process steps to each other. The term is derived from SysML and IDEF0 representations of processes. Note that a port in DEXPI Process is a conceptual object. It represents a contract regarding the material, energy or information coming into or out of a process step.
ProcessConnection ¶
Bases: ConceptualObject
This data structure is the abstract parent for all connections between blocks in the DEXPI Process model.
ProcessInstrumentationFunction ¶
Bases: ConceptualObject, SignalConveyingFunctionSource, SignalConveyingFunctionTarget, TechnicalItem
A requirement for instrumentation and/or control structures relating to Process Engineering.
ProcessSignalGeneratingFunction ¶
Bases: ConceptualObject, SignalConveyingFunctionSource, TechnicalItem
A function for instrumentation and/or control structures relating to Process Engineering.
ProcessStep ¶
Bases: ConceptualObject
The ProcessStep type is the parent type for all process steps in the model.
ProcessStepDetail ¶
Bases: ConceptualObject
This type is the parent type for processes that cannot be run independently of another process.
PureMaterialComponent ¶
Bases: MaterialComponent
A PureMaterialComponent represents a MaterialComponent that is modelled as a pure chemical compound with defined composition and structure. This is used in the MaterialTemplate to document the composition vector and define properties for use in the process.
PyDexpiBaseModel ¶
Bases: ABC, BaseModel
Base Model for the classes created for the dexpi data model, containing some overarching functionality
SignalConveyingFunction ¶
Bases: ConceptualObject
A function for conveying a signal.
SignalOffPageConnector ¶
Bases: ConceptualObject
A signal connector that indicates that a SignalConveyingFunction is continued elsewhere, either on the same P&ID or on another P&ID. Graphically, it is usually represented as an arrow.
TaggedPlantItem ¶
Bases: ConceptualObject, TechnicalItem
A fully tagged item in a plant.
_clean_string ¶
Return value stripped, or None if it is not a usable string.
DEXPI string attributes are typed str | Undefined | None, where
Undefined is a sentinel model instance rather than a string. Only
genuine, non-blank strings are accepted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
object
|
The candidate value. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The stripped string, or |
_first_string_attribute ¶
Return the first non-blank string attribute of obj, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
object
|
The object to read attributes from. |
required |
attribute_names
|
tuple of str
|
Attribute names to try, in priority order. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The first usable value, stripped of surrounding whitespace. |
_first_string_value ¶
_first_string_value(attributes: Mapping[str, object], attribute_names: tuple[str, ...]) -> str | None
Return the first non-blank string entry of attributes, or None.
The mapping counterpart of :func:_first_string_attribute, used for graph
attribute dictionaries rather than model objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attributes
|
Mapping
|
The attribute dictionary to read from. |
required |
attribute_names
|
tuple of str
|
Keys to try, in priority order. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The first usable value, stripped of surrounding whitespace. |
_resolve_identifier ¶
Resolve the identifier part of the display name for obj.
Resolution order:
- The tag, as resolved by :func:
get_tag. - The :data:
CAPTION_FALLBACK_ATTRIBUTES(a device type, or a process object's free-text label), which are not tags. - The object
id, unless it was auto-generated bymake_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
PyDexpiBaseModel
|
The object to resolve. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The identifier, or |
combine_name_and_tag ¶
Combine a name and a tag into a single caption.
Applies the formatting rules for display names: blank and None values
are ignored, identical values are not repeated, and parentheses are only
used when both values are present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str or None
|
The leading part of the caption. |
required |
tag
|
str or None
|
The identifier shown in parentheses. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The combined caption, or |
Examples:
get_display_name ¶
Return a human-readable caption for a pyDEXPI object.
The caption is "{Humanised class name} ({identifier})", where the
identifier is resolved from type-specific attributes. Objects without any
usable identifier are captioned with the humanised class name alone. The
result is deterministic and never empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
PyDexpiBaseModel
|
The object to name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The display name, e.g. |
Examples:
get_relationship_display_name ¶
get_relationship_display_name(label: str, attributes: Mapping[str, object] | None = None, inheritance: str = '') -> str | None
Return the caption for a relationship formed by collapsing label.
Collapsed relationships are captioned with a concise engineer-facing term
from :data:RELATIONSHIP_DISPLAY_NAMES instead of the class-name caption a
node would receive. Classes that are not registered there return None,
which leaves the caption inherited from the collapsed node untouched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The concrete class name of the collapsed node. |
required |
attributes
|
Mapping
|
The attributes of the relationship being built. Searched for the
identifier shown in parentheses, which may originate from a node that
was merged into the collapsed one earlier -- a pipe's |
None
|
inheritance
|
str
|
The collapsed node's colon-separated |
""
|
Returns:
| Type | Description |
|---|---|
str or None
|
The caption, or |
Examples:
get_tag ¶
Return the engineering tag of obj, or None if it carries none.
This is the tag part of :func:get_display_name without its last resort: the object id is
never returned. Use it wherever a tag is wanted as a tag -- a label in a diff, a search
field -- rather than as a caption, so that an untagged object reads as untagged instead of
as its id.
Resolution order:
- The type-specific attributes registered in :data:
DISPLAY_NAME_RESOLVERS. - The generic sweep over :data:
GENERIC_TAG_ATTRIBUTES.
Only non-blank strings count; Undefined and blank values are skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
PyDexpiBaseModel
|
The object to read the tag of. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
The tag, stripped of surrounding whitespace, or |
Examples:
humanize_class_name
cached
¶
Split a CamelCase DEXPI class name into space-separated words.
Cached (bounded): it runs once per object whenever captions are built, over a few hundred distinct class names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
class_name
|
str
|
The class name, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The humanised name, e.g. |
instrument_tag ¶
The loop tag of an instrumentation function as a P&ID prints it: "PICSA-4712.02".
That is the category letter and the further function letters, a hyphen, and the number.
:func:get_tag resolves an instrumentation function by its number alone
("4712.02"), which is what the diff matches on and the query index keeps as its canonical
key. Queries also accept this printed tag as an additional key. This is the tag a
person reads, and the one to show in a list, where the number alone repeats -- a PI and a
PT of one loop share it.
Missing parts are left out rather than raising: no letters gives the number, no number gives
the letters, neither gives None.
Examples:
>>> from pydexpi.pydexpi_classes.plant.instrumentation import ProcessInstrumentationFunction
>>> instrument_tag(ProcessInstrumentationFunction(
... processInstrumentationFunctionCategory="P",
... processInstrumentationFunctions="ICSA",
... processInstrumentationFunctionNumber="4712.02",
... ))
'PICSA-4712.02'
is_generated_id ¶
Return True if value has the shape of an id minted by make_id.
A generated id says nothing about where the object came from, only that no id was supplied when it was built. What that means for stability depends on the caller: an object loaded from a file without an id gets a fresh one on every load, while an object created in code keeps the id it was minted with once the model is saved as JSON. This function answers the shape question only; deciding whether to trust such an id is the caller's call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
object
|
The value to examine, normally an object's |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
referenced_object_name ¶
Name an object that a message mentions but affected_component_ids does not list.
The display name, as for the affected objects. For an untagged object whose caption does not
already show its id (the id was minted by make_id), the id is appended: the message is
then the only place a reader, or an agent fixing the model, can learn which node or item is
meant. Ids are deliberately not added to affected_component_ids instead, because that
list identifies the issue.
Validation messages and other messages that name objects use it, which is why it lives here rather than in the validation package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
PyDexpiBaseModel or None
|
The referenced object, or |
required |
Returns:
| Type | Description |
|---|---|
str
|
E.g. |
Display Labels Toolkit¶
Human-readable labels for enum values (units as their symbol, "barg"; other
values in sentence case, "Fail close") and attribute names ("Design pressure").
pydexpi.toolkits.display_labels ¶
Human-readable labels for enum values and attribute names.
:mod:pydexpi.toolkits.display_name captions objects. This module labels the two other things
a user interface shows next to them: the value of an enumerated attribute, and the name of an
attribute. Both follow the same rule as object captions, so that one source of wording serves the
graph exports, the table export, search, validation messages and any API built on pyDEXPI:
- :func:
enum_value_label--FailActionClassification.FailClose->"Fail close", and a unit such asTemperatureUnit.DegreeCelsius->"°C". - :func:
attribute_label--"designPressure"->"Design pressure".
Neither function imports :mod:pydexpi.exports, whose __init__ pulls in pandas, openpyxl and
networkx. The dependency points the other way: the table export's
:func:~pydexpi.exports.table_export.format_string delegates to :func:humanize_attribute_name
here, so there is one implementation of the attribute-name casing.
Units are not labelled here. :func:enum_value_label hands them to
:func:pydexpi.toolkits.quantity_format.unit_symbol, the single home of the unit-symbol map.
Examples:
>>> from pydexpi.pydexpi_classes.core.physicalQuantities import PressureGaugeUnit
>>> from pydexpi.pydexpi_classes.plant.enumerations import FailActionClassification
>>> enum_value_label(FailActionClassification.FAILCLOSE)
'Fail close'
>>> enum_value_label(PressureGaugeUnit.BAR)
'barg'
>>> attribute_label("nominalDiameterRepresentation")
'Nominal diameter representation'
ATTRIBUTE_LABEL_OVERRIDES
module-attribute
¶
ATTRIBUTE_LABEL_OVERRIDES: dict[str, str] = {'processInstrumentationFunctionCategory': 'Measured variable letters', 'processInstrumentationFunctions': 'Function letters', 'processInstrumentationFunctionNumber': 'Loop number', 'processInstrumentationFunctionModifier': 'Modifier letters', 'caseUID': 'Case UID', 'chEBI_identifier': 'ChEBI identifier', 'iUPAC_identifier': 'IUPAC identifier', 'provenanceURI': 'Provenance URI', 'referenceDataURI': 'Reference data URI', 'sourceURI': 'Source URI', 'uri_': 'URI', 'pipingComponentName': 'Component name', 'pipingComponentNumber': 'Component number'}
ENUM_VALUE_LABEL_OVERRIDES
module-attribute
¶
ENUM_VALUE_LABEL_OVERRIDES: dict[tuple[type[Enum], str], str] = {(MeasuredQuantity, 'AudioVisual'): 'Audiovisual', (MeasuredQuantity, 'WeightMassForce'): 'Weight, mass or force'}
_DESIGNATION_ENUMS
module-attribute
¶
_DESIGNATION_ENUMS: frozenset[type[Enum]] = frozenset({NominalDiameterStandardClassification, NominalPressureStandardClassification})
_DESIGNATION_FILLER_WORDS
module-attribute
¶
_ENCODED_CHARACTERS
module-attribute
¶
_ENUM_WORD_SPELLINGS
module-attribute
¶
_ENUM_WORD_SPELLINGS: dict[str, str] = {'din': 'DIN', 'dn': 'DN', 'en': 'EN', 'gmp': 'GMP', 'iso': 'ISO', 'kpa': 'kPa', 'nps': 'NPS', 'pn': 'PN'}
__all__
module-attribute
¶
__all__ = ['ATTRIBUTE_LABEL_OVERRIDES', 'ENUM_VALUE_LABEL_OVERRIDES', 'attribute_label', 'enum_value_label', 'humanize_attribute_name']
MeasuredQuantity ¶
Bases: StrEnum
NominalDiameterStandardClassification ¶
Bases: StrEnum
NominalPressureStandardClassification ¶
Bases: StrEnum
PhysicalQuantityUnit ¶
Bases: StrEnum
_enum_value_label
cached
¶
Label the member member_name of enum_class; see :func:enum_value_label.
_sentence_case_enum_value ¶
Split a generated enum value into words and sentence-case them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
The raw enum value, e.g. |
required |
drop_filler
|
bool
|
Whether to drop the |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label, e.g. |
attribute_label
cached
¶
Return the human-readable label for a DEXPI attribute name.
The curated :data:ATTRIBUTE_LABEL_OVERRIDES first, then the generic
:func:humanize_attribute_name casing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The attribute name as the generated classes spell it, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label, e.g. |
Examples:
enum_value_label ¶
Return the human-readable label for an enum member.
- A unit (any :class:
~pydexpi.pydexpi_classes.core.physicalQuantities.PhysicalQuantityUnit) is labelled with its symbol, the pressure reference spelled out:DegreeCelsius->"°C",PressureGaugeUnit.Bar->"barg". A unit with no symbol is labelled like any other member:RatioUnit.Fraction->"Fraction". - Any other member is labelled with its value in sentence case:
"FailClose"->"Fail close". Acronyms keep their spelling ("Non-GMP relevant function"), standard designations lose theirObject/Artefactsuffix ("NPS 1 1/2","EN 1333 PN 2.5"), and a few members have a curated label in :data:ENUM_VALUE_LABEL_OVERRIDES.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
member
|
Enum
|
The enum member, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The label. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If member is not an enum member. |
Examples:
humanize_attribute_name
cached
¶
Sentence-case a camelCase attribute name, without curated overrides.
This is the casing of the table export's column headers
(:func:pydexpi.exports.table_export.format_string delegates here), and therefore of the keys
of the translation tables in pydexpi.exports.table_locale. Changing it changes those keys.
For a label to show a user, prefer :func:attribute_label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The attribute name, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The first character capitalised, and every later capital turned into a space and its
lowercase form: |
Examples:
humanize_class_name
cached
¶
Split a CamelCase DEXPI class name into space-separated words.
Cached (bounded): it runs once per object whenever captions are built, over a few hundred distinct class names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
class_name
|
str
|
The class name, e.g. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The humanised name, e.g. |
unit_symbol ¶
Return the display symbol for a PhysicalQuantityUnit literal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit
|
Enum
|
A unit literal from the generated DEXPI unit enums. |
required |
qualify_pressure_reference
|
bool
|
If True, append |
``False``
|
Returns:
| Type | Description |
|---|---|
str
|
The conventional symbol ( |
Versioning¶
Semantic diff and the Git projection. See Versioning for how they relate.
Semantic diff¶
pydexpi.diff.id_diff ¶
id_diff(model1: EngineeringModel, model2: EngineeringModel, *, track_order: bool = False, scope: Iterable[str] | None = None) -> DiffResult
Compute the diff between two models using ID-based equality.
Every call builds its own PyDexpiDiffEngine. The engine keeps its object and match maps
as instance state for the duration of a diff, so an engine shared between calls -- this
function used to reuse one module-wide instance -- is neither re-entrant nor thread-safe: two
concurrent diffs would overwrite each other's maps. Building an engine is cheap; the diff
itself dominates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model1
|
EngineeringModel
|
The first model to compare. |
required |
model2
|
EngineeringModel
|
The second model to compare. |
required |
track_order
|
bool
|
Also report reorders of order-significant composition lists (z-order, a segment's flow
order); see |
False
|
scope
|
iterable of str
|
Ids of objects to restrict the result to, each with its composition subtree in either
model -- one piece of equipment, a piping network system, a segment. This filters the
whole-model diff: everything is still compared, so a valve moved out of a scoped segment
is reported as moved, with its new parent outside the scope. To compare two subtrees on
their own instead, use |
None
|
Returns:
| Type | Description |
|---|---|
DiffResult
|
The result of the diff operation, containing all detected changes (within scope, if given). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a scope id is in neither model -- more likely a typo than an intent. |
pydexpi.diff.DiffResult ¶
Bases: BaseModel
Top-level container for the result of comparing two pyDEXPI models.
Attributes:
| Name | Type | Description |
|---|---|---|
format_version |
str
|
The serialized format's version: :data: |
matching |
Matching
|
How objects were matched between the models. Only an |
track_order |
bool
|
Whether reorders of the composition lists pyDEXPI orders beyond the specification (a
segment's flow order) were compared. An empty result with |
moved_objects |
list[MovedObject]
|
Objects that exist in both models but have a changed parent in the composition hierarchy. |
added_objects |
list[AddedObject]
|
Objects present only in the new model. |
removed_objects |
list[RemovedObject]
|
Objects present only in the old model. |
modified_objects |
list[ModifiedObject]
|
Objects present in both models with changed attributes. |
added_objects
class-attribute
instance-attribute
¶
format_version
class-attribute
instance-attribute
¶
has_graphic_changes
property
¶
Return True if any change concerns drawing/layout data.
This does not mean the changes are limited to graphics: a diff can have both graphic
and semantic changes. Check has_semantic_changes for the other half.
has_metadata_changes
property
¶
Return True if any change concerns document metadata.
has_semantic_changes
property
¶
Return True if any change concerns the engineering content (the conceptual model).
Document metadata -- the modification date, the exporting tool -- is not engineering content, and neither is the drawing.
modified_objects
class-attribute
instance-attribute
¶
moved_objects
class-attribute
instance-attribute
¶
removed_objects
class-attribute
instance-attribute
¶
filter ¶
Return a diff with only the entries predicate accepts, and the same metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
callable
|
Called with each |
required |
Returns:
| Type | Description |
|---|---|
DiffResult
|
A new result. The original is not changed. |
for_objects ¶
Return the changes concerning the given objects.
An entry is kept when its object's id -- in either model -- is one of object_ids. To
keep a whole subtree, pass its ids: pydexpi.diff.subtree_ids collects them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object_ids
|
iterable of str
|
The ids of the objects of interest. |
required |
Returns:
| Type | Description |
|---|---|
DiffResult
|
A new result with the same metadata. |
in_domains ¶
Return the changes in the given domains ("conceptual", "graphics", ...).
pydexpi.diff.PyDexpiDiffEngine ¶
A diff engine for comparing two pyDEXPI models based on a provided semantic equality.
The diff engine identifies changes in form of added, removed, moved, and modified objects, where modified objects are objects with changes in their data attributes or connectivity. The diff engine relies on a provided semantic equality function that defines the criteria for semantic equality between pyDEXPI objects of the same type.
To calculate the diffs, the engine first creates a mapping between semantically equal objects in the two models based on the provided SemanticEquality implementation. It also keeps track of objects, ids, and composition hierarchy for both models. Then, diffs are calculated: - Added objects: Objects in model 2 that have no semantic match in model 1. - Removed objects: Objects in model 1 that have no semantic match in model 2. - Moved objects: Objects that have a semantic match in the other model but a different container (i.e. parent in the composition hierarchy). - Modified objects: Objects that have a semantic match in the other model but changes in reference or data attributes.
Attributes:
| Name | Type | Description |
|---|---|---|
semantic_equality |
SemanticEquality
|
The semantic equality function used to compare pyDEXPI objects of the same type. |
_object_map1 |
dict[str, PyDexpiBaseModel]
|
A mapping of object IDs to pyDEXPI objects for model 1. |
_object_map2 |
dict[str, PyDexpiBaseModel]
|
A mapping of object IDs to pyDEXPI objects for model 2. |
_semantic_map |
dict[str, str]
|
A mapping of object IDs from model 1 to object IDs from model 2 for pairs of objects considered equal within the composition hierarchy. |
_second_semantic_map |
dict[str, str]
|
A mapping of object IDs from model 1 to object IDs from model 2 for pairs of objects considered equal globally. |
_container_map1 |
dict[str, str | None]
|
A mapping of object IDs from model 1 to the ID of their direct container (i.e. parent in
the composition hierarchy), or |
_container_map2 |
dict[str, str | None]
|
A mapping of object IDs from model 2 to the ID of their direct container (i.e. parent in
the composition hierarchy), or |
_non_mapped_obj1 |
list[PyDexpiBaseModel]
|
A list of objects from model 1 that have no semantic match in model 2. |
_non_mapped_obj2 |
list[PyDexpiBaseModel]
|
A list of objects from model 2 that have no semantic match in model 1. |
Example usage
from pydexpi.diff.engine import PyDexpiDiffEngine from pydexpi.diff.semantic_equality import id_equality from pydexpi.pydexpi_classes.pydexpi_classes import EngineeringModel
Create two models to compare¶
model1 = EngineeringModel(...) model2 = EngineeringModel(...)
Initialize the diff engine with ID-based semantic equality¶
diff_engine = PyDexpiDiffEngine(semantic_equality=id_equality)
Perform the diff operation¶
diff_result = diff_engine.diff(model1, model2)
Initialize the diff engine with a specific semantic equality function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
semantic_equality
|
SemanticEquality
|
The semantic equality function to use for comparing pyDEXPI objects of the same type. |
required |
track_order
|
bool
|
Also report reorders of the composition lists pyDEXPI gives an order -- a segment's
flow order ( |
False
|
diff ¶
Calculate the diff between model1 and model2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model1
|
EngineeringModel
|
The first pyDEXPI model to compare, considered the "old model". |
required |
model2
|
EngineeringModel
|
The second pyDEXPI model to compare, considered the "new model". |
required |
Returns:
| Type | Description |
|---|---|
DiffResult
|
The result of the diff operation. |
diff_objects ¶
Compare two subtrees -- one piece of equipment, a segment -- on their own.
This is a comparison scope: only the two subtrees are traversed and matched, and
nothing outside them is looked at. That differs from filtering a whole-model diff
(id_diff(..., scope=...), DiffResult.for_objects) in ways that matter:
- An object moved across the boundary -- a valve moved out of the compared segment -- is reported as removed (or added), not as moved: its other parent is out of view.
- The two roots are matched like any other pair. If they do not match (different ids, under id equality), every object is reported as removed on one side and added on the other.
- Domains are classified by class, since the subtree's location in a model is unknown.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
object1
|
PyDexpiBaseModel
|
The root of the old subtree. |
required |
object2
|
PyDexpiBaseModel
|
The root of the new subtree. |
required |
Returns:
| Type | Description |
|---|---|
DiffResult
|
The changes within the two subtrees. |
Git projection¶
pydexpi.versioning.model_to_projection ¶
model_to_projection(model: PyDexpiBaseModel, *, id_policy: IdPolicy = 'source', include_graphics: bool = True) -> str
Render model as stable, reviewable text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PyDexpiBaseModel
|
Normally an |
required |
id_policy
|
('source', 'all')
|
Which ids to trust across revisions (see the module docstring). |
"source"
|
include_graphics
|
bool
|
Include the drawing ( |
True
|
Returns:
| Type | Description |
|---|---|
str
|
The projection, |
MCP server extensions¶
The MCP server registers the extensions installed under the entry-point group
pydexpi.mcp_extensions (needs the mcp extra).
pydexpi.mcp_server.extensions ¶
How the MCP server is extended: entry-point plug-ins, and the fields they add to replies.
The core server answers questions about a model; an installed extension can add tools, prompts and
resources of its own. An extension is a callable register(mcp, sessions, file_access) that adds
its tools, prompts and resources to a server, declared under the entry-point group
:data:ENTRY_POINT_GROUP::
[project.entry-points."pydexpi.mcp_extensions"]
my_extension = "my_package.mcp:register"
The core never imports an extension by name: :func:discover_extensions finds what is
installed, and create_server registers it after the core's own tools. Registered last, its
tools would be listed last; an extension that wants them listed among the core's, where a client
reads them in context, says so with :func:place_tools_after, and create_server arranges the
list (:func:arrange_tools) once every extension is registered.
Discovery replies can say more than the core knows how to compute, and an extension can add fields
to them. The core reply calls :class:ItemFields at each place such a field goes, and an extension
registers a provider for that place with :func:register_item_fields; without one the reply simply
leaves the field out. A tool's description says what its reply holds, so the core declares the
passage of the description that documents each place (:func:declare_field_passages), and a
provider may give the passage that documents its fields: the core's description then names no
field the core does not give, and the extension's is the full one. Providers are per server, so
a server built without extensions never shows another server's fields.
ITEM_FIELD_KINDS
module-attribute
¶
McpExtension ¶
Bases: Protocol
What an entry point of :data:ENTRY_POINT_GROUP loads: a function registering an extension.
It is called once per server, after the core's tools, with the server, its sessions and its
file-access policy; a tool that touches a file goes through file_access.resolve.
discover_extensions ¶
The installed MCP server extensions, in the order of their entry-point names.
An entry point that fails to load is skipped, with a warning naming it and the error: one broken plug-in must not take the server down, and nothing else would say it is missing.
register_item_fields ¶
register_item_fields(mcp: Any, kind: str, provider: ItemFieldProvider, passage: str | None = None) -> None
Have provider add fields to mcp's discovery replies at the place kind names.
passage is what the replying tool's description says of that place with these fields
(:meth:ItemFields.register).
declare_field_passages ¶
Declare which passage of tool's description documents each place of extension fields.
passages maps a kind (:data:ITEM_FIELD_KINDS) to the core's passage for it: dedented
whole lines, found once in the description. :func:arrange_tools puts a provider's passage
in its place.
place_tools_after ¶
List the tools names, in that order, right after the tool anchor in mcp's list.
Applied by :func:arrange_tools, once every extension is registered, in the order the
placements were made.
arrange_tools ¶
Apply the tool placements and description passages made for mcp.
create_server calls it after the extensions. The SDK has no public way to order its
tool list or to change a registered tool's description, so this rebuilds its tool registry
(_tool_manager._tools, insertion-ordered, which is the order tools/list gives); it
is the one place in pyDEXPI that does. A placement or passage naming what is not there is a
programming error and raises.
CLI plug-ins¶
The CLI adds the commands installed under the entry-point group pydexpi.cli_plugins
and diff --patch prints with the writer installed
under pydexpi.patch_writers (needs the cli extra).
pydexpi.cli ¶
Command-line interface for pyDEXPI.
Exposes a practical subset of pyDEXPI's functionality through a clean,
user-friendly terminal interface built with Typer_.
Available commands
pydexpi info— print a model summary (equipment, lines, instruments).pydexpi validate— run every validation rule, reporting file:line per finding.pydexpi convert— convert between DEXPI XML and JSON (--fastskips validation).pydexpi svg— render a P&ID diagram to SVG.pydexpi export-table— export equipment / line / instrument tables.pydexpi export-graph— export the plant graph at a chosen abstraction level.pydexpi diff— compare two model states by stable id (--json,--patch).pydexpi git-projection— print stable review text of a model, for Git.
Plug-ins add commands: each callable declared under the entry-point group
pydexpi.cli_plugins is called with the Typer app, and diff --patch prints with the
writer declared under pydexpi.patch_writers. Without an installed writer, --patch
exits 2.
Every command that loads a model takes --lenient (strict=False), which a DEXPI 2.0
process export usually needs. Run pydexpi --help or pydexpi <command> --help for full
option details.
.. _Typer: https://typer.tiangolo.com/
discover_plugins ¶
The callables installed under the entry-point group, in the order of their names.
An entry point that fails to load is skipped, with a warning naming it and the error: one broken plug-in must not take the CLI down, and nothing else would say its commands are missing.
build_app ¶
The CLI: the core's commands, then every installed plug-in's (:data:CLI_PLUGIN_GROUP).
A plug-in may place its commands among the core's in the help, by rearranging
app.registered_commands.
Synthetic Data Generation¶
Generate synthetic P&ID patterns for AI/ML applications.
SyntheticPIDGenerator¶
Main class for generating synthetic P&ID patterns.
pydexpi.syndata.generator.SyntheticPIDGenerator ¶
SyntheticPIDGenerator(generator_function: GeneratorFunction, capping_function: CappingFunction = None, renaming_convention: ConnectorRenamingConvention = None, max_steps=100)
The generator contains mechanisms to connect patterns to a larger pattern and collect them in pattern distributions.
Attributes:
| Name | Type | Description |
|---|---|---|
_current_pattern |
Pattern
|
This attribute stores the (partially) generated P&ID as a pattern. |
generator_function |
GeneratorFunction
|
The generator function that defines the generation strategy to be applied by this generator. |
capping_function |
CappingFunction
|
The capping function that defines the capping strategy to be applied by this generator. |
renaming_convention |
ConnectorRenamingConvention
|
The renaming convention that renames connectors of new patterns that are included. |
max_steps |
int
|
The maximum number of steps to take before early termination. |
_generation_history |
GenerationHistory
|
The generation history of the generator, which stores the steps taken during the generation process. |
_current_step |
int
|
Attribute keeping track of the current step the generator is in. |
_termination_flag |
bool
|
Internal flag that can be used to invoke termination of the current generation process. |
Constructor of a synthetic P&ID generator
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
generator_function
|
GeneratorFunction
|
The function that defines the generation logic to be followed |
required |
capping_function
|
CappingFunction
|
The function that defines the capping logic to be followed, by default None |
None
|
renaming_convention
|
ConnectorRenamingConvention
|
The function that defines the renaming logic to be followed, by default None |
None
|
max_steps
|
int
|
The maximum amount of steps to be taken before terminating the generation, by default 100 |
100
|
generate_pattern ¶
Top-level function to create a random dexpi flowsheet.
For this, a pattern is first initialized according to the initialization defined by the generator_function. Then, while no stopping criteria are reached, the _current_pattern is aggregated iteratively with the next_step() function. Finally, unconnected connectors are dropped by calling drop_connector() on all connectors of the pattern, and the generated pattern is returned. The _current_pattern is reset to None for the next call of generate_pattern().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
new_pattern_label
|
str
|
The label the new pattern should have. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Pattern |
Pattern
|
The generated pattern. |
Raises:
| Type | Description |
|---|---|
AttributeError:
|
If the _current_pattern was already initialized before the call of this method. |
PatternDistribution¶
Manage and load pattern distributions for synthetic generation.
pydexpi.syndata.pattern_distribution.PatternDistribution ¶
PatternDistribution(name: str, patterns: dict[str, Pattern], probabilities: dict[str, float], connector_labels: set[str])
A PatternDistribution is a collection of Patterns that share the same properties with regards to the generation logic. The patterns are grouped for the sake of randomness and for structure. The grouping is based on similarity mainly regarding: - Outside connectors. - Transition logic. - Conceptual similarity (e.g. distillation equipment).
Note: Pattern labels in a distribution are exclusive
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name of the pattern distribution, preferably an umbrella term for the patterns contained. |
patterns |
dict[str, Pattern]
|
A dictionary containing the distributions patterns as values, with the pattern labels as keys. |
probabilities |
dict[str, float]
|
A dictionary containing the distributions probabilities as values, with the pattern labels as keys. |
connector_labels |
list[str]
|
The labels of the connectors that each of the patterns in the distribution must contain. |
Constructor of a pattern distribution. Name, distribution, and connector_labels stored as attributes. Each pattern in the distribution is validated against the connector labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the distribution. |
required |
patterns
|
dict[str, Pattern]
|
A dictionary containing the distributions patterns as values, with the pattern labels as keys. |
required |
probabilities
|
dict[str, float]
|
A dictionary containing the distributions probabilities as values, with the pattern labels as keys. |
required |
connector_labels
|
set[str]
|
The labels of the connectors that each pattern needs to have to be part of the distribution. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
In case a pattern of the distribution does not fulfil the connector requirements. |
load
classmethod
¶
Loads a distribution in the directory specified in dir_path with the name given in name, as previously saved by the save_distribution method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dir_path
|
Path
|
The directory the distribution is saved in. |
required |
name
|
str
|
The name of the distribution. |
required |
Returns:
| Type | Description |
|---|---|
PatternDistribution
|
The loaded pattern distribution. |
save ¶
Saves a distribution to a directory in dirpath with the folder name taken from its name attribute. All patterns are saved in a new folder with the distribution name. Metadata, specifically the distribution probabilities and connector labels, are saved along in a separate json in that folder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dir_path
|
str
|
The path of the directory the pattern is stored in |
required |
Additional Resources¶
- Source Code: GitHub Repository
- DEXPI Data Model: Official Documentation
- Examples: See the Usage page for practical examples