query

syside.query – read-only queries over a loaded model.

Use this package to ask questions about a model that syside.load_model() has produced: find elements by name or metadata, walk generalization and containment hierarchies, and inspect features, connections, and variation points. No function here modifies the model.

Three kinds of names are exported:

Every function returns a plain, fully materialized value – a list, set, bool, scalar, or None – never a lazy view of the model.

Index

Classes

MetadataIndex

A metadata lookup over a model, built once by build_metadata_index().

MultiplicityBounds

The resolved multiplicity bounds of a feature.

NameIndex

A name -> elements lookup over a model, built once by build_name_index().

TypeIndex

A concrete-type -> elements lookup, built once by build_type_index().

Unbounded

The unbounded (*) upper multiplicity bound: a top element ordered above every int.

Attributes

UNBOUNDED

R

The singleton Unbounded upper bound (*).

Functions

actors

Return the actor parameters of element, linear in the number of actors.

containers

Yield the owner chain above element, nearest first.

annotated_elements

Return the elements metadata_usage annotates, linear in the number of annotated elements.

build_metadata_index

Build a MetadataIndex for model in one pass, linear in model size.

build_name_index

Build a NameIndex for model in one pass, linear in model size.

build_type_index

Build a TypeIndex for model in one pass, linear in model size.

contains

Return whether container contains element (at any depth).

contents

Return the direct contents (owned elements) of element, linear in the number of owned elements.

connection_ends

Return the end features of connection (its connector_ends) as a list.

bindings_of

Return the binding connectors touching element and the bound counterpart.

cross_subsets

Return the features feature cross-subsets (via cross subsetting), linear in the number of cross-subsetted features.

all_contents

Yield every element below element in ownership, breadth-first.

description

Return the concatenated body text of element’s documentation, or None.

types_of

Return the types that directly type feature (its types), linear in the number of declared types.

documentation

Return the documentation elements owned by element, linear in the number of documentation elements.

enumeration_values

Return the enumerated values of enum_def, linear in the number of values.

exposed_elements

Return the elements exposed by view (its exposed_elements), linear in the number of exposed elements.

feature_chain

Return the chaining features of feature (the feature-chain links), linear in the chain length.

featured_in

Return the types that feature is featured by (its featuring contexts).

features_of

Return all features of type_ (owned and inherited), linear in the feature count.

find_by_name

Find an element named name within scope.

find_by_name_and_type

Find the first model element of type element_type named name.

find_by_path

Resolve path (a list of simple names) starting from root.

find_by_qualified_name

Find the element whose rendered qualified name is exactly qualified_name.

find_elements_of_type

Return every model element of type element_type.

find_metadata_usages

Find metadata usages within scope, filtered by name or type.

find_relationships

Return every relationship of type relationship_type in the model.

has_metadata_prefix

Return whether element carries a metadata prefix named prefix_name.

incoming_connections

Return the connectors that have element among their target features.

inherited_features

Return the features type_ inherits from its supertypes, linear in the inherited-feature count.

is_contained_in

Return whether element is nested anywhere inside container.

metadata_prefixes

Return element’s prefix metadata usages (the #Name prefixes), linear in the number of prefixes.

metadata_usages

Return all metadata usages owned by element (prefix and body), linear in the number of metadata usages.

multiplicity_bounds

Return the resolved multiplicity bounds of feature, or None.

outgoing_connections

Return the connectors whose source feature is element.

generalization_names

Return the rendered qualified names of the direct heritage targets.

redefined_by

Return the features that redefine feature (inverse of redefinition).

redefines

Return the features feature redefines (via redefinition), linear in the number of redefined features.

referenced_feature

Return the feature referenced by expression.

siblings

Return the other direct contents of element’s owner.

specializes

Return whether type_ specializes candidate_supertype (transitively).

direct_supertypes

Return the types type_ subclassifies (via subclassification), linear in the number of supertypes.

subjects

Return the subject parameter(s) of element, constant time.

subsets

Return the features feature subsets (via subsetting heritage), linear in the number of subsetted features.

subtypes

Return the direct subtypes of type_ (the inverse of subclassification).

supertypes

Return the transitive closure of type_’s heritage targets (types).

textual_representation

Return the body of element’s textual representation in language.

variants

Return the variant usages of a variation variation, linear in the number of variants.

specializations

Return the elements declaring a heritage relationship to target.

generalizations

Return the heritage targets declared by element.

walk_containment

Walk the ownership tree from element, yielding the elements reached.


Attributes

UNBOUNDED: Final = 'Unbounded(...)'

The singleton Unbounded upper bound (*).

Functions

actors(element: syside.query._CaseLike) list[syside.PartUsage]

Return the actor parameters of element, linear in the number of actors.

Reads the core actor_parameters accessor, present on requirement and case usages/definitions (and their subtypes – use-case, verification-case, analysis-case, …).

containers(element: syside.Element) Iterator[syside.Element]

Yield the owner chain above element, nearest first.

annotated_elements(metadata_usage: syside.MetadataUsage) list[syside.Element]

Return the elements metadata_usage annotates, linear in the number of annotated elements.

build_metadata_index(model: syside.Model) syside.query.MetadataIndex

Build a MetadataIndex for model in one pass, linear in model size.

Scans every syside.MetadataUsage once, recording it and, when its metadata_definition resolves to a named definition, indexing it under that definition’s simple name.

build_name_index(model: syside.Model) syside.query.NameIndex

Build a NameIndex for model in one pass, linear in model size.

Scans model.nodes(syside.Element, include_subtypes=True) once, recording each element under its rendered qualified_name (when it has one – the first element to claim a qualified name wins, matching core semantics) and appending it to the per-simple-name bucket (when it has a name).

build_type_index(model: syside.Model) syside.query.TypeIndex

Build a TypeIndex for model in one pass, linear in model size.

Scans every element once and buckets it under its exact runtime class.

contains(container: syside.Element, element: syside.Element) bool

Return whether container contains element (at any depth).

The inverse reading of is_contained_in(): contains(a, b) == is_contained_in(b, a).

contents(element: syside.Element) list[syside.Element]

Return the direct contents (owned elements) of element, linear in the number of owned elements.

connection_ends(connection: syside.ConnectionUsage) list[syside.Feature]

Return the end features of connection (its connector_ends) as a list.

connector_ends is a syside.LazyIterator. .collect() drains it into a Python list in a single O(n) pass over the n ends, which is what you want when you need every end materialized — to index it, take its length, hold it, or walk it more than once. (Most connections have only two ends, so the cost is negligible here; the guidance matters for the larger lazy collections these query helpers also wrap.)

.for_each(callback) on the underlying connection.connector_ends is the alternative, but it is worth it only when the callback can stop early: it allocates no list and short-circuits when the callback returns a stop signal, yet it pays a Python call per element where .collect() builds the list in native code. For a full drain it is therefore the slower of the two — measured 561 us against 392 us for .collect() at n=4000. Prefer .collect() unless you genuinely short-circuit, and expect the crossover only on large collections.

Do not read the ends back with repeated .at(i) / [i] indexing in a loop. A LazyIterator re-seeks from the head on each index, so an index-per-element loop is O(n²) — measured ~67x slower than .collect() at n=4000 (74 ms vs 1.1 ms). A LazyIterator is not a Python iterable, so .collect() or .for_each() is the only correct way to walk all ends.

bindings_of(element: syside.Feature) list[tuple[syside.ConnectorAsUsage, syside.Feature]]

Return the binding connectors touching element and the bound counterpart.

For each syside.BindingConnectorAsUsage in element’s owner subtree whose related features include element, returns a (connector, other) pair where other is the related feature that is not element. A binding both of whose ends are element contributes a pair with other == element.

cross_subsets(feature: syside.Feature) list[syside.Feature]

Return the features feature cross-subsets (via cross subsetting), linear in the number of cross-subsetted features.

Cross subsetting is the connection/association-end crosses (=>) relationship; its target is the cross feature, typically a feature chain (the other end followed by that end’s cross feature). An ordinary feature yields [].

all_contents(element: syside.Element) Iterator[syside.Element]

Yield every element below element in ownership, breadth-first.

Backed by walk_containment(); the walk drains each owned_elements iterator once and de-duplicates by identity.

description(element: syside.Element) str | None

Return the concatenated body text of element’s documentation, or None.

The bodies of every syside.Documentation owned by element are joined, in owned order, with a blank line between them – matching the way a VS Code hover concatenates multiple doc comments rather than showing only the first. None if element has no documentation.

types_of(feature: syside.Feature) list[syside.Type]

Return the types that directly type feature (its types), linear in the number of declared types.

documentation(element: syside.Element) list[syside.Documentation]

Return the documentation elements owned by element, linear in the number of documentation elements.

enumeration_values(enum_def: syside.EnumerationDefinition) list[syside.EnumerationUsage]

Return the enumerated values of enum_def, linear in the number of values.

exposed_elements(view: syside.ViewUsage) list[syside.Element]

Return the elements exposed by view (its exposed_elements), linear in the number of exposed elements.

feature_chain(feature: syside.Feature) list[syside.Feature]

Return the chaining features of feature (the feature-chain links), linear in the chain length.

A feature that is not a chain yields [].

featured_in(feature: syside.Feature) list[syside.Type]

Return the types that feature is featured by (its featuring contexts).

Backed by the direct Feature.featuring_types accessor (linear in the number of featuring types). Unlike the other inverse queries this needs no model: type featuring is a forward accessor on the feature, not an inverse model scan.

features_of(type_: syside.Type) list[syside.Feature]

Return all features of type_ (owned and inherited), linear in the feature count.

find_by_name(scope: syside.Element, name: str, *, recursive: bool = False) syside.Element | None

Find an element named name within scope.

With recursive=False (the default) only the direct members of scope are searched, via the constant-time Namespace.get_member accessor. A non-syside.Namespace scope has no members and yields None.

With recursive=True the whole subtree below scope is searched and the first element (breadth-first) whose simple name equals name is returned, or None if none matches.

find_by_name_and_type(model: syside.Model, name: str, element_type: type[syside.query.find_by_name_and_type.ElementT], *, index: syside.query.NameIndex | None = None) syside.query.find_by_name_and_type.ElementT | None

Find the first model element of type element_type named name.

Searches the whole model and returns the first (scan-order) element whose simple name equals name and which is an instance of element_type (subtypes included), or None. With an index this is a single name-bucket read; without one it is a model.nodes scan, linear in model size, over element_type.

find_by_path(root: syside.Element, path: list[str]) syside.Element | None

Resolve path (a list of simple names) starting from root.

Each segment is looked up among the direct members of the current element with the constant-time Namespace.get_member accessor. Returns the element reached by the final segment, or None if any segment does not resolve or a non-namespace is reached mid-path. An empty path returns root.

find_by_qualified_name(model: syside.Model, qualified_name: str, *, index: syside.query.NameIndex | None = None) syside.Element | None

Find the element whose rendered qualified name is exactly qualified_name.

With an index this is a single dict read. Without one it walks the qualified name segment by segment from each document root using the constant-time Namespace.get_member accessor (never a per-element path scan), returning the first match or None.

find_elements_of_type(model: syside.Model, element_type: type[syside.query.find_elements_of_type.ElementT], *, include_subtypes: bool = True, exclude_stdlib: bool = True, index: syside.query.TypeIndex | None = None) list[syside.query.find_elements_of_type.ElementT]

Return every model element of type element_type.

With include_subtypes=True (the default) instances of subclasses are included. With exclude_stdlib=True (the default) library elements are dropped (model.nodes already scopes to the user model, so this only matters for the rare library element reachable through it).

With a TypeIndex this unions the relevant exact-type buckets; without one it is a single model.nodes scan, linear in model size.

find_metadata_usages(scope: syside.Element, *, name: str | None = None, metadata_type: type[syside.Element] | None = None, index: syside.query.MetadataIndex | None = None) list[syside.Feature]

Find metadata usages within scope, filtered by name or type.

Exactly one of name or metadata_type must be given. name matches the simple name of a usage’s resolved metadata_definition (its metaclass); metadata_type matches usages whose metadata_definition is an instance of that type.

The search is over the subtree below scope (scope included), in scan order. An index (built with build_metadata_index()) is a model-wide pool of usages; it is restricted to scope before filtering, so the result is identical to the unindexed subtree walk for any scope, not only the model root. It pays off when scope is large or the lookup is repeated.

find_relationships(model: syside.Model, relationship_type: type[syside.Relationship], *, exclude_subtypes_of: type[syside.Relationship] | None = None, index: syside.query.TypeIndex | None = None) list[syside.Relationship]

Return every relationship of type relationship_type in the model.

Subtypes of relationship_type are included. exclude_subtypes_of, if given, drops any relationship that is also an instance of that type (and its subtypes) – e.g. relationship_type=Specialization, exclude_subtypes_of=Subsetting keeps specializations that are not subsettings. The two are conjunctive (ANDed): a relationship is returned only when it is an instance of relationship_type and not an instance of exclude_subtypes_of (or exclude_subtypes_of is None).

With a TypeIndex this unions the matching exact-type buckets; without one it is a single model.nodes scan, linear in model size.

has_metadata_prefix(element: syside.Namespace, prefix_name: str) bool

Return whether element carries a metadata prefix named prefix_name.

Matches prefix_name against the simple name of each prefix usage’s resolved metadata_definition (its metaclass).

incoming_connections(element: syside.Feature) list[syside.ConnectorAsUsage]

Return the connectors that have element among their target features.

Searches the connectors owned within element’s owner subtree (the scope a connector and its ends share) and keeps those whose target_features include element. Returns the connectors in scan order.

inherited_features(type_: syside.Type) list[syside.Feature]

Return the features type_ inherits from its supertypes, linear in the inherited-feature count.

is_contained_in(element: syside.Element, container: syside.Element) bool

Return whether element is nested anywhere inside container.

True when container is element’s owner, its owner’s owner, and so on – i.e. element appears (at any depth) within container’s braces.

metadata_prefixes(element: syside.Namespace) list[syside.MetadataUsage]

Return element’s prefix metadata usages (the #Name prefixes), linear in the number of prefixes.

The core prefixes view yields (membership, feature) pairs; only syside.MetadataUsage features are returned.

metadata_usages(element: syside.Element) list[syside.MetadataUsage]

Return all metadata usages owned by element (prefix and body), linear in the number of metadata usages.

Reads the core metadata view and keeps the syside.MetadataUsage entries.

multiplicity_bounds(feature: syside.Feature) syside.query.MultiplicityBounds | None

Return the resolved multiplicity bounds of feature, or None.

None if feature declares no multiplicity, or if its multiplicity is not a syside.MultiplicityRange with computed bounds. The core cached_upper_bound is an exclusive end, so the inclusive upper bound returned here is cached_upper_bound - 1 (a None core bound becomes UNBOUNDED, an unbounded *). The lower bound is taken as-is.

outgoing_connections(element: syside.Feature) list[syside.ConnectorAsUsage]

Return the connectors whose source feature is element.

Searches the connectors owned within element’s owner subtree and keeps those whose source_feature is element. Returns them in scan order.

generalization_names(type_: syside.Type) list[str]

Return the rendered qualified names of the direct heritage targets.

Unnamed targets contribute their str(qualified_name) ("None") only if they have one; targets with no qualified name are skipped.

redefined_by(model: syside.Model, feature: syside.Feature) list[syside.Feature]

Return the features that redefine feature (inverse of redefinition).

Linear in model size: scans the model’s redefinition relationships via specializations().

redefines(feature: syside.Feature) list[syside.Feature]

Return the features feature redefines (via redefinition), linear in the number of redefined features.

referenced_feature(expression: syside.FeatureReferenceExpression) syside.Feature

Return the feature referenced by expression.

Raises ValueError if the reference is unresolved (the core referent is None), so the return type is a non-optional syside.Feature.

siblings(element: syside.Element) list[syside.Element]

Return the other direct contents of element’s owner.

element itself is excluded. An element with no owner has no siblings.

specializes(type_: syside.Type, candidate_supertype: syside.Type) bool

Return whether type_ specializes candidate_supertype (transitively).

True iff candidate_supertype appears in the transitive heritage targets of type_.

direct_supertypes(type_: syside.Type) list[syside.Type]

Return the types type_ subclassifies (via subclassification), linear in the number of supertypes.

subjects(element: syside.query._CaseLike) list[syside.Usage]

Return the subject parameter(s) of element, constant time.

Reads the core subject_parameter accessor, present on requirement and case usages/definitions (and their subtypes). The accessor is a single optional parameter, so the result is a list of zero or one element.

subsets(feature: syside.Feature) list[syside.Feature]

Return the features feature subsets (via subsetting heritage), linear in the number of subsetted features.

subtypes(model: syside.Model, type_: syside.Type) list[syside.Type]

Return the direct subtypes of type_ (the inverse of subclassification).

Linear in model size: the core exposes no inverse index, so this scans the model’s subclassification relationships via specializations().

supertypes(type_: syside.Type) set[syside.Type]

Return the transitive closure of type_’s heritage targets (types).

textual_representation(element: syside.Namespace, language: str) str | None

Return the body of element’s textual representation in language.

Matches language case-insensitively (KerML defines language names as case-insensitive). Returns the body of the first matching representation, or None if none matches. Linear in the number of owned textual representations.

variants(variation: syside.Definition | syside.Usage) list[syside.Element]

Return the variant usages of a variation variation, linear in the number of variants.

Both syside.Usage and syside.Definition expose a variants accessor.

specializations(model: syside.Model, target: syside.Element, *, via: type[syside.Specialization] | type[syside.Conjugation] | None = None) list[syside.Element]

Return the elements declaring a heritage relationship to target.

This is the inverse of generalizations() (one direct step): the elements e such that target appears among generalizations(e). For example, with via=syside.Subclassification it returns the direct subtypes of target; with via=syside.Redefinition the features that redefine target.

via filters by relationship type exactly as in generalizations() (subtypes included; None accepts every kind).

Cost and scope: the core exposes no inverse-heritage index, so this scans the model’s relationship nodes via model.nodes(via or syside.Relationship, include_subtypes=True) and keeps those whose resolved target is target. The scan spans every document of the user model – cross-document subtypes are found – and is keyed on object identity, so the match is exact (no name collisions). Cost is linear in the model’s relationship count. Standard-library documents fall outside the MODEL document scope and are not searched.

Results are de-duplicated by object identity and returned in scan order. target itself is never included (a self-referential heritage relationship is ignored).

generalizations(element: syside.Type, *, via: type[syside.Specialization] | type[syside.Conjugation] | None = None, exclude: type[syside.Specialization] | type[syside.Conjugation] | None = None, transitive: bool = False) list[syside.Element]

Return the heritage targets declared by element.

The heritage of a syside.Type is the set of specialization and conjugation relationships it declares; each one points at a target type (the general of a specialization, the original_type of a conjugation). This returns those targets.

via filters to relationships that are instances of the given relationship type, e.g. syside.FeatureTyping, syside.Subsetting, syside.Redefinition, syside.Subclassification or syside.CrossSubsetting. Subtypes of the given type are included (e.g. via=syside.Subsetting also matches Redefinition and CrossSubsetting). via=None accepts every heritage kind. Only heritage relationships – subtypes of syside.Specialization or syside.Conjugation – are valid; a non-heritage kind such as syside.Disjoining is a static type error (it never appears in heritage, so it could only ever yield []).

exclude is the complement of via: relationships that are instances of the given type (and its subtypes) are skipped. A transitive walk does not cross an excluded relationship, so the result is the closure reachable without that heritage kind. For example, exclude=syside.ReferenceSubsetting follows every other specialization (including plain syside.Subsetting, of which ReferenceSubsetting is a subtype) but never the reference-subsetting edge a connector introduces – the basis for asking what types a feature has independently of what a connection imposes on it.

``via`` and ``exclude`` are conjunctive (ANDed). A relationship is followed if and only if it satisfies both restrictions: it is an instance of via (or via is None) and it is not an instance of exclude (or exclude is None). Passing the same type to both – or a via that is a subtype of exclude – therefore matches nothing and yields [].

With transitive=False (the default) only the direct targets are returned. With transitive=True the chain is followed to its closure: each target’s own heritage targets are added, transitively. The via and exclude filters apply at every step of a transitive walk.

Results are de-duplicated by object identity and returned in first-seen order; element itself is never included. Cyclic heritage (which a malformed model can contain) terminates rather than looping forever.

element is a syside.Type: only types declare heritage. This is linear in the number of heritage relationships visited (the direct count, or the reachable closure when transitive is set), each step constant time.

walk_containment(element: syside.Element, *, direction: Literal[up, down], max_depth: int | None = None) Iterator[syside.Element]

Walk the ownership tree from element, yielding the elements reached.

With direction="up" the walk follows the owner chain: it yields element’s owner, then that owner’s owner, and so on up to the root namespace. With direction="down" it traverses owned_elements breadth-first: first the direct contents, then their contents, and so on. In neither direction is element itself yielded.

max_depth bounds the walk. max_depth=1 yields only the elements one step away (the owner, or the direct contents); max_depth=None (the default) is unbounded. max_depth=0 yields nothing. A negative max_depth is treated as 0.

The downward walk de-duplicates by object identity and is cycle-safe: a well-formed ownership tree is acyclic, but a malformed model is still guaranteed to terminate and to yield each element at most once. The upward walk likewise stops if the owner chain ever revisits an element.

Downward traversal drains each owned_elements LazyIterator with a single .collect() (linear per node), never per-element Python iteration over the live iterator.