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.
Files changed (82) hide show
  1. sysml2kit-0.1.0/CHANGELOG.md +33 -0
  2. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/PKG-INFO +5 -3
  3. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/README.md +4 -2
  4. sysml2kit-0.1.0/docs/api-client.md +28 -0
  5. sysml2kit-0.1.0/docs/backends.md +34 -0
  6. sysml2kit-0.1.0/docs/cli.md +19 -0
  7. sysml2kit-0.1.0/docs/concepts.md +46 -0
  8. sysml2kit-0.1.0/docs/index.md +24 -0
  9. sysml2kit-0.1.0/docs/interchange.md +31 -0
  10. sysml2kit-0.1.0/docs/quickstart.md +44 -0
  11. sysml2kit-0.1.0/docs/reference.md +31 -0
  12. sysml2kit-0.1.0/docs/rf-library.md +33 -0
  13. sysml2kit-0.1.0/docs/traceability.md +47 -0
  14. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/mkdocs.yml +9 -0
  15. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/__init__.py +5 -3
  16. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/_version.py +2 -2
  17. sysml2kit-0.1.0/src/sysml2kit/api/__init__.py +7 -0
  18. sysml2kit-0.1.0/src/sysml2kit/api/client.py +120 -0
  19. sysml2kit-0.1.0/src/sysml2kit/api/errors.py +12 -0
  20. sysml2kit-0.1.0/src/sysml2kit/api/models.py +36 -0
  21. sysml2kit-0.1.0/src/sysml2kit/backends/__init__.py +31 -0
  22. sysml2kit-0.1.0/src/sysml2kit/backends/protocol.py +38 -0
  23. sysml2kit-0.1.0/src/sysml2kit/backends/sysmlpy.py +119 -0
  24. sysml2kit-0.1.0/src/sysml2kit/cli.py +143 -0
  25. sysml2kit-0.1.0/src/sysml2kit/diff.py +106 -0
  26. sysml2kit-0.1.0/src/sysml2kit/graph.py +36 -0
  27. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/reader.py +2 -2
  28. sysml2kit-0.1.0/src/sysml2kit/interop/__init__.py +5 -0
  29. sysml2kit-0.1.0/src/sysml2kit/interop/requirements.py +113 -0
  30. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/container.py +8 -0
  31. sysml2kit-0.1.0/src/sysml2kit/query.py +133 -0
  32. sysml2kit-0.1.0/src/sysml2kit/text/__init__.py +5 -0
  33. sysml2kit-0.1.0/src/sysml2kit/text/keywords.py +52 -0
  34. sysml2kit-0.1.0/src/sysml2kit/text/writer.py +179 -0
  35. sysml2kit-0.1.0/src/sysml2kit/validation.py +186 -0
  36. sysml2kit-0.1.0/tests/conftest.py +46 -0
  37. sysml2kit-0.1.0/tests/test_api_client.py +87 -0
  38. sysml2kit-0.1.0/tests/test_backend_sysmlpy.py +54 -0
  39. sysml2kit-0.1.0/tests/test_cli.py +87 -0
  40. sysml2kit-0.1.0/tests/test_diff.py +61 -0
  41. sysml2kit-0.1.0/tests/test_interchange.py +56 -0
  42. sysml2kit-0.1.0/tests/test_interop.py +80 -0
  43. sysml2kit-0.1.0/tests/test_model.py +105 -0
  44. sysml2kit-0.1.0/tests/test_query.py +59 -0
  45. sysml2kit-0.1.0/tests/test_roundtrip_json.py +45 -0
  46. sysml2kit-0.1.0/tests/test_roundtrip_text.py +44 -0
  47. sysml2kit-0.1.0/tests/test_text_writer/test_vehicle_golden.sysml +27 -0
  48. sysml2kit-0.1.0/tests/test_text_writer.py +51 -0
  49. sysml2kit-0.1.0/tests/test_validation.py +82 -0
  50. sysml2kit-0.1.0/tests/test_values_units.py +42 -0
  51. sysml2kit-0.0.1/CHANGELOG.md +0 -12
  52. sysml2kit-0.0.1/docs/index.md +0 -19
  53. sysml2kit-0.0.1/src/sysml2kit/cli.py +0 -26
  54. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.gitignore +0 -0
  55. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.pre-commit-config.yaml +0 -0
  56. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/.python-version +0 -0
  57. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CITATION.cff +0 -0
  58. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CLAUDE.md +0 -0
  59. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/CONTRIBUTING.md +0 -0
  60. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/LICENSE +0 -0
  61. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/NOTICE +0 -0
  62. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/SECURITY.md +0 -0
  63. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/SPEC.md +0 -0
  64. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/pyproject.toml +0 -0
  65. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/scripts/slopcheck.sh +0 -0
  66. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/__init__.py +0 -0
  67. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/typemap.py +0 -0
  68. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/interchange/writer.py +0 -0
  69. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/__init__.py +0 -0
  70. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/analysis.py +0 -0
  71. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/base.py +0 -0
  72. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/builder.py +0 -0
  73. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/metadata.py +0 -0
  74. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/relations.py +0 -0
  75. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/requirements.py +0 -0
  76. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/structure.py +0 -0
  77. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/model/values.py +0 -0
  78. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/py.typed +0 -0
  79. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/src/sysml2kit/units.py +0 -0
  80. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/tests/test_package.py +0 -0
  81. {sysml2kit-0.0.1 → sysml2kit-0.1.0}/tools/conformance/run_oracle.py +0 -0
  82. {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.1
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.0.x releases claim the package name and publish
48
- > the skeleton while the core lands. Pin an exact version if you depend on it.
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.0.x releases claim the package name and publish
12
- > the skeleton while the core lands. Pin an exact version if you depend on it.
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
- The 0.0.x releases are a published skeleton; the object model, writer, and
4
- queries land in 0.1.0. See https://github.com/jman4162/sysml2kit.
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.1'
22
- __version_tuple__ = version_tuple = (0, 0, 1)
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