sysml2kit 0.0.1__py3-none-any.whl
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/__init__.py +14 -0
- sysml2kit/_version.py +24 -0
- sysml2kit/cli.py +26 -0
- sysml2kit/interchange/__init__.py +13 -0
- sysml2kit/interchange/reader.py +106 -0
- sysml2kit/interchange/typemap.py +62 -0
- sysml2kit/interchange/writer.py +73 -0
- sysml2kit/model/__init__.py +59 -0
- sysml2kit/model/analysis.py +17 -0
- sysml2kit/model/base.py +73 -0
- sysml2kit/model/builder.py +239 -0
- sysml2kit/model/container.py +220 -0
- sysml2kit/model/metadata.py +19 -0
- sysml2kit/model/relations.py +26 -0
- sysml2kit/model/requirements.py +23 -0
- sysml2kit/model/structure.py +60 -0
- sysml2kit/model/values.py +31 -0
- sysml2kit/py.typed +0 -0
- sysml2kit/units.py +60 -0
- sysml2kit-0.0.1.dist-info/METADATA +166 -0
- sysml2kit-0.0.1.dist-info/RECORD +25 -0
- sysml2kit-0.0.1.dist-info/WHEEL +4 -0
- sysml2kit-0.0.1.dist-info/entry_points.txt +2 -0
- sysml2kit-0.0.1.dist-info/licenses/LICENSE +202 -0
- sysml2kit-0.0.1.dist-info/licenses/NOTICE +10 -0
sysml2kit/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""API-first Python tooling for building, querying, validating, and automating SysML v2 models.
|
|
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.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = version("sysml2kit")
|
|
11
|
+
except PackageNotFoundError: # running from a source tree without an install
|
|
12
|
+
__version__ = "0.0.0.dev0"
|
|
13
|
+
|
|
14
|
+
__all__ = ["__version__"]
|
sysml2kit/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
sysml2kit/cli.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Command line for sysml2kit.
|
|
2
|
+
|
|
3
|
+
Subcommands ``show``, ``validate``, ``diff``, and ``export`` arrive with the
|
|
4
|
+
0.1.0 core; this module currently exposes ``version`` only.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
import sysml2kit
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help=__doc__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.callback()
|
|
15
|
+
def main() -> None:
|
|
16
|
+
"""Work with SysML v2 models from the command line."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def version() -> None:
|
|
21
|
+
"""Print the installed sysml2kit version."""
|
|
22
|
+
typer.echo(sysml2kit.__version__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
app()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Systems Modeling API JSON interchange: the primary lossless format."""
|
|
2
|
+
|
|
3
|
+
from sysml2kit.interchange.reader import InterchangeError, model_from_json, record_to_element
|
|
4
|
+
from sysml2kit.interchange.writer import element_to_record, model_to_json, write_json
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"InterchangeError",
|
|
8
|
+
"element_to_record",
|
|
9
|
+
"model_from_json",
|
|
10
|
+
"model_to_json",
|
|
11
|
+
"record_to_element",
|
|
12
|
+
"write_json",
|
|
13
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Read Systems Modeling API JSON records into a Model.
|
|
2
|
+
|
|
3
|
+
Unknown ``@type`` records become :class:`OpaqueElement` with the raw record
|
|
4
|
+
preserved verbatim (and ownership links kept), so re-export reproduces them
|
|
5
|
+
unchanged.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
|
|
15
|
+
from pydantic import ValidationError
|
|
16
|
+
|
|
17
|
+
from sysml2kit.model.base import Element, OpaqueElement, Ref
|
|
18
|
+
from sysml2kit.model.container import Model
|
|
19
|
+
from sysml2kit.model.values import AttributeValue
|
|
20
|
+
|
|
21
|
+
from .typemap import TYPE_TO_CLASS
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InterchangeError(ValueError):
|
|
25
|
+
"""A record could not be turned into an element."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _snake(name: str) -> str:
|
|
29
|
+
out = []
|
|
30
|
+
for ch in name:
|
|
31
|
+
if ch.isupper():
|
|
32
|
+
out.append("_")
|
|
33
|
+
out.append(ch.lower())
|
|
34
|
+
else:
|
|
35
|
+
out.append(ch)
|
|
36
|
+
return "".join(out)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _decode(field_type: Any, value: Any) -> Any:
|
|
40
|
+
if isinstance(value, dict) and set(value) == {"@id"}:
|
|
41
|
+
return Ref(target=UUID(value["@id"]))
|
|
42
|
+
if isinstance(value, dict) and field_type is AttributeValue:
|
|
43
|
+
return AttributeValue(**value)
|
|
44
|
+
if isinstance(value, list):
|
|
45
|
+
return [_decode(None, item) for item in value]
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def record_to_element(record: dict[str, Any]) -> Element:
|
|
50
|
+
"""Deserialize one interchange record to an element."""
|
|
51
|
+
type_name = record.get("@type")
|
|
52
|
+
if not isinstance(type_name, str):
|
|
53
|
+
raise InterchangeError(f"record without a string @type: {record.get('@id', '?')}")
|
|
54
|
+
eid = record.get("@id")
|
|
55
|
+
if not isinstance(eid, str):
|
|
56
|
+
raise InterchangeError(f"record without a string @id (type {type_name})")
|
|
57
|
+
cls = TYPE_TO_CLASS.get(type_name)
|
|
58
|
+
if cls is None:
|
|
59
|
+
return OpaqueElement(element_id=UUID(eid), type_name=type_name, raw=dict(record))
|
|
60
|
+
kwargs: dict[str, Any] = {"element_id": UUID(eid)}
|
|
61
|
+
fields = cls.model_fields
|
|
62
|
+
for key, value in record.items():
|
|
63
|
+
if key in {"@id", "@type", "owningRelatedElement"}:
|
|
64
|
+
continue
|
|
65
|
+
field = _snake(key)
|
|
66
|
+
if field not in fields:
|
|
67
|
+
continue
|
|
68
|
+
annotation = fields[field].annotation
|
|
69
|
+
target_type = (
|
|
70
|
+
AttributeValue if annotation in (AttributeValue, AttributeValue | None) else None
|
|
71
|
+
)
|
|
72
|
+
kwargs[field] = _decode(target_type, value)
|
|
73
|
+
try:
|
|
74
|
+
return cls(**kwargs)
|
|
75
|
+
except ValidationError as exc:
|
|
76
|
+
raise InterchangeError(f"invalid {type_name} record {eid}: {exc}") from exc
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def model_from_json(data: list[dict[str, Any]] | str | Path) -> Model:
|
|
80
|
+
"""Build a Model from a record list, a JSON string, or a file path."""
|
|
81
|
+
if isinstance(data, Path):
|
|
82
|
+
records = json.loads(data.read_text())
|
|
83
|
+
elif isinstance(data, str):
|
|
84
|
+
source = Path(data)
|
|
85
|
+
text = source.read_text() if source.exists() else data
|
|
86
|
+
records = json.loads(text)
|
|
87
|
+
else:
|
|
88
|
+
records = data
|
|
89
|
+
if not isinstance(records, list):
|
|
90
|
+
raise InterchangeError("interchange JSON must be a list of records")
|
|
91
|
+
|
|
92
|
+
model = Model()
|
|
93
|
+
owners: dict[UUID, UUID] = {}
|
|
94
|
+
for record in records:
|
|
95
|
+
element = record_to_element(record)
|
|
96
|
+
model.elements[element.element_id] = element
|
|
97
|
+
owning = record.get("owningRelatedElement")
|
|
98
|
+
if isinstance(owning, dict) and "@id" in owning:
|
|
99
|
+
owners[element.element_id] = UUID(owning["@id"])
|
|
100
|
+
|
|
101
|
+
for eid, oid in owners.items():
|
|
102
|
+
if oid in model.elements:
|
|
103
|
+
model.owner[eid] = oid
|
|
104
|
+
model.owned.setdefault(oid, []).append(eid)
|
|
105
|
+
model.roots = [eid for eid in model.elements if eid not in model.owner]
|
|
106
|
+
return model
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""The ``@type`` vocabulary mapping, pinned to SysML-v2-Release 2026-05.
|
|
2
|
+
|
|
3
|
+
This is the only module allowed to hard-code spec ``@type`` strings; spec
|
|
4
|
+
bumps start (and mostly end) here. The reified traceability relationships
|
|
5
|
+
serialize under the closest standard names with a simplified
|
|
6
|
+
``source``/``target`` structure — a documented deviation, see SPEC.md.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from sysml2kit.model.analysis import AnalysisCaseDefinition, AnalysisCaseUsage
|
|
12
|
+
from sysml2kit.model.base import Element
|
|
13
|
+
from sysml2kit.model.metadata import MetadataDefinition, MetadataUsage
|
|
14
|
+
from sysml2kit.model.relations import (
|
|
15
|
+
AllocateRelationship,
|
|
16
|
+
DeriveRelationship,
|
|
17
|
+
SatisfyRelationship,
|
|
18
|
+
VerifyRelationship,
|
|
19
|
+
)
|
|
20
|
+
from sysml2kit.model.requirements import (
|
|
21
|
+
ConstraintUsage,
|
|
22
|
+
RequirementDefinition,
|
|
23
|
+
RequirementUsage,
|
|
24
|
+
)
|
|
25
|
+
from sysml2kit.model.structure import (
|
|
26
|
+
AttributeDefinition,
|
|
27
|
+
AttributeUsage,
|
|
28
|
+
ConnectionUsage,
|
|
29
|
+
InterfaceDefinition,
|
|
30
|
+
Package,
|
|
31
|
+
PartDefinition,
|
|
32
|
+
PartUsage,
|
|
33
|
+
PortDefinition,
|
|
34
|
+
PortUsage,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
#: Class -> interchange ``@type`` string.
|
|
38
|
+
CLASS_TO_TYPE: dict[type[Element], str] = {
|
|
39
|
+
Package: "Package",
|
|
40
|
+
PartDefinition: "PartDefinition",
|
|
41
|
+
PartUsage: "PartUsage",
|
|
42
|
+
PortDefinition: "PortDefinition",
|
|
43
|
+
PortUsage: "PortUsage",
|
|
44
|
+
InterfaceDefinition: "InterfaceDefinition",
|
|
45
|
+
ConnectionUsage: "ConnectionUsage",
|
|
46
|
+
AttributeDefinition: "AttributeDefinition",
|
|
47
|
+
AttributeUsage: "AttributeUsage",
|
|
48
|
+
RequirementDefinition: "RequirementDefinition",
|
|
49
|
+
RequirementUsage: "RequirementUsage",
|
|
50
|
+
ConstraintUsage: "ConstraintUsage",
|
|
51
|
+
AnalysisCaseDefinition: "AnalysisCaseDefinition",
|
|
52
|
+
AnalysisCaseUsage: "AnalysisCaseUsage",
|
|
53
|
+
MetadataDefinition: "MetadataDefinition",
|
|
54
|
+
MetadataUsage: "MetadataUsage",
|
|
55
|
+
SatisfyRelationship: "SatisfyRequirementUsage",
|
|
56
|
+
VerifyRelationship: "VerifyRequirementUsage",
|
|
57
|
+
DeriveRelationship: "DeriveRequirementUsage",
|
|
58
|
+
AllocateRelationship: "AllocationUsage",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#: Interchange ``@type`` string -> class (inverse of CLASS_TO_TYPE).
|
|
62
|
+
TYPE_TO_CLASS: dict[str, type[Element]] = {name: cls for cls, name in CLASS_TO_TYPE.items()}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Write a Model as Systems Modeling API JSON records.
|
|
2
|
+
|
|
3
|
+
Output is deterministic — elements sorted by qualified name, keys sorted
|
|
4
|
+
within each record — so committed interchange files diff cleanly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
from uuid import UUID
|
|
13
|
+
|
|
14
|
+
from sysml2kit.model.base import Element, OpaqueElement, Ref
|
|
15
|
+
from sysml2kit.model.container import Model
|
|
16
|
+
from sysml2kit.model.values import AttributeValue
|
|
17
|
+
|
|
18
|
+
from .typemap import CLASS_TO_TYPE
|
|
19
|
+
|
|
20
|
+
#: Element fields serialized by the generic path for every class.
|
|
21
|
+
_COMMON_FIELDS = ("declared_name", "declared_short_name", "doc")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _camel(name: str) -> str:
|
|
25
|
+
head, *tail = name.split("_")
|
|
26
|
+
return head + "".join(word.capitalize() for word in tail)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _encode(value: Any) -> Any:
|
|
30
|
+
if isinstance(value, Ref):
|
|
31
|
+
return {"@id": str(value.target)}
|
|
32
|
+
if isinstance(value, UUID):
|
|
33
|
+
return {"@id": str(value)}
|
|
34
|
+
if isinstance(value, AttributeValue):
|
|
35
|
+
return {k: v for k, v in value.model_dump().items() if v is not None}
|
|
36
|
+
if isinstance(value, list):
|
|
37
|
+
return [_encode(item) for item in value]
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def element_to_record(model: Model, element: Element) -> dict[str, Any]:
|
|
42
|
+
"""Serialize one element to its interchange record."""
|
|
43
|
+
if isinstance(element, OpaqueElement):
|
|
44
|
+
return dict(element.raw)
|
|
45
|
+
type_name = CLASS_TO_TYPE.get(type(element))
|
|
46
|
+
if type_name is None:
|
|
47
|
+
raise TypeError(f"no @type mapping for {type(element).__name__}")
|
|
48
|
+
record: dict[str, Any] = {"@id": str(element.element_id), "@type": type_name}
|
|
49
|
+
owner = model.owner.get(element.element_id)
|
|
50
|
+
if owner is not None:
|
|
51
|
+
record["owningRelatedElement"] = {"@id": str(owner)}
|
|
52
|
+
for field in type(element).model_fields:
|
|
53
|
+
if field == "element_id":
|
|
54
|
+
continue
|
|
55
|
+
value = getattr(element, field)
|
|
56
|
+
if value is None or value == [] or value == {}:
|
|
57
|
+
continue
|
|
58
|
+
record[_camel(field)] = _encode(value)
|
|
59
|
+
return record
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def model_to_json(model: Model) -> list[dict[str, Any]]:
|
|
63
|
+
"""Serialize the model to a sorted list of interchange records."""
|
|
64
|
+
ordered = sorted(model.elements.values(), key=lambda el: model.qualified_name(el))
|
|
65
|
+
return [
|
|
66
|
+
{k: record[k] for k in sorted(record)}
|
|
67
|
+
for record in (element_to_record(model, el) for el in ordered)
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def write_json(model: Model, path: str | Path) -> None:
|
|
72
|
+
"""Write the model to a JSON file with a trailing newline."""
|
|
73
|
+
Path(path).write_text(json.dumps(model_to_json(model), indent=2) + "\n")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""The sysml2kit object model: the pragmatic profile plus the Model container."""
|
|
2
|
+
|
|
3
|
+
from sysml2kit.model.analysis import AnalysisCaseDefinition, AnalysisCaseUsage
|
|
4
|
+
from sysml2kit.model.base import Element, OpaqueElement, Ref, Relationship
|
|
5
|
+
from sysml2kit.model.container import STABLE_ID_NAMESPACE, Model
|
|
6
|
+
from sysml2kit.model.metadata import MetadataDefinition, MetadataUsage
|
|
7
|
+
from sysml2kit.model.relations import (
|
|
8
|
+
AllocateRelationship,
|
|
9
|
+
DeriveRelationship,
|
|
10
|
+
SatisfyRelationship,
|
|
11
|
+
VerifyRelationship,
|
|
12
|
+
)
|
|
13
|
+
from sysml2kit.model.requirements import (
|
|
14
|
+
ConstraintUsage,
|
|
15
|
+
RequirementDefinition,
|
|
16
|
+
RequirementUsage,
|
|
17
|
+
)
|
|
18
|
+
from sysml2kit.model.structure import (
|
|
19
|
+
AttributeDefinition,
|
|
20
|
+
AttributeUsage,
|
|
21
|
+
ConnectionUsage,
|
|
22
|
+
InterfaceDefinition,
|
|
23
|
+
Package,
|
|
24
|
+
PartDefinition,
|
|
25
|
+
PartUsage,
|
|
26
|
+
PortDefinition,
|
|
27
|
+
PortUsage,
|
|
28
|
+
)
|
|
29
|
+
from sysml2kit.model.values import AttributeValue
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"STABLE_ID_NAMESPACE",
|
|
33
|
+
"AllocateRelationship",
|
|
34
|
+
"AnalysisCaseDefinition",
|
|
35
|
+
"AnalysisCaseUsage",
|
|
36
|
+
"AttributeDefinition",
|
|
37
|
+
"AttributeUsage",
|
|
38
|
+
"AttributeValue",
|
|
39
|
+
"ConnectionUsage",
|
|
40
|
+
"ConstraintUsage",
|
|
41
|
+
"DeriveRelationship",
|
|
42
|
+
"Element",
|
|
43
|
+
"InterfaceDefinition",
|
|
44
|
+
"MetadataDefinition",
|
|
45
|
+
"MetadataUsage",
|
|
46
|
+
"Model",
|
|
47
|
+
"OpaqueElement",
|
|
48
|
+
"Package",
|
|
49
|
+
"PartDefinition",
|
|
50
|
+
"PartUsage",
|
|
51
|
+
"PortDefinition",
|
|
52
|
+
"PortUsage",
|
|
53
|
+
"Ref",
|
|
54
|
+
"Relationship",
|
|
55
|
+
"RequirementDefinition",
|
|
56
|
+
"RequirementUsage",
|
|
57
|
+
"SatisfyRelationship",
|
|
58
|
+
"VerifyRelationship",
|
|
59
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Analysis case elements (stubs in v0.1: subject and objective, no actions)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from sysml2kit.model.base import Element, Ref
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AnalysisCaseDefinition(Element):
|
|
9
|
+
"""A reusable definition of an analysis to run against a subject."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AnalysisCaseUsage(Element):
|
|
13
|
+
"""An analysis occurrence with a subject and an objective statement."""
|
|
14
|
+
|
|
15
|
+
definition: Ref | None = None
|
|
16
|
+
subject: Ref | None = None
|
|
17
|
+
objective: str | None = None
|
sysml2kit/model/base.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Base element classes and the cross-reference type.
|
|
2
|
+
|
|
3
|
+
Every cross-reference between elements is a :class:`Ref` (a UUID wrapper),
|
|
4
|
+
never a direct Python object reference, so any element serializes on its own
|
|
5
|
+
and maps 1:1 onto the Systems Modeling API JSON ``{"@id": ...}`` form.
|
|
6
|
+
Ownership is not stored on elements either; the ``Model`` container keeps it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
from uuid import UUID, uuid4
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sysml2kit.model.container import Model
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Ref(BaseModel):
|
|
21
|
+
"""Reference to another element by id."""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(frozen=True)
|
|
24
|
+
|
|
25
|
+
target: UUID
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def to(cls, element: Element | UUID | Ref) -> Ref:
|
|
29
|
+
"""Build a Ref from an element, a UUID, or another Ref."""
|
|
30
|
+
if isinstance(element, Ref):
|
|
31
|
+
return element
|
|
32
|
+
if isinstance(element, UUID):
|
|
33
|
+
return cls(target=element)
|
|
34
|
+
return cls(target=element.element_id)
|
|
35
|
+
|
|
36
|
+
def resolve(self, model: Model) -> Element:
|
|
37
|
+
"""Return the referenced element, raising KeyError if absent."""
|
|
38
|
+
return model.elements[self.target]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Element(BaseModel):
|
|
42
|
+
"""Common base for every model element in the pragmatic profile."""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(validate_assignment=True)
|
|
45
|
+
|
|
46
|
+
element_id: UUID = Field(default_factory=uuid4)
|
|
47
|
+
declared_name: str | None = None
|
|
48
|
+
declared_short_name: str | None = None
|
|
49
|
+
doc: str | None = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def label(self) -> str:
|
|
53
|
+
"""A human-readable identifier: name, short name, or the id."""
|
|
54
|
+
return self.declared_name or self.declared_short_name or str(self.element_id)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Relationship(Element):
|
|
58
|
+
"""Common base for reified relationships with a source and a target."""
|
|
59
|
+
|
|
60
|
+
source: Ref
|
|
61
|
+
target: Ref
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class OpaqueElement(Element):
|
|
65
|
+
"""An element outside the pragmatic profile, preserved verbatim.
|
|
66
|
+
|
|
67
|
+
``raw`` holds the original JSON interchange record; it re-exports
|
|
68
|
+
unchanged, so reading and writing a model does not drop content the
|
|
69
|
+
profile has no class for.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
type_name: str
|
|
73
|
+
raw: dict[str, Any] = Field(default_factory=dict)
|