sdmxlib 0.8.0__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.
- sdmxlib/__init__.py +155 -0
- sdmxlib/api/__init__.py +7 -0
- sdmxlib/api/client.py +246 -0
- sdmxlib/api/filters.py +67 -0
- sdmxlib/api/providers.py +66 -0
- sdmxlib/api/query.py +428 -0
- sdmxlib/api/registry.py +666 -0
- sdmxlib/api/session.py +124 -0
- sdmxlib/formats/__init__.py +166 -0
- sdmxlib/formats/sdmx_csv/__init__.py +5 -0
- sdmxlib/formats/sdmx_csv/reader.py +147 -0
- sdmxlib/formats/sdmx_json/__init__.py +1 -0
- sdmxlib/formats/sdmx_json/reader.py +897 -0
- sdmxlib/formats/sdmx_json/writer.py +698 -0
- sdmxlib/formats/sdmx_ml21/__init__.py +5 -0
- sdmxlib/formats/sdmx_ml21/namespaces.py +12 -0
- sdmxlib/formats/sdmx_ml21/reader.py +1147 -0
- sdmxlib/formats/sdmx_ml21/writer.py +709 -0
- sdmxlib/formats/sdmx_ml30/__init__.py +1 -0
- sdmxlib/formats/sdmx_ml30/namespaces.py +12 -0
- sdmxlib/formats/sdmx_ml30/reader.py +1138 -0
- sdmxlib/formats/sdmx_ml30/writer.py +730 -0
- sdmxlib/local/__init__.py +6 -0
- sdmxlib/local/_registry.py +368 -0
- sdmxlib/model/__init__.py +122 -0
- sdmxlib/model/annotations.py +103 -0
- sdmxlib/model/base.py +61 -0
- sdmxlib/model/category.py +88 -0
- sdmxlib/model/codelist.py +91 -0
- sdmxlib/model/collections.py +382 -0
- sdmxlib/model/concept.py +121 -0
- sdmxlib/model/constraint.py +92 -0
- sdmxlib/model/convert.py +268 -0
- sdmxlib/model/dataflow.py +49 -0
- sdmxlib/model/dataset.py +337 -0
- sdmxlib/model/datastructure.py +390 -0
- sdmxlib/model/expr.py +120 -0
- sdmxlib/model/hierarchy.py +111 -0
- sdmxlib/model/istring.py +81 -0
- sdmxlib/model/mapping.py +134 -0
- sdmxlib/model/message.py +70 -0
- sdmxlib/model/organisation.py +149 -0
- sdmxlib/model/provision.py +45 -0
- sdmxlib/model/ref.py +195 -0
- sdmxlib/model/registry.py +191 -0
- sdmxlib/model/representation.py +99 -0
- sdmxlib/model/urn.py +130 -0
- sdmxlib/model/validation.py +338 -0
- sdmxlib/polars.py +392 -0
- sdmxlib/py.typed +0 -0
- sdmxlib/storage/__init__.py +28 -0
- sdmxlib/storage/lazy.py +329 -0
- sdmxlib/storage/readers.py +310 -0
- sdmxlib/storage/schema.py +289 -0
- sdmxlib/storage/writers.py +503 -0
- sdmxlib-0.8.0.dist-info/METADATA +102 -0
- sdmxlib-0.8.0.dist-info/RECORD +58 -0
- sdmxlib-0.8.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
"""SDMX-ML 2.1 writer: domain model → lxml → bytes.
|
|
2
|
+
|
|
3
|
+
Serialises a StructureMessage (or any subset of its artefacts) to a
|
|
4
|
+
valid SDMX-ML 2.1 Structure message.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from sdmxlib.formats.sdmx_ml21.writer import write
|
|
8
|
+
|
|
9
|
+
xml_bytes = write(msg)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from lxml import etree
|
|
15
|
+
|
|
16
|
+
from sdmxlib.model.category import Categorisation, Category, CategoryScheme
|
|
17
|
+
from sdmxlib.model.codelist import Code, Codelist
|
|
18
|
+
from sdmxlib.model.concept import Concept, ConceptScheme
|
|
19
|
+
from sdmxlib.model.constraint import ConstraintAttachment, ContentConstraint, CubeRegion
|
|
20
|
+
from sdmxlib.model.dataflow import Dataflow
|
|
21
|
+
from sdmxlib.model.datastructure import (
|
|
22
|
+
AttachmentLevel,
|
|
23
|
+
Attribute,
|
|
24
|
+
DataStructure,
|
|
25
|
+
Dimension,
|
|
26
|
+
Group,
|
|
27
|
+
Measure,
|
|
28
|
+
TimeDimension,
|
|
29
|
+
)
|
|
30
|
+
from sdmxlib.model.hierarchy import HierarchicalCode, Hierarchy
|
|
31
|
+
from sdmxlib.model.istring import InternationalString
|
|
32
|
+
from sdmxlib.model.mapping import (
|
|
33
|
+
CategorySchemeMap,
|
|
34
|
+
CodelistMap,
|
|
35
|
+
ConceptSchemeMap,
|
|
36
|
+
DataflowMap,
|
|
37
|
+
DataStructureMap,
|
|
38
|
+
StructureSet,
|
|
39
|
+
)
|
|
40
|
+
from sdmxlib.model.message import StructureMessage
|
|
41
|
+
from sdmxlib.model.organisation import Agency, AgencyScheme, DataProvider, DataProviderScheme
|
|
42
|
+
from sdmxlib.model.provision import ProvisionAgreement
|
|
43
|
+
from sdmxlib.model.ref import AnyRef, Ref
|
|
44
|
+
from sdmxlib.model.representation import Facet
|
|
45
|
+
from sdmxlib.model.urn import SdmxUrn
|
|
46
|
+
|
|
47
|
+
from .namespaces import COM, MES, STR
|
|
48
|
+
|
|
49
|
+
# ── Namespace helpers ─────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
_NSMAP: dict[Any, str] = {
|
|
52
|
+
"mes": MES,
|
|
53
|
+
"str": STR,
|
|
54
|
+
"com": COM,
|
|
55
|
+
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_XML_LANG = "{http://www.w3.org/XML/1998/namespace}lang"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _m(tag: str) -> str:
|
|
62
|
+
return f"{{{MES}}}{tag}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _s(tag: str) -> str:
|
|
66
|
+
return f"{{{STR}}}{tag}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _c(tag: str) -> str:
|
|
70
|
+
return f"{{{COM}}}{tag}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ── Common writers ────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _write_istring(
|
|
77
|
+
parent: etree._Element, istring: InternationalString | dict[str, str], child_tag: str = "Name"
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Append com:Name (or com:Description) children for each language."""
|
|
80
|
+
for lang in istring:
|
|
81
|
+
el = etree.SubElement(parent, _c(child_tag))
|
|
82
|
+
el.set(_XML_LANG, lang)
|
|
83
|
+
el.text = istring[lang] # type: ignore[index]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _write_maintainable_attrs(el: etree._Element, obj: Any) -> None:
|
|
87
|
+
"""Set id, agencyID, version, isFinal on a maintainable element."""
|
|
88
|
+
el.set("id", obj.id)
|
|
89
|
+
el.set("agencyID", obj.agency_id)
|
|
90
|
+
el.set("version", obj.version)
|
|
91
|
+
if getattr(obj, "is_final", False):
|
|
92
|
+
el.set("isFinal", "true")
|
|
93
|
+
if getattr(obj, "valid_from", None) is not None:
|
|
94
|
+
el.set("validFrom", obj.valid_from.isoformat())
|
|
95
|
+
if getattr(obj, "valid_to", None) is not None:
|
|
96
|
+
el.set("validTo", obj.valid_to.isoformat())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _urn_to_ref_el(urn: SdmxUrn[Any]) -> etree._Element:
|
|
100
|
+
"""Build a <Ref> element from an SdmxUrn."""
|
|
101
|
+
ref = etree.Element("Ref")
|
|
102
|
+
ref.set("agencyID", urn.agency)
|
|
103
|
+
ref.set("id", urn.id)
|
|
104
|
+
if urn.version != "latest":
|
|
105
|
+
ref.set("version", urn.version)
|
|
106
|
+
if urn.item_id is not None:
|
|
107
|
+
ref.set("maintainableParentID", urn.id)
|
|
108
|
+
ref.set("id", urn.item_id)
|
|
109
|
+
ref.set("maintainableParentVersion", urn.version if urn.version != "latest" else "1.0")
|
|
110
|
+
ref.set("class", urn.artefact_class)
|
|
111
|
+
ref.set("package", urn.package)
|
|
112
|
+
return ref
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _write_ref(parent: etree._Element, ref: AnyRef) -> None:
|
|
116
|
+
"""Append a <Ref> child carrying the reference URN."""
|
|
117
|
+
parent.append(_urn_to_ref_el(ref.urn))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _write_text_format(parent: etree._Element, tf: Facet) -> None: # noqa: C901
|
|
121
|
+
"""Append a str:TextFormat element."""
|
|
122
|
+
el = etree.SubElement(parent, _s("TextFormat"))
|
|
123
|
+
if tf.type is not None:
|
|
124
|
+
el.set("textType", tf.type)
|
|
125
|
+
if tf.max_length is not None:
|
|
126
|
+
el.set("maxLength", str(tf.max_length))
|
|
127
|
+
if tf.min_length is not None:
|
|
128
|
+
el.set("minLength", str(tf.min_length))
|
|
129
|
+
if tf.max_value is not None:
|
|
130
|
+
el.set("maxValue", str(tf.max_value))
|
|
131
|
+
if tf.min_value is not None:
|
|
132
|
+
el.set("minValue", str(tf.min_value))
|
|
133
|
+
if tf.decimals is not None:
|
|
134
|
+
el.set("decimals", str(tf.decimals))
|
|
135
|
+
if tf.pattern is not None:
|
|
136
|
+
el.set("pattern", tf.pattern)
|
|
137
|
+
if tf.start_value is not None:
|
|
138
|
+
el.set("startValue", str(tf.start_value))
|
|
139
|
+
if tf.end_value is not None:
|
|
140
|
+
el.set("endValue", str(tf.end_value))
|
|
141
|
+
if tf.is_sequence is not None:
|
|
142
|
+
el.set("isSequence", "true" if tf.is_sequence else "false")
|
|
143
|
+
if tf.is_multi_lingual is not None:
|
|
144
|
+
el.set("isMultiLingual", "true" if tf.is_multi_lingual else "false")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _write_representation(
|
|
148
|
+
parent: etree._Element,
|
|
149
|
+
representation: AnyRef | Facet | None,
|
|
150
|
+
tag: str = "LocalRepresentation",
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Append a str:LocalRepresentation (or str:CoreRepresentation) element."""
|
|
153
|
+
if representation is None:
|
|
154
|
+
return
|
|
155
|
+
rep_el = etree.SubElement(parent, _s(tag))
|
|
156
|
+
match representation:
|
|
157
|
+
case Facet():
|
|
158
|
+
_write_text_format(rep_el, representation)
|
|
159
|
+
case Ref():
|
|
160
|
+
enum_el = etree.SubElement(rep_el, _s("Enumeration"))
|
|
161
|
+
enum_el.append(_urn_to_ref_el(representation.urn))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _write_concept_identity(parent: etree._Element, ref: AnyRef) -> None:
|
|
165
|
+
"""Append a str:ConceptIdentity with a <Ref> child."""
|
|
166
|
+
ci = etree.SubElement(parent, _s("ConceptIdentity"))
|
|
167
|
+
_write_ref(ci, ref)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ── Codelists ─────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _write_code(parent: etree._Element, code: Code) -> None:
|
|
174
|
+
el = etree.SubElement(parent, _s("Code"))
|
|
175
|
+
el.set("id", code.id)
|
|
176
|
+
_write_istring(el, code.name)
|
|
177
|
+
if code.description:
|
|
178
|
+
_write_istring(el, code.description, "Description")
|
|
179
|
+
if code.parent_id is not None:
|
|
180
|
+
parent_el = etree.SubElement(el, _s("Parent"))
|
|
181
|
+
ref = etree.SubElement(parent_el, "Ref")
|
|
182
|
+
ref.set("id", code.parent_id)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _write_agency(parent: etree._Element, agency: Agency) -> None:
|
|
186
|
+
el = etree.SubElement(parent, _s("Agency"))
|
|
187
|
+
el.set("id", agency.id)
|
|
188
|
+
if agency.name:
|
|
189
|
+
_write_istring(el, agency.name)
|
|
190
|
+
if agency.description:
|
|
191
|
+
_write_istring(el, agency.description, "Description")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _write_agency_scheme(parent: etree._Element, scheme: AgencyScheme) -> None:
|
|
195
|
+
el = etree.SubElement(parent, _s("AgencyScheme"))
|
|
196
|
+
_write_maintainable_attrs(el, scheme)
|
|
197
|
+
if scheme.name:
|
|
198
|
+
_write_istring(el, scheme.name)
|
|
199
|
+
if scheme.description:
|
|
200
|
+
_write_istring(el, scheme.description, "Description")
|
|
201
|
+
for agency in scheme.agencies:
|
|
202
|
+
_write_agency(el, agency)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _write_data_provider(parent: etree._Element, provider: DataProvider) -> None:
|
|
206
|
+
el = etree.SubElement(parent, _s("DataProvider"))
|
|
207
|
+
el.set("id", provider.id)
|
|
208
|
+
if provider.name:
|
|
209
|
+
_write_istring(el, provider.name)
|
|
210
|
+
if provider.description:
|
|
211
|
+
_write_istring(el, provider.description, "Description")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _write_data_provider_scheme(parent: etree._Element, scheme: DataProviderScheme) -> None:
|
|
215
|
+
el = etree.SubElement(parent, _s("DataProviderScheme"))
|
|
216
|
+
_write_maintainable_attrs(el, scheme)
|
|
217
|
+
if scheme.name:
|
|
218
|
+
_write_istring(el, scheme.name)
|
|
219
|
+
if scheme.description:
|
|
220
|
+
_write_istring(el, scheme.description, "Description")
|
|
221
|
+
for provider in scheme.providers:
|
|
222
|
+
_write_data_provider(el, provider)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _write_codelist(parent: etree._Element, cl: Codelist) -> None:
|
|
226
|
+
el = etree.SubElement(parent, _s("Codelist"))
|
|
227
|
+
_write_maintainable_attrs(el, cl)
|
|
228
|
+
_write_istring(el, cl.name)
|
|
229
|
+
if cl.description:
|
|
230
|
+
_write_istring(el, cl.description, "Description")
|
|
231
|
+
for code in cl.codes:
|
|
232
|
+
_write_code(el, code)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── ConceptSchemes ────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _write_concept(parent: etree._Element, concept: Concept) -> None:
|
|
239
|
+
el = etree.SubElement(parent, _s("Concept"))
|
|
240
|
+
el.set("id", concept.id)
|
|
241
|
+
_write_istring(el, concept.name)
|
|
242
|
+
if concept.description:
|
|
243
|
+
_write_istring(el, concept.description, "Description")
|
|
244
|
+
if concept.core_representation is not None:
|
|
245
|
+
_write_representation(el, concept.core_representation, tag="CoreRepresentation")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _write_concept_scheme(parent: etree._Element, cs: ConceptScheme) -> None:
|
|
249
|
+
el = etree.SubElement(parent, _s("ConceptScheme"))
|
|
250
|
+
_write_maintainable_attrs(el, cs)
|
|
251
|
+
_write_istring(el, cs.name)
|
|
252
|
+
if cs.description:
|
|
253
|
+
_write_istring(el, cs.description, "Description")
|
|
254
|
+
for concept in cs.concepts:
|
|
255
|
+
_write_concept(el, concept)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ── DataStructures ────────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _write_dimension(parent: etree._Element, dim: Dimension, position: int) -> None:
|
|
262
|
+
el = etree.SubElement(parent, _s("Dimension"))
|
|
263
|
+
el.set("id", dim.id)
|
|
264
|
+
el.set("position", str(position))
|
|
265
|
+
if isinstance(dim.concept, Ref):
|
|
266
|
+
_write_concept_identity(el, dim.concept)
|
|
267
|
+
if isinstance(dim.representation, (Ref, Facet)):
|
|
268
|
+
_write_representation(el, dim.representation)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _write_time_dimension(parent: etree._Element, td: TimeDimension) -> None:
|
|
272
|
+
el = etree.SubElement(parent, _s("TimeDimension"))
|
|
273
|
+
el.set("id", td.id)
|
|
274
|
+
if isinstance(td.concept, Ref):
|
|
275
|
+
_write_concept_identity(el, td.concept)
|
|
276
|
+
if td.representation is not None:
|
|
277
|
+
local_rep = etree.SubElement(el, _s("LocalRepresentation"))
|
|
278
|
+
_write_text_format(local_rep, td.representation)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _write_attribute_relationship(parent: etree._Element, attr: Attribute) -> None:
|
|
282
|
+
ar = etree.SubElement(parent, _s("AttributeRelationship"))
|
|
283
|
+
match attr.attachment:
|
|
284
|
+
case AttachmentLevel.OBSERVATION:
|
|
285
|
+
pm = etree.SubElement(ar, _s("PrimaryMeasure"))
|
|
286
|
+
ref = etree.SubElement(pm, "Ref")
|
|
287
|
+
ref.set("id", attr.related_measure or "OBS_VALUE")
|
|
288
|
+
case AttachmentLevel.SERIES:
|
|
289
|
+
for dim_id in attr.related_dimensions or []:
|
|
290
|
+
dim_el = etree.SubElement(ar, _s("Dimension"))
|
|
291
|
+
ref = etree.SubElement(dim_el, "Ref")
|
|
292
|
+
ref.set("id", dim_id)
|
|
293
|
+
case AttachmentLevel.GROUP:
|
|
294
|
+
etree.SubElement(ar, _s("Group"))
|
|
295
|
+
case _:
|
|
296
|
+
etree.SubElement(ar, _s("None"))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _write_attribute(parent: etree._Element, attr: Attribute) -> None:
|
|
300
|
+
el = etree.SubElement(parent, _s("Attribute"))
|
|
301
|
+
el.set("id", attr.id)
|
|
302
|
+
if attr.usage is not None:
|
|
303
|
+
el.set("assignmentStatus", attr.usage)
|
|
304
|
+
if isinstance(attr.concept, Ref):
|
|
305
|
+
_write_concept_identity(el, attr.concept)
|
|
306
|
+
if isinstance(attr.representation, (Ref, Facet)):
|
|
307
|
+
_write_representation(el, attr.representation)
|
|
308
|
+
_write_attribute_relationship(el, attr)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _write_measure(parent: etree._Element, measure: Measure) -> None:
|
|
312
|
+
el = etree.SubElement(parent, _s("PrimaryMeasure"))
|
|
313
|
+
el.set("id", measure.id)
|
|
314
|
+
if isinstance(measure.concept, Ref):
|
|
315
|
+
_write_concept_identity(el, measure.concept)
|
|
316
|
+
if isinstance(measure.representation, (Ref, Facet)):
|
|
317
|
+
_write_representation(el, measure.representation)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _write_group(parent: etree._Element, group: Group) -> None:
|
|
321
|
+
el = etree.SubElement(parent, _s("Group"))
|
|
322
|
+
el.set("id", group.id)
|
|
323
|
+
for dim_id in group.dimension_ids:
|
|
324
|
+
gd = etree.SubElement(el, _s("GroupDimension"))
|
|
325
|
+
dr = etree.SubElement(gd, _s("DimensionReference"))
|
|
326
|
+
ref = etree.SubElement(dr, "Ref")
|
|
327
|
+
ref.set("id", dim_id)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _write_data_structure(parent: etree._Element, dsd: DataStructure) -> None:
|
|
331
|
+
el = etree.SubElement(parent, _s("DataStructure"))
|
|
332
|
+
_write_maintainable_attrs(el, dsd)
|
|
333
|
+
_write_istring(el, dsd.name)
|
|
334
|
+
if dsd.description:
|
|
335
|
+
_write_istring(el, dsd.description, "Description")
|
|
336
|
+
|
|
337
|
+
components = etree.SubElement(el, _s("DataStructureComponents"))
|
|
338
|
+
|
|
339
|
+
dim_list = etree.SubElement(components, _s("DimensionList"))
|
|
340
|
+
dim_list.set("id", "DimensionDescriptor")
|
|
341
|
+
for i, dim in enumerate(dsd.dimensions, start=1):
|
|
342
|
+
_write_dimension(dim_list, dim, i)
|
|
343
|
+
if dsd.time_dimension is not None:
|
|
344
|
+
_write_time_dimension(dim_list, dsd.time_dimension)
|
|
345
|
+
|
|
346
|
+
if dsd.groups:
|
|
347
|
+
for group in dsd.groups:
|
|
348
|
+
_write_group(components, group)
|
|
349
|
+
|
|
350
|
+
if dsd.attributes:
|
|
351
|
+
attr_list = etree.SubElement(components, _s("AttributeList"))
|
|
352
|
+
attr_list.set("id", "AttributeDescriptor")
|
|
353
|
+
for attr in dsd.attributes:
|
|
354
|
+
_write_attribute(attr_list, attr)
|
|
355
|
+
|
|
356
|
+
if dsd.measures:
|
|
357
|
+
measure_list = etree.SubElement(components, _s("MeasureList"))
|
|
358
|
+
measure_list.set("id", "MeasureDescriptor")
|
|
359
|
+
for measure in dsd.measures:
|
|
360
|
+
_write_measure(measure_list, measure)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# ── Dataflows ─────────────────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _write_dataflow(parent: etree._Element, df: Dataflow) -> None:
|
|
367
|
+
el = etree.SubElement(parent, _s("Dataflow"))
|
|
368
|
+
_write_maintainable_attrs(el, df)
|
|
369
|
+
_write_istring(el, df.name)
|
|
370
|
+
if df.description:
|
|
371
|
+
_write_istring(el, df.description, "Description")
|
|
372
|
+
if isinstance(df.structure, Ref):
|
|
373
|
+
struct_el = etree.SubElement(el, _s("Structure"))
|
|
374
|
+
_write_ref(struct_el, df.structure)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ── CategorySchemes & Categorisations ─────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _write_category(parent: etree._Element, cat: Category) -> None:
|
|
381
|
+
el = etree.SubElement(parent, _s("Category"))
|
|
382
|
+
el.set("id", cat.id)
|
|
383
|
+
_write_istring(el, cat.name)
|
|
384
|
+
if cat.description:
|
|
385
|
+
_write_istring(el, cat.description, "Description")
|
|
386
|
+
for child in cat.categories:
|
|
387
|
+
_write_category(el, child)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _write_category_scheme(parent: etree._Element, cs: CategoryScheme) -> None:
|
|
391
|
+
el = etree.SubElement(parent, _s("CategoryScheme"))
|
|
392
|
+
_write_maintainable_attrs(el, cs)
|
|
393
|
+
_write_istring(el, cs.name)
|
|
394
|
+
if cs.description:
|
|
395
|
+
_write_istring(el, cs.description, "Description")
|
|
396
|
+
for cat in cs.categories:
|
|
397
|
+
_write_category(el, cat)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _write_categorisation(parent: etree._Element, cat: Categorisation) -> None:
|
|
401
|
+
el = etree.SubElement(parent, _s("Categorisation"))
|
|
402
|
+
_write_maintainable_attrs(el, cat)
|
|
403
|
+
_write_istring(el, cat.name)
|
|
404
|
+
if cat.description:
|
|
405
|
+
_write_istring(el, cat.description, "Description")
|
|
406
|
+
if cat.source is not None:
|
|
407
|
+
source_el = etree.SubElement(el, _s("Source"))
|
|
408
|
+
_write_ref(source_el, cat.source)
|
|
409
|
+
if cat.target is not None:
|
|
410
|
+
target_el = etree.SubElement(el, _s("Target"))
|
|
411
|
+
_write_ref(target_el, cat.target)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# ── ProvisionAgreements ───────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _write_provision_agreement(parent: etree._Element, pa: ProvisionAgreement) -> None:
|
|
418
|
+
el = etree.SubElement(parent, _s("ProvisionAgreement"))
|
|
419
|
+
_write_maintainable_attrs(el, pa)
|
|
420
|
+
_write_istring(el, pa.name)
|
|
421
|
+
if pa.description:
|
|
422
|
+
_write_istring(el, pa.description, "Description")
|
|
423
|
+
if pa.dataflow is not None:
|
|
424
|
+
su_el = etree.SubElement(el, _s("StructureUsage"))
|
|
425
|
+
_write_ref(su_el, pa.dataflow)
|
|
426
|
+
if pa.data_provider is not None:
|
|
427
|
+
dp_el = etree.SubElement(el, _s("DataProvider"))
|
|
428
|
+
_write_ref(dp_el, pa.data_provider)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# ── Constraints ───────────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _write_cube_region(parent: etree._Element, region: CubeRegion) -> None:
|
|
435
|
+
el = etree.SubElement(parent, _s("CubeRegion"))
|
|
436
|
+
el.set("include", "true" if region.include else "false")
|
|
437
|
+
for kv in region.keys:
|
|
438
|
+
kv_el = etree.SubElement(el, _c("KeyValue"))
|
|
439
|
+
kv_el.set("id", kv.dimension_id)
|
|
440
|
+
for value in sorted(kv.values):
|
|
441
|
+
v_el = etree.SubElement(kv_el, _c("Value"))
|
|
442
|
+
v_el.text = value
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _write_constraint_attachment(parent: etree._Element, attachment: ConstraintAttachment) -> None:
|
|
446
|
+
att_el = etree.SubElement(parent, _s("ConstraintAttachment"))
|
|
447
|
+
if attachment.dataflow is not None:
|
|
448
|
+
df_el = etree.SubElement(att_el, _s("Dataflow"))
|
|
449
|
+
_write_ref(df_el, attachment.dataflow)
|
|
450
|
+
elif attachment.data_structure is not None:
|
|
451
|
+
ds_el = etree.SubElement(att_el, _s("DataStructure"))
|
|
452
|
+
_write_ref(ds_el, attachment.data_structure)
|
|
453
|
+
elif attachment.provision_agreement is not None:
|
|
454
|
+
pa_el = etree.SubElement(att_el, _s("ProvisionAgreement"))
|
|
455
|
+
_write_ref(pa_el, attachment.provision_agreement)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _write_content_constraint(parent: etree._Element, cc: ContentConstraint) -> None:
|
|
459
|
+
el = etree.SubElement(parent, _s("ContentConstraint"))
|
|
460
|
+
_write_maintainable_attrs(el, cc)
|
|
461
|
+
el.set("type", cc.constraint_type)
|
|
462
|
+
_write_istring(el, cc.name)
|
|
463
|
+
if cc.description:
|
|
464
|
+
_write_istring(el, cc.description, "Description")
|
|
465
|
+
_write_constraint_attachment(el, cc.attachment)
|
|
466
|
+
for region in cc.regions:
|
|
467
|
+
_write_cube_region(el, region)
|
|
468
|
+
if cc.reference_period is not None:
|
|
469
|
+
rp_el = etree.SubElement(el, _s("ReferencePeriod"))
|
|
470
|
+
rp_el.set("startTime", cc.reference_period.start_period.isoformat())
|
|
471
|
+
rp_el.set("endTime", cc.reference_period.end_period.isoformat())
|
|
472
|
+
if cc.release_calendar is not None:
|
|
473
|
+
rc_el = etree.SubElement(el, _s("ReleaseCalendar"))
|
|
474
|
+
etree.SubElement(rc_el, _s("Periodicity")).text = cc.release_calendar.periodicity
|
|
475
|
+
etree.SubElement(rc_el, _s("Offset")).text = cc.release_calendar.offset
|
|
476
|
+
etree.SubElement(rc_el, _s("Tolerance")).text = cc.release_calendar.tolerance
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# ── StructureSets ─────────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _write_codelist_map(parent: etree._Element, cm: CodelistMap) -> None:
|
|
483
|
+
el = etree.SubElement(parent, _s("CodelistMap"))
|
|
484
|
+
el.set("id", cm.id)
|
|
485
|
+
_write_istring(el, cm.name)
|
|
486
|
+
if cm.description:
|
|
487
|
+
_write_istring(el, cm.description, "Description")
|
|
488
|
+
if cm.source is not None:
|
|
489
|
+
src_el = etree.SubElement(el, _s("Source"))
|
|
490
|
+
_write_ref(src_el, cm.source)
|
|
491
|
+
if cm.target is not None:
|
|
492
|
+
tgt_el = etree.SubElement(el, _s("Target"))
|
|
493
|
+
_write_ref(tgt_el, cm.target)
|
|
494
|
+
for code_map in cm.maps:
|
|
495
|
+
m_el = etree.SubElement(el, _s("CodeMap"))
|
|
496
|
+
src_el = etree.SubElement(m_el, _s("Source"))
|
|
497
|
+
etree.SubElement(src_el, "Ref").set("id", code_map.source_id)
|
|
498
|
+
tgt_el = etree.SubElement(m_el, _s("Target"))
|
|
499
|
+
etree.SubElement(tgt_el, "Ref").set("id", code_map.target_id)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _write_concept_scheme_map(parent: etree._Element, csm: ConceptSchemeMap) -> None:
|
|
503
|
+
el = etree.SubElement(parent, _s("ConceptSchemeMap"))
|
|
504
|
+
el.set("id", csm.id)
|
|
505
|
+
_write_istring(el, csm.name)
|
|
506
|
+
if csm.description:
|
|
507
|
+
_write_istring(el, csm.description, "Description")
|
|
508
|
+
if csm.source is not None:
|
|
509
|
+
src_el = etree.SubElement(el, _s("Source"))
|
|
510
|
+
_write_ref(src_el, csm.source)
|
|
511
|
+
if csm.target is not None:
|
|
512
|
+
tgt_el = etree.SubElement(el, _s("Target"))
|
|
513
|
+
_write_ref(tgt_el, csm.target)
|
|
514
|
+
for concept_map in csm.maps:
|
|
515
|
+
m_el = etree.SubElement(el, _s("ConceptMap"))
|
|
516
|
+
src_el = etree.SubElement(m_el, _s("Source"))
|
|
517
|
+
etree.SubElement(src_el, "Ref").set("id", concept_map.source_id)
|
|
518
|
+
tgt_el = etree.SubElement(m_el, _s("Target"))
|
|
519
|
+
etree.SubElement(tgt_el, "Ref").set("id", concept_map.target_id)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _write_category_scheme_map(parent: etree._Element, csm: CategorySchemeMap) -> None:
|
|
523
|
+
el = etree.SubElement(parent, _s("CategorySchemeMap"))
|
|
524
|
+
el.set("id", csm.id)
|
|
525
|
+
_write_istring(el, csm.name)
|
|
526
|
+
if csm.description:
|
|
527
|
+
_write_istring(el, csm.description, "Description")
|
|
528
|
+
if csm.source is not None:
|
|
529
|
+
src_el = etree.SubElement(el, _s("Source"))
|
|
530
|
+
_write_ref(src_el, csm.source)
|
|
531
|
+
if csm.target is not None:
|
|
532
|
+
tgt_el = etree.SubElement(el, _s("Target"))
|
|
533
|
+
_write_ref(tgt_el, csm.target)
|
|
534
|
+
for category_map in csm.maps:
|
|
535
|
+
m_el = etree.SubElement(el, _s("CategoryMap"))
|
|
536
|
+
src_el = etree.SubElement(m_el, _s("Source"))
|
|
537
|
+
etree.SubElement(src_el, "Ref").set("id", category_map.source_id)
|
|
538
|
+
tgt_el = etree.SubElement(m_el, _s("Target"))
|
|
539
|
+
etree.SubElement(tgt_el, "Ref").set("id", category_map.target_id)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _write_data_structure_map(parent: etree._Element, dsm: DataStructureMap) -> None:
|
|
543
|
+
el = etree.SubElement(parent, _s("DataStructureMap"))
|
|
544
|
+
el.set("id", dsm.id)
|
|
545
|
+
_write_istring(el, dsm.name)
|
|
546
|
+
if dsm.description:
|
|
547
|
+
_write_istring(el, dsm.description, "Description")
|
|
548
|
+
if dsm.source is not None:
|
|
549
|
+
src_el = etree.SubElement(el, _s("Source"))
|
|
550
|
+
_write_ref(src_el, dsm.source)
|
|
551
|
+
if dsm.target is not None:
|
|
552
|
+
tgt_el = etree.SubElement(el, _s("Target"))
|
|
553
|
+
_write_ref(tgt_el, dsm.target)
|
|
554
|
+
for comp_map in dsm.maps:
|
|
555
|
+
m_el = etree.SubElement(el, _s("ComponentMap"))
|
|
556
|
+
src_el = etree.SubElement(m_el, _s("Source"))
|
|
557
|
+
etree.SubElement(src_el, "Ref").set("id", comp_map.source_id)
|
|
558
|
+
tgt_el = etree.SubElement(m_el, _s("Target"))
|
|
559
|
+
etree.SubElement(tgt_el, "Ref").set("id", comp_map.target_id)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _write_dataflow_map(parent: etree._Element, dfm: DataflowMap) -> None:
|
|
563
|
+
el = etree.SubElement(parent, _s("DataflowMap"))
|
|
564
|
+
el.set("id", dfm.id)
|
|
565
|
+
_write_istring(el, dfm.name)
|
|
566
|
+
if dfm.description:
|
|
567
|
+
_write_istring(el, dfm.description, "Description")
|
|
568
|
+
if dfm.source is not None:
|
|
569
|
+
src_el = etree.SubElement(el, _s("Source"))
|
|
570
|
+
_write_ref(src_el, dfm.source)
|
|
571
|
+
if dfm.target is not None:
|
|
572
|
+
tgt_el = etree.SubElement(el, _s("Target"))
|
|
573
|
+
_write_ref(tgt_el, dfm.target)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _write_structure_set(parent: etree._Element, ss: StructureSet) -> None:
|
|
577
|
+
el = etree.SubElement(parent, _s("StructureSet"))
|
|
578
|
+
_write_maintainable_attrs(el, ss)
|
|
579
|
+
_write_istring(el, ss.name)
|
|
580
|
+
if ss.description:
|
|
581
|
+
_write_istring(el, ss.description, "Description")
|
|
582
|
+
for cm in ss.codelist_maps:
|
|
583
|
+
_write_codelist_map(el, cm)
|
|
584
|
+
for csm in ss.concept_scheme_maps:
|
|
585
|
+
_write_concept_scheme_map(el, csm)
|
|
586
|
+
for csm in ss.category_scheme_maps:
|
|
587
|
+
_write_category_scheme_map(el, csm)
|
|
588
|
+
for dsm in ss.data_structure_maps:
|
|
589
|
+
_write_data_structure_map(el, dsm)
|
|
590
|
+
for dfm in ss.dataflow_maps:
|
|
591
|
+
_write_dataflow_map(el, dfm)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
# ── Hierarchies ───────────────────────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _write_hierarchical_code(parent: etree._Element, hc: HierarchicalCode) -> None:
|
|
598
|
+
el = etree.SubElement(parent, _s("HierarchicalCode"))
|
|
599
|
+
el.set("id", hc.id)
|
|
600
|
+
_write_istring(el, hc.name)
|
|
601
|
+
if hc.description:
|
|
602
|
+
_write_istring(el, hc.description, "Description")
|
|
603
|
+
if hc.code is not None and isinstance(hc.code, Ref):
|
|
604
|
+
code_id_el = etree.SubElement(el, _s("CodeID"))
|
|
605
|
+
_write_ref(code_id_el, hc.code)
|
|
606
|
+
for child in hc.children:
|
|
607
|
+
_write_hierarchical_code(el, child)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _write_hierarchy(parent: etree._Element, h: Hierarchy) -> None:
|
|
611
|
+
hcl_el = etree.SubElement(parent, _s("HierarchicalCodelist"))
|
|
612
|
+
_write_maintainable_attrs(hcl_el, h)
|
|
613
|
+
_write_istring(hcl_el, h.name)
|
|
614
|
+
if h.description:
|
|
615
|
+
_write_istring(hcl_el, h.description, "Description")
|
|
616
|
+
hier_el = etree.SubElement(hcl_el, _s("Hierarchy"))
|
|
617
|
+
hier_el.set("id", h.id)
|
|
618
|
+
_write_istring(hier_el, h.name)
|
|
619
|
+
if h.description:
|
|
620
|
+
_write_istring(hier_el, h.description, "Description")
|
|
621
|
+
for hc in h.tree:
|
|
622
|
+
_write_hierarchical_code(hier_el, hc)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
# ── Entry point ───────────────────────────────────────────────────────────────
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def write(msg: StructureMessage, *, pretty_print: bool = False) -> bytes: # noqa: C901, PLR0912
|
|
629
|
+
"""Serialise a StructureMessage to SDMX-ML 2.1 XML bytes.
|
|
630
|
+
|
|
631
|
+
Example:
|
|
632
|
+
from sdmxlib.formats.sdmx_ml21.writer import write
|
|
633
|
+
|
|
634
|
+
xml_bytes = write(msg)
|
|
635
|
+
xml_bytes = write(msg, pretty_print=True) # indented output
|
|
636
|
+
"""
|
|
637
|
+
root = etree.Element(_m("Structure"), nsmap=_NSMAP)
|
|
638
|
+
root.set(
|
|
639
|
+
"{http://www.w3.org/2001/XMLSchema-instance}schemaLocation",
|
|
640
|
+
f"{MES} SDMXMessage.xsd",
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
header = etree.SubElement(root, _m("Header"))
|
|
644
|
+
etree.SubElement(header, _m("ID")).text = "SDMX_MSG"
|
|
645
|
+
etree.SubElement(header, _m("Test")).text = "false"
|
|
646
|
+
etree.SubElement(header, _m("Prepared")).text = "2000-01-01T00:00:00"
|
|
647
|
+
sender = etree.SubElement(header, _m("Sender"))
|
|
648
|
+
sender.set("id", "sdmxlib")
|
|
649
|
+
|
|
650
|
+
structures = etree.SubElement(root, _m("Structures"))
|
|
651
|
+
|
|
652
|
+
if msg.agency_schemes or msg.data_provider_schemes:
|
|
653
|
+
container = etree.SubElement(structures, _s("OrganisationSchemes"))
|
|
654
|
+
for scheme in msg.agency_schemes:
|
|
655
|
+
_write_agency_scheme(container, scheme)
|
|
656
|
+
for scheme in msg.data_provider_schemes:
|
|
657
|
+
_write_data_provider_scheme(container, scheme)
|
|
658
|
+
|
|
659
|
+
if msg.codelists:
|
|
660
|
+
container = etree.SubElement(structures, _s("Codelists"))
|
|
661
|
+
for cl in msg.codelists:
|
|
662
|
+
_write_codelist(container, cl)
|
|
663
|
+
|
|
664
|
+
if msg.concept_schemes:
|
|
665
|
+
container = etree.SubElement(structures, _s("Concepts"))
|
|
666
|
+
for cs in msg.concept_schemes:
|
|
667
|
+
_write_concept_scheme(container, cs)
|
|
668
|
+
|
|
669
|
+
if msg.category_schemes:
|
|
670
|
+
container = etree.SubElement(structures, _s("CategorySchemes"))
|
|
671
|
+
for cs in msg.category_schemes:
|
|
672
|
+
_write_category_scheme(container, cs)
|
|
673
|
+
|
|
674
|
+
if msg.data_structures:
|
|
675
|
+
container = etree.SubElement(structures, _s("DataStructures"))
|
|
676
|
+
for dsd in msg.data_structures:
|
|
677
|
+
_write_data_structure(container, dsd)
|
|
678
|
+
|
|
679
|
+
if msg.dataflows:
|
|
680
|
+
container = etree.SubElement(structures, _s("Dataflows"))
|
|
681
|
+
for df in msg.dataflows:
|
|
682
|
+
_write_dataflow(container, df)
|
|
683
|
+
|
|
684
|
+
if msg.categorisations:
|
|
685
|
+
container = etree.SubElement(structures, _s("Categorisations"))
|
|
686
|
+
for cat in msg.categorisations:
|
|
687
|
+
_write_categorisation(container, cat)
|
|
688
|
+
|
|
689
|
+
if msg.provision_agreements:
|
|
690
|
+
container = etree.SubElement(structures, _s("ProvisionAgreements"))
|
|
691
|
+
for pa in msg.provision_agreements:
|
|
692
|
+
_write_provision_agreement(container, pa)
|
|
693
|
+
|
|
694
|
+
if msg.constraints:
|
|
695
|
+
container = etree.SubElement(structures, _s("Constraints"))
|
|
696
|
+
for cc in msg.constraints:
|
|
697
|
+
_write_content_constraint(container, cc)
|
|
698
|
+
|
|
699
|
+
if msg.structure_sets:
|
|
700
|
+
container = etree.SubElement(structures, _s("StructureSets"))
|
|
701
|
+
for ss in msg.structure_sets:
|
|
702
|
+
_write_structure_set(container, ss)
|
|
703
|
+
|
|
704
|
+
if msg.hierarchies:
|
|
705
|
+
container = etree.SubElement(structures, _s("HierarchicalCodelists"))
|
|
706
|
+
for h in msg.hierarchies:
|
|
707
|
+
_write_hierarchy(container, h)
|
|
708
|
+
|
|
709
|
+
return etree.tostring(root, xml_declaration=True, encoding="UTF-8", pretty_print=pretty_print)
|