Iterate and Transform

The SDK provides various ways how you can loop through the elements of the model, and how these elements can be transformed. Each following section will look into one of the approaches.

over_X_or_empty

For all the optional lists, there is a corresponding over_{property name}_or_empty getter. It gives you an Iterator. If the property is not set, this getter will yield empty. Otherwise, it will yield from the actual property value.

For example, see aas_core3.types.Environment.over_submodels_or_empty().

descend_once and descend

If you are writing a simple script and do not care about the performance, the SDK provides two methods in the most general interface aas_core3.types.Class, descend_once() and descend(), which you can use to loop through the instances.

Both descend_once() and descend() iterate over referenced children of an instance of Class. descend_once(), as it names suggests, stops after all the children has been iterated over. descend() continues recursively to grand-children, grand-grand-children etc.

Here is a short example how you can get all the properties from an environment whose ID-short starts with another:

import aas_core3.types as aas_types

# Prepare the environment
environment = aas_types.Environment(
    submodels=[
        aas_types.Submodel(
            id="some-unique-global-identifier",
            submodel_elements=[
                aas_types.Property(
                    id_short="some_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1984"
                ),
                aas_types.Property(
                    id_short="another_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1985"
                ),
                aas_types.Property(
                    id_short="yet_another_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1986"
                )
            ]
        )
    ]
)

for something in environment.descend():
    if (
        isinstance(something, aas_types.Property)
        and "another" in something.id_short
    ):
        print(something.id_short)
another_property
yet_another_property

Iteration with descend_once() and descend() works well if the performance is irrelevant. However, if the performance matters, this is not a good approach. First, all the children will be visited (even though you need only a small subset). Second, you need to switch with isinstance() on the runtime type, which grows linearly in computational cost with the number of types you switch on.

Let’s see in the next section how we could use a more efficient, but also a more complex approach.

Visitor

Visitor pattern is a common design pattern in software engineering. We will not explain the details of the pattern here as you can read about in the ample literature in books or in Internet.

The cornerstone of the visitor pattern is double dispatch: instead of casting to the desired type during the iteration, the method aas_core3.types.Class.accept() directly dispatches to the appropriate visitation method.

This allows us to spare runtime type switches and directly dispatch the execution. The SDK already implements accept() methods, so you only have to implement the visitor.

The visitor class has a visiting method for each class of the meta-model. In the SDK, we provide different flavors of the visitor abstract classes which you can readily implement:

Let us re-write the above example related to descend() method with a visitor pattern:

import aas_core3.types as aas_types

class Visitor(aas_types.PassThroughVisitor):
    def visit_property(self, that: aas_types.Property):
        if "another" in that.id_short:
            print(that.id_short)

# Prepare the environment
environment = aas_types.Environment(
    submodels=[
        aas_types.Submodel(
            id="some-unique-global-identifier",
            submodel_elements=[
                aas_types.Property(
                    id_short="some_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1984"
                ),
                aas_types.Property(
                    id_short="another_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1985"
                ),
                aas_types.Property(
                    id_short="yet_another_property",
                    value_type=aas_types.DataTypeDefXSD.INT,
                    value="1986"
                )
            ]
        )
    ]
)

# Iterate
visitor = Visitor()
visitor.visit(environment)

Expected output:

another_property
yet_another_property

There are important differences to iteration with descend():

  • Due to double dispatch, we spare a cast. This is usually more efficient.

  • The iteration logic in descend() lives very close to where it is executed. In contrast, the visitor needs to be defined as a separate class. While sometimes faster, writing the visitor makes the code less readable.

Descend or Visitor?

In general, people familiar with the visitor pattern and object-oriented programming will prefer, obviously, visitor class. People who like functional programming, generator expressions and ilks will prefer descend().

It is difficult to discuss different tastes, so you should probably come up with explicit code guidelines in your code and stick to them.

Make sure you always profile before you sacrifice readability and blindly apply one or the other approach for performance reasons.

Transformer

A transformer pattern is an analogous to visitor pattern, where we “transform” the visited element into some other form (be it a string or a different object). It is very common in compiler design, where the abstract syntax tree is transformed into a different representation.

The SDK provides different flavors of a transformer:

Usually you implement for each concrete class how it should be transformed. If you want to specify only a subset of transformations, and provide the default value for the remainder, the SDK provides TransformerWithDefault and TransformerWithDefaultAndContext.

We deliberately omit an example due to the length of the code. Please let us know by creating an issue if you would like to have an example here.