sysml2kit 0.0.1__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sysml2kit-0.1.0/CHANGELOG.md +33 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/PKG-INFO +5 -3
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/README.md +4 -2
- sysml2kit-0.1.0/docs/api-client.md +28 -0
- sysml2kit-0.1.0/docs/backends.md +34 -0
- sysml2kit-0.1.0/docs/cli.md +19 -0
- sysml2kit-0.1.0/docs/concepts.md +46 -0
- sysml2kit-0.1.0/docs/index.md +24 -0
- sysml2kit-0.1.0/docs/interchange.md +31 -0
- sysml2kit-0.1.0/docs/quickstart.md +44 -0
- sysml2kit-0.1.0/docs/reference.md +31 -0
- sysml2kit-0.1.0/docs/rf-library.md +33 -0
- sysml2kit-0.1.0/docs/traceability.md +47 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/mkdocs.yml +9 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/__init__.py +5 -3
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/_version.py +2 -2
- sysml2kit-0.1.0/src/sysml2kit/api/__init__.py +7 -0
- sysml2kit-0.1.0/src/sysml2kit/api/client.py +120 -0
- sysml2kit-0.1.0/src/sysml2kit/api/errors.py +12 -0
- sysml2kit-0.1.0/src/sysml2kit/api/models.py +36 -0
- sysml2kit-0.1.0/src/sysml2kit/backends/__init__.py +31 -0
- sysml2kit-0.1.0/src/sysml2kit/backends/protocol.py +38 -0
- sysml2kit-0.1.0/src/sysml2kit/backends/sysmlpy.py +119 -0
- sysml2kit-0.1.0/src/sysml2kit/cli.py +143 -0
- sysml2kit-0.1.0/src/sysml2kit/diff.py +106 -0
- sysml2kit-0.1.0/src/sysml2kit/graph.py +36 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/reader.py +2 -2
- sysml2kit-0.1.0/src/sysml2kit/interop/__init__.py +5 -0
- sysml2kit-0.1.0/src/sysml2kit/interop/requirements.py +113 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/container.py +8 -0
- sysml2kit-0.1.0/src/sysml2kit/query.py +133 -0
- sysml2kit-0.1.0/src/sysml2kit/text/__init__.py +5 -0
- sysml2kit-0.1.0/src/sysml2kit/text/keywords.py +52 -0
- sysml2kit-0.1.0/src/sysml2kit/text/writer.py +179 -0
- sysml2kit-0.1.0/src/sysml2kit/validation.py +186 -0
- sysml2kit-0.1.0/tests/conftest.py +46 -0
- sysml2kit-0.1.0/tests/test_api_client.py +87 -0
- sysml2kit-0.1.0/tests/test_backend_sysmlpy.py +54 -0
- sysml2kit-0.1.0/tests/test_cli.py +87 -0
- sysml2kit-0.1.0/tests/test_diff.py +61 -0
- sysml2kit-0.1.0/tests/test_interchange.py +56 -0
- sysml2kit-0.1.0/tests/test_interop.py +80 -0
- sysml2kit-0.1.0/tests/test_model.py +105 -0
- sysml2kit-0.1.0/tests/test_query.py +59 -0
- sysml2kit-0.1.0/tests/test_roundtrip_json.py +45 -0
- sysml2kit-0.1.0/tests/test_roundtrip_text.py +44 -0
- sysml2kit-0.1.0/tests/test_text_writer/test_vehicle_golden.sysml +27 -0
- sysml2kit-0.1.0/tests/test_text_writer.py +51 -0
- sysml2kit-0.1.0/tests/test_validation.py +82 -0
- sysml2kit-0.1.0/tests/test_values_units.py +42 -0
- sysml2kit-0.0.1/CHANGELOG.md +0 -12
- sysml2kit-0.0.1/docs/index.md +0 -19
- sysml2kit-0.0.1/src/sysml2kit/cli.py +0 -26
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.gitignore +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.pre-commit-config.yaml +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.python-version +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CITATION.cff +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CLAUDE.md +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CONTRIBUTING.md +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/LICENSE +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/NOTICE +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/SECURITY.md +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/SPEC.md +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/pyproject.toml +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/scripts/slopcheck.sh +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/__init__.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/typemap.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/writer.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/__init__.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/analysis.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/base.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/builder.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/metadata.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/relations.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/requirements.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/structure.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/values.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/py.typed +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/units.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/tests/test_package.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/tools/conformance/run_oracle.py +0 -0
- {sysml2kit-0.0.1 → sysml2kit-0.1.0}/uv.lock +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-08-21
|
|
4
|
+
|
|
5
|
+
First working release.
|
|
6
|
+
|
|
7
|
+
- Object model: the pragmatic profile (~20 element kinds) as pydantic
|
|
8
|
+
classes, `Model` container with identity/ownership/qualified names,
|
|
9
|
+
`assign_stable_ids()` (UUIDv5), fluent builder API, `AttributeValue` with
|
|
10
|
+
unit text and provenance, pint-backed unit helpers.
|
|
11
|
+
- JSON interchange: Systems Modeling API serialization reader/writer,
|
|
12
|
+
deterministic output, `OpaqueElement` passthrough for unknown `@type`s;
|
|
13
|
+
json→model→json fixpoint property-tested.
|
|
14
|
+
- Textual notation writer: deterministic `.sysml` output; verified against
|
|
15
|
+
the sysmlpy parser (write → parse → structural compare).
|
|
16
|
+
- Traceability queries: satisfied_by/verified_by/derived_from,
|
|
17
|
+
unsatisfied/unverified requirements, allocation table, trace matrix.
|
|
18
|
+
- Validation: rules S2K001–S2K009 with severities and stable ids.
|
|
19
|
+
- Diff: element-level with `--by-name` matching for regenerated ids.
|
|
20
|
+
- API client: hand-written httpx client for the Systems Modeling API
|
|
21
|
+
(projects/branches/commits/elements reads, create_project, push_model).
|
|
22
|
+
- Parser backends: `ParserBackend` protocol; sysmlpy adapter behind the
|
|
23
|
+
`parse` extra.
|
|
24
|
+
- Interop: `extract_requirements` reading the metricKey convention, with
|
|
25
|
+
dual-form thresholds for operator-style and bound-style engines.
|
|
26
|
+
- CLI: `show` (`--traceability`), `validate`, `diff`, `export`
|
|
27
|
+
(`--stable-ids`), `version`.
|
|
28
|
+
- Docs site, conformance-oracle workflow scaffold, prose lint tooling.
|
|
29
|
+
|
|
30
|
+
## 0.0.1 — 2026-08-21
|
|
31
|
+
|
|
32
|
+
- Package skeleton published to claim the PyPI name. Importable, no usable
|
|
33
|
+
functionality yet.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysml2kit
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: API-first Python tooling for building, querying, validating, and automating SysML v2 models
|
|
5
5
|
Project-URL: Homepage, https://github.com/jman4162/sysml2kit
|
|
6
6
|
Project-URL: Source, https://github.com/jman4162/sysml2kit
|
|
@@ -44,8 +44,10 @@ Description-Content-Type: text/markdown
|
|
|
44
44
|
API-first Python tooling for building, querying, validating, and automating
|
|
45
45
|
SysML v2 models.
|
|
46
46
|
|
|
47
|
-
> **Status: pre-alpha.** The 0.
|
|
48
|
-
>
|
|
47
|
+
> **Status: pre-alpha.** The 0.1.x line has a working core (model, writer,
|
|
48
|
+
> interchange, queries, validation, diff, API client, parse backend); the API
|
|
49
|
+
> may still move between minor versions. Pin an exact version if you depend
|
|
50
|
+
> on it.
|
|
49
51
|
|
|
50
52
|
`sysml2kit` is the requirements/architecture/traceability layer for
|
|
51
53
|
engineering automation stacks: build a system model in Python, emit standard
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
API-first Python tooling for building, querying, validating, and automating
|
|
9
9
|
SysML v2 models.
|
|
10
10
|
|
|
11
|
-
> **Status: pre-alpha.** The 0.
|
|
12
|
-
>
|
|
11
|
+
> **Status: pre-alpha.** The 0.1.x line has a working core (model, writer,
|
|
12
|
+
> interchange, queries, validation, diff, API client, parse backend); the API
|
|
13
|
+
> may still move between minor versions. Pin an exact version if you depend
|
|
14
|
+
> on it.
|
|
13
15
|
|
|
14
16
|
`sysml2kit` is the requirements/architecture/traceability layer for
|
|
15
17
|
engineering automation stacks: build a system model in Python, emit standard
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# API client
|
|
2
|
+
|
|
3
|
+
`sysml2kit.api.SysMLApiClient` is a hand-written httpx client for the OMG
|
|
4
|
+
Systems Modeling API and Services endpoints (the REST binding any conformant
|
|
5
|
+
model server exposes).
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from sysml2kit.api import SysMLApiClient
|
|
9
|
+
|
|
10
|
+
with SysMLApiClient("https://models.example.com", token="…") as client:
|
|
11
|
+
for project in client.list_projects():
|
|
12
|
+
print(project.id, project.name)
|
|
13
|
+
model = client.list_elements(project_id, commit_id) # -> sysml2kit Model
|
|
14
|
+
client.push_model(project_id, model, message="update")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Covered in v0.1: `list_projects`, `get_project`, `list_branches`,
|
|
18
|
+
`list_commits`, `get_commit`, `get_element`, `list_elements` (returns a
|
|
19
|
+
`Model` via the interchange reader), `create_project`, and `push_model`
|
|
20
|
+
(POSTs a commit whose change set inserts the model's records). Branch
|
|
21
|
+
management and merges are not covered yet.
|
|
22
|
+
|
|
23
|
+
Failures raise `ApiError(status, detail)`. The client sends the bearer token
|
|
24
|
+
to whatever base URL you configure; use HTTPS.
|
|
25
|
+
|
|
26
|
+
Server compatibility note: the pilot API-Services implementation has JSON
|
|
27
|
+
quirks relative to the spec; compatibility is tested best-effort behind the
|
|
28
|
+
`api` pytest marker against a local server, not in default CI.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Parser backends
|
|
2
|
+
|
|
3
|
+
sysml2kit does not implement the SysML v2 grammar. Reading textual notation
|
|
4
|
+
goes through a `ParserBackend` (see `sysml2kit.backends.protocol`):
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from sysml2kit.backends import get_backend
|
|
8
|
+
|
|
9
|
+
backend = get_backend("sysmlpy")
|
|
10
|
+
model = backend.parse(text)
|
|
11
|
+
model = backend.parse_files([path_a, path_b])
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## The sysmlpy backend
|
|
15
|
+
|
|
16
|
+
`pip install sysml2kit[parse]` installs
|
|
17
|
+
[sysmlpy](https://github.com/mycr0ft/sysmlpy) (MIT, ANTLR4-based). The
|
|
18
|
+
adapter maps its parse tree into the pragmatic profile; constructs the
|
|
19
|
+
profile lacks come back as `OpaqueElement` with a logged warning.
|
|
20
|
+
|
|
21
|
+
Fidelity: sysmlpy 0.36 does not surface typing, multiplicity, or attribute
|
|
22
|
+
values on its wrapper objects, so those fields are empty after a parse.
|
|
23
|
+
Names, kinds, docs, and ownership round-trip; the writer-emit → parse →
|
|
24
|
+
compare test in CI pins exactly that contract. Use JSON interchange when you
|
|
25
|
+
need full fidelity.
|
|
26
|
+
|
|
27
|
+
The dependency is capped (`sysmlpy>=0.36.2,<0.37`) because it has a single
|
|
28
|
+
maintainer; bumps are deliberate, after reading the release notes.
|
|
29
|
+
|
|
30
|
+
## Conformance oracle
|
|
31
|
+
|
|
32
|
+
A scheduled workflow (`conformance.yml`) downloads the EPL-2.0 OMG pilot
|
|
33
|
+
implementation at run time (never vendored) and checks it accepts every
|
|
34
|
+
`.sysml` file our writer emits. See `tools/conformance/run_oracle.py`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Command line
|
|
2
|
+
|
|
3
|
+
`sysml2kit` accepts `.json` interchange files everywhere; `.sysml` inputs
|
|
4
|
+
need the parse extra (`pip install sysml2kit[parse]`).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
sysml2kit show model.json # element tree + counts
|
|
8
|
+
sysml2kit show model.json --traceability # + requirement-to-part matrix
|
|
9
|
+
sysml2kit validate a.json b.sysml # exit 1 on error-severity issues
|
|
10
|
+
sysml2kit diff old.json new.json # exit 1 when models differ
|
|
11
|
+
sysml2kit diff old.json new.json --by-name # match by qualified name, not id
|
|
12
|
+
sysml2kit export model.sysml --to json -o model.json
|
|
13
|
+
sysml2kit export model.json --to sysml
|
|
14
|
+
sysml2kit export model.json --to json --stable-ids # UUIDv5 ids for committing
|
|
15
|
+
sysml2kit version
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Exit codes: `validate` returns 1 when any error-severity issue is found;
|
|
19
|
+
`diff` returns 1 when the models differ. Both suit CI gates.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Concepts
|
|
2
|
+
|
|
3
|
+
## The pragmatic profile
|
|
4
|
+
|
|
5
|
+
sysml2kit implements ~20 element kinds (packages, part/port/attribute
|
|
6
|
+
definitions and usages, interfaces, connections, requirements, constraints,
|
|
7
|
+
analysis cases, metadata, and four traceability relationships), not the full
|
|
8
|
+
~270-metaclass abstract syntax. Anything outside the profile round-trips
|
|
9
|
+
through `OpaqueElement`: on JSON import an unrecognized `@type` keeps its raw
|
|
10
|
+
record and ownership links, and re-exports unchanged.
|
|
11
|
+
|
|
12
|
+
The repo's `SPEC.md` lists the profile, the pinned spec release
|
|
13
|
+
(`SysML-v2-Release` tag `2026-05`), and every known deviation.
|
|
14
|
+
|
|
15
|
+
## Identity, ownership, refs
|
|
16
|
+
|
|
17
|
+
- Every element has a UUID `element_id`, matching the API JSON `@id`.
|
|
18
|
+
- Cross-references are `Ref` objects (UUID wrappers) resolved through the
|
|
19
|
+
model, never direct Python references, so any element serializes alone.
|
|
20
|
+
- Ownership lives in the `Model` container (owner/owned maps), not on
|
|
21
|
+
elements.
|
|
22
|
+
- `Model.assign_stable_ids()` rewrites ids as UUIDv5 hashes of qualified
|
|
23
|
+
names, so generated interchange files diff cleanly under version control.
|
|
24
|
+
Run it before committing generated models.
|
|
25
|
+
|
|
26
|
+
## Values with units and provenance
|
|
27
|
+
|
|
28
|
+
`AttributeValue` holds a literal plus optional `unit` (text, e.g. `"dBW"`),
|
|
29
|
+
`source`, and `confidence`. Units stay text in the model for round-trip
|
|
30
|
+
fidelity; `sysml2kit.units` (pint) checks them during validation and offers
|
|
31
|
+
conversion helpers.
|
|
32
|
+
|
|
33
|
+
## Two output formats, one lossless
|
|
34
|
+
|
|
35
|
+
- **JSON interchange** (`sysml2kit.interchange`) is the lossless format and
|
|
36
|
+
what the Systems Modeling API speaks.
|
|
37
|
+
- **Textual notation** (`sysml2kit.text`) is deterministic and parseable, but
|
|
38
|
+
relationship kinds without a standalone textual statement (verify, derive)
|
|
39
|
+
emit as marked dependencies. Round-tripping text preserves structure;
|
|
40
|
+
round-tripping JSON preserves everything.
|
|
41
|
+
|
|
42
|
+
## Spec churn policy
|
|
43
|
+
|
|
44
|
+
The `@type` vocabulary lives in one module
|
|
45
|
+
(`sysml2kit/interchange/typemap.py`). The spec pin moves at most quarterly,
|
|
46
|
+
in a minor release, noted in the changelog.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# sysml2kit
|
|
2
|
+
|
|
3
|
+
API-first Python tooling for building, querying, validating, and automating
|
|
4
|
+
SysML v2 models.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install sysml2kit # build, write, query, validate, diff
|
|
8
|
+
pip install "sysml2kit[parse]" # + read .sysml files (sysmlpy backend)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`sysml2kit` is the requirements/architecture/traceability layer for
|
|
12
|
+
engineering automation stacks: build a system model in Python, emit standard
|
|
13
|
+
SysML v2 textual notation and Systems Modeling API JSON, answer traceability
|
|
14
|
+
questions (which requirements are unsatisfied? unverified? allocated where?),
|
|
15
|
+
validate, and diff.
|
|
16
|
+
|
|
17
|
+
Reference spec release: OMG `SysML-v2-Release` tag `2026-05`. The element
|
|
18
|
+
subset and known deviations are documented in
|
|
19
|
+
[concepts](concepts.md) and the repo's `SPEC.md`.
|
|
20
|
+
|
|
21
|
+
Start with the [quickstart](quickstart.md), then the
|
|
22
|
+
[traceability](traceability.md) page — the queries there are the point of the
|
|
23
|
+
package. For antenna/RF domain content, see
|
|
24
|
+
[the RF library](rf-library.md).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Interchange
|
|
2
|
+
|
|
3
|
+
## JSON (lossless)
|
|
4
|
+
|
|
5
|
+
`sysml2kit.interchange` reads and writes the Systems Modeling API
|
|
6
|
+
serialization: a flat list of records like
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{"@id": "…", "@type": "PartUsage", "declaredName": "battery",
|
|
10
|
+
"owningRelatedElement": {"@id": "…"}, "definition": {"@id": "…"}}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `model_to_json(model)` / `write_json(model, path)` — deterministic output:
|
|
14
|
+
elements sorted by qualified name, keys sorted per record, so committed
|
|
15
|
+
files diff cleanly. Pair with `model.assign_stable_ids()` for generated
|
|
16
|
+
models.
|
|
17
|
+
- `model_from_json(records_or_path)` — unknown `@type` records become
|
|
18
|
+
`OpaqueElement` and re-export byte-identically.
|
|
19
|
+
|
|
20
|
+
`json -> model -> json` is a fixpoint; the property is tested with hypothesis.
|
|
21
|
+
|
|
22
|
+
## Textual notation (readable, parseable)
|
|
23
|
+
|
|
24
|
+
`sysml2kit.text.write_model(model)` emits deterministic `.sysml` text:
|
|
25
|
+
ownership order, four-space indent, values as `= 52.0 [dBW]`, requirement
|
|
26
|
+
statements as `doc` bodies, `satisfy X by Y;` / `allocate X to Y;`
|
|
27
|
+
statements. Verify and derive have no standalone textual statement in the
|
|
28
|
+
grammar, so they emit as `dependency from A to B; // verify` — the JSON keeps
|
|
29
|
+
the precise kind.
|
|
30
|
+
|
|
31
|
+
Reading text back goes through a [parser backend](backends.md).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
Build a model, run the traceability queries, and emit both output formats.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from sysml2kit import Model, builder
|
|
7
|
+
from sysml2kit.query import trace_matrix, unverified_requirements
|
|
8
|
+
from sysml2kit.text import write_model
|
|
9
|
+
from sysml2kit.interchange import write_json
|
|
10
|
+
from sysml2kit.validation import validate
|
|
11
|
+
|
|
12
|
+
model = Model()
|
|
13
|
+
pkg = builder.pkg(model, "Vehicle")
|
|
14
|
+
battery = builder.part(model, "battery", owner=pkg)
|
|
15
|
+
range_req = builder.req(
|
|
16
|
+
model, "REQ-001", "Range", owner=pkg,
|
|
17
|
+
text="The vehicle shall travel at least 400 km on one charge.",
|
|
18
|
+
)
|
|
19
|
+
builder.satisfy(model, source=battery, target=range_req)
|
|
20
|
+
|
|
21
|
+
print(unverified_requirements(model)) # [REQ-001] - nothing verifies it yet
|
|
22
|
+
print(trace_matrix(model).render()) # requirement-by-part grid
|
|
23
|
+
for issue in validate(model):
|
|
24
|
+
print(issue.rule_id, issue.severity, issue.message)
|
|
25
|
+
|
|
26
|
+
print(write_model(model)) # SysML v2 textual notation
|
|
27
|
+
write_json(model, "vehicle.json") # Systems Modeling API interchange
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The same operations from the command line:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
sysml2kit show vehicle.json --traceability
|
|
34
|
+
sysml2kit validate vehicle.json
|
|
35
|
+
sysml2kit export vehicle.json --to sysml
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Reading `.sysml` text back requires the parse extra:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from sysml2kit.backends import get_backend
|
|
42
|
+
|
|
43
|
+
model = get_backend("sysmlpy").parse(open("vehicle.sysml").read())
|
|
44
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
## Model and elements
|
|
4
|
+
|
|
5
|
+
::: sysml2kit.model.container.Model
|
|
6
|
+
|
|
7
|
+
::: sysml2kit.model.base
|
|
8
|
+
|
|
9
|
+
## Builder
|
|
10
|
+
|
|
11
|
+
::: sysml2kit.model.builder
|
|
12
|
+
|
|
13
|
+
## Queries
|
|
14
|
+
|
|
15
|
+
::: sysml2kit.query
|
|
16
|
+
|
|
17
|
+
## Validation
|
|
18
|
+
|
|
19
|
+
::: sysml2kit.validation
|
|
20
|
+
|
|
21
|
+
## Diff
|
|
22
|
+
|
|
23
|
+
::: sysml2kit.diff
|
|
24
|
+
|
|
25
|
+
## Interop
|
|
26
|
+
|
|
27
|
+
::: sysml2kit.interop.requirements
|
|
28
|
+
|
|
29
|
+
## Units
|
|
30
|
+
|
|
31
|
+
::: sysml2kit.units
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# The RF library
|
|
2
|
+
|
|
3
|
+
sysml2kit stays domain-general. Antenna/RF vocabulary lives in
|
|
4
|
+
[sysml2kit-rf-library](https://github.com/jman4162/sysml2kit-rf-library):
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install sysml2kit-rf-library
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from sysml2kit_rf_library import load_model
|
|
12
|
+
from sysml2kit.interop import extract_requirements
|
|
13
|
+
from sysml2kit.query import trace_matrix
|
|
14
|
+
|
|
15
|
+
model = load_model("satcom-terminal-t3001")
|
|
16
|
+
print(trace_matrix(model).render())
|
|
17
|
+
for spec in extract_requirements(model):
|
|
18
|
+
print(spec.id, spec.metric_key, spec.op, spec.value, spec.units)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It ships four library packages (RFVocabulary quantity kinds with units,
|
|
22
|
+
RFParts part/port definitions, RFRequirements requirement definitions using
|
|
23
|
+
the metricKey convention, RFAnalyses analysis case definitions) plus
|
|
24
|
+
**SatcomTerminal28GHz** — a worked example mirroring the aedl `t3-001`
|
|
25
|
+
benchmark: a 28 GHz LEO uplink phased-array terminal with eight
|
|
26
|
+
machine-checkable requirements (worst-case link margin, sidelobe level,
|
|
27
|
+
independent link crosscheck, clear-sky and gain agreement, prime-power and
|
|
28
|
+
unit-cost ceilings, grating-lobe margin), each satisfied by a part and
|
|
29
|
+
verified by an analysis.
|
|
30
|
+
|
|
31
|
+
The library demonstrates the intended division of labor: domain vocabulary
|
|
32
|
+
as SysML v2 model content, generic mechanics in the kit, physics engines
|
|
33
|
+
downstream.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Traceability
|
|
2
|
+
|
|
3
|
+
The queries in `sysml2kit.query` answer the questions a requirements-driven
|
|
4
|
+
workflow actually asks:
|
|
5
|
+
|
|
6
|
+
| Question | Call |
|
|
7
|
+
|---|---|
|
|
8
|
+
| What satisfies this requirement? | `satisfied_by(model, req)` |
|
|
9
|
+
| What verifies it? | `verified_by(model, req)` |
|
|
10
|
+
| What does it derive from? | `derived_from(model, req)` |
|
|
11
|
+
| Which requirements have no satisfier? | `unsatisfied_requirements(model)` |
|
|
12
|
+
| Which have no verifier? | `unverified_requirements(model)` |
|
|
13
|
+
| What is allocated where? | `allocation_table(model)` |
|
|
14
|
+
| The whole grid at once? | `trace_matrix(model).render()` |
|
|
15
|
+
|
|
16
|
+
The four relationship kinds are first-class elements
|
|
17
|
+
(`SatisfyRelationship`, `VerifyRelationship`, `DeriveRelationship`,
|
|
18
|
+
`AllocateRelationship`) created through the builder:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
builder.satisfy(model, source=battery, target=mass_req)
|
|
22
|
+
builder.verify(model, source=range_analysis, target=range_req)
|
|
23
|
+
builder.derive(model, source=mass_req, target=range_req)
|
|
24
|
+
builder.allocate(model, source=range_req, target=battery)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Handing requirements to an engine: the metricKey convention
|
|
28
|
+
|
|
29
|
+
A requirement usage that owns attributes `metricKey` (string), `threshold`
|
|
30
|
+
(number with unit), `op` (`>=`, `<=`, `==`, `>`, `<`), and optionally
|
|
31
|
+
`severity` (`must`/`should`/`nice`) is machine-checkable.
|
|
32
|
+
`sysml2kit.interop.extract_requirements` turns each into a `RequirementSpec`
|
|
33
|
+
carrying the threshold in both operator form (`op` + `value`) and bound form
|
|
34
|
+
(`minimum`/`maximum`), plus the satisfy/verify trace as qualified names:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from sysml2kit.interop import extract_requirements
|
|
38
|
+
|
|
39
|
+
for spec in extract_requirements(model):
|
|
40
|
+
print(spec.id, spec.metric_key, spec.op, spec.value, spec.satisfied_by)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Both forms are always populated (`>= 40` also sets `minimum=40`), so an
|
|
44
|
+
operator-style requirements engine (phased-array-systems) and a bound-style
|
|
45
|
+
one (aedl) each need only a small adapter, which lives in those packages.
|
|
46
|
+
The metric key names the entry in the engine's metrics dict; the model never
|
|
47
|
+
computes anything itself.
|
|
@@ -31,3 +31,12 @@ plugins:
|
|
|
31
31
|
|
|
32
32
|
nav:
|
|
33
33
|
- Home: index.md
|
|
34
|
+
- Quickstart: quickstart.md
|
|
35
|
+
- Concepts: concepts.md
|
|
36
|
+
- Traceability: traceability.md
|
|
37
|
+
- Interchange: interchange.md
|
|
38
|
+
- Command line: cli.md
|
|
39
|
+
- API client: api-client.md
|
|
40
|
+
- Parser backends: backends.md
|
|
41
|
+
- RF library: rf-library.md
|
|
42
|
+
- Reference: reference.md
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"""API-first Python tooling for building, querying, validating, and automating SysML v2 models.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
See https://github.com/jman4162/sysml2kit and SPEC.md for the element subset
|
|
4
|
+
(the "pragmatic profile") and the pinned spec release.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from importlib.metadata import PackageNotFoundError, version
|
|
8
8
|
|
|
9
|
+
from sysml2kit.model import Model, builder
|
|
10
|
+
|
|
9
11
|
try:
|
|
10
12
|
__version__ = version("sysml2kit")
|
|
11
13
|
except PackageNotFoundError: # running from a source tree without an install
|
|
12
14
|
__version__ = "0.0.0.dev0"
|
|
13
15
|
|
|
14
|
-
__all__ = ["__version__"]
|
|
16
|
+
__all__ = ["Model", "__version__", "builder"]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.0
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""HTTP client for the OMG Systems Modeling API and Services."""
|
|
2
|
+
|
|
3
|
+
from sysml2kit.api.client import SysMLApiClient
|
|
4
|
+
from sysml2kit.api.errors import ApiError
|
|
5
|
+
from sysml2kit.api.models import Branch, Commit, Project
|
|
6
|
+
|
|
7
|
+
__all__ = ["ApiError", "Branch", "Commit", "Project", "SysMLApiClient"]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""A thin, hand-written client for the OMG Systems Modeling API.
|
|
2
|
+
|
|
3
|
+
Covers the read paths (projects, branches, commits, elements) plus creating
|
|
4
|
+
a project and pushing a model as a commit. Hand-written on purpose: the
|
|
5
|
+
official generated Python client is LGPL and unmaintained; this one is a few
|
|
6
|
+
hundred lines against the REST/JSON binding and returns sysml2kit models.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import types
|
|
12
|
+
from typing import Any, Self
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from sysml2kit.interchange import model_from_json, model_to_json
|
|
17
|
+
from sysml2kit.model.container import Model
|
|
18
|
+
|
|
19
|
+
from .errors import ApiError
|
|
20
|
+
from .models import Branch, Commit, Project
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SysMLApiClient:
|
|
24
|
+
"""Synchronous client; use as a context manager to reuse one connection."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, base_url: str, *, token: str | None = None, timeout: float = 30.0) -> None:
|
|
27
|
+
headers = {"Accept": "application/json"}
|
|
28
|
+
if token:
|
|
29
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
30
|
+
self._client = httpx.Client(base_url=base_url.rstrip("/"), headers=headers, timeout=timeout)
|
|
31
|
+
|
|
32
|
+
# ------------------------------------------------------------- lifecycle
|
|
33
|
+
def close(self) -> None:
|
|
34
|
+
"""Close the underlying connection pool."""
|
|
35
|
+
self._client.close()
|
|
36
|
+
|
|
37
|
+
def __enter__(self) -> Self:
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def __exit__(
|
|
41
|
+
self,
|
|
42
|
+
exc_type: type[BaseException] | None,
|
|
43
|
+
exc: BaseException | None,
|
|
44
|
+
tb: types.TracebackType | None,
|
|
45
|
+
) -> None:
|
|
46
|
+
self.close()
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------- plumbing
|
|
49
|
+
def _get(self, path: str) -> Any:
|
|
50
|
+
response = self._client.get(path)
|
|
51
|
+
if response.status_code >= 400:
|
|
52
|
+
raise ApiError(response.status_code, response.text)
|
|
53
|
+
return response.json()
|
|
54
|
+
|
|
55
|
+
def _post(self, path: str, payload: Any) -> Any:
|
|
56
|
+
response = self._client.post(path, json=payload)
|
|
57
|
+
if response.status_code >= 400:
|
|
58
|
+
raise ApiError(response.status_code, response.text)
|
|
59
|
+
return response.json()
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------- reads
|
|
62
|
+
def list_projects(self) -> list[Project]:
|
|
63
|
+
"""List projects on the server."""
|
|
64
|
+
return [Project.model_validate(item) for item in self._get("/projects")]
|
|
65
|
+
|
|
66
|
+
def get_project(self, project_id: str) -> Project:
|
|
67
|
+
"""Fetch one project."""
|
|
68
|
+
return Project.model_validate(self._get(f"/projects/{project_id}"))
|
|
69
|
+
|
|
70
|
+
def list_branches(self, project_id: str) -> list[Branch]:
|
|
71
|
+
"""List a project's branches."""
|
|
72
|
+
return [
|
|
73
|
+
Branch.model_validate(item) for item in self._get(f"/projects/{project_id}/branches")
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def list_commits(self, project_id: str) -> list[Commit]:
|
|
77
|
+
"""List a project's commits."""
|
|
78
|
+
return [
|
|
79
|
+
Commit.model_validate(item) for item in self._get(f"/projects/{project_id}/commits")
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
def get_commit(self, project_id: str, commit_id: str) -> Commit:
|
|
83
|
+
"""Fetch one commit."""
|
|
84
|
+
return Commit.model_validate(self._get(f"/projects/{project_id}/commits/{commit_id}"))
|
|
85
|
+
|
|
86
|
+
def get_element(self, project_id: str, commit_id: str, element_id: str) -> dict[str, Any]:
|
|
87
|
+
"""Fetch one element's raw interchange record."""
|
|
88
|
+
record = self._get(f"/projects/{project_id}/commits/{commit_id}/elements/{element_id}")
|
|
89
|
+
return dict(record)
|
|
90
|
+
|
|
91
|
+
def list_elements(self, project_id: str, commit_id: str) -> Model:
|
|
92
|
+
"""Fetch every element at a commit and build a Model from them."""
|
|
93
|
+
records = self._get(f"/projects/{project_id}/commits/{commit_id}/elements")
|
|
94
|
+
return model_from_json(records)
|
|
95
|
+
|
|
96
|
+
# --------------------------------------------------------------- writes
|
|
97
|
+
def create_project(self, name: str, *, description: str | None = None) -> Project:
|
|
98
|
+
"""Create a project."""
|
|
99
|
+
payload: dict[str, Any] = {"@type": "Project", "name": name}
|
|
100
|
+
if description:
|
|
101
|
+
payload["description"] = description
|
|
102
|
+
return Project.model_validate(self._post("/projects", payload))
|
|
103
|
+
|
|
104
|
+
def push_model(
|
|
105
|
+
self,
|
|
106
|
+
project_id: str,
|
|
107
|
+
model: Model,
|
|
108
|
+
*,
|
|
109
|
+
branch: str | None = None,
|
|
110
|
+
message: str | None = None,
|
|
111
|
+
) -> Commit:
|
|
112
|
+
"""Create a commit whose change set inserts every element of the model."""
|
|
113
|
+
change = [{"@type": "DataVersion", "payload": record} for record in model_to_json(model)]
|
|
114
|
+
payload: dict[str, Any] = {"@type": "Commit", "change": change}
|
|
115
|
+
if message:
|
|
116
|
+
payload["description"] = message
|
|
117
|
+
path = f"/projects/{project_id}/commits"
|
|
118
|
+
if branch:
|
|
119
|
+
path += f"?branchId={branch}"
|
|
120
|
+
return Commit.model_validate(self._post(path, payload))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""API client errors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApiError(RuntimeError):
|
|
7
|
+
"""A Systems Modeling API request failed."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, status: int, detail: str) -> None:
|
|
10
|
+
super().__init__(f"HTTP {status}: {detail}")
|
|
11
|
+
self.status = status
|
|
12
|
+
self.detail = detail
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Resource models for the Systems Modeling API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Project(BaseModel):
|
|
9
|
+
"""A model repository project."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
12
|
+
|
|
13
|
+
id: str = Field(alias="@id")
|
|
14
|
+
name: str | None = None
|
|
15
|
+
description: str | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Branch(BaseModel):
|
|
19
|
+
"""A branch within a project."""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
22
|
+
|
|
23
|
+
id: str = Field(alias="@id")
|
|
24
|
+
name: str | None = None
|
|
25
|
+
head: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Commit(BaseModel):
|
|
29
|
+
"""A commit within a project."""
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
32
|
+
|
|
33
|
+
id: str = Field(alias="@id")
|
|
34
|
+
description: str | None = None
|
|
35
|
+
created: str | None = None
|
|
36
|
+
owning_project: str | None = None
|