This notebook introduces model.find() and model.get() for navigating model elements by qualified name; after running it you can inspect any named element in the cumulative model directly.
Chapter 5 shifts from defining the model to inspecting and extending it. The cumulative model now carries dozens of named elements: part definitions, part usages, ports, an allocation, a requirement, and item definitions. Navigating by position in a query result is fragile; navigating by qualified name (package::element) is stable as the model grows.
model.find(name) returns a Symbol or None for a short or fully-qualified name. model.get(fqn) returns a Symbol and raises if the name is absent. Together they are the navigation layer the rest of this chapter, and Chapter 6, build on.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
source = Path("../../models/ch05-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"The containment skeleton of everything built so far, before the chapter adds a port and an allocation. The one solid, hollow-triangle edge is Toaster’s own specialization of ToastingSystem (Chapter 1), not containment.
from toaster.render import model_to_dot, render_dot
from IPython.display import SVG
out_path = Path("../../figures/ch05-structure.svg")
out_path.parent.mkdir(exist_ok=True)
dot = model_to_dot(model, title="Ch5")
render_dot(dot, out_path)
SVG(filename=str(out_path))model.find() and model.get() only resolve elements that parsed. The negative control below loads a model with an unresolved specialization and confirms it fails before navigation is attempted.
bad_source = """
package BadNav {
private import ScalarValues::*;
part def Probe :> UndefinedBase;
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print(f"Neg control diagnostics: {bad.diagnostics[0].message!r}")Neg control diagnostics: 'unresolved reference: UndefinedBase'
The diagnostic reports an unresolved reference: UndefinedBase is not a name in scope, so the model fails to load before any navigation can run.
ApplyHeat was already drawn in Chapter 4’s own action-flow diagram, nested inside ToastBread; the containment diagram above shows parts, not actions.
heating = model.find("ToasterDemo::HeatingSystem")
print(f"find result: id={heating.id!r}, kind={heating.kind!r}")
apply_heat = model.get("ToasterDemo::ApplyHeat")
print(f"get result: id={apply_heat.id!r}, kind={apply_heat.kind!r}")
missing = model.find("ToasterDemo::Nonexistent")
assert missing is None
print(f"missing element: {missing}")find result: id='ToasterDemo::HeatingSystem', kind='partDef'
get result: id='ToasterDemo::ApplyHeat', kind='actionDef'
missing element: None
The qualified names printed above resolve to the same HeatingSystem and ApplyHeat declared in models/ch05-cumulative.sysml, confirmed by the Symbol each call returns.
Try the chapter exercise in exercises/ch05/exercise.ipynb: use model.find() to navigate to CoffeeDemo::CoffeeFlow after building the assembly, then confirm model.find() returns None for an element that does not exist.