Using CIM Objects¶
Once you have a CIM profile imported into your Python environment, you can create, manipulate, and query CIM objects. This notebook explores the fundamental operations for working with CIM dataclass objects in CIMantic Graphs.
All CIM objects in CIMantic Graphs inherit from the Identity base class, which provides essential functionality for object identification, UUID management, and JSON-LD serialization.
The Identity Base Class¶
Every CIM object in CIMantic Graphs inherits from the Identity class, which provides core functionality:
Key Features:¶
- UUID Management - Automatic generation and management of RFC 4122 UUIDs
- URI Generation - Creates unique URIs for use in RDF and graph databases
- JSON-LD Serialization - Native support for JSON-LD format with
@idand@type - Pretty Printing - Human-readable display of objects
- Dictionary Conversion - Easy conversion to/from Python dictionaries
Identity Class Hierarchy:¶
Creating CIM Objects¶
Let's start by importing a profile and creating some basic objects:
Using Attribute Utilities¶
CIMantic Graphs provides utility functions to inspect attribute metadata programmatically. These are especially useful when building generic tools that work with any CIM profile:
get_attr_field_type()- Returns the Python field type (str, list, Optional, etc.)get_attr_uml_type()- Returns the UML type (Attribute, Association, enumeration)get_attr_datatype()- Returns the CIM datatype(s) for the attributeget_attr_inverse()- Returns the inverse relationship for associations
Let's see these in action:
Working with Attributes¶
CIM dataclasses have three types of fields:
- Attributes - Simple data values (strings, numbers, booleans)
- Associations - References to other CIM objects
- Enumerations - Predefined sets of values
Each field includes metadata describing its UML type, cardinality, and inverse relationships (for associations).
Summary¶
This notebook covered the fundamentals of working with CIM objects in CIMantic Graphs:
Key Concepts:¶
Identity Base Class - All CIM objects inherit from Identity, providing UUID management, URI generation, and JSON-LD serialization
Object Creation - Create objects with or without mRIDs; mRIDs should be provided for persistent data
Attributes and Metadata - Each field includes metadata about its UML type, cardinality, and inverse relationships
Associations - Relationships between objects can use object references or URI strings
Serialization - Objects support JSON-LD format and dictionary conversion
Inspection Tools - Validator utilities enable generic code that works with any profile
Next Steps:¶
- Incrementals - Learn how to track and manage model changes
- Units - Work with physical quantities and unit conversion
- FeederModel - Build complete network models with databases
With these object fundamentals in place, you're ready to build and manipulate complete CIM power system models!
Best Practices for Working with CIM Objects¶
1. Always Provide mRIDs for Persistent Objects¶
For objects that will be saved to databases or exchanged between systems, always provide explicit mRIDs:
# Good - explicit mRID ensures persistence
line = cim.ACLineSegment(mRID='_line-001', name='Feeder_1')
# Avoid for persistent data - UUID will change between runs
line = cim.ACLineSegment(name='Feeder_1')
2. Use Consistent mRID Formatting¶
Follow a consistent format for mRIDs across your organization:
- Leading underscore:
_line-001 - UUID format:
_12345678-1234-1234-1234-123456789abc - Hierarchical:
_substation-A_feeder-1_line-001
3. Maintain Bidirectional Associations¶
When creating associations, remember to set both directions:
# Set both sides of the relationship
terminal.ConductingEquipment = line # Forward
line.Terminals.append(terminal) # Inverse
4. Choose the Right Association Format¶
- Use object references for small, in-memory models (easier to navigate)
- Use URI strings for large models (more memory efficient)
- Be consistent within a single model
5. Leverage Type Hints and IDEs¶
The generated dataclasses include full type hints. Use a modern IDE (VS Code, PyCharm) to get:
- Autocomplete for attributes
- Type checking warnings
- Inline documentation from docstrings
6. Use Validator Utilities for Generic Code¶
When writing tools that work with any CIM profile, use the attribute utility functions instead of hardcoding field names:
# Generic code that works with any class
from cimgraph.validators.attribute_utils import get_attr_uml_type
def find_associations(cim_obj):
for field in fields(cim_obj):
if get_attr_uml_type(type(cim_obj), field.name) == 'Association':
print(f"Found association: {field.name}")
import json
# Create an object
transformer = cim.PowerTransformer(
mRID='_xfmr-001',
name='Substation_Transformer_1',
description='138kV/12.47kV substation transformer'
)
# JSON-LD representation using repr()
print("JSON-LD representation (repr):")
print(repr(transformer))
# Pretty print (formatted JSON-LD)
print("\\nFormatted JSON-LD (pprint):")
transformer.pprint()
# Convert to dictionary
print("\\nDictionary representation:")
obj_dict = transformer.__dict__()
print(json.dumps(obj_dict, indent=2))
# Alternative: Use URI strings for associations
# This is more efficient for large models
line2 = cim.ACLineSegment(
mRID='_line-002',
name='Feeder_Line_2'
)
term3 = cim.Terminal(
mRID='_term-003',
name='Line_Terminal_3',
sequenceNumber=1,
ConductingEquipment=line2.uri() # Use URI string instead of object reference
)
# Add terminal to line using URI
line2.Terminals = [term3.uri()]
print("Line created with URI-based associations:")
line2.pprint()
print("\\nTerminal 3:")
term3.pprint()
# Create a line segment
line = cim.ACLineSegment(
mRID='_line-001',
name='Feeder_Line_1'
)
# Create terminals for the line (one at each end)
term1 = cim.Terminal(
mRID='_term-001',
name='Line_Terminal_1',
sequenceNumber=1
)
term2 = cim.Terminal(
mRID='_term-002',
name='Line_Terminal_2',
sequenceNumber=2
)
# Associate terminals with the line using object references
line.Terminals = [term1, term2]
# Associate line with terminals (inverse relationship)
term1.ConductingEquipment = line
term2.ConductingEquipment = line
print("Line created with terminals:")
line.pprint()
print("\\nTerminal 1:")
term1.pprint()
Working with Associations¶
Associations represent relationships between CIM objects. In CIMantic Graphs, associations can reference objects in two ways:
- By URI string - Using the object's UUID string
- By object reference - Using the actual Python object
Both approaches are supported. Using URI strings is more efficient for large models, while object references are more convenient for small, in-memory graphs.
# Additional examples of attribute utilities
from cimgraph.validators.attribute_utils import (
get_attr_datatype,
get_attr_uml_type,
get_attr_field_type,
get_attr_inverse
)
# Inspect a simple attribute
print("Attribute inspection for ACLineSegment.name:")
print(f" Field type: {get_attr_field_type(cim_class=cim.ACLineSegment, attribute='name')}")
print(f" UML type: {get_attr_uml_type(cim_class=cim.ACLineSegment, attribute='name')}")
print(f" Datatype: {get_attr_datatype(cim_class=cim.ACLineSegment, attribute='name')}")
print("\\nAssociation inspection for Terminal.Measurements:")
field_type = get_attr_field_type(cim_class=cim.Terminal, attribute='Measurements')
uml_type = get_attr_uml_type(cim_class=cim.Terminal, attribute='Measurements')
datatype = get_attr_datatype(cim_class=cim.Terminal, attribute='Measurements')
inverse = get_attr_inverse(cim_class=cim.Terminal, attribute='Measurements')
print(f" Field type: {field_type}") # list
print(f" UML type: {uml_type}") # Association
print(f" Datatype: {datatype}") # ['Measurement']
print(f" Inverse: {inverse}") # Measurement.Terminal
# Create an object with a specific mRID
# When mRID is provided, UUID is deterministically generated from it
line2 = cim.ACLineSegment(
mRID='_12345678-1234-1234-1234-123456789abc',
name='Line_002',
description='Main feeder line from substation A to load center B'
)
print("\\nObject created with specific mRID:")
line2.pprint()
# Access the URI
print(f"\\nObject URI: {line2.uri()}")
print(f"Object mRID: {line2.mRID}")
print(f"Object name: {line2.name}")
# Import a CIM profile
import cimgraph.data_profile.cim17v40 as cim
# Create a simple object without mRID (UUID auto-generated)
line1 = cim.ACLineSegment(name='Line_001')
print("Object created with auto-generated UUID:")
line1.pprint()
classDiagram
class Identity{
+ identifier: uuid
# \_\_uuid\_\_: class UUID_Meta
# \_\_json_ld\_\_: str~repr~
+ pprint()
+ uri() str~UUID~
~ uuid(mRID,name,str)
# \_\_repr\_\_() str~JSON-LD~
# \_\_dict\_\_() dict
# \_\_str\_\_() str~dict~
}
from cimgraph.validators.attribute_utils import get_attr_datatype, get_attr_uml_type, get_attr_field_type, get_attr_inverse
field_type = get_attr_field_type(cim_class = cim.Terminal, attribute='Measurements') # field = 'list'
uml_type = get_attr_uml_type(cim_class=cim.Terminal, attribute='Measurements') # uml_type = 'Association'
datatype = get_attr_datatype(cim_class=cim.Terminal, attribute='Measurements') # datatype = ['Measurement']
inverse = get_attr_inverse(cim_class=cim.Terminal, attribute='Measurements') # inverse = 'Measurement.Terminal'