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 :param s:
119 The string contained serialized SysML model in JSON array.
120 :param document:
121 The document the model will be deserialized into.
122 :param attributes:
123 Attribute mapping of ``s``. If none provided, this will attempt to infer
124 a corresponding mapping or raise a ``ValueError``.
125 :return:
126 Model deserialized from JSON array. Note that references into other
127 documents will not be resolved, users will need to resolve them by
128 calling ``link`` on the returned model. See also :py:class:`IdMap
129 <syside.IdMap>`.
130 """
131
132
133@overload
134def loads(
135 s: str,
136 document: syside.Url | str,
137 attributes: syside.AttributeMap | None = None,
138) -> tuple[syside.DeserializedModel, syside.SharedMutex[syside.Document]]:
139 """
140 Create a new ``document`` and deserialize a model from ``s`` into it.
141
142 :param s:
143 The string contained serialized SysML model in JSON array.
144 :param document:
145 A URI in the form of :py:class:`Url <syside.Url>` or a string, new
146 document will be created with. If URI path has no extension, or the
147 extension does not match ``sysml`` or ``kerml``, ``ValueError`` is
148 raised.
149 :param attributes:
150 Attribute mapping of ``s``. If none provided, this will attempt to infer
151 a corresponding mapping or raise a ``ValueError``.
152 :return:
153 Model deserialized from JSON array and the newly created document. Note that
154 references into other documents will not be resolved, users will need to
155 resolve them by calling ``link`` on the returned model. See also
156 :py:class:`IdMap <syside.IdMap>`.
157 """
158
159
[docs]
160def loads(
161 s: str,
162 document: syside.Document | syside.Url | str,
163 attributes: syside.AttributeMap | None = None,
164) -> (
165 syside.DeserializedModel
166 | tuple[syside.DeserializedModel, syside.SharedMutex[syside.Document]]
167):
168 """loads implementation"""
169 reader = syside.JsonReader()
170
171 new_doc: syside.SharedMutex[syside.Document] | None = None
172 if isinstance(document, str):
173 document = syside.Url(document)
174 if isinstance(document, syside.Url):
175 ext = document.path.rsplit(".", 1)[-1].lower()
176 if ext == "sysml":
177 lang = syside.ModelLanguage.SysML
178 elif ext == "kerml":
179 lang = syside.ModelLanguage.KerML
180 else:
181 raise ValueError(f"Unknown document language, could not infer from '{ext}'")
182 new_doc = syside.Document.create_st(url=document, language=lang)
183 with new_doc.lock() as doc:
184 document = doc # appease pylint
185
186 with reader.bind(s) as json:
187 if attributes is None:
188 attributes = json.attribute_hint()
189 if attributes is None:
190 raise ValueError("Cannot deserialize model with unmapped attributes")
191
192 model, report = syside.deserialize(document, json, attributes)
193
194 if not report:
195 raise DeserializationError(model, report)
196
197 for msg in report.messages:
198 if msg.severity == syside.DiagnosticSeverity.Warning:
199 warn(msg.message, category=SerdeWarning, stacklevel=2)
200
201 if new_doc:
202 return model, new_doc
203 return model