Interchange¶
longeron.export¶
Exporters: model -> JSON and model -> SysML v2 textual notation.
to_sysml regenerates parseable textual notation from a model (whether it
was parsed from text or built programmatically); to_dict/to_json
serialize the model structure, with expressions carried both as structured
trees and rendered text.
- longeron.export.ExportFormat¶
the on-disk formats
save()writes (inferred from the file suffix when not given explicitly)alias of
Literal[‘sysml’, ‘kerml’, ‘json’]
- longeron.export.fmt_qname(qname)[source]¶
Format a stored qualified-name string (
::/.separated).- Return type:
- longeron.export.doc_comment_body(text)[source]¶
The canonical
/* ... */body for documentationtext.Single-line text becomes
/* text */; multi-line text uses the conventional*-prefixed continuation lines. This form is a textual-export fixpoint: stripping it back to text (text()) and re-rendering yields the identical body, no matter how deeply the owner is indented – which is why_Printer.emit_Documentation()re-renders multi-line bodies through it instead of echoing them verbatim (verbatim multi-line bodies accumulate indentation on every parse/print cycle).longeron.edit.set_doc()writes bodies in this same form.- Return type:
- longeron.export.to_dict(element)[source]¶
Convert a model element (or expression) to JSON-able data.
- longeron.export.save(element, path, fmt=None)[source]¶
Write a model element to disk as
.sysml,.kerml, or.json.The format is inferred from the file suffix unless given explicitly.
- Return type:
- longeron.export.workspace_plan(model, changes, indent=4)[source]¶
The files a workspace save would rewrite, and their new text.
modelis a directory-loaded model whose top-level members carry the per-file breadcrumbslongeron.workspace.load_dir()stamps;changesis its tracked edit record (longeron.edit.Tracker.changes– any iterable of(op, qname, detail)tuples). Each edit maps to its top-level member (through the change records’detail["tops"]breadcrumbs; a rename maps to every top-level member its cascade rewrote references in), and every mapped member’s source file is re-rendered whole withto_sysml(); files whose regenerated text already matches the disk content are dropped, so untouched files are never rewritten. Returns{path: new_text}in model member order.Honest refusals (
SysMLError, before anything is written): a top-level member with no recorded source file, or an edit that no longer names one.
- longeron.export.save_workspace(model, changes, indent=4)[source]¶
Write a directory-loaded model’s tracked edits back, file by file.
The write half of
workspace_plan()(same arguments, same refusals – and a refusal writes NOTHING): each file whose regenerated content differs from disk is rewritten into_sysml()canonical form; every other file is left untouched, byte for byte. Returns the paths written, in model member order.
- longeron.export.indent_string(indent)[source]¶
Normalize an
indentargument (space count or literal string).- Return type:
- longeron.export.find_emitter(printer, element, prefix='emit_')[source]¶
Look up
<prefix><ClassName>onprinteralong the element’s MRO.Shared dispatch helper for the textual printers (
_Printerhere andlongeron.kerml._KerMLPrinter): the most specific handler wins, andNonesignals “no handler” so each printer keeps its own unknown-element failure behavior.
longeron.importer¶
Rebuild models from their JSON export (longeron.export.to_dict()).
The JSON produced by to_dict/to_json is lossless for the model layer:
from_dict/from_json reconstruct the same element tree, so JSON is a
first-class interchange format alongside the textual notation:
model = longeron.loads("package P { part def X; }")
clone = longeron.from_json(longeron.to_json(model))
assert longeron.to_dict(clone) == longeron.to_dict(model)
- longeron.importer.from_dict(data)[source]¶
Reconstruct a model element from
longeron.export.to_dict()data.- Return type:
longeron.kerml¶
Best-effort projection of a SysML model onto KerML textual notation.
SysML v2 is defined as an extension of KerML: every SysML definition kind
maps to a kernel metatype (part def -> struct, calc def ->
function, constraint -> inv/predicate, …). to_kerml
renders that projection for the structural subset of a model. It is
one-way and lossy: behavioral statements (assignments, control flow,
transitions), connections, views, and metadata have no kernel-level textual
equivalent here and are emitted as /* omitted: ... */ comments.
The output is guaranteed parseable by the bundled KerML grammar (this is enforced by the test suite).
longeron.ecore¶
Requires the ecore extra (pip install "longeron[ecore]").
Bridge to the OMG SysML v2 specification metamodel (Stage B prototype).
The pragmatic dataclasses in longeron.model are shaped like the textual
notation. The OMG abstract syntax is different: ~175 metaclasses where
ownership goes through reified OwningMembership elements and every
specialization/typing is itself an element. This module projects a longeron
model onto that abstract syntax using the pilot implementation’s published
SysML.ecore (vendored under longeron/_spec/) and pyecore.
Scope (prototype): element skeletons, names, common flags, reified
memberships, and Specialization / FeatureTyping / Subsetting / Redefinition
relationships for targets that resolve inside the model. View persistence
projects too: expose becomes MembershipExpose / NamespaceExpose
records (targets wired when they resolve in-model) and filter becomes
ElementFilterMembership records whose condition Expression carries the
rendered text as a TextualRepresentation. Expression trees and
unresolved (standard-library) references are counted in the report, not
mapped. Requires the ecore extra: pip install longeron[ecore].
- class longeron.ecore.SpecReport(elements=0, memberships=0, relationships=0, skipped_elements=<factory>, unresolved_references=<factory>)[source]¶
Bases:
objectWhat the projection covered (and what it had to skip).
- class longeron.ecore.SpecModel(root, report, instances=None)[source]¶
Bases:
objectA model projected onto the OMG abstract syntax.
longeron.api¶
Requires the ecore extra (pip install "longeron[ecore]").
Relationship records carry the derived source/target endpoint arrays by
default (to_api_records(..., derived=True)): the OMG pilot-implementation
API servers serialize these derived properties, and pilot-ecosystem
consumers (pymbe, for one) use their presence to recognize relationship
records and navigate the model graph — an export without them loads but is
unnavigable. Pass derived=False (CLI: longeron export --format api --no-derived) for minimal records restricted to stored features; round
trips are lossless either way.
OMG Systems Modeling API JSON interchange (Stage E prototype).
SysML v2 tools exchange models through the “Systems Modeling API &
Services” JSON: one flat record per element, @type naming the spec
metaclass, @id/elementId UUIDs, and every reference expressed as
{"@id": ...}. This module rides on the longeron.ecore projection
(so it inherits its scope and its pyecore requirement):
records = longeron.api.to_api_json(model) # model -> API JSON clone = longeron.api.model_from_api_json(records) # API JSON -> Model (inverse) spec = longeron.api.spec_from_api_json(records) # API JSON -> spec instances
The export is a structural prototype: element skeletons, names, flags,
memberships, specialization/typing relationships, and the view-persistence
records (exposes and element filters) – not expression trees.
See longeron.ecore for what is and is not projected.
Relationship records carry the derived source/target endpoint
arrays by default (derived=True), matching what the OMG pilot-
implementation API servers serialize – see to_api_records().
- longeron.api.to_api_records(model, *, implied=False, derived=True)[source]¶
Flat API-style records for a model (or an existing projection).
With
derived=True(the default), every relationship record also carries the spec-derivedsourceandtargetendpoint arrays ([{"@id": ...}, ...]), computed from the relationship’s role features:subclassifier/superclassifierfor a Subclassification,typedFeature/typefor a FeatureTyping, the owning namespace / owned member for memberships, and so on (the role-to-endpoint mapping is read from theredefines/subsetsannotation chains in the vendored spec Ecore, not hard-coded). The OMG pilot-implementation API servers always serialize these derived properties, and pilot-ecosystem consumers (e.g. pymbe) rely on them both to recognize relationship records and to navigate the model graph – without them an export loads but is unnavigable. Records whose endpoints are not derivable (an import whose target never resolved, a bare Dependency) simply omit the fields. Passderived=Falsefor minimal records restricted to stored features. Round-trips are lossless either way:spec_from_api_records()accepts records with or without the endpoint fields, and a re-export reproduces them.With
implied=True(requires aModel), the implied standard-library specializations (Resolver.implied_generals: a plainpart defspecializesParts::Part, a plainpartusage subsetsParts::parts, …) are emitted too, as additionalSubclassification/Subsettingrecords flagged"isImplied": true. Off by default: the extra records reference library elements that are not part of the export (their@idis a deterministic UUID of the library element’s qualified name), which would break lossless round-trips.
- longeron.api.spec_from_api_records(records)[source]¶
Rebuild spec (pyecore) instances from API records.
This is the spec-level import: the result mirrors the metamodel instances the records serialize. It is not the inverse of
to_api_records()– for API records back to alongeron.model.Model, usemodel_from_api_records().- Return type:
- longeron.api.spec_from_api_json(text)[source]¶
Parse API JSON into spec (pyecore) instances (see
spec_from_api_records()). Not the inverse ofto_api_json()– that ismodel_from_api_json().- Return type:
- longeron.api.from_api_records(records)¶
back-compat aliases; prefer the explicit
spec_from_api_*names (ormodel_from_api_*for the model-layer inverse ofto_api_*)- Return type:
- longeron.api.from_api_json(text)¶
Parse API JSON into spec (pyecore) instances (see
spec_from_api_records()). Not the inverse ofto_api_json()– that ismodel_from_api_json().- Return type:
- longeron.api.model_from_api_records(records)[source]¶
Rebuild a
Modelfrom flat API records.This is the reverse of
to_api_records()at the same structural fidelity: element kinds, names, flags, ownership (via the reified membership records), the FeatureTyping / Subclassification / Subsetting / Redefinition relationships, and view persistence –expose(MembershipExpose / NamespaceExpose, targets rebound by qualified name) andfilter(ElementFilterMembership, conditions re-parsed from their textual representation) – come back; expression trees, attribute values, multiplicities, and import/dependency targets are not part of API records and are therefore absent from the result. An expose whose target reference is missing (it never resolved at export) has no textual form and is dropped. Relationship endpoints are read from the stored role features when present and from the derivedsource/targetarrays otherwise, so both longeron exports and pilot-server payloads import. Records are accepted in flat GET form or pilot POSTidentity/payloadform; unknown@typevalues are skipped, never fatal. Unlikespec_from_api_records()this needs no pyecore.- Return type:
- longeron.api.model_from_api_json(text)[source]¶
Parse API JSON (see
model_from_api_records()).- Return type:
longeron.rdf¶
Requires the rdf extra (pip install "longeron[rdf]").
Project a model onto RDF for SPARQL querying and linked-data interchange.
The projection turns a Model into an
rdflib Graph: every element becomes a
subject, its spec metaclass becomes the rdf:type, and memberships,
specialization/subsetting/redefinition/typing, and attribute values
(typed literals) become predicates. Requires the rdf extra
(pip install "longeron[rdf]"):
from longeron import rdf
graph = rdf.to_graph(model) # rdflib.Graph
rdf.to_turtle(model, "model.ttl") # Turtle serialization
rdf.to_jsonld(model) # JSON-LD text
rows = rdf.sparql(model, '''
SELECT ?def ?mass WHERE {
?def a sysml:PartDefinition ; sysml:ownedMember ?attr .
?attr sysml:name "mass" ; sysml:value ?mass .
}''')
Vocabulary design¶
Class names derive from the OMG projection, not a parallel invention.
rdf:type uses the same spec metaclass per element kind that
longeron.ecore / longeron.api emit as @type in Systems
Modeling API records (part def -> sysml:PartDefinition, a
subject usage -> sysml:ReferenceUsage, …), so the RDF view and
the API JSON view of one model agree on what things are. Property names
likewise follow the spec’s derived-property vocabulary (ownedMember,
specializes, subsets, redefines, definedBy) rather than the
reified relationship records: RDF triples are already edges, so the
Subclassification/FeatureTyping reification the flat API records
need is collapsed into direct predicates (the reified form stays available
via longeron.api).
The namespace is this package’s own, not OMG’s. Classes and properties
live under https://sanbales.github.io/longeron/rdf/sysml# (bound to the
prefix sysml:), elements under .../rdf/element/ by default. OMG has
not (yet) published an official RDF vocabulary for SysML v2; squatting on a
plausible-looking OMG IRI would be worse than owning an honest one. When an
official vocabulary lands, the local names here – taken verbatim from the
spec metamodel – should map 1:1, making migration a namespace substitution
(and an owl:equivalentClass/owl:equivalentProperty bridge trivial to
generate). Element IRIs are minted from qualified names
(DeepScout::Propulsion::HoverPower ->
.../element/DeepScout/Propulsion/HoverPower, percent-encoded per
segment); anonymous elements fall back to blank nodes labeled in document
order, so a rebuilt graph is isomorphic to the last one.
Scope mirrors the longeron.ecore prototype: structure, names, flags,
relationships, documentation, and attribute values – expression trees
are carried as rendered text (sysml:valueExpression), not as RDF
sub-structure. Action/state statement bodies are not projected.
- longeron.rdf.VOCABULARY = 'https://sanbales.github.io/longeron/rdf/sysml#'¶
default namespace for classes and properties (prefix
sysml)
- longeron.rdf.ELEMENT_BASE = 'https://sanbales.github.io/longeron/rdf/element/'¶
default namespace under which element IRIs are minted
- longeron.rdf.to_graph(model, *, base='https://sanbales.github.io/longeron/rdf/element/', evaluated=False)[source]¶
Project a model onto an
rdflib.Graph.baseoverrides the namespace under which element IRIs are minted. Withevaluated=True, attribute values that are expressions (not plain literals) are additionally evaluated by theInterpreterper owning definition and emitted assysml:evaluatedValuetyped literals (best-effort: definitions that fail to instantiate are skipped silently).- Return type:
Graph
- longeron.rdf.to_turtle(model_or_graph, path=None, **kwargs)[source]¶
Serialize a model (or an existing graph) as Turtle; optionally write it.
- Return type:
longeron.rag¶
No extra required — the retrieval substrate is stdlib only.
A deterministic retrieval substrate for LLM/RAG pipelines (stdlib only).
This module carries no LLM, embedding, or vendor dependencies: it turns
a model into stable, re-parseable textual chunks that an embedding index, a
keyword search, or an agent’s context window can consume – and is useful
without any of them via search().
model_chunks()– deterministic chunking: one chunk per package, definition, and package-level usage, each re-printed as valid SysML v2 text by the existing exporter (longeron.to_sysml()), with a stable qualified-nameid, an ancestry breadcrumb, outgoing references, and doc text. Same model in, byte-identical chunks out – so embedding caches keyed on(id, text)stay warm across runs.neighborhood()– graph-RAG helper: the chunks of an element’s semantic neighborhood (specializations, types, members, incoming and outgoing references), breadth-first by hop count.search()– an embedding-free fallback: TF-IDF-style token scoring (camelCase-aware, stdlibmathonly) over the chunks.
The intended LLM workflow is retrieval -> cite qualified names ->
resolve those names back through Interpreter
for ground truth (evaluate the calc, check the constraint) instead of
trusting generated text – the chunks exist to get the right element names
into the conversation, not to replace execution.
- longeron.rag.MAX_CHARS = 4000¶
default chunk-size budget, in characters of printed SysML text
- longeron.rag.model_chunks(model, *, max_chars=4000)[source]¶
Deterministic retrieval chunks for a model, in document order.
Chunk units are packages, definitions, and package-level usages. Packages chunk shallow (declaration, doc, imports – their definitions are chunks of their own, so no text is duplicated). A definition whose printed text exceeds
max_charsalso chunks shallow, and its named nested members become chunks of their own (best-effort: a single oversized leaf is emitted whole rather than dropped). Ids, ordering, and text are stable across runs.
- longeron.rag.neighborhood(model, qname, hops=1, *, max_chars=4000)[source]¶
The chunks of an element’s semantic neighborhood.
Starting from the chunk containing
qname, follows outgoing references (typing, specialization, subsetting, values), incoming references (who mentions me), and ownership (members and owner) forhopssteps. The seed chunk comes first; the rest follow in (distance, document order).
- longeron.rag.search(model, terms, *, limit=10, max_chars=4000)[source]¶
Embedding-free keyword retrieval over the model’s chunks.
termsis a query string (split on whitespace) or a list of terms. Scoring is TF-IDF-style token overlap – camelCase-aware, so"station"matchesstationMinutes– with name and doc tokens weighted above body text. Returns thelimitbest chunks with positive scores, ties broken by document order.- Return type: