1"""
2Convenience module intending to match the standard library ``json`` module.
3"""
4
5from typing import overload
6from warnings import warn
7from dataclasses import dataclass
8
9from .. import core as syside
10
11
[docs]
12@dataclass
13class SerializationError(Exception):
14 """
15 Error serializing element to SysML v2 JSON.
16 """
17
18 report: syside.SerdeReport[syside.Element]
19
20
[docs]
21@dataclass
22class DeserializationError(Exception):
23 """
24 Error serializing element to SysML v2 JSON.
25 """
26
27 model: syside.DeserializedModel
28 report: syside.SerdeReport[syside.Element | str | syside.DocumentSegment]
29
30
[docs]
31class SerdeWarning(Warning):
32 """
33 Class for warnings from serialization and deserialization
34 """
35
36
[docs]
37def dumps(
38 element: syside.Element,
39 options: syside.SerializationOptions,
40 indent: int = 2,
41 use_spaces: bool = True,
42 final_new_line: bool = True,
43 include_cross_ref_uris: bool = True,
44) -> str:
45 """
46 Serialize ``element`` to a SysML v2 JSON ``str``.
47
48 See the documentation of the :py:class:`SerializationOptions
49 <syside.SerializationOptions>` class for documentation of the possible
50 options. The options object constructed with
51 :py:meth:`SerializationOptions.minimal
52 <syside.SerializationOptions.minimal>` instructs to produce a minimal JSON
53 without any redundant elements that results in significantly smaller JSONs.
54 Examples of redundant information that is avoided using minimal
55 configuration are:
56
57 + including fields for null values;
58 + including fields whose values match the default values;
59 + including redefined fields that are duplicates of redefining fields;
60 + including derived fields that can be computed from minimal JSON (for
61 example, the result value of evaluating an expression);
62 + including implied relationships.
63
64 .. note::
65
66 SysIDE does not construct all derived properties yet. Therefore, setting
67 ``options.include_derived`` to ``True`` may result in a JSON that does
68 not satisfy the schema.
69
70 :param element:
71 The SysML v2 element to be serialized to SysML v2 JSON.
72 :param options:
73 The serialization options to use when serializing SysML v2 to JSON.
74 :param indent:
75 How many space or tab characters to use for indenting the JSON.
76 :param use_spaces:
77 Whether use spaces or tabs for indentation.
78 :param final_new_line:
79 Whether to add a newline character at the end of the generated string.
80 :param include_cross_ref_uris:
81 Whether to add potentially relative URIs as ``@uri`` property to
82 references of Elements from documents other than the one owning
83 ``element``. Note that while such references are non-standard, they
84 match the behaviour of XMI exports in Pilot implementation which use
85 relative URIs for references instead of plain element IDs.
86 :return:
87 ``element`` serialized as JSON.
88 """
89
90 writer = syside.JsonStringWriter(
91 indent=indent,
92 use_spaces=use_spaces,
93 final_new_line=final_new_line,
94 include_cross_ref_uris=include_cross_ref_uris,
95 )
96
97 report = syside.serialize(element, writer, options)
98
99 if not report:
100 raise SerializationError(report)
101
102 for msg in report.messages:
103 if msg.severity == syside.DiagnosticSeverity.Warning:
104 warn(msg.message, category=SerdeWarning, stacklevel=2)
105
106 return writer.result
107
108
109@overload
110def loads(
111 s: str,
112 document: syside.Document,
113 attributes: syside.AttributeMap | None = None,
114) -> syside.DeserializedModel:
115 """
116 Deserialize a model from ``s`` into an already existing ``document``.
117
118 Root node will be inferred as:
119
120 1. The first ``Namespace`` (not subtype) without an owning relationship.
121 2. The first ``Element`` that has no serialized owning related element or owning relationship,
122 starting from the first element in the JSON array, and following owning elements up.
123 3. The first element in the array otherwise.
124
125 :param s:
126 The string contained serialized SysML model in JSON array.
127 :param document:
128 The document the model will be deserialized into.
129 :param attributes:
130 Attribute mapping of ``s``. If none provided, this will attempt to infer
131 a corresponding mapping or raise a ``ValueError``.
132 :return:
133 Model deserialized from JSON array. Note that references into other
134 documents will not be resolved, users will need to resolve them by
135 calling ``link`` on the returned model. See also :py:class:`IdMap
136 <syside.IdMap>`.
137 """
138
139
140@overload
141def loads(
142 s: str,
143 document: syside.Url | str,
144 attributes: syside.AttributeMap | None = None,
145) -> tuple[syside.DeserializedModel, syside.SharedMutex[syside.Document]]:
146 """
147 Create a new ``document`` and deserialize a model from ``s`` into it.
148
149 Root node will be inferred as:
150
151 1. The first ``Namespace`` (not subtype) without an owning relationship.
152 2. The first ``Element`` that has no serialized owning related element or owning relationship,
153 starting from the first element in the JSON array, and following owning elements up.
154 3. The first element in the array otherwise.
155
156 :param s:
157 The string contained serialized SysML model in JSON array.
158 :param document:
159 A URI in the form of :py:class:`Url <syside.Url>` or a string, new
160 document will be created with. If URI path has no extension, or the
161 extension does not match ``sysml`` or ``kerml``, ``ValueError`` is
162 raised.
163 :param attributes:
164 Attribute mapping of ``s``. If none provided, this will attempt to infer
165 a corresponding mapping or raise a ``ValueError``.
166 :return:
167 Model deserialized from JSON array and the newly created document. Note that
168 references into other documents will not be resolved, users will need to
169 resolve them by calling ``link`` on the returned model. See also
170 :py:class:`IdMap <syside.IdMap>`.
171 """
172
173
[docs]
174def loads(
175 s: str,
176 document: syside.Document | syside.Url | str,
177 attributes: syside.AttributeMap | None = None,
178) -> (
179 syside.DeserializedModel
180 | tuple[syside.DeserializedModel, syside.SharedMutex[syside.Document]]
181):
182 """loads implementation"""
183 reader = syside.JsonReader()
184
185 new_doc: syside.SharedMutex[syside.Document] | None = None
186 if isinstance(document, str):
187 document = syside.Url(document)
188 if isinstance(document, syside.Url):
189 ext = document.path.rsplit(".", 1)[-1].lower()
190 if ext == "sysml":
191 lang = syside.ModelLanguage.SysML
192 elif ext == "kerml":
193 lang = syside.ModelLanguage.KerML
194 else:
195 raise ValueError(f"Unknown document language, could not infer from '{ext}'")
196 new_doc = syside.Document.create_st(url=document, language=lang)
197 with new_doc.lock() as doc:
198 document = doc # appease pylint
199
200 with reader.bind(s) as json:
201 if attributes is None:
202 attributes = json.attribute_hint()
203 if attributes is None:
204 raise ValueError("Cannot deserialize model with unmapped attributes")
205
206 model, report = syside.deserialize(document, json, attributes)
207
208 if not report:
209 raise DeserializationError(model, report)
210
211 for msg in report.messages:
212 if msg.severity == syside.DiagnosticSeverity.Warning:
213 warn(msg.message, category=SerdeWarning, stacklevel=2)
214
215 if new_doc:
216 return model, new_doc
217 return model