pysdmx 1.10.1__py3-none-any.whl → 1.12.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.
Files changed (38) hide show
  1. pysdmx/__init__.py +1 -1
  2. pysdmx/api/fmr/maintenance.py +10 -5
  3. pysdmx/io/input_processor.py +4 -0
  4. pysdmx/io/json/fusion/messages/constraint.py +22 -1
  5. pysdmx/io/json/fusion/messages/dsd.py +20 -14
  6. pysdmx/io/json/fusion/messages/msd.py +6 -9
  7. pysdmx/io/json/fusion/messages/schema.py +20 -1
  8. pysdmx/io/json/sdmxjson2/messages/core.py +12 -5
  9. pysdmx/io/json/sdmxjson2/messages/dsd.py +11 -17
  10. pysdmx/io/json/sdmxjson2/messages/msd.py +2 -5
  11. pysdmx/io/json/sdmxjson2/messages/report.py +7 -3
  12. pysdmx/io/json/sdmxjson2/messages/schema.py +38 -5
  13. pysdmx/io/json/sdmxjson2/messages/structure.py +7 -3
  14. pysdmx/io/json/sdmxjson2/reader/metadata.py +3 -3
  15. pysdmx/io/json/sdmxjson2/reader/structure.py +3 -3
  16. pysdmx/io/json/sdmxjson2/writer/_helper.py +118 -0
  17. pysdmx/io/json/sdmxjson2/writer/v2_0/__init__.py +1 -0
  18. pysdmx/io/json/sdmxjson2/writer/v2_0/metadata.py +33 -0
  19. pysdmx/io/json/sdmxjson2/writer/v2_0/structure.py +33 -0
  20. pysdmx/io/json/sdmxjson2/writer/v2_1/__init__.py +1 -0
  21. pysdmx/io/json/sdmxjson2/writer/v2_1/metadata.py +31 -0
  22. pysdmx/io/json/sdmxjson2/writer/v2_1/structure.py +33 -0
  23. pysdmx/io/reader.py +12 -3
  24. pysdmx/io/writer.py +13 -3
  25. pysdmx/io/xml/__ss_aux_reader.py +39 -17
  26. pysdmx/io/xml/__structure_aux_reader.py +221 -33
  27. pysdmx/io/xml/__structure_aux_writer.py +304 -5
  28. pysdmx/io/xml/__tokens.py +12 -0
  29. pysdmx/io/xml/__write_aux.py +9 -0
  30. pysdmx/io/xml/sdmx21/writer/generic.py +2 -2
  31. pysdmx/model/dataflow.py +11 -2
  32. pysdmx/toolkit/pd/_data_utils.py +1 -1
  33. {pysdmx-1.10.1.dist-info → pysdmx-1.12.0.dist-info}/METADATA +7 -1
  34. {pysdmx-1.10.1.dist-info → pysdmx-1.12.0.dist-info}/RECORD +36 -31
  35. {pysdmx-1.10.1.dist-info → pysdmx-1.12.0.dist-info}/WHEEL +1 -1
  36. pysdmx/io/json/sdmxjson2/writer/metadata.py +0 -60
  37. pysdmx/io/json/sdmxjson2/writer/structure.py +0 -61
  38. {pysdmx-1.10.1.dist-info → pysdmx-1.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,10 @@
1
1
  """Parsers for reading metadata."""
2
2
 
3
3
  from datetime import datetime
4
- from typing import Any, Callable, Dict, List, Optional, Sequence, Union
4
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Type, Union
5
5
 
6
6
  from msgspec import Struct
7
+ from msgspec.structs import asdict
7
8
 
8
9
  from pysdmx.io.xml.__tokens import (
9
10
  AGENCIES,
@@ -29,6 +30,8 @@ from pysdmx.io.xml.__tokens import (
29
30
  CLS,
30
31
  CODE,
31
32
  CODES_LOW,
33
+ COMPONENT_MAP,
34
+ COMPONENT_MAPS,
32
35
  COMPS,
33
36
  CON,
34
37
  CON_ID,
@@ -44,6 +47,7 @@ from pysdmx.io.xml.__tokens import (
44
47
  CUSTOM_TYPE_SCHEMES,
45
48
  CUSTOM_TYPES,
46
49
  DATA_PROV,
50
+ DATE_PATTERN_MAP,
47
51
  DEPARTMENT,
48
52
  DESC,
49
53
  DFW,
@@ -64,6 +68,8 @@ from pysdmx.io.xml.__tokens import (
64
68
  FACETS,
65
69
  FAX,
66
70
  FAXES,
71
+ FIXED_VALUE_MAP,
72
+ FIXED_VALUE_MAPS,
67
73
  GROUP,
68
74
  GROUP_DIM,
69
75
  GROUPS_LOW,
@@ -98,6 +104,8 @@ from pysdmx.io.xml.__tokens import (
98
104
  PROV_AGREEMENT,
99
105
  PROV_AGREEMENTS,
100
106
  REF,
107
+ REPRESENTATION_MAP,
108
+ REPRESENTATION_MAPS,
101
109
  REQUIRED,
102
110
  ROLE,
103
111
  RULE,
@@ -110,6 +118,8 @@ from pysdmx.io.xml.__tokens import (
110
118
  STR_URL_LOW,
111
119
  STR_USAGE,
112
120
  STRUCTURE,
121
+ STRUCTURE_MAP,
122
+ STRUCTURE_MAPS,
113
123
  TELEPHONE,
114
124
  TELEPHONES,
115
125
  TEXT,
@@ -152,10 +162,19 @@ from pysdmx.model import (
152
162
  AgencyScheme,
153
163
  Code,
154
164
  Codelist,
165
+ ComponentMap,
155
166
  Concept,
156
167
  ConceptScheme,
157
168
  DataType,
169
+ DatePatternMap,
158
170
  Facets,
171
+ FixedValueMap,
172
+ ImplicitComponentMap,
173
+ MultiComponentMap,
174
+ MultiValueMap,
175
+ RepresentationMap,
176
+ StructureMap,
177
+ ValueMap,
159
178
  VtlCodelistMapping,
160
179
  VtlConceptMapping,
161
180
  )
@@ -207,6 +226,11 @@ STRUCTURES_MAPPING = {
207
226
  UDO_SCHEME: UserDefinedOperatorScheme,
208
227
  TRANS_SCHEME: TransformationScheme,
209
228
  VTL_MAPPING_SCHEME: VtlMappingScheme,
229
+ STRUCTURE_MAP: StructureMap,
230
+ COMPONENT_MAP: ComponentMap,
231
+ FIXED_VALUE_MAP: FixedValueMap,
232
+ DATE_PATTERN_MAP: DatePatternMap,
233
+ REPRESENTATION_MAP: RepresentationMap,
210
234
  NAME_PER_SCHEME: NamePersonalisationScheme,
211
235
  CUSTOM_TYPE_SCHEME: CustomTypeScheme,
212
236
  PROV_AGREEMENTS: ProvisionAgreement,
@@ -308,6 +332,10 @@ class StructureParser(Struct):
308
332
  rulesets: Dict[str, RulesetScheme] = {}
309
333
  udos: Dict[str, UserDefinedOperatorScheme] = {}
310
334
  vtl_mappings: Dict[str, VtlMappingScheme] = {}
335
+ structure_maps: Dict[str, StructureMap] = {}
336
+ component_maps: Dict[str, ComponentMap] = {}
337
+ fixed_value_maps: Dict[str, FixedValueMap] = {}
338
+ representation_maps: Dict[str, RepresentationMap] = {}
311
339
  name_personalisations: Dict[str, NamePersonalisationScheme] = {}
312
340
  custom_types: Dict[str, CustomTypeScheme] = {}
313
341
  transformations: Dict[str, TransformationScheme] = {}
@@ -529,16 +557,11 @@ class StructureParser(Struct):
529
557
  if FACETS.lower() in rep:
530
558
  representation_info[LOCAL_FACETS_LOW] = rep.pop(FACETS.lower())
531
559
 
532
- def __format_con_id(self, concept_ref: Dict[str, Any]) -> Dict[str, Any]:
533
- rep = {}
560
+ def __format_con_id(
561
+ self, concept_ref: Union[str, Dict[str, Any]]
562
+ ) -> Dict[str, Any]:
534
563
  if isinstance(concept_ref, str):
535
564
  item_reference = parse_urn(concept_ref)
536
- scheme_reference = Reference(
537
- sdmx_type=CS,
538
- agency=item_reference.agency,
539
- id=item_reference.id,
540
- version=item_reference.version,
541
- )
542
565
  else:
543
566
  item_reference = ItemReference(
544
567
  sdmx_type=concept_ref[CLASS],
@@ -547,27 +570,44 @@ class StructureParser(Struct):
547
570
  version=concept_ref[PAR_VER],
548
571
  item_id=concept_ref[ID],
549
572
  )
550
- scheme_reference = Reference(
551
- sdmx_type=CS,
552
- agency=concept_ref[AGENCY_ID],
553
- id=concept_ref[PAR_ID],
554
- version=concept_ref[PAR_VER],
555
- )
573
+ scheme_reference = Reference(
574
+ sdmx_type=CS,
575
+ agency=item_reference.agency,
576
+ id=item_reference.id,
577
+ version=item_reference.version,
578
+ )
556
579
 
557
580
  concept_scheme = self.concepts.get(str(scheme_reference))
581
+
558
582
  if concept_scheme is None:
559
583
  return {CON: item_reference}
584
+
585
+ short_urn = str(item_reference)
560
586
  for con in concept_scheme.concepts:
561
- if isinstance(concept_ref, str):
562
- if con.id == item_reference.item_id:
563
- rep[CON] = parse_urn(concept_ref)
564
- break
565
- elif con.id == concept_ref[ID]:
566
- rep[CON] = con
567
- break
568
- if CON not in rep:
569
- return {CON: item_reference}
570
- return rep
587
+ con_short = str(
588
+ ItemReference(
589
+ sdmx_type=item_reference.sdmx_type,
590
+ agency=item_reference.agency,
591
+ id=item_reference.id,
592
+ version=item_reference.version,
593
+ item_id=con.id,
594
+ )
595
+ )
596
+
597
+ if con_short == short_urn:
598
+ if con.urn is None:
599
+ con = Concept(
600
+ **{
601
+ **asdict(con),
602
+ "urn": (
603
+ "urn:sdmx:org.sdmx.infomodel.conceptscheme."
604
+ f"{short_urn}"
605
+ ),
606
+ }
607
+ )
608
+ return {CON: con}
609
+
610
+ return {CON: item_reference}
571
611
 
572
612
  @staticmethod
573
613
  def __get_attachment_level( # noqa: C901
@@ -751,6 +791,11 @@ class StructureParser(Struct):
751
791
  del comp[ATT_REL]
752
792
 
753
793
  if ME_REL in comp:
794
+ measures = add_list(comp[ME_REL][MSR])
795
+ if len(measures) == 1 and measures[0] == "OBS_VALUE":
796
+ comp[ATT_LVL] = "O"
797
+ else:
798
+ comp[ATT_LVL] = ",".join(measures)
754
799
  del comp[ME_REL]
755
800
 
756
801
  if AS_STATUS in comp or USAGE in comp:
@@ -762,14 +807,10 @@ class StructureParser(Struct):
762
807
  comp[REQUIRED] = False
763
808
  del comp[status_key]
764
809
 
765
- if "position" in comp:
766
- del comp["position"]
767
-
768
- if ANNOTATIONS in comp:
769
- del comp[ANNOTATIONS]
770
-
771
- if CON_ROLE in comp:
772
- del comp[CON_ROLE]
810
+ unwanted_keys = ["position", ANNOTATIONS, CON_ROLE]
811
+ for key in unwanted_keys:
812
+ if key in comp:
813
+ del comp[key]
773
814
 
774
815
  return Component(**comp)
775
816
 
@@ -1035,6 +1076,124 @@ class StructureParser(Struct):
1035
1076
  )
1036
1077
  return json_elem
1037
1078
 
1079
+ def __build_component_map(
1080
+ self, child_dict: Dict[str, Any]
1081
+ ) -> Union[ComponentMap, MultiComponentMap, ImplicitComponentMap]:
1082
+ if "values" not in child_dict:
1083
+ return ImplicitComponentMap(
1084
+ source=child_dict["source"],
1085
+ target=child_dict["target"],
1086
+ )
1087
+
1088
+ src_list = add_list(child_dict.get("source"))
1089
+ tgt_list = add_list(child_dict.get("target"))
1090
+
1091
+ if len(src_list) != 1 or len(tgt_list) != 1:
1092
+ return MultiComponentMap(
1093
+ source=src_list,
1094
+ target=tgt_list,
1095
+ values=child_dict["values"],
1096
+ )
1097
+
1098
+ return ComponentMap(
1099
+ source=src_list[0],
1100
+ target=tgt_list[0],
1101
+ values=child_dict["values"],
1102
+ )
1103
+
1104
+ def __build_representation_mapping(
1105
+ self, child_dict: Dict[str, Any]
1106
+ ) -> Union[ValueMap, MultiValueMap]:
1107
+ src = child_dict.get("source")
1108
+ tgt = child_dict.get("target")
1109
+
1110
+ src_list = add_list(src) if src is not None else []
1111
+ tgt_list = add_list(tgt) if tgt is not None else []
1112
+
1113
+ if len(src_list) != 1 or len(tgt_list) != 1:
1114
+ return MultiValueMap(
1115
+ source=src_list,
1116
+ target=tgt_list,
1117
+ valid_from=child_dict.get("valid_from"),
1118
+ valid_to=child_dict.get("valid_to"),
1119
+ )
1120
+
1121
+ return ValueMap(
1122
+ source=src_list[0],
1123
+ target=tgt_list[0],
1124
+ valid_from=child_dict.get("valid_from"),
1125
+ valid_to=child_dict.get("valid_to"),
1126
+ )
1127
+
1128
+ def __format_maps(self, element: Dict[str, Any]) -> Dict[str, Any]:
1129
+ if "sourcePattern" in element:
1130
+ element["pattern_type"] = (
1131
+ # DatePatternMap.pattern_type defaults value is fixed
1132
+ "variable" if "FrequencyDimension" in element else "fixed"
1133
+ )
1134
+
1135
+ renames = {
1136
+ "Source": "source",
1137
+ "Target": "target",
1138
+ "Value": "value",
1139
+ "SourceCodelist": "source",
1140
+ "TargetCodelist": "target",
1141
+ "SourceValue": "source",
1142
+ "TargetValue": "target",
1143
+ "RepresentationMap": "values",
1144
+ "sourcePattern": "pattern",
1145
+ "resolvePeriod": "resolve_period",
1146
+ "TargetFrequencyID": "frequency",
1147
+ "FrequencyDimension": "frequency",
1148
+ "validFrom": "valid_from",
1149
+ "validTo": "valid_to",
1150
+ }
1151
+
1152
+ for xml_key, py_key in renames.items():
1153
+ if xml_key in element:
1154
+ element[py_key] = element.pop(xml_key)
1155
+
1156
+ child_class_mapping: Dict[str, Type[Any]] = {
1157
+ "ComponentMap": ComponentMap,
1158
+ "FixedValueMap": FixedValueMap,
1159
+ "DatePatternMap": DatePatternMap,
1160
+ "RepresentationMapping": ValueMap,
1161
+ }
1162
+
1163
+ MapChild = Union[
1164
+ ComponentMap,
1165
+ MultiComponentMap,
1166
+ ImplicitComponentMap,
1167
+ FixedValueMap,
1168
+ DatePatternMap,
1169
+ ValueMap,
1170
+ MultiValueMap,
1171
+ ]
1172
+ consolidated_children: List[MapChild] = []
1173
+
1174
+ for xml_tag, target_class in child_class_mapping.items():
1175
+ if xml_tag not in element:
1176
+ continue
1177
+
1178
+ for child_dict in add_list(element.pop(xml_tag)):
1179
+ self.__format_maps(child_dict)
1180
+
1181
+ if xml_tag == "ComponentMap":
1182
+ consolidated_children.append(
1183
+ self.__build_component_map(child_dict)
1184
+ )
1185
+ elif xml_tag == "RepresentationMapping":
1186
+ consolidated_children.append(
1187
+ self.__build_representation_mapping(child_dict)
1188
+ )
1189
+ else:
1190
+ consolidated_children.append(target_class(**child_dict))
1191
+
1192
+ if consolidated_children:
1193
+ element["maps"] = consolidated_children
1194
+
1195
+ return element
1196
+
1038
1197
  def __format_scheme(
1039
1198
  self, json_elem: Dict[str, Any], scheme: str, item: str
1040
1199
  ) -> Dict[str, ItemScheme]:
@@ -1116,6 +1275,7 @@ class StructureParser(Struct):
1116
1275
  element = self.__format_validity(element)
1117
1276
  element = self.__format_groups(element)
1118
1277
  element = self.__format_components(element)
1278
+ element = self.__format_maps(element)
1119
1279
  if item == PROV_AGREEMENT:
1120
1280
  element = self.__format_prov_agreement(element)
1121
1281
 
@@ -1300,6 +1460,34 @@ class StructureParser(Struct):
1300
1460
  ),
1301
1461
  "transformations",
1302
1462
  ),
1463
+ STRUCTURE_MAPS: process_structure(
1464
+ STRUCTURE_MAPS,
1465
+ lambda data: self.__format_schema(
1466
+ data, STRUCTURE_MAP, STRUCTURE_MAP
1467
+ ),
1468
+ "structure_maps",
1469
+ ),
1470
+ COMPONENT_MAPS: process_structure(
1471
+ COMPONENT_MAPS,
1472
+ lambda data: self.__format_schema(
1473
+ data, COMPONENT_MAP, COMPONENT_MAP
1474
+ ),
1475
+ "component_maps",
1476
+ ),
1477
+ FIXED_VALUE_MAPS: process_structure(
1478
+ FIXED_VALUE_MAPS,
1479
+ lambda data: self.__format_schema(
1480
+ data, FIXED_VALUE_MAP, FIXED_VALUE_MAP
1481
+ ),
1482
+ "fixed_value_maps",
1483
+ ),
1484
+ REPRESENTATION_MAPS: process_structure(
1485
+ REPRESENTATION_MAPS,
1486
+ lambda data: self.__format_schema(
1487
+ data, REPRESENTATION_MAP, REPRESENTATION_MAP
1488
+ ),
1489
+ "representation_maps",
1490
+ ),
1303
1491
  TRANS_SCHEMES: process_structure(
1304
1492
  TRANS_SCHEMES,
1305
1493
  lambda data: self.__format_scheme(