Query API

New in v0.11.0

syside.query answers questions about a loaded model in one call each: which element has this name, which parts specialize this definition, what does this element contain, which constraints does this instance violate. It sits on top of the syside element classes you already use, so a script can adopt it one function at a time; nothing it returns needs a lock, a callback or a cast.

Two companion packages arrived with it. syside.evaluation checks the assert constraint statements in a model against the instances that bind values, and syside.testing turns modelling rules into pytest tests whose failure messages name the offending elements.

Try it in five minutes

Given the model from the extract parts example, listing every part that is Electrical takes a lookup and a filter:

def show_parts_of_type(model: syside.Model, part_type: str) -> None:
    # Look the definition up once, then ask each part whether it
    # specializes it. `specializes` follows the whole heritage chain, so
    # a part typed by a subtype of `part_type` is found as well.
    definition = query.find_by_name_and_type(
        model, part_type, syside.PartDefinition
    )
    assert definition is not None, f"no part definition named {part_type}"
    for part in query.find_elements_of_type(model, syside.PartUsage):
        if query.specializes(part, definition):
            print("- ", part.name)

find_by_name_and_type returns the definition or None, already typed as a PartDefinition; find_elements_of_type returns a list of every part usage in the model, library elements excluded; and specializes follows the whole heritage chain, so a part typed by a subtype of Electrical is found too. The same function written against the element classes directly walks model.nodes, iterates part.heritage.elements, and compares declared_name on each; see it side by side in Rewrite a script with syside.query.

Why use it

  • Every result is a plain Python value. A list, set, bool, scalar or None, never a LazyIterator to drain or a callback to pass to for_each. Results can be indexed, sliced, passed to len() and iterated more than once.

  • One name per question. contents, containers, features_of, redefines, variants: the function is named for what you want, not for the accessor that happens to hold it.

  • Repeated lookups stay cheap. Build a NameIndex, TypeIndex or MetadataIndex once and pass it as index= to make each later lookup a dictionary read. An index changes a query’s cost, never its result.

  • Read-only by construction. No function in syside.query modifies the model, so a report or a check written with it cannot corrupt what it reads.

The element classes remain the way to create and edit a model; syside.query does not replace them, it replaces the loops you wrote around them.

Where to go next

The complete list of functions, with the cost of each, is in the API reference.