Skip to content

Enhancement: Python client cannot tell part from ref part: Symbol has no composite/reference flag and the query API cannot select isComposite #1986

Open

Description

@hideo67

This issue was written with AI help. The model, the reproduction script and its output are
attached and were run against the version below.

This is a feature request. The issue form points feature requests to Discussions, but
Discussions are not enabled on this repository, so it is filed as an issue.

What happened, and what you expected instead

part def Car {
    part engine : Engine;          // composite (owned) part
    ref part driver : Person;      // reference part
}

Whether a part is owned or only referenced is central to walking a structure: a bill of
materials, a mass roll-up, or a containment tree has to include engine and skip driver. From
Python the two are indistinguishable:

Route engine driver
Symbol.kind partUsage partUsage
Symbol.metadata {type, visibility} {type, visibility}
Symbol.facts() no composite/reference field same
Car.parts() listed listed
model.query(select=["isComposite"]) InvalidRequestError: unknown query property "isComposite" (isAbstract and isIndividual are queryable) same
reflection isComposite / isReference True / False ✔ False / True ✔
api-json isComposite true ✔ false ✔

The service has the flag (Feature::isComposite, and Usage::isReference, derived as its
negation; see docs/reference/rdf-mapping.md), but the only ways to read it from Python are one
reflective eval per element or exporting the whole model.

Requested:

  1. Symbol (and SymbolFacts) report is_composite / is_reference for features and usages, or
    at least a metadata entry, as direction already is for parameters.
  2. isComposite (and isReference) become queryable properties of model.query, beside
    isAbstract and isIndividual.
  3. Optionally, parts() documents that it includes reference parts, or takes a filter for them.

Reproduction

$ python repro.py          # loads model.sysml with opensysml.loads() over sysml-grpc
## Symbol API
[INFO] engine (part)
       observed: {'kind': 'partUsage', 'metadata keys': ['type', 'visibility'], 'facts fields': ['attributes', 'id', 'kind', 'multiplicity', 'name', 'specializations', 'type', 'withheld_library_attributes']}
[INFO] driver (ref part)
       observed: {'kind': 'partUsage', 'metadata keys': ['type', 'visibility'], 'facts fields': ['attributes', 'id', 'kind', 'multiplicity', 'name', 'specializations', 'type', 'withheld_library_attributes']}
[BUG ] Symbol tells a composite part from a reference part
       observed: 'identical: no composite/reference flag on Symbol, its metadata or facts()'
[INFO] Car.parts() lists both without distinction
       observed: ['engine', 'driver']

## Query API
[BUG ] model.query can select isComposite
       observed: InvalidRequestError('unknown query property "isComposite"; queryable properties are @id, @type, body, declaredName, declaredShortName, documentation, elementId, general, isAbstract, isIndividual, language, locale, multiplicityLower, multiplicityUpper, name, owner, qualifiedName, satisfiedRequirement, satisfyingFeature, shortName, textualRepresentation, type')

## Reflection and api-json (both correct)
[INFO] reflection engine
       observed: {'isComposite': True, 'isReference': False}
[INFO] reflection driver
       observed: {'isComposite': False, 'isReference': True}
[INFO] api-json engine
       observed: {'isComposite': True, 'isReference': '<absent>'}
[INFO] api-json driver
       observed: {'isComposite': False, 'isReference': True}

model.sysml:
package RefParts {
    part def Engine;
    part def Person;

    part def Car {
        part engine : Engine;          // composite (owned) part
        ref part driver : Person;      // reference part
    }
}

Reproduction files (attached)

File Contents
model.sysml.txt the model above (GitHub does not accept .sysml uploads): rename it to model.sysml next to repro.py
repro.py loads model.sysml with opensysml.loads() and compares the Symbol shape of the two parts, tries isComposite in model.query, and shows the flags through reflection and api-json. Each check prints OK, BUG or INFO, and the script exits 1 while any BUG reproduces.
_common.py shared helper the script imports: starts the sysml-grpc named by $OPENSYSML_BINARY, prints the server and client versions, and formats the checks
output.txt the full output of the run reported here

model.sysml.txt

repro.py

_common.py

output.txt

To run it, put _common.py in the parent directory of the folder holding repro.py and the model,
install the client (pip install -e client/python from an OpenSysML checkout), and point
OPENSYSML_BINARY at a sysml-grpc build:

$ OPENSYSML_BINARY=/path/to/OpenSysML/bin/sysml-grpc python repro.py

Without OPENSYSML_BINARY, _common.py assumes an OpenSysML checkout next to the directory
that holds _common.py, with bin/sysml-grpc built by make build-grpc.

Version

sysml-grpc v0.12.0-262-gc86932321 (develop at c869323), opensysml Python client 0.12.0, Python 3.13.15

Surface

Python client (opensysml)

Activity

  1. devin-ai-integration commented on Nov 6, 2026

    @devin-ai-integration
    Contributor

    Confirmed as an enhancement, not a bug: the flag is modelled correctly, it just has no Symbol or query surface. Reproduced on develop at d46c639 with the attached script (2 BUG lines, as in your output; reflection and api-json agree with the specification).

    SysML v2.1 Beta 2 §8.3.6.4 deriveUsageIsReference: isReference = not isComposite; the textual notation (§8.2) parses a usage without ref as isComposite = true, and with ref (or as an end, a parameter, or in the other listed cases) as isReference = true. Our reflective Feature::isComposite / Usage::isReference and api-json already derive both that way.

    What is missing:

    • SymbolInfo / Symbol facts (internal/frontend/symbolfacts, Python Symbol.metadata / facts()) carry direction but no composite/reference flag.
    • internal/semantic/query exposes isAbstract and isIndividual (properties.go) but not isComposite / isReference, so model.query(select=["isComposite"]) is rejected.

    Planned: expose is_composite / is_reference on Symbol facts and metadata (same path as direction), add isComposite and isReference as queryable properties beside isAbstract/isIndividual (query, OSLC name table, gRPC, Python client), and document on parts() that reference parts are included, with a filter so a containment walk can skip them.

  2. devin-ai-integration commented on Nov 6, 2026

    @devin-ai-integration
    Contributor

    Implemented in #1993. SymbolInfo now carries is_composite / is_reference (new optional fields, so the wire stays compatible) together with the metadata flags composite / reference, read from the same Feature::isComposite / Usage::isReference derivation that reflection and api-json already use, so a part and a ref part — and likewise an end, an in/out parameter, a ref attribute, an item, a port or a KerML feature — are told apart without re-reading the notation. The query API selects and filters isComposite and isReference beside isAbstract / isIndividual over gRPC, REST and OSLC, and the unknown query property message lists them. In the Python client Symbol.is_composite / is_reference are typed properties (None for a non-feature or an older service), facts() carries them, and children() / parts() document that reference usages are included and take composite=True / False to filter, so a containment walk can skip ref parts; the Go and Node clients mirror the fields. Covered by symbolfacts and query unit tests, gRPC conformance cases, Python client tests, and the reporter's script, which now reports no defect.

  3. devin-ai-integration commented on Nov 11, 2026

    @devin-ai-integration
    Contributor

    Status on develop at 98d8eca775 (the bespoke is_composite/is_reference Symbol fields of #1993 were dropped in favour of the generic per-element read of #2013, which is merged; this is what answers the two requests now):

    1. Read the flag. model.element_properties("Car::engine", "isComposite", "isReference") answers isComposite = [true] for engine and isComposite = [false], isReference = [true] for driver — Feature::isComposite as parsed from the notation and Usage::isReference as not isComposite (SysML v2.1 Beta 2 §8.3.6.4 deriveUsageIsReference), through the same metamodel derivation reflection and api-json use. The Symbol itself stays a curated surface without these flags.
    2. Query by it. model.query(select=["name", "isComposite"], where={"@type": "PrimitiveConstraint", "operator": "=", "property": "@type", "value": ["PartUsage"]}) answers engine → isComposite true, driver → isComposite false; isReference is selectable the same way.

    One defect found while checking: on a composite usage, isReference answers an empty value instead of false on both GetElementProperties and api-json (engine carries no isReference key; driver carries true). Usage::isReference is a required Boolean, so engine has to answer false. That is a small bug in the Boolean property path, fixed separately; I will link the pull request here. The parts() filter of request 3 is not changed.

  4. devin-ai-integration commented on Nov 12, 2026

    @devin-ai-integration
    Contributor

    The isReference residual is addressed in #2166. What was wrong: no false was being dropped in serialisation; Usage::isReference was missing from the completion table of the shared element form that GetElementProperties and sysml -convert api-json both read, so it appeared only where the graph stated it, which is on referential usages. A composite usage therefore answered nothing, although isReference is a required Boolean derived as not isComposite (SysML v2.1 Beta 2 §8.3.6.4 deriveUsageIsReference). With the fix, the element form completes it on every usage as the negation of the isComposite the same element states: M::Car::engine now answers isReference = [false] (isComposite = [true]) over gRPC and writes "isReference": false in api-json, and M::Car::driver still answers isReference = true, isComposite = false, in agreement with the query leg.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions