This notebook introduces attribute :>> override; after running it you can override an inherited attribute value on a named usage.
The previous notebook declared that the toaster must complete a cycle in at most 180 seconds, and added nominal, a usage of Toaster with no attribute values set. This notebook introduces attribute :>> override: a part usage can redeclare an inherited attribute with a specific value. We use it to build slow: not a design variant or an operating condition, but a fixture whose cycle time is a deliberately injected fault value, built to exercise the requirement’s failing branch.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
# editor.add_part(owner='ToasterDemo', name='slow', type='Toaster') when API ships
SLOW_PART = "part slow : Toaster {"
print(SLOW_PART)part slow : Toaster {
slow is a named usage of Toaster, built to fail TimelyToast’s check. The open brace introduces a body where the inherited attribute will be overridden with a deliberately injected fault value.
# editor.add_attribute_override(owner='ToasterDemo::slow', name='cycleTime', ...) when API ships
# attribute :>> redefinition: the Editor API does not yet author it (https://github.com/Open-MBEE/toaster/issues/10 / https://github.com/Open-MBEE/OpenSysML/issues/596);
# it parses and loads correctly via conn.load_from_content(), confirmed below.
# spec: KerML formal/2026-03-02 §8.3.7 (FeatureChaining, anonymous redefinition)
CYCLE_OVERRIDE = " attribute :>> cycleTime = 200.0 [SI::s];"
print(CYCLE_OVERRIDE) attribute :>> cycleTime = 200.0 [SI::s];
attribute :>> cycleTime redeclares the inherited cycleTime under slow. The :>> operator is a redefinition; it can only name an attribute that already exists in the type chain. The value it is bound to, 200.0 [SI::s], is a separate matter: writing = value with no default keyword gives a fixed binding, while default = value (which Toaster::cycleTime no longer has, since Chapter 1) would bind a value that a further usage could still override. slow’s 200 seconds is deliberately fixed and deliberately faulty: chosen to exceed TimelyToast’s 180-second bound, not to represent a plausible design point.
TOASTER_INCREMENT = f"{SLOW_PART}\n{CYCLE_OVERRIDE}\n}}"
source = Path("../../models/ch02-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"slow loads with its overridden cycleTime and no diagnostics. The next cell checks what :>> does when there is no inherited attribute to override.
# Negative control: :>> can only override an attribute that already
# exists in the inherited chain. Overriding a non-existent name fails.
bad_source = """
package Bad {
private import ScalarValues::*;
part def Toaster { attribute cycleTime : Real default = 120.0; }
part slow : Toaster {
attribute :>> nonExistent = 200.0;
}
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print("Expected error:", bad.diagnostics[0].message)Expected error: unresolved reference: nonExistent
With the negative control confirmed, the next cell looks up both nominal and slow in the loaded model.
nominal = model.find("ToasterDemo::nominal")
slow = model.find("ToasterDemo::slow")
assert nominal is not None
assert slow is not None
print("nominal:", nominal.id, "| kind:", nominal.kind)
print("slow :", slow.id, "| kind:", slow.kind)
slow_attrs = slow.attributes()
print(f"slow overridden attributes ({len(slow_attrs)}):")
for a in slow_attrs:
print(f" {a.id}")
conn.close()nominal: ToasterDemo::nominal | kind: partUsage
slow : ToasterDemo::slow | kind: partUsage
slow overridden attributes (1):
ToasterDemo::slow::cycleTime
The slow fragments printed above loaded without error, and slow.attributes() confirms the override is now part of the model.
Try the chapter exercise in exercises/ch02/exercise.ipynb: create a hot usage of your BrewUnit with an overridden brewTemp and confirm the override loads.