pysdmx 1.3.0__py3-none-any.whl → 1.4.0rc1__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.
Files changed (100) hide show
  1. pysdmx/__extras_check.py +3 -2
  2. pysdmx/__init__.py +1 -1
  3. pysdmx/api/fmr/__init__.py +4 -4
  4. pysdmx/api/gds/__init__.py +328 -0
  5. pysdmx/api/qb/gds.py +153 -0
  6. pysdmx/api/qb/service.py +91 -3
  7. pysdmx/api/qb/structure.py +1 -0
  8. pysdmx/api/qb/util.py +1 -0
  9. pysdmx/io/__init__.py +2 -1
  10. pysdmx/io/csv/sdmx10/reader/__init__.py +4 -2
  11. pysdmx/io/csv/sdmx10/writer/__init__.py +15 -2
  12. pysdmx/io/csv/sdmx20/reader/__init__.py +5 -2
  13. pysdmx/io/csv/sdmx20/writer/__init__.py +13 -2
  14. pysdmx/io/format.py +4 -0
  15. pysdmx/io/input_processor.py +12 -3
  16. pysdmx/io/json/fusion/messages/core.py +2 -0
  17. pysdmx/io/json/fusion/messages/report.py +13 -7
  18. pysdmx/io/json/gds/messages/__init__.py +35 -0
  19. pysdmx/io/json/gds/messages/agencies.py +41 -0
  20. pysdmx/io/json/gds/messages/catalog.py +79 -0
  21. pysdmx/io/json/gds/messages/sdmx_api.py +23 -0
  22. pysdmx/io/json/gds/messages/services.py +49 -0
  23. pysdmx/io/json/gds/messages/urn_resolver.py +43 -0
  24. pysdmx/io/json/gds/reader/__init__.py +12 -0
  25. pysdmx/io/json/sdmxjson2/messages/__init__.py +12 -4
  26. pysdmx/io/json/sdmxjson2/messages/agency.py +72 -0
  27. pysdmx/io/json/sdmxjson2/messages/category.py +22 -29
  28. pysdmx/io/json/sdmxjson2/messages/code.py +68 -64
  29. pysdmx/io/json/sdmxjson2/messages/concept.py +9 -18
  30. pysdmx/io/json/sdmxjson2/messages/constraint.py +2 -13
  31. pysdmx/io/json/sdmxjson2/messages/core.py +113 -21
  32. pysdmx/io/json/sdmxjson2/messages/dataflow.py +51 -21
  33. pysdmx/io/json/sdmxjson2/messages/dsd.py +110 -36
  34. pysdmx/io/json/sdmxjson2/messages/map.py +61 -49
  35. pysdmx/io/json/sdmxjson2/messages/pa.py +9 -17
  36. pysdmx/io/json/sdmxjson2/messages/provider.py +88 -0
  37. pysdmx/io/json/sdmxjson2/messages/report.py +84 -14
  38. pysdmx/io/json/sdmxjson2/messages/schema.py +14 -5
  39. pysdmx/io/json/sdmxjson2/messages/structure.py +105 -36
  40. pysdmx/io/json/sdmxjson2/messages/vtl.py +42 -96
  41. pysdmx/io/pd.py +2 -9
  42. pysdmx/io/reader.py +72 -27
  43. pysdmx/io/serde.py +11 -0
  44. pysdmx/io/writer.py +134 -0
  45. pysdmx/io/xml/{sdmx21/reader/__data_aux.py → __data_aux.py} +9 -2
  46. pysdmx/io/xml/{sdmx21/reader/__parse_xml.py → __parse_xml.py} +30 -6
  47. pysdmx/io/xml/__ss_aux_reader.py +96 -0
  48. pysdmx/io/xml/__structure_aux_reader.py +1174 -0
  49. pysdmx/io/xml/__structure_aux_writer.py +1233 -0
  50. pysdmx/io/xml/{sdmx21/__tokens.py → __tokens.py} +33 -1
  51. pysdmx/io/xml/{sdmx21/writer/__write_aux.py → __write_aux.py} +129 -37
  52. pysdmx/io/xml/{sdmx21/writer/__write_data_aux.py → __write_data_aux.py} +1 -1
  53. pysdmx/io/xml/__write_structure_specific_aux.py +254 -0
  54. pysdmx/io/xml/{sdmx21/reader/doc_validation.py → doc_validation.py} +10 -2
  55. pysdmx/io/xml/{sdmx21/reader/header.py → header.py} +11 -3
  56. pysdmx/io/xml/sdmx21/reader/error.py +2 -2
  57. pysdmx/io/xml/sdmx21/reader/generic.py +12 -8
  58. pysdmx/io/xml/sdmx21/reader/structure.py +5 -840
  59. pysdmx/io/xml/sdmx21/reader/structure_specific.py +13 -97
  60. pysdmx/io/xml/sdmx21/reader/submission.py +2 -2
  61. pysdmx/io/xml/sdmx21/writer/error.py +1 -1
  62. pysdmx/io/xml/sdmx21/writer/generic.py +13 -7
  63. pysdmx/io/xml/sdmx21/writer/structure.py +16 -828
  64. pysdmx/io/xml/sdmx21/writer/structure_specific.py +13 -238
  65. pysdmx/io/xml/sdmx30/__init__.py +1 -0
  66. pysdmx/io/xml/sdmx30/reader/__init__.py +1 -0
  67. pysdmx/io/xml/sdmx30/reader/structure.py +39 -0
  68. pysdmx/io/xml/sdmx30/reader/structure_specific.py +39 -0
  69. pysdmx/io/xml/sdmx30/writer/__init__.py +1 -0
  70. pysdmx/io/xml/sdmx30/writer/structure.py +67 -0
  71. pysdmx/io/xml/sdmx30/writer/structure_specific.py +108 -0
  72. pysdmx/model/__base.py +99 -34
  73. pysdmx/model/__init__.py +4 -0
  74. pysdmx/model/category.py +20 -0
  75. pysdmx/model/code.py +29 -8
  76. pysdmx/model/concept.py +52 -11
  77. pysdmx/model/dataflow.py +117 -33
  78. pysdmx/model/dataset.py +66 -14
  79. pysdmx/model/gds.py +161 -0
  80. pysdmx/model/map.py +51 -8
  81. pysdmx/model/message.py +235 -55
  82. pysdmx/model/metadata.py +79 -16
  83. pysdmx/model/submission.py +12 -7
  84. pysdmx/model/vtl.py +30 -13
  85. pysdmx/toolkit/__init__.py +1 -1
  86. pysdmx/toolkit/pd/__init__.py +85 -0
  87. pysdmx/toolkit/vtl/__init__.py +2 -1
  88. pysdmx/toolkit/vtl/_validations.py +1 -1
  89. pysdmx/toolkit/vtl/{generate_vtl_script.py → script_generation.py} +30 -4
  90. pysdmx/toolkit/vtl/validation.py +119 -0
  91. pysdmx/util/_model_utils.py +1 -1
  92. pysdmx-1.4.0rc1.dist-info/METADATA +119 -0
  93. pysdmx-1.4.0rc1.dist-info/RECORD +140 -0
  94. pysdmx/io/json/sdmxjson2/messages/org.py +0 -140
  95. pysdmx/toolkit/vtl/model_validations.py +0 -50
  96. pysdmx-1.3.0.dist-info/METADATA +0 -76
  97. pysdmx-1.3.0.dist-info/RECORD +0 -116
  98. /pysdmx/io/xml/{sdmx21/writer/config.py → config.py} +0 -0
  99. {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/LICENSE +0 -0
  100. {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/WHEEL +0 -0
@@ -1,841 +1,25 @@
1
1
  """Module for writing metadata to XML files."""
2
2
 
3
- from collections import OrderedDict
4
- from typing import Any, Dict, Optional, Sequence, Union
3
+ from pathlib import Path
4
+ from typing import Dict, Optional, Sequence, Union
5
5
 
6
- from pysdmx.errors import Invalid
7
6
  from pysdmx.io.format import Format
8
- from pysdmx.io.xml.sdmx21.__tokens import (
9
- AGENCIES,
10
- AGENCY_ID,
11
- AS_STATUS,
12
- ATT,
13
- ATT_REL,
14
- CL,
15
- CL_LOW,
16
- CLASS,
17
- CON,
18
- CON_ID,
19
- CONDITIONAL,
20
- CORE_REP,
21
- CS,
22
- DEPARTMENT,
23
- DFW,
24
- DIM,
25
- DSD,
26
- DSD_COMPS,
27
- EMAIL,
28
- ENUM,
29
- ENUM_FORMAT,
30
- FAX,
31
- ID,
32
- LOCAL_REP,
33
- MANDATORY,
34
- MEASURE,
35
- NAME,
36
- PACKAGE,
37
- PAR_ID,
38
- PAR_VER,
39
- POSITION,
40
- PRIM_MEASURE,
41
- REF,
42
- ROLE,
43
- RULE,
44
- RULE_SCHEME,
45
- TELEPHONE,
46
- TEXT_FORMAT,
47
- TEXT_TYPE,
48
- TIME_DIM,
49
- TRANS_SCHEME,
50
- TRANSFORMATION,
51
- UDO,
52
- UDO_SCHEME,
53
- URI,
54
- URN,
55
- VERSION,
56
- VTL_MAPPING_SCHEME,
57
- VTLMAPPING,
7
+ from pysdmx.io.xml.__structure_aux_writer import (
8
+ STR_DICT_TYPE_LIST_21,
9
+ __write_structures,
58
10
  )
59
- from pysdmx.io.xml.sdmx21.writer.__write_aux import (
60
- ABBR_COM,
61
- ABBR_MSG,
62
- ABBR_STR,
63
- MSG_CONTENT_PKG,
64
- __escape_xml,
65
- __to_lower_camel_case,
11
+ from pysdmx.io.xml.__write_aux import (
66
12
  __write_header,
67
- add_indent,
68
13
  create_namespaces,
69
14
  get_end_message,
70
15
  )
71
- from pysdmx.model import (
72
- AgencyScheme,
73
- Codelist,
74
- Concept,
75
- ConceptScheme,
76
- DataType,
77
- Facets,
78
- Hierarchy,
79
- Ruleset,
80
- RulesetScheme,
81
- Transformation,
82
- TransformationScheme,
83
- UserDefinedOperator,
84
- UserDefinedOperatorScheme,
85
- VtlDataflowMapping,
86
- VtlMappingScheme,
87
- VtlScheme,
88
- )
89
- from pysdmx.model.__base import (
90
- Agency,
91
- AnnotableArtefact,
92
- Contact,
93
- IdentifiableArtefact,
94
- Item,
95
- ItemReference,
96
- ItemScheme,
97
- MaintainableArtefact,
98
- NameableArtefact,
99
- Reference,
100
- VersionableArtefact,
101
- )
102
- from pysdmx.model.dataflow import (
103
- Component,
104
- Dataflow,
105
- DataStructureDefinition,
106
- Role,
107
- )
16
+ from pysdmx.model.__base import MaintainableArtefact
108
17
  from pysdmx.model.message import Header
109
- from pysdmx.util import (
110
- parse_item_urn,
111
- parse_short_urn,
112
- )
113
-
114
- ANNOTATION_WRITER = OrderedDict(
115
- {
116
- "title": "AnnotationTitle",
117
- "type": "AnnotationType",
118
- "text": "AnnotationText",
119
- "url": "AnnotationURL",
120
- }
121
- )
122
-
123
- ROLE_MAPPING = {
124
- Role.DIMENSION: DIM,
125
- Role.ATTRIBUTE: ATT,
126
- Role.MEASURE: PRIM_MEASURE,
127
- }
128
-
129
- STR_TYPES = Union[
130
- ItemScheme,
131
- Codelist,
132
- ConceptScheme,
133
- DataStructureDefinition,
134
- Dataflow,
135
- RulesetScheme,
136
- UserDefinedOperatorScheme,
137
- TransformationScheme,
138
- ]
139
-
140
- STR_DICT_TYPE_LIST = {
141
- AgencyScheme: "OrganisationSchemes",
142
- Codelist: "Codelists",
143
- ConceptScheme: "Concepts",
144
- DataStructureDefinition: "DataStructures",
145
- Dataflow: "Dataflows",
146
- RulesetScheme: "Rulesets",
147
- UserDefinedOperatorScheme: "UserDefinedOperators",
148
- TransformationScheme: "Transformations",
149
- VtlMappingScheme: "VtlMappings",
150
- }
151
-
152
-
153
- def __write_annotable(annotable: AnnotableArtefact, indent: str) -> str:
154
- """Writes the annotations to the XML file."""
155
- if len(annotable.annotations) == 0:
156
- return ""
157
-
158
- child1 = indent
159
- child2 = add_indent(child1)
160
- child3 = add_indent(child2)
161
-
162
- outfile = f"{child1}<{ABBR_COM}:Annotations>"
163
- for annotation in annotable.annotations:
164
- if annotation.id is None:
165
- outfile += f"{child2}<{ABBR_COM}:Annotation>"
166
- else:
167
- outfile += (
168
- f"{child2}<{ABBR_COM}:Annotation " f"id={annotation.id!r}>"
169
- )
170
- outfile = outfile.replace("'", '"')
171
-
172
- for attr, label in ANNOTATION_WRITER.items():
173
- if getattr(annotation, attr, None) is not None:
174
- value = getattr(annotation, attr)
175
- value = __escape_xml(str(value))
176
-
177
- if attr == "text":
178
- head_tag = f'{ABBR_COM}:{label} xml:lang="en"'
179
-
180
- else:
181
- head_tag = f"{ABBR_COM}:{label}"
182
-
183
- outfile += (
184
- f"{child3}<{head_tag}>" f"{value}" f"</{ABBR_COM}:{label}>"
185
- )
186
-
187
- outfile += f"{child2}</{ABBR_COM}:Annotation>"
188
- outfile += f"{child1}</{ABBR_COM}:Annotations>"
189
- return outfile
190
-
191
-
192
- def __write_identifiable(
193
- identifiable: IdentifiableArtefact, indent: str
194
- ) -> Dict[str, Any]:
195
- """Writes the IdentifiableArtefact to the XML file."""
196
- attributes = ""
197
-
198
- attributes += f" id={identifiable.id!r}"
199
-
200
- if identifiable.uri is not None:
201
- attributes += f" uri={identifiable.uri!r}"
202
-
203
- if identifiable.urn is not None:
204
- attributes += f" urn={identifiable.urn!r}"
205
-
206
- outfile = {
207
- "Annotations": __write_annotable(identifiable, indent),
208
- "Attributes": attributes,
209
- }
210
-
211
- return outfile
212
-
213
-
214
- def __write_nameable(
215
- nameable: NameableArtefact, indent: str
216
- ) -> Dict[str, Any]:
217
- """Writes the NameableArtefact to the XML file."""
218
- outfile = __write_identifiable(nameable, indent)
219
- attrs = ["Name", "Description"]
220
-
221
- for attr in attrs:
222
- value = getattr(nameable, attr.lower(), None)
223
- if value is not None:
224
- value = __escape_xml(str(value))
225
- outfile[attr] = [
226
- (
227
- f"{indent}"
228
- f'<{ABBR_COM}:{attr} xml:lang="en">'
229
- f"{value}"
230
- f"</{ABBR_COM}:{attr}>"
231
- )
232
- ]
233
- elif attr == "Name":
234
- raise Invalid(
235
- "Name is required for NameableArtefact" f" id= {nameable.id}"
236
- )
237
-
238
- return outfile
239
-
240
-
241
- def __write_versionable(
242
- versionable: VersionableArtefact, indent: str
243
- ) -> Dict[str, Any]:
244
- """Writes the VersionableArtefact to the XML file."""
245
- outfile = __write_nameable(versionable, add_indent(indent))
246
-
247
- outfile["Attributes"] += f" version={versionable.version!r}"
248
-
249
- if versionable.valid_from is not None:
250
- valid_from_str = versionable.valid_from.strftime("%Y-%m-%dT%H:%M:%S")
251
- outfile["Attributes"] += f" validFrom={valid_from_str!r}"
252
-
253
- if versionable.valid_to is not None:
254
- valid_to_str = versionable.valid_to.strftime("%Y-%m-%dT%H:%M:%S")
255
- outfile["Attributes"] += f" validTo={valid_to_str!r}"
256
-
257
- return outfile
258
-
259
-
260
- def __write_maintainable(
261
- maintainable: MaintainableArtefact, indent: str
262
- ) -> Dict[str, Any]:
263
- """Writes the MaintainableArtefact to the XML file."""
264
- outfile = __write_versionable(maintainable, indent)
265
-
266
- outfile["Attributes"] += (
267
- f" isExternalReference="
268
- f"{str(maintainable.is_external_reference).lower()!r}"
269
- )
270
-
271
- outfile["Attributes"] += f" isFinal={str(maintainable.is_final).lower()!r}"
272
-
273
- if isinstance(maintainable.agency, str):
274
- outfile["Attributes"] += f" agencyID={maintainable.agency!r}"
275
- else:
276
- outfile["Attributes"] += f" agencyID={maintainable.agency.id!r}"
277
-
278
- return outfile
279
-
280
-
281
- def __write_contact(contact: Contact, indent: str) -> str:
282
- """Writes the contact to the XML file."""
283
-
284
- def __item_to_str(item: Any, ns: str, tag: str) -> str:
285
- return (
286
- f"{add_indent(indent)}"
287
- f"<{ns}:{tag}>"
288
- f"{__escape_xml(str(item))}"
289
- f"</{ns}:{tag}>"
290
- )
291
-
292
- def __items_to_str(items: Any, ns: str, tag: str) -> str:
293
- return "".join([__item_to_str(item, ns, tag) for item in items])
294
-
295
- outfile = f"{indent}<{ABBR_STR}:Contact>"
296
- if contact.name is not None:
297
- outfile += __item_to_str(contact.name, ABBR_COM, NAME)
298
- if contact.department is not None:
299
- outfile += __item_to_str(contact.department, ABBR_STR, DEPARTMENT)
300
- if contact.role is not None:
301
- outfile += __item_to_str(contact.role, ABBR_STR, ROLE)
302
- if contact.telephones is not None:
303
- outfile += __items_to_str(contact.telephones, ABBR_STR, TELEPHONE)
304
- if contact.faxes is not None:
305
- outfile += __items_to_str(contact.faxes, ABBR_STR, FAX)
306
- if contact.uris is not None:
307
- outfile += __items_to_str(contact.uris, ABBR_STR, URI)
308
- if contact.emails is not None:
309
- outfile += __items_to_str(contact.emails, ABBR_STR, EMAIL)
310
- outfile += f"{indent}</{ABBR_STR}:Contact>"
311
-
312
- return outfile
313
-
314
-
315
- def __write_item(item: Item, indent: str) -> str:
316
- """Writes the item to the XML file."""
317
- head = f"{ABBR_STR}:" + type(item).__name__
318
-
319
- data = __write_nameable(item, add_indent(indent))
320
- attributes = data["Attributes"].replace("'", '"')
321
- outfile = f"{indent}<{head}{attributes}>"
322
- outfile += __export_intern_data(data)
323
- if isinstance(item, Agency) and len(item.contacts) > 0:
324
- for contact in item.contacts:
325
- outfile += __write_contact(contact, add_indent(indent))
326
- if isinstance(item, Concept) and (
327
- item.codes is not None
328
- or item.facets is not None
329
- or item.dtype is not None
330
- ):
331
- outfile += f"{add_indent(indent)}<{ABBR_STR}:{CORE_REP}>"
332
- if item.codes is not None:
333
- outfile += __write_enumeration(item.codes, add_indent(indent))
334
- if item.facets is not None or item.dtype is not None:
335
- outfile += __write_text_format(
336
- item.dtype, item.facets, TEXT_FORMAT, add_indent(indent)
337
- )
338
- outfile += f"{add_indent(indent)}</{ABBR_STR}:{CORE_REP}>"
339
- outfile += f"{indent}</{head}>"
340
- return outfile
341
-
342
-
343
- def __write_components(item: DataStructureDefinition, indent: str) -> str:
344
- """Writes the components to the XML file."""
345
- outfile = f"{indent}<{ABBR_STR}:{DSD_COMPS}>"
346
-
347
- components: Dict[str, Any] = {
348
- DIM: [],
349
- ATT: [],
350
- PRIM_MEASURE: [],
351
- }
352
-
353
- for comp in item.components:
354
- if comp.role == Role.DIMENSION:
355
- components[DIM].append(comp)
356
- elif comp.role == Role.ATTRIBUTE:
357
- components[ATT].append(comp)
358
- else:
359
- components[PRIM_MEASURE].append(comp)
360
-
361
- position = 1
362
- for _, comps in components.items():
363
- if comps:
364
- role_name = ROLE_MAPPING[comps[0].role]
365
- if role_name == PRIM_MEASURE:
366
- role_name = MEASURE
367
- outfile += f"{add_indent(indent)}<{ABBR_STR}:{role_name}List>"
368
- for comp in comps:
369
- outfile += __write_component(
370
- comp, position, add_indent(add_indent(indent)), components
371
- )
372
- position += 1
373
- outfile += f"{add_indent(indent)}</{ABBR_STR}:{role_name}List>"
374
-
375
- outfile += f"{indent}</{ABBR_STR}:{DSD_COMPS}>"
376
- return outfile
377
-
378
-
379
- def __write_attribute_relation(
380
- item: Component, indent: str, component_info: Dict[str, Any]
381
- ) -> str:
382
- outfile = f"{indent}<{ABBR_STR}:{ATT_REL}>"
383
- att_rel = item.attachment_level
384
- if att_rel is None or att_rel == "D":
385
- outfile += f"{add_indent(indent)}<{ABBR_STR}:None/>"
386
- else:
387
- # Check if it is a list of Dimensions or it is related to the
388
- # primary measure
389
- if "," in att_rel:
390
- comps_to_relate = att_rel.split(",")
391
- elif att_rel == "O":
392
- comps_to_relate = [component_info[PRIM_MEASURE][0].id]
393
- else:
394
- comps_to_relate = [att_rel]
395
-
396
- dim_names = [comp.id for comp in component_info[DIM]]
397
-
398
- for comp_name in comps_to_relate:
399
- role = Role.DIMENSION if comp_name in dim_names else Role.MEASURE
400
- related_role = ROLE_MAPPING[role]
401
- outfile += f"{add_indent(indent)}<{ABBR_STR}:{related_role}>"
402
- outfile += (
403
- f"{add_indent(add_indent(indent))}"
404
- f"<{REF} {ID}={comp_name!r}/>"
405
- )
406
- outfile += f"{add_indent(indent)}</{ABBR_STR}:{related_role}>"
407
- outfile += f"{indent}</{ABBR_STR}:{ATT_REL}>"
408
-
409
- return outfile
410
-
411
-
412
- def __write_component(
413
- item: Component, position: int, indent: str, component_info: Dict[str, Any]
414
- ) -> str:
415
- """Writes the component to the XML file."""
416
- role_name = ROLE_MAPPING[item.role]
417
- if role_name == DIM and item.id == "TIME_PERIOD":
418
- role_name = TIME_DIM
419
- head = f"{indent}<{ABBR_STR}:{role_name} "
420
-
421
- attributes = ""
422
- attribute_relation = ""
423
- if item.role == Role.ATTRIBUTE:
424
- status = MANDATORY if item.required else CONDITIONAL
425
- attributes += f"{AS_STATUS}={status!r} "
426
- attribute_relation = __write_attribute_relation(
427
- item, add_indent(indent), component_info
428
- )
429
-
430
- attributes += f"{ID}={item.id!r}"
431
- if item.role == Role.DIMENSION:
432
- attributes += f" {POSITION}={str(position)!r}"
433
-
434
- if item.urn is not None:
435
- attributes += f" {URN.lower()}={item.urn!r}"
436
-
437
- attributes += ">"
438
-
439
- concept_identity = __write_concept_identity(
440
- item.concept, add_indent(indent)
441
- )
442
- representation = __write_representation(item, add_indent(indent))
443
-
444
- outfile = head
445
- outfile += attributes
446
- outfile += concept_identity
447
- outfile += representation
448
- outfile += attribute_relation
449
-
450
- outfile += f"{indent}</{ABBR_STR}:{role_name}>"
451
-
452
- outfile = outfile.replace("'", '"')
453
- return outfile
454
-
455
-
456
- def __write_concept_identity(
457
- identity: Union[Concept, ItemReference], indent: str
458
- ) -> str:
459
- if isinstance(identity, ItemReference):
460
- ref = identity
461
- else:
462
- ref = parse_item_urn(identity.urn) # type: ignore[arg-type]
463
-
464
- outfile = f"{indent}<{ABBR_STR}:{CON_ID}>"
465
- outfile += f"{add_indent(indent)}<{REF} "
466
- outfile += f"{AGENCY_ID}={ref.agency!r} "
467
- outfile += f"{CLASS}={CON!r} "
468
- outfile += f"{ID}={ref.item_id!r} "
469
- outfile += f"{PAR_ID}={ref.id!r} "
470
- outfile += f"{PAR_VER}={ref.version!r} "
471
- outfile += f"{PACKAGE}={CS.lower()!r}/>"
472
- outfile += f"{indent}</{ABBR_STR}:{CON_ID}>"
473
-
474
- outfile = outfile.replace("'", '"')
475
- return outfile
476
-
477
-
478
- def __write_representation(item: Component, indent: str) -> str:
479
- representation = ""
480
- local_representation = ""
481
-
482
- if item.local_codes is not None:
483
- local_representation += __write_enumeration(item.local_codes, indent)
484
-
485
- if item.local_facets is not None or item.local_dtype is not None:
486
- type_ = ENUM_FORMAT if item.local_codes is not None else TEXT_FORMAT
487
- local_representation += __write_text_format(
488
- item.local_dtype, item.local_facets, type_, indent
489
- )
490
-
491
- representation += f"{indent}<{ABBR_STR}:{LOCAL_REP}>"
492
- if len(local_representation) == 0:
493
- representation += f"{add_indent(indent)}<{ABBR_STR}:{TEXT_FORMAT}/>"
494
- representation += local_representation
495
- representation += f"{indent}</{ABBR_STR}:{LOCAL_REP}>"
496
-
497
- return representation
498
-
499
-
500
- def __write_text_format(
501
- dtype: Optional[DataType],
502
- facets: Optional[Facets],
503
- type_: str,
504
- indent: str,
505
- ) -> str:
506
- """Writes the text format to the XML file."""
507
- outfile = f"{add_indent(indent)}<{ABBR_STR}:{type_}"
508
- if facets is not None:
509
- active_facets = facets.__str__().replace("=", '="').split(", ")
510
- for facet in active_facets:
511
- facet = __to_lower_camel_case(facet)
512
- outfile += f' {facet}"'
513
- if dtype is not None:
514
- outfile += f" {TEXT_TYPE}={dtype.value!r}"
515
- outfile += "/>"
516
-
517
- outfile = outfile.replace("'", '"')
518
- return outfile
519
-
520
-
521
- def __write_enumeration(codes: Union[Codelist, Hierarchy], indent: str) -> str:
522
- """Writes the enumeration to the XML file."""
523
- ref = parse_short_urn(codes.short_urn)
524
-
525
- outfile = f"{add_indent(indent)}<{ABBR_STR}:{ENUM}>"
526
- outfile += f"{add_indent(add_indent(indent))}<{REF} "
527
- outfile += f"{AGENCY_ID}={ref.agency!r} "
528
- outfile += f"{CLASS}={CL!r} "
529
- outfile += f"{ID}={ref.id!r} "
530
- outfile += f"{PACKAGE}={CL_LOW!r} "
531
- outfile += f"{VERSION}={ref.version!r}/>"
532
- outfile += f"{add_indent(indent)}</{ABBR_STR}:{ENUM}>"
533
-
534
- outfile = outfile.replace("'", '"')
535
- return outfile
536
-
537
-
538
- def __write_structure(item: str, indent: str) -> str:
539
- """Writes the dataflow structure to the XML file."""
540
- ref = parse_short_urn(item)
541
- outfile = f"{indent}<{ABBR_STR}:Structure>"
542
- outfile += (
543
- f"{add_indent(indent)}<{REF} "
544
- f'{PACKAGE}="datastructure" '
545
- f"{AGENCY_ID}={ref.agency!r} "
546
- f"{ID}={ref.id!r} "
547
- f"{VERSION}={ref.version!r} "
548
- f"{CLASS}={DSD!r}/>"
549
- )
550
- outfile += f"{indent}</{ABBR_STR}:Structure>"
551
-
552
- outfile = outfile.replace("'", '"')
553
- return outfile
554
-
555
-
556
- def __write_scheme(item_scheme: Any, indent: str, scheme: str) -> str:
557
- """Writes the scheme to the XML file."""
558
- label = f"{ABBR_STR}:{scheme}"
559
- components = ""
560
- data = __write_maintainable(item_scheme, indent)
561
-
562
- if scheme == DSD:
563
- components = __write_components(item_scheme, add_indent(indent))
564
-
565
- if scheme not in [
566
- DSD,
567
- DFW,
568
- RULE_SCHEME,
569
- UDO_SCHEME,
570
- TRANS_SCHEME,
571
- VTL_MAPPING_SCHEME,
572
- ]:
573
- data["Attributes"] += (
574
- f" isPartial={str(item_scheme.is_final).lower()!r}"
575
- )
576
- if scheme in [RULE_SCHEME, UDO_SCHEME, TRANS_SCHEME, VTL_MAPPING_SCHEME]:
577
- data["Attributes"] += f" {_write_vtl(item_scheme, indent)}"
578
-
579
- outfile = ""
580
-
581
- attributes = data.get("Attributes") or ""
582
- attributes = attributes.replace("'", '"')
583
-
584
- outfile += f"{indent}<{label}{attributes}>"
585
-
586
- outfile += __export_intern_data(data)
587
-
588
- outfile += components
589
-
590
- if scheme == DFW:
591
- outfile += __write_structure(item_scheme.structure, add_indent(indent))
592
-
593
- if scheme not in [
594
- DSD,
595
- DFW,
596
- RULE_SCHEME,
597
- UDO_SCHEME,
598
- TRANS_SCHEME,
599
- VTL_MAPPING_SCHEME,
600
- ]:
601
- for item in item_scheme.items:
602
- outfile += __write_item(item, add_indent(indent))
603
- if scheme in [RULE_SCHEME, UDO_SCHEME, TRANS_SCHEME, VTL_MAPPING_SCHEME]:
604
- for item in item_scheme.items:
605
- outfile += _write_vtl(item, add_indent(indent))
606
- outfile += _write_vtl_references(item_scheme, add_indent(indent))
607
- outfile += f"{indent}</{label}>"
608
-
609
- return outfile
610
-
611
-
612
- def __write_metadata_element(
613
- package: Dict[str, Any], key: str, prettyprint: object
614
- ) -> str:
615
- """Writes the metadata element to the XML file.
616
-
617
- Args:
618
- package: The package to be written
619
- key: The key to be used
620
- prettyprint: Prettyprint or not
621
-
622
- Returns:
623
- A string with the metadata element
624
- """
625
- outfile = ""
626
- nl = "\n" if prettyprint else ""
627
- child2 = "\t\t" if prettyprint else ""
628
-
629
- base_indent = f"{nl}{child2}"
630
-
631
- if key in package:
632
- outfile += f"{base_indent}<{ABBR_STR}:{MSG_CONTENT_PKG[key]}>"
633
- for element in package[key].values():
634
- item = (
635
- DSD
636
- if issubclass(element.__class__, DataStructureDefinition)
637
- else (
638
- AGENCIES
639
- if element.id == "AGENCIES"
640
- else type(element).__name__
641
- )
642
- )
643
- outfile += __write_scheme(element, add_indent(base_indent), item)
644
- outfile += f"{base_indent}</{ABBR_STR}:{MSG_CONTENT_PKG[key]}>"
645
-
646
- return outfile
647
-
648
-
649
- def __get_outfile(obj_: Dict[str, Any], key: str = "") -> str:
650
- """Generates an outfile from the object.
651
-
652
- Args:
653
- obj_: The object to be used
654
- key: The key to be used
655
-
656
- Returns:
657
- A string with the outfile
658
-
659
- """
660
- element = obj_.get(key) or []
661
-
662
- return "".join(element)
663
-
664
-
665
- def __export_intern_data(data: Dict[str, Any]) -> str:
666
- """Export internal data (Annotations, Name, Description) on the XML file.
667
-
668
- Args:
669
- data: Information to be exported
670
-
671
- Returns:
672
- The XML string with the exported data
673
- """
674
- outfile = __get_outfile(data, "Annotations")
675
- outfile += __get_outfile(data, "Name")
676
- outfile += __get_outfile(data, "Description")
677
-
678
- return outfile
679
-
680
-
681
- def __write_structures(content: Dict[str, Any], prettyprint: bool) -> str:
682
- """Writes the structures to the XML file.
683
-
684
- Args:
685
- content: The Message Content to be written
686
- prettyprint: Prettyprint or not
687
-
688
- Returns:
689
- A string with the structures
690
- """
691
- nl = "\n" if prettyprint else ""
692
- child1 = "\t" if prettyprint else ""
693
-
694
- outfile = f"{nl}{child1}<{ABBR_MSG}:Structures>"
695
-
696
- for key in MSG_CONTENT_PKG:
697
- outfile += __write_metadata_element(content, key, prettyprint)
698
-
699
- outfile += f"{nl}{child1}</{ABBR_MSG}:Structures>"
700
-
701
- # Replace &amp; with & in the outfile
702
- outfile = outfile.replace("& ", "&amp; ")
703
-
704
- return outfile
705
-
706
-
707
- def _write_vtl(item_or_scheme: Union[Item, ItemScheme], indent: str) -> str:
708
- """Writes the VTL attribute to the XML file for a single item.
709
-
710
- This function writes an item or an item scheme to the XML file,
711
- following the standard format.
712
-
713
- Args:
714
- item_or_scheme: The item or item scheme to be written
715
- Item: The item to be written
716
- ItemScheme: The item scheme to be written
717
- indent: The current indentation level
718
- """
719
- outfile = ""
720
-
721
- if isinstance(item_or_scheme, Item):
722
- label = ""
723
- nameable = __write_nameable(item_or_scheme, add_indent(indent))
724
- attrib = nameable["Attributes"].replace("'", '"')
725
- data = __export_intern_data(nameable)
726
-
727
- if isinstance(item_or_scheme, Ruleset):
728
- label = f"{ABBR_STR}:{RULE}"
729
- data += f"{add_indent(indent)}<{ABBR_STR}:RulesetDefinition>"
730
- data += (
731
- f"{__escape_xml(item_or_scheme.ruleset_definition)}"
732
- f"</{ABBR_STR}:RulesetDefinition>"
733
- )
734
- attrib += (
735
- f" rulesetScope={item_or_scheme.ruleset_scope!r} "
736
- f"rulesetType={item_or_scheme.ruleset_type!r}"
737
- )
738
-
739
- if isinstance(item_or_scheme, Transformation):
740
- label = f"{ABBR_STR}:{TRANSFORMATION}"
741
- data += f"{add_indent(indent)}<{ABBR_STR}:Expression>"
742
- data += (
743
- f"{__escape_xml(item_or_scheme.expression)}"
744
- f"</{ABBR_STR}:Expression>"
745
- )
746
- data += f"{add_indent(indent)}<{ABBR_STR}:Result>"
747
- data += f"{item_or_scheme.result}</{ABBR_STR}:Result>"
748
- attrib += (
749
- f" isPersistent="
750
- f"{str(item_or_scheme.is_persistent).lower()!r}"
751
- )
752
-
753
- if isinstance(item_or_scheme, UserDefinedOperator):
754
- label = f"{ABBR_STR}:{UDO}"
755
- data += f"{add_indent(indent)}<{ABBR_STR}:OperatorDefinition>"
756
- data += (
757
- f"{__escape_xml(item_or_scheme.operator_definition)}"
758
- f"</{ABBR_STR}:OperatorDefinition>"
759
- )
760
- if isinstance(item_or_scheme, VtlDataflowMapping):
761
- label = f"{ABBR_STR}:{VTLMAPPING}"
762
- data += f"{add_indent(indent)}<{ABBR_STR}:Dataflow>"
763
- reference = item_or_scheme.dataflow
764
- data += (
765
- f"{indent}\t\t<{REF} package='datastructure' "
766
- f"agencyID={reference.agency!r} id={reference.id!r} "
767
- f"version={reference.version!r} class={DFW!r} />"
768
- f"{add_indent(indent)}</{ABBR_STR}:Dataflow>"
769
- )
770
- attrib += f" alias={item_or_scheme.dataflow_alias!r}"
771
-
772
- outfile += f"{indent}<{label}{attrib}>"
773
- outfile += data
774
- outfile += f"{indent}</{label}>"
775
-
776
- if isinstance(item_or_scheme, VtlScheme):
777
- outfile += f" vtlVersion={item_or_scheme.vtl_version!r}"
778
-
779
- outfile = outfile.replace("'", '"')
780
-
781
- return outfile
782
-
783
-
784
- def _write_vtl_references(scheme: ItemScheme, indent: str) -> str:
785
- """Writes references to VTL elements to the XML file."""
786
-
787
- def process_references(
788
- references: Union[Any, Sequence[Any]], element_name: str
789
- ) -> str:
790
- """Process the references to VTL elements."""
791
- outreference = []
792
- if not isinstance(references, (list, tuple)):
793
- references = [references]
794
-
795
- for ref in references:
796
- if isinstance(ref, Reference):
797
- outreference.append(
798
- f"{indent}<{ABBR_STR}:{element_name}>"
799
- f"{add_indent(indent)}<{REF} "
800
- f"{PACKAGE}={TRANSFORMATION.lower()!r} "
801
- f"{AGENCY_ID}={ref.agency!r} "
802
- f"{ID}={ref.id!r} "
803
- f"{VERSION}={ref.version!r} "
804
- f"{CLASS}={ref.sdmx_type!r}/>"
805
- f"{indent}</{ABBR_STR}:{element_name}>"
806
- )
807
- if isinstance(ref, ItemScheme):
808
- ref_to_use = parse_short_urn(ref.short_urn)
809
- outreference.append(
810
- f"{indent}<{ABBR_STR}:{element_name}>"
811
- f"{add_indent(indent)}<{REF} "
812
- f"{PACKAGE}={TRANSFORMATION.lower()!r} "
813
- f"{AGENCY_ID}={ref_to_use.agency!r} "
814
- f"{ID}={ref_to_use.id!r} "
815
- f"{VERSION}={ref_to_use.version!r} "
816
- f"{CLASS}={ref_to_use.sdmx_type!r}/>"
817
- f"{indent}</{ABBR_STR}:{element_name}>"
818
- )
819
-
820
- return "".join(outreference)
821
-
822
- outfile = ""
823
- if isinstance(scheme, TransformationScheme):
824
- outfile += process_references(scheme.ruleset_schemes, "RulesetScheme")
825
- outfile += process_references(
826
- scheme.user_defined_operator_schemes, "UserDefinedOperatorScheme"
827
- )
828
- if isinstance(scheme, UserDefinedOperatorScheme):
829
- outfile += process_references(scheme.ruleset_schemes, "RulesetScheme")
830
-
831
- outfile = outfile.replace("'", '"')
832
-
833
- return outfile
834
18
 
835
19
 
836
20
  def write(
837
- structures: Sequence[STR_TYPES],
838
- output_path: str = "",
21
+ structures: Sequence[MaintainableArtefact],
22
+ output_path: Optional[Union[str, Path]] = None,
839
23
  prettyprint: bool = True,
840
24
  header: Optional[Header] = None,
841
25
  ) -> Optional[str]:
@@ -855,9 +39,9 @@ def write(
855
39
  if header is None:
856
40
  header = Header()
857
41
 
858
- content: Dict[str, Dict[str, STR_TYPES]] = {}
42
+ content: Dict[str, Dict[str, MaintainableArtefact]] = {}
859
43
  for urn, element in elements.items():
860
- list_ = STR_DICT_TYPE_LIST[type(element)]
44
+ list_ = STR_DICT_TYPE_LIST_21[type(element)]
861
45
  if list_ not in content:
862
46
  content[list_] = {}
863
47
  content[list_][urn] = element
@@ -871,7 +55,11 @@ def write(
871
55
 
872
56
  outfile += get_end_message(type_, prettyprint)
873
57
 
874
- if output_path == "":
58
+ output_path = (
59
+ str(output_path) if isinstance(output_path, Path) else output_path
60
+ )
61
+
62
+ if output_path is None or output_path == "":
875
63
  return outfile
876
64
  with open(output_path, "w", encoding="UTF-8", errors="replace") as f:
877
65
  f.write(outfile)