Skip to main content

sysml2py

A pure-Python library for building SysML v2 models — generated, not handcrafted.

sysml2py is a pure-Python API for constructing SysML v2 textual models. You write Python; it emits spec-correct .sysml notation. Its AST node classes, builder API, and printer are deterministically generated by a companion project (sysml2py-lab) from the SysML v2 grammar + relationship model + corpus — so the library stays in lockstep with the language instead of drifting behind a hand-written parser.

from sysml2py import PackageBuilder, Part, Attribute

pkg = PackageBuilder('MyModel')
stage = Part('Stage_1')
stage.add(Attribute('mass'))
stage.add(Attribute('thrust'))
pkg.add(stage)
print(pkg.dump())
package MyModel
{
   part Stage_1
   {
      attribute mass;
      attribute thrust;
   }
}

Why a full overhaul? What happened before 1.0.0

The 0.5.x line was a hand-maintained parser built on textX, astropy, and PyYAML (grammar/SysML_compiled.tx + hand-written classes) that loaded slow, dragged in heavy runtime dependencies, and covered only a sliver of SysML v2. Maintaining it meant hand-editing parser code every time the language moved.

1.0.0 is a ground-up rewrite with a different philosophy. We no longer handcraft the implementation. Instead, sysml2py-lab — the generation factory — consumes:

  1. the SysML v2 grammar (the authoritative syntax + relationships),
  2. a relationship model of which modifiers and children each element allows, and
  3. a growing corpus of real models as usage evidence,

…and deterministically generates the entire sysml2py library from those inputs. No growing pile of hand-written classes to maintain — when the language or the corpus changes, we regenerate.

What that buys you

  • Pure standard library at runtime. pip install sysml2py now pulls nothing beyond the stdlib — no textX, no astropy, no yaml. (The old 0.5.x line shipped a textX-backed parser with astropy as a runtime dependency.)
  • Broad AST coverage with an honest fallback. Hundreds of generated node classes mirror the grammar rules. Constructs not yet structurally represented are preserved as Unsupported nodes carrying their raw text — zero NotImplementedError; nothing is silently dropped.
  • Reproducible provenance. Every install records the exact grammar, spec, relationship-model, and corpus SHAs plus the generator version that produced the wheel — you can always prove which inputs your library came from.
  • A cast that stays true (during generation). Output the generator produces is re-parsed and compared element-for-element against the model that produced it, and validated against an independent SysML v2 oracle — if it doesn't round-trip, it doesn't ship.

Why generate instead of handcrafting it ourselves?

Two words: drift and scale.

Hand-written parsers drift. They accumulate special cases, silently diverge from the spec, and every grammar adjustment becomes a manual archaeology project. Generation inverts that: the grammar is the source of truth, and the implementation is derived from it mechanically. When SysML v2 evolves or the corpus reveals a new construct, we update the inputs and regenerate — the whole library stays consistent by construction, and a regression that breaks an earlier model is caught the same way it was introduced.

This is the same reason we validate the output against an external oracle rather than our own assumptions: the generator never gets to grade its own homework.


Building a model (friendly builder API)

Friendly names drop the Builder/Usage/Definition suffixes — Part, Attribute, Item, Port, Requirement, Reference, ... are builders. Usage vs. definition is chosen at creation via kind=; add() appends children so a full package dumps from nested composition.

from sysml2py import PackageBuilder, Part

pkg = PackageBuilder('Rocket')
pkg.add(Part('Stage_1'))
pkg.add(Part('Stage_2'))
print(pkg.dump())
package Rocket
{
   part Stage_1;
   part Stage_2;
}

A definition is the same class with kind= at creation:

>>> Part('Stage_1', kind='definition').dump()
'part def Stage_1;'

Kinds without a definition form (Package, Comment, Actor, Reference, Transition, Import, Message, Succession, AliasMember, Subject, ObjectiveRequirement) reject kind='definition' with a ValueError — the generator never emits a definition it cannot cast back, and a class-name kind like 'PartDefinition' raises too, since it would print invalid SysML.

Container names that collide with AST node classes (Package, Comment, Import, Message, Succession, AliasMember) keep the *Builder suffix — use PackageBuilder, ImportBuilder, etc. The classic *Builder names (PartUsageBuilder, ...) remain available if you prefer them.

Units

Value-bearing attributes are first-class. Pass a value and a unit and the library renders the SysML [unit] syntax; inside a container it also imports the unit automatically as a public import (KERML-74 requires an explicit visibility modifier):

from sysml2py import PackageBuilder, Attribute

pkg = PackageBuilder('Rocket')
pkg.add(Attribute('mass', value=1000, unit='kg'))
print(pkg.dump())
package Rocket
{
   public import SI::kg;
   attribute mass = 1000[kg];
}

Note: a bare, childless attribute dumps without the import — the import is hosted in a container's body, so the container form above is the one that emits it: Attribute('mass', value=1000, unit='kg').dump() → attribute mass = 1000[kg];

You can also pass a pint or astropy Quantity and the unit is derived from it — either integrates as a drop-in convenience:

# pint
import pint
ureg = pint.UnitRegistry()
Attribute('mass', value=1000 * ureg.kg).dump()      # attribute mass = 1000[kg];

# astropy (the original Quantity integration, still supported)
import astropy.units as u
Attribute('mass', value=1000 * u.kg).dump()          # attribute mass = 1000[kg];

Both derive the unit from the quantity object and render composite units faithfully (e.g. 9.8 * ureg.m / ureg.s**2 → attribute accel = 9.8[m/s**2];).

The unit='kg' string path above is pure standard library — no astropy or pint needed for that. Only the Quantity forms require you to install the respective library (pip install pint or pip install astropy; both ship only in the dev extra) and import it yourself, since the sysml2py runtime stays pure-stdlib.

Provenance & reproducibility

Check which exact inputs produced an install:

from sysml2py import provenance
print(provenance.PROVENANCE)
{'grammar_sha256': '...', 'spec_sha256': '...',
 'children_sha256': '...', 'modifiers_sha256': '...',
 'corpus_manifest_sha256': '...', 'generator_version': '1.1.0',
 'generated_at': '...'}

Also in the package

The generated AST classes and helpers are all public: Node and its hundreds of subclasses, walk / walk_preorder / walk_postorder / find_all traversal, print_model / beautify, normalize_text, canonical_equals for structure-aware comparison, and the *Builder classes. See the wiki for tutorials and deeper examples.

Compatibility & migrating from 0.5.x

This is the 1.x line. The builder names you remember — Part, Attribute, Item, Port — are preserved (0.5.x already exported them), and _set_name / _set_child / dump() still work. But 1.0.0 is a ground-up rewrite, so several things changed deliberately:

  • No text parser / no Model. 0.5.x's headline feature was parsing text into a model (load, loads, load_grammar). 1.0.0 is a builder library — it constructs models in Python and dumps them to text; it does not parse text back. Model is gone.
  • No runtime deps. 0.5.x bundled astropy; 1.0.0 pulls nothing beyond the stdlib. Add astropy to your own project only if you use the Quantity convenience for units.
  • Package changed meaning. In 0.5.x from sysml2py import Package was a package builder; in 1.0.0 it is the generated AST node class. For a builder use PackageBuilder.
  • Usage vs. definition chosen at creation via kind=. (In 0.5.x the first positional argument was a definition boolean, so Part('X') bound 'X' to that boolean rather than to the name — a silent break.)
  • Friendly names drop the Builder/Usage/Definition suffixes (PartUsageBuilder is now just Part, etc.). Container names that collide with AST node classes keep *Builder (PackageBuilder, ...).
  • set_value / get_value / set_direction are gone — pass value= / unit= to the builder constructor instead.
  • get_definition is on Node, not the builder — use build_node().get_definition().

For the full step-by-step migration guide, see migration-0.5-to-1.0.md in the sysml2py-lab repo.


Background

  • sysml2py (this repo) — the generated, distributable library on PyPI.
  • sysml2py-lab — the generation factory that produces this library from the SysML v2 grammar, relationship model, and corpus. Grammar and corpus content, and bug reports against generated behavior, live there.
  • Windtrader — the independent SysML v2 validator used as the correctness oracle during generation.

License

MIT.

Metadata

Release files for sysml2py 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sysml2py 1.1.0
File Size Uploaded Provenance
sysml2py-1.1.0.tar.gz 87.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sysml2py 1.1.0
File Interpreter ABI Platform
sysml2py-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 178.5 kB

Release files / sysml2py-1.1.0.tar.gz

Download URL sysml2py-1.1.0.tar.gz
Size 87.0 kB
Tags Source
SHA-256 checksum
How to use checksums
6c60fa5e72d9a5d65ec61d839dee1418e044821f9d38d384e7d84af93e6ab57c
BLAKE2b-256 checksum
How to use checksums
a5b88a9ca21dd3d223a4c97cbde14ebf926e714189eb54fa4b3c4318e0c87c1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / sysml2py-1.1.0-py3-none-any.whl

Download URL sysml2py-1.1.0-py3-none-any.whl
Size 91.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e41ca07dad3d44d2e0363ef1dbdb2f27c7ea60fd6c7d4a954306c6983b9afa5
BLAKE2b-256 checksum
How to use checksums
891c04176b72c1eba038043547304889dd464536b96e81a6e1510c257b80276b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for sysml2py 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page