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,856 +1,21 @@
1
1
  """Parsers for reading metadata."""
2
2
 
3
- from datetime import datetime
4
- from typing import Any, Callable, Dict, List, Optional, Sequence, Union
5
-
6
- from msgspec import Struct
3
+ from typing import Sequence, Union
7
4
 
8
5
  from pysdmx.errors import Invalid
9
- from pysdmx.io.xml.sdmx21.__tokens import (
10
- AGENCIES,
11
- AGENCY,
12
- AGENCY_ID,
13
- ALIAS_LOW,
14
- ANNOTATION,
15
- ANNOTATION_TEXT,
16
- ANNOTATION_TITLE,
17
- ANNOTATION_TYPE,
18
- ANNOTATION_URL,
19
- ANNOTATIONS,
20
- AS_STATUS,
21
- ATT,
22
- ATT_LIST,
23
- ATT_LVL,
24
- ATT_REL,
25
- CL,
26
- CLASS,
27
- CLS,
28
- CODE,
29
- CODES_LOW,
30
- COMPS,
31
- CON,
32
- CON_ID,
33
- CON_LOW,
34
- CON_ROLE,
35
- CONCEPTS,
36
- CONTACT,
37
- CORE_REP,
38
- CS,
39
- DEPARTMENT,
40
- DESC,
41
- DFW,
42
- DFW_ALIAS_LOW,
43
- DFW_LOW,
44
- DFWS,
45
- DIM,
46
- DIM_LIST,
47
- DSD,
48
- DSD_COMPS,
49
- DSDS,
50
- DTYPE,
51
- EMAIL,
52
- EMAILS,
53
- ENUM,
54
- ENUM_FORMAT,
55
- FACETS,
56
- FAX,
57
- FAXES,
58
- GROUP,
59
- GROUP_DIM,
60
- ID,
61
- IS_EXTERNAL_REF,
62
- IS_EXTERNAL_REF_LOW,
63
- IS_FINAL,
64
- IS_FINAL_LOW,
65
- IS_PARTIAL,
66
- IS_PARTIAL_LOW,
67
- LOCAL_CODES_LOW,
68
- LOCAL_DTYPE,
69
- LOCAL_FACETS_LOW,
70
- LOCAL_REP,
71
- MANDATORY,
72
- ME_LIST,
73
- NAME,
74
- ORGS,
75
- PAR_ID,
76
- PAR_VER,
77
- PRIM_MEASURE,
78
- REF,
79
- REQUIRED,
80
- ROLE,
81
- RULE,
82
- RULE_SCHEME,
83
- RULESETS,
84
- SER_URL,
85
- SER_URL_LOW,
86
- STR_URL,
87
- STR_URL_LOW,
6
+ from pysdmx.io.xml.__parse_xml import parse_xml
7
+ from pysdmx.io.xml.__structure_aux_reader import StructureParser
8
+ from pysdmx.io.xml.__tokens import (
88
9
  STRUCTURE,
89
10
  STRUCTURES,
90
- TELEPHONE,
91
- TELEPHONES,
92
- TEXT,
93
- TEXT_FORMAT,
94
- TEXT_TYPE,
95
- TIME_DIM,
96
- TITLE,
97
- TRANS_SCHEME,
98
- TRANSFORMATION,
99
- TRANSFORMATIONS,
100
- TYPE,
101
- UDO,
102
- UDO_SCHEME,
103
- UDOS,
104
- URI,
105
- URIS,
106
- URL,
107
- URN,
108
- VALID_FROM,
109
- VALID_FROM_LOW,
110
- VALID_TO,
111
- VALID_TO_LOW,
112
- VERSION,
113
- VTL_MAPPING_SCHEME,
114
- VTLMAPPING,
115
- VTLMAPPINGS,
116
- )
117
- from pysdmx.io.xml.sdmx21.reader.__parse_xml import parse_xml
118
- from pysdmx.io.xml.utils import add_list
119
- from pysdmx.model import (
120
- AgencyScheme,
121
- Code,
122
- Codelist,
123
- Concept,
124
- ConceptScheme,
125
- DataType,
126
- Facets,
127
11
  )
128
12
  from pysdmx.model.__base import (
129
- Agency,
130
- Annotation,
131
- Contact,
132
- DataflowRef,
133
- Item,
134
- ItemReference,
135
13
  ItemScheme,
136
- Reference,
137
14
  )
138
15
  from pysdmx.model.dataflow import (
139
- Component,
140
- Components,
141
16
  Dataflow,
142
17
  DataStructureDefinition,
143
- Role,
144
18
  )
145
- from pysdmx.model.vtl import (
146
- Ruleset,
147
- RulesetScheme,
148
- Transformation,
149
- TransformationScheme,
150
- UserDefinedOperator,
151
- UserDefinedOperatorScheme,
152
- VtlDataflowMapping,
153
- VtlMappingScheme,
154
- )
155
- from pysdmx.util import find_by_urn, parse_urn
156
-
157
- STRUCTURES_MAPPING = {
158
- CL: Codelist,
159
- AGENCIES: AgencyScheme,
160
- CS: ConceptScheme,
161
- DFWS: Dataflow,
162
- DSDS: DataStructureDefinition,
163
- RULE_SCHEME: RulesetScheme,
164
- UDO_SCHEME: UserDefinedOperatorScheme,
165
- TRANS_SCHEME: TransformationScheme,
166
- VTL_MAPPING_SCHEME: VtlMappingScheme,
167
- }
168
- ITEMS_CLASSES = {
169
- AGENCY: Agency,
170
- CODE: Code,
171
- CON: Concept,
172
- RULE: Ruleset,
173
- UDO: UserDefinedOperator,
174
- TRANSFORMATION: Transformation,
175
- VTLMAPPING: VtlDataflowMapping,
176
- }
177
-
178
- COMP_TYPES = [DIM, ATT, PRIM_MEASURE, GROUP_DIM]
179
-
180
- ROLE_MAPPING = {
181
- DIM: Role.DIMENSION,
182
- ATT: Role.ATTRIBUTE,
183
- PRIM_MEASURE: Role.MEASURE,
184
- }
185
-
186
- FACETS_MAPPING = {
187
- "minLength": "min_length",
188
- "maxLength": "max_length",
189
- "minValue": "min_value",
190
- "maxValue": "max_value",
191
- "startValue": "start_value",
192
- "endValue": "end_value",
193
- "interval": "interval",
194
- "timeInterval": "time_interval",
195
- "decimals": "decimals",
196
- "pattern": "pattern",
197
- "startTime": "start_time",
198
- "endTime": "end_time",
199
- "isSequence": "is_sequence",
200
- }
201
-
202
-
203
- def _extract_text(element: Any) -> str:
204
- """Extracts the text from the element.
205
-
206
- Args:
207
- element: The element to extract the text from
208
-
209
- Returns:
210
- The text extracted from the element
211
- """
212
- if isinstance(element, list):
213
- aux = {}
214
- for language_element in element:
215
- if "lang" in language_element and language_element["lang"] == "en":
216
- aux = language_element
217
- if not aux:
218
- aux = element[0]
219
- element = aux
220
- if isinstance(element, dict) and "#text" in element:
221
- element = element["#text"]
222
- return element
223
-
224
-
225
- class StructureParser(Struct):
226
- """StructureParser class for SDMX-ML 2.1."""
227
-
228
- agencies: Dict[str, Any] = {}
229
- codelists: Dict[str, Any] = {}
230
- concepts: Dict[str, Any] = {}
231
- datastructures: Dict[str, Any] = {}
232
- dataflows: Dict[str, Any] = {}
233
- rulesets: Dict[str, Any] = {}
234
- udos: Dict[str, Any] = {}
235
- transformations: Dict[str, Any] = {}
236
-
237
- def __format_contact(self, json_contact: Dict[str, Any]) -> Contact:
238
- """Creates a Contact object from a json_contact.
239
-
240
- Args:
241
- json_contact: The element to create the Contact object from
242
-
243
- Returns:
244
- Contact object created from the json_contact
245
- """
246
- self.__format_name_description(json_contact)
247
-
248
- xml_node_to_attribute = {
249
- NAME: NAME.lower(),
250
- DEPARTMENT: DEPARTMENT.lower(),
251
- ROLE: ROLE.lower(),
252
- URI: URIS,
253
- EMAIL: EMAILS,
254
- TELEPHONE: TELEPHONES,
255
- FAX: FAXES,
256
- }
257
-
258
- for k, v in xml_node_to_attribute.items():
259
- if k in json_contact:
260
- if k in [DEPARTMENT, ROLE]:
261
- json_contact[v] = _extract_text(json_contact.pop(k))
262
- continue
263
- field_info = add_list(json_contact.pop(k))
264
- for i, element in enumerate(field_info):
265
- field_info[i] = _extract_text(element)
266
- json_contact[v] = field_info
267
-
268
- return Contact(**json_contact)
269
-
270
- @staticmethod
271
- def __format_annotations(item_elem: Any) -> Dict[str, Any]:
272
- """Formats the annotations in this element.
273
-
274
- Args:
275
- item_elem: The element to be formatted
276
-
277
- Returns:
278
- annotations formatted
279
- """
280
- if ANNOTATIONS not in item_elem:
281
- return item_elem
282
- annotations = []
283
-
284
- ann = item_elem[ANNOTATIONS]
285
- ann[ANNOTATION] = add_list(ann[ANNOTATION])
286
- for e in ann[ANNOTATION]:
287
- if ANNOTATION_TITLE in e:
288
- e[TITLE] = e.pop(ANNOTATION_TITLE)
289
- if ANNOTATION_TYPE in e:
290
- e[TYPE] = e.pop(ANNOTATION_TYPE)
291
- if ANNOTATION_TEXT in e:
292
- e[TEXT] = _extract_text(e[ANNOTATION_TEXT])
293
- del e[ANNOTATION_TEXT]
294
- if ANNOTATION_URL in e:
295
- e[URL] = e.pop(ANNOTATION_URL)
296
-
297
- annotations.append(Annotation(**e))
298
-
299
- item_elem[ANNOTATIONS.lower()] = annotations
300
- del item_elem[ANNOTATIONS]
301
-
302
- return item_elem
303
-
304
- @staticmethod
305
- def __format_name_description(element: Any) -> Dict[str, Any]:
306
- node = [NAME, DESC]
307
- for field in node:
308
- if field in element:
309
- element[field.lower()] = _extract_text(element[field])
310
- del element[field]
311
- return element
312
-
313
- @staticmethod
314
- def __format_facets(
315
- json_fac: Dict[str, Any], json_obj: Dict[str, Any]
316
- ) -> None:
317
- """Formats the facets from the JSON information.
318
-
319
- Args:
320
- json_fac: The element with the facets to be formatted
321
- json_obj: The element to store the formatted facets
322
- """
323
- if json_fac is None:
324
- return
325
- for key, _value in json_fac.items():
326
- if key == TEXT_TYPE and json_fac[TEXT_TYPE] in list(DataType):
327
- json_obj["dtype"] = DataType(json_fac[TEXT_TYPE])
328
-
329
- if key in FACETS_MAPPING:
330
- facet_kwargs = {
331
- FACETS_MAPPING[k]: v
332
- for k, v in json_fac.items()
333
- if k in FACETS_MAPPING
334
- }
335
- json_obj[FACETS.lower()] = Facets(**facet_kwargs)
336
-
337
- @staticmethod
338
- def __format_validity(element: Dict[str, Any]) -> Dict[str, Any]:
339
- if VALID_FROM in element:
340
- element[VALID_FROM_LOW] = datetime.fromisoformat(
341
- element.pop(VALID_FROM)
342
- )
343
- if VALID_TO in element:
344
- element[VALID_TO_LOW] = datetime.fromisoformat(
345
- element.pop(VALID_TO)
346
- )
347
- return element
348
-
349
- @staticmethod
350
- def __format_urls(json_elem: Dict[str, Any]) -> Dict[str, Any]:
351
- """Formats the STR_URL and SER_URL keys in the element.
352
-
353
- Args:
354
- json_elem: The element to be formatted
355
-
356
- Returns:
357
- The json_elem with STR_URL and SER_URL keys formatted.
358
- """
359
- if STR_URL in json_elem:
360
- json_elem[STR_URL_LOW] = json_elem.pop(STR_URL)
361
- if SER_URL in json_elem:
362
- json_elem[SER_URL_LOW] = json_elem.pop(SER_URL)
363
- return json_elem
364
-
365
- def __format_agency(self, element: Dict[str, Any]) -> Dict[str, Any]:
366
- """Formats the AGENCY_ID key in the element to the maintainer.
367
-
368
- Args:
369
- element: The element with the Agency ID to be formatted
370
-
371
- Returns:
372
- element with the Agency ID formatted
373
- """
374
- element[AGENCY.lower()] = self.agencies.get(
375
- element[AGENCY_ID], element[AGENCY_ID]
376
- )
377
- del element[AGENCY_ID]
378
- return element
379
-
380
- def __format_orgs(self, json_orgs: Dict[str, Any]) -> Dict[str, Any]:
381
- orgs: Dict[str, Any] = {}
382
- json_list = add_list(json_orgs)
383
- for e in json_list:
384
- ag_sch = self.__format_scheme(
385
- e,
386
- AGENCIES,
387
- AGENCY,
388
- )
389
- orgs = {**orgs, **ag_sch}
390
- return orgs
391
-
392
- def __format_representation(
393
- self, json_rep: Dict[str, Any], json_obj: Dict[str, Any]
394
- ) -> None:
395
- """Formats the representation in the JSON Representation."""
396
- if TEXT_FORMAT in json_rep:
397
- self.__format_facets(json_rep[TEXT_FORMAT], json_obj)
398
-
399
- if ENUM in json_rep and len(self.codelists) > 0:
400
- ref = json_rep[ENUM].get(REF, json_rep[ENUM])
401
-
402
- if "URN" in ref:
403
- codelist = find_by_urn(
404
- list(self.codelists.values()), ref["URN"]
405
- ).codes
406
-
407
- else:
408
- short_urn = str(
409
- Reference(
410
- sdmx_type=ref[CLASS],
411
- agency=ref[AGENCY_ID],
412
- id=ref[ID],
413
- version=ref[VERSION],
414
- )
415
- )
416
- codelist = self.codelists[short_urn]
417
-
418
- json_obj[CODES_LOW] = codelist
419
- if ENUM_FORMAT in json_rep:
420
- self.__format_facets(json_rep[ENUM_FORMAT], json_obj)
421
-
422
- def __format_local_rep(self, representation_info: Dict[str, Any]) -> None:
423
- rep: Dict[str, Any] = {}
424
-
425
- self.__format_representation(representation_info[LOCAL_REP], rep)
426
- del representation_info[LOCAL_REP]
427
-
428
- if CODES_LOW in rep:
429
- representation_info[LOCAL_CODES_LOW] = rep.pop(CODES_LOW)
430
-
431
- if DTYPE in rep:
432
- representation_info[LOCAL_DTYPE] = rep.pop(DTYPE)
433
-
434
- if FACETS.lower() in rep:
435
- representation_info[LOCAL_FACETS_LOW] = rep.pop(FACETS.lower())
436
-
437
- def __format_con_id(self, concept_ref: Dict[str, Any]) -> Dict[str, Any]:
438
- rep = {}
439
- item_reference = ItemReference(
440
- sdmx_type=concept_ref[CLASS],
441
- agency=concept_ref[AGENCY_ID],
442
- id=concept_ref[PAR_ID],
443
- version=concept_ref[PAR_VER],
444
- item_id=concept_ref[ID],
445
- )
446
- scheme_reference = Reference(
447
- sdmx_type=CS,
448
- agency=concept_ref[AGENCY_ID],
449
- id=concept_ref[PAR_ID],
450
- version=concept_ref[PAR_VER],
451
- )
452
-
453
- concept_scheme = self.concepts.get(str(scheme_reference))
454
- if concept_scheme is None:
455
- return {CON: item_reference}
456
- for con in concept_scheme.concepts:
457
- if con.id == concept_ref[ID]:
458
- rep[CON] = con
459
- break
460
- if CON not in rep:
461
- return {CON: item_reference}
462
- return rep
463
-
464
- @staticmethod
465
- def __format_relationship(json_rel: Dict[str, Any]) -> Optional[str]:
466
- att_level = None
467
-
468
- for scheme in [DIM, PRIM_MEASURE]:
469
- if scheme in json_rel:
470
- if scheme == DIM:
471
- dims = add_list(json_rel[DIM])
472
- dims = [dim[REF][ID] for dim in dims]
473
- att_level = ",".join(dims)
474
- else:
475
- att_level = "O"
476
-
477
- return att_level
478
-
479
- def __format_vtl_references(
480
- self, json_elem: Dict[str, Any]
481
- ) -> Dict[str, Any]:
482
- """Formats the references in the VTL element."""
483
-
484
- def extract_references(
485
- scheme: str, new_key: str, object_list: Dict[str, Any]
486
- ) -> None:
487
- if scheme in json_elem:
488
- references = []
489
- scheme_entries = (
490
- json_elem[scheme]
491
- if isinstance(json_elem[scheme], list)
492
- else [json_elem[scheme]]
493
- )
494
- for entry in scheme_entries:
495
- ref = entry[REF]
496
-
497
- ref_id = ref[ID]
498
- matching_object = next(
499
- (
500
- obj
501
- for obj in object_list.values()
502
- if getattr(obj, ID, None) == ref_id
503
- ),
504
- None,
505
- )
506
-
507
- if matching_object:
508
- references.append(matching_object)
509
- else:
510
- references.append(
511
- Reference(
512
- sdmx_type=ref[CLASS],
513
- agency=ref[AGENCY_ID],
514
- id=ref_id,
515
- version=ref[VERSION],
516
- )
517
- )
518
-
519
- json_elem[new_key] = references
520
- json_elem.pop(scheme)
521
-
522
- extract_references(RULE_SCHEME, "ruleset_schemes", self.rulesets)
523
- extract_references(
524
- UDO_SCHEME, "user_defined_operator_schemes", self.udos
525
- )
526
-
527
- return json_elem
528
-
529
- def __format_dataflow(
530
- self, json_rep: Dict[str, Any], json_obj: Dict[str, Any]
531
- ) -> None:
532
- json_obj[DFW_ALIAS_LOW] = json_obj.pop(ALIAS_LOW)
533
- dataflow_ref = {
534
- "agency": json_rep[REF][AGENCY_ID],
535
- "id": json_rep[REF][ID],
536
- "version": json_rep[REF][VERSION],
537
- }
538
- json_obj[DFW_LOW] = DataflowRef(**dataflow_ref)
539
- json_rep.pop(REF)
540
- json_obj.pop(DFW)
541
- if self.dataflows:
542
- for dataflow in self.dataflows.values():
543
- if dataflow.id == dataflow_ref[ID]:
544
- json_obj[DFW_LOW] = dataflow
545
-
546
- def __format_component(
547
- self, comp: Dict[str, Any], role: Role
548
- ) -> Component:
549
- comp[ROLE.lower()] = role
550
- comp[REQUIRED] = True
551
-
552
- self.__format_local_rep(comp) if LOCAL_REP in comp else None
553
-
554
- concept_id = self.__format_con_id(comp[CON_ID][REF])
555
- comp[CON_LOW] = concept_id.pop(CON)
556
- del comp[CON_ID]
557
-
558
- # Attribute Handling
559
- if ATT_REL in comp:
560
- comp[ATT_LVL] = self.__format_relationship(comp[ATT_REL])
561
- del comp[ATT_REL]
562
-
563
- if AS_STATUS in comp:
564
- if comp[AS_STATUS] != MANDATORY:
565
- comp[REQUIRED] = False
566
- del comp[AS_STATUS]
567
-
568
- if "position" in comp:
569
- del comp["position"]
570
-
571
- if ANNOTATIONS in comp:
572
- del comp[ANNOTATIONS]
573
-
574
- if CON_ROLE in comp:
575
- del comp[CON_ROLE]
576
-
577
- return Component(**comp)
578
-
579
- def __format_component_lists(
580
- self, element: Dict[str, Any]
581
- ) -> List[Component]:
582
- comp_list = []
583
-
584
- if TIME_DIM in element:
585
- element[DIM] = add_list(element[DIM])
586
- element[DIM].append(element[TIME_DIM])
587
- del element[TIME_DIM]
588
-
589
- role_name = list(set(element.keys()).intersection(COMP_TYPES))[0]
590
- role = ROLE_MAPPING[role_name]
591
- element[role_name] = add_list(element[role_name])
592
-
593
- for comp in element[role_name]:
594
- formatted_comp = self.__format_component(comp, role)
595
- comp_list.append(formatted_comp)
596
-
597
- return comp_list
598
-
599
- def __format_components(self, element: Dict[str, Any]) -> Dict[str, Any]:
600
- if DSD_COMPS in element:
601
- element[COMPS] = []
602
- comps = element[DSD_COMPS]
603
-
604
- for comp_list in [DIM_LIST, ME_LIST, GROUP, ATT_LIST]:
605
- if comp_list == GROUP and comp_list in comps:
606
- del comps[GROUP]
607
-
608
- elif comp_list in comps:
609
- fmt_comps = self.__format_component_lists(comps[comp_list])
610
- element[COMPS].extend(fmt_comps)
611
-
612
- element[COMPS] = Components(element[COMPS])
613
- del element[DSD_COMPS]
614
-
615
- return element
616
-
617
- @staticmethod
618
- def __format_vtl(json_vtl: Dict[str, Any]) -> Dict[str, Any]:
619
- if "isPersistent" in json_vtl:
620
- json_vtl["is_persistent"] = (
621
- json_vtl.pop("isPersistent").lower() == "true"
622
- )
623
- if "Expression" in json_vtl:
624
- json_vtl["expression"] = json_vtl.pop("Expression")
625
- if "Result" in json_vtl:
626
- json_vtl["result"] = json_vtl.pop("Result")
627
- if "vtlVersion" in json_vtl:
628
- json_vtl["vtl_version"] = json_vtl.pop("vtlVersion")
629
- if "rulesetScope" in json_vtl:
630
- json_vtl["ruleset_scope"] = json_vtl.pop("rulesetScope")
631
- if "rulesetType" in json_vtl:
632
- json_vtl["ruleset_type"] = json_vtl.pop("rulesetType")
633
- if "RulesetDefinition" in json_vtl:
634
- json_vtl["ruleset_definition"] = json_vtl.pop("RulesetDefinition")
635
- if "OperatorDefinition" in json_vtl:
636
- json_vtl["operator_definition"] = json_vtl.pop(
637
- "OperatorDefinition"
638
- )
639
- return json_vtl
640
-
641
- def __format_item(
642
- self, item_json_info: Dict[str, Any], item_name_class: str
643
- ) -> Item:
644
- item_json_info = self.__format_annotations(item_json_info)
645
- item_json_info = self.__format_name_description(item_json_info)
646
- if CONTACT in item_json_info and item_name_class == AGENCY:
647
- item_json_info[CONTACT] = add_list(item_json_info[CONTACT])
648
- contacts = []
649
- for e in item_json_info[CONTACT]:
650
- contacts.append(self.__format_contact(e))
651
- item_json_info[CONTACT.lower() + "s"] = contacts
652
- del item_json_info[CONTACT]
653
-
654
- if CORE_REP in item_json_info and item_name_class == CON:
655
- self.__format_representation(
656
- item_json_info[CORE_REP], item_json_info
657
- )
658
- del item_json_info[CORE_REP]
659
-
660
- if "Parent" in item_json_info:
661
- del item_json_info["Parent"]
662
- if DFW in item_json_info:
663
- self.__format_dataflow(item_json_info[DFW], item_json_info)
664
-
665
- item_json_info = self.__format_vtl(item_json_info)
666
-
667
- return ITEMS_CLASSES[item_name_class](**item_json_info)
668
-
669
- def __format_scheme(
670
- self, json_elem: Dict[str, Any], scheme: str, item: str
671
- ) -> Dict[str, ItemScheme]:
672
- elements: Dict[str, ItemScheme] = {}
673
-
674
- json_elem[scheme] = add_list(json_elem[scheme])
675
- for element in json_elem[scheme]:
676
- element["items"] = []
677
-
678
- element = self.__format_annotations(element)
679
- element = self.__format_name_description(element)
680
- element = self.__format_urls(element)
681
- if IS_EXTERNAL_REF in element:
682
- element[IS_EXTERNAL_REF_LOW] = (
683
- element.pop(IS_EXTERNAL_REF) == "true"
684
- )
685
- if IS_FINAL in element:
686
- element[IS_FINAL_LOW] = element.pop(IS_FINAL) == "true"
687
- if IS_PARTIAL in element:
688
- element[IS_PARTIAL_LOW] = element.pop(IS_PARTIAL) == "true"
689
- element[item] = add_list(element[item])
690
- items = []
691
- for item_elem in element[item]:
692
- # Dynamic
693
- items.append(self.__format_item(item_elem, item))
694
- del element[item]
695
- element["items"] = items
696
- element = self.__format_agency(element)
697
- element = self.__format_validity(element)
698
- element = self.__format_vtl(element)
699
- element = self.__format_vtl_references(element)
700
- if "xmlns" in element:
701
- del element["xmlns"]
702
- # Dynamic creation with specific class
703
- result: ItemScheme = STRUCTURES_MAPPING[scheme](**element)
704
- elements[result.short_urn] = result
705
-
706
- return elements
707
-
708
- def __format_schema(
709
- self, json_element: Dict[str, Any], schema: str, item: str
710
- ) -> Dict[str, Any]:
711
- """Formats the structures in json format.
712
-
713
- Args:
714
- json_element: The structures in json format
715
- schema: The scheme of the structures
716
- item: The item of the structures
717
-
718
- Returns:
719
- A dictionary with the structures formatted
720
- """
721
- schemas = {}
722
-
723
- json_element[item] = add_list(json_element[item])
724
- for element in json_element[item]:
725
- if URN.lower() in element and element[URN.lower()] is not None:
726
- short_urn = parse_urn(element[URN.lower()]).__str__()
727
- else:
728
- short_urn = Reference(
729
- sdmx_type=item,
730
- agency=element[AGENCY_ID],
731
- id=element[ID],
732
- version=element[VERSION],
733
- ).__str__()
734
-
735
- element = self.__format_annotations(element)
736
- element = self.__format_name_description(element)
737
- element = self.__format_urls(element)
738
- element = self.__format_agency(element)
739
- element = self.__format_validity(element)
740
- element = self.__format_components(element)
741
-
742
- if "xmlns" in element:
743
- del element["xmlns"]
744
- if IS_EXTERNAL_REF in element:
745
- element[IS_EXTERNAL_REF_LOW] = element.pop(IS_EXTERNAL_REF)
746
- element[IS_EXTERNAL_REF_LOW] = (
747
- str(element[IS_EXTERNAL_REF_LOW]).lower() == "true"
748
- )
749
- if IS_FINAL in element:
750
- element[IS_FINAL_LOW] = element.pop(IS_FINAL)
751
- element[IS_FINAL_LOW] = (
752
- str(element[IS_FINAL_LOW]).lower() == "true"
753
- )
754
-
755
- if item == DFW:
756
- ref_data = element[STRUCTURE][REF]
757
- reference_str = (
758
- f"{ref_data[CLASS]}={ref_data[AGENCY_ID]}"
759
- f":{ref_data[ID]}({ref_data[VERSION]})"
760
- )
761
- element[STRUCTURE] = reference_str
762
-
763
- structure = {key.lower(): value for key, value in element.items()}
764
- if schema == DSDS:
765
- if COMPS in structure:
766
- structure[COMPS] = Components(structure[COMPS])
767
- else:
768
- structure[COMPS] = Components([])
769
- schemas[short_urn] = STRUCTURES_MAPPING[schema](**structure)
770
-
771
- return schemas
772
-
773
- def format_structures(
774
- self, json_meta: Dict[str, Any]
775
- ) -> Sequence[Union[ItemScheme, DataStructureDefinition, Dataflow]]:
776
- """Formats the structures in JSON format.
777
-
778
- Args:
779
- json_meta: The structures in JSON format.
780
-
781
- Returns:
782
- A list with the formatted structures.
783
- """
784
-
785
- def process_structure(
786
- key: str,
787
- formatter: Callable[[Dict[str, Any]], Dict[Any, Any]],
788
- attr: Optional[str] = None,
789
- ) -> Dict[Any, Any]:
790
- """Helper function to process and store formatted structures."""
791
- if key in json_meta:
792
- formatted = formatter(json_meta[key])
793
- if attr:
794
- setattr(self, attr, formatted)
795
- return formatted
796
- return {}
797
-
798
- structures = {
799
- ORGS: process_structure(ORGS, self.__format_orgs, "agencies"),
800
- CLS: process_structure(
801
- CLS,
802
- lambda data: self.__format_scheme(data, CL, CODE),
803
- "codelists",
804
- ),
805
- CONCEPTS: process_structure(
806
- CONCEPTS,
807
- lambda data: self.__format_scheme(data, CS, CON),
808
- "concepts",
809
- ),
810
- DSDS: process_structure(
811
- DSDS,
812
- lambda data: self.__format_schema(data, DSDS, DSD),
813
- "datastructures",
814
- ),
815
- DFWS: process_structure(
816
- DFWS,
817
- lambda data: self.__format_schema(data, DFWS, DFW),
818
- "dataflows",
819
- ),
820
- RULESETS: process_structure(
821
- RULESETS,
822
- lambda data: self.__format_scheme(data, RULE_SCHEME, RULE),
823
- "rulesets",
824
- ),
825
- UDOS: process_structure(
826
- UDOS,
827
- lambda data: self.__format_scheme(data, UDO_SCHEME, UDO),
828
- "udos",
829
- ),
830
- TRANSFORMATIONS: process_structure(
831
- TRANSFORMATIONS,
832
- lambda data: self.__format_scheme(
833
- data,
834
- TRANS_SCHEME,
835
- TRANSFORMATION,
836
- ),
837
- "transformations",
838
- ),
839
- VTLMAPPINGS: process_structure(
840
- VTLMAPPINGS,
841
- lambda data: self.__format_scheme(
842
- data,
843
- VTL_MAPPING_SCHEME,
844
- VTLMAPPING,
845
- ),
846
- ),
847
- }
848
- return [
849
- compound
850
- for value in structures.values()
851
- if value
852
- for compound in value.values()
853
- ]
854
19
 
855
20
 
856
21
  def read(
@@ -860,7 +25,7 @@ def read(
860
25
  """Reads an SDMX-ML 2.1 Structure data and returns the structures.
861
26
 
862
27
  Args:
863
- input_str: SDMX-ML data to read.
28
+ input_str: SDMX-ML structure message to read.
864
29
  validate: If True, the XML data will be validated against the XSD.
865
30
 
866
31
  Returns: