How to access inherited attributes

I have a part def where an attribute is defined, and then in a part usage I want to access the value of the attribute which I expect to be inherited.

part def MyPartDef {

    attribute attr1 = 1;

    attribute attr2 = 2;

    attribute attrSum = attr1 + attr2;

}



part myPartUsage : MyPartDef;

In automator I’ve got this script:

def get_node(model: syside.Model, qualified_name: Sequence[str]) -> syside.Element:
    # Using the function provided in syside_helpers.py in the state machine example

def get_attribute_value(node: syside.AttributeUsage):
    return syside.Compiler().evaluate(node.feature_value_expression)[0]


print(get_node(model, ['MyPartDef','attrSum']))
print(get_attribute_value(get_node(model, ['MyPartDef','attrSum'])))
print(get_node(model, ['myPartUsage']))
print(get_node(model, ['myPartUsage','attr1']))

The output becomes:

MyPartDef::attrSum
3
myPartUsage
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)

...

File XXXXXXX, in _get_node_by_qualified_name(node, qualified_name)
     31 for segment in qualified_name:
     32     assert isinstance(current_node, syside.Namespace)
---> 33     current_node = current_node[segment]
     34 return current_node

KeyError: "No member named 'attr1' exists in this namespace"

Surely it is not an incorrect expectation that the attributes in the defintion would be inherited by the usages? Please advise.

Hi @Axel,

Namespace.__getitem__ is intended to only return direct members which can be done cheaply, as opposed to an expensive graph search walking the inheritance and import graph. Inherited and imported members are not inlined to keep memory usage down. At the moment you will have to walk the inheritance graph manually:

queue: list[syside.Namespace] = [node]
visited: set[syside.Namespace] = set()
while queue:
    element = queue.pop(0)
    if isinstance(element, syside.Feature):
        element = element.feature_target
    if element in visited:
        continue
    visited.add(element)
    child = element.get_member(name)
    if child is not None:
        return child
    if isinstance(element, syside.Type):
        queue[0:0] = element.heritage.elements
return None

High-level API is a work-in-progress.

Hi @Daumantas, thanks for your reply.

I am able to use the code you provided to retrieve the properties of the part definition. Wrapping it in a function get_namespace_member() I am able to do this:

part = get_node(model, ["myPartUsage"])

attr1 = get_namespace_member(part, "attr1")
attr2 = get_namespace_member(part, "attr2")
attrSum = get_namespace_member(part, "attrSum")

print(attr1, "=", get_attribute_value(attr1))
print(attr2, "=", get_attribute_value(attr2))
print(attrSum, "=", get_attribute_value(attrSum))

Which prints this:

MyPartDef::attr1 = 1
MyPartDef::attr2 = 2
MyPartDef::attrSum = 3

Now I tried to mix things up a bit by redefining the value of attr1, in the hope that attrSum would calculate a new value. For me, this is the reason behind this whole exercise: to define a calculated attribute in a part definition, and be able to re-use that calculation in the part usages.

I changed the sysml snippet to this:

part def MyPartDef {
    attribute attr1 default 1;
    attribute attr2 = 2;
    attribute attrSum = attr1 + attr2;
}

part myPartUsage : MyPartDef {
    attribute redefines attr1 = 10;
}

And now when I run the same python code as above, the printout is instead this:

myPartUsage::attr1 = 10
MyPartDef::attr2 = 2
MyPartDef::attrSum = 3

As you see, the value of attrSum is not changing to 12 as I had hoped to see. Is there some different to accomplish this? Or am I running into a limitation of the current implementation of automator?

There is a Compiler.evaluate_feature method that will do what you would expect by taking redefinitions into account:

result, report = compiler.evaluate_feature(
    feature=feature, scope=owning_type
)

Here, feature can be MyPartDef::attrSum and owning_type - myPartUsage, and it will correctly evaluate attrSum to 12.

Summary
import syside

SRC = """\
part def MyPartDef {
    attribute attr1 default 1;
    attribute attr2 = 2;
    attribute attrSum = attr1 + attr2;
}

part myPartUsage : MyPartDef {
    attribute redefines attr1 = 10;
}
"""

model, _ = syside.load_model(sysml_source=SRC)
with model.user_docs[0].lock() as doc:
    pass

compiler = syside.Compiler()
attr1 = (
    doc.root_node["MyPartDef"]
    .cast(syside.PartDefinition)["attrSum"]
    .cast(syside.AttributeUsage)
)
usage = doc.root_node["myPartUsage"].cast(syside.PartUsage)

result, report = compiler.evaluate_feature(attr1, scope=usage)

assert not report.fatal, str(report.diagnostics)
print(result) # prints 12

Greatly appreciated @Daumantas, syside.Compiler().evaluate_feature() was precisely what I needed in this case.

This brings me to the next step of what I am trying to accomplish: using automator, I want to change the value of an inherited attribute. So if my model is like this:

part def MyPartDef {
    attribute attr1 default 1;
    attribute attr2 default 2;
    attribute attrSum = attr1 + attr2;
}

part myPartUsage : MyPartDef {
    attribute redefines attr1 = 10;
}

I want to use automator to change the value of myPartUsage.attr2, for example to 50. I know how change the value of an attribute usage in this way:

value_node = node.feature_value_member.set_member_element(syside.LiteralInteger)[1]
value_node.value = 50

But when I try this on the inherited attribute, as accessed in the way you showed earlier, with my get_namespace_member() function, the value that gets changed in the attribute in the definition instead of the usage: MyPartDef.attr2

Is it possible to edit the value of an inherited attribute in this way? Are you required to redefine the attribute with a line like attribute redefines attr2 in the part usage in order to be able to change the value only for that usage?

You will need to create an attribute with the required redefinition:

_, attribute = my_part_usage.children.append(
    syside.FeatureMembership,
    syside.AttributeUsage
)
attribute.heritage.append(syside.Redefinition, attr2)

You can then change it’s value as before.

1 Like