pysdmx 1.6.0__py3-none-any.whl → 1.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.
Files changed (44) hide show
  1. pysdmx/__init__.py +1 -1
  2. pysdmx/api/fmr/__init__.py +266 -0
  3. pysdmx/api/qb/refmeta.py +1 -1
  4. pysdmx/api/qb/schema.py +4 -10
  5. pysdmx/api/qb/service.py +26 -4
  6. pysdmx/api/qb/structure.py +2 -2
  7. pysdmx/io/input_processor.py +4 -4
  8. pysdmx/io/json/fusion/messages/__init__.py +14 -0
  9. pysdmx/io/json/fusion/messages/concept.py +2 -8
  10. pysdmx/io/json/fusion/messages/dsd.py +52 -1
  11. pysdmx/io/json/fusion/messages/metadataflow.py +44 -0
  12. pysdmx/io/json/fusion/messages/mpa.py +45 -0
  13. pysdmx/io/json/fusion/messages/msd.py +121 -0
  14. pysdmx/io/json/fusion/messages/org.py +90 -0
  15. pysdmx/io/json/fusion/reader/__init__.py +5 -0
  16. pysdmx/io/json/sdmxjson2/messages/__init__.py +15 -1
  17. pysdmx/io/json/sdmxjson2/messages/code.py +35 -13
  18. pysdmx/io/json/sdmxjson2/messages/concept.py +5 -8
  19. pysdmx/io/json/sdmxjson2/messages/dsd.py +9 -6
  20. pysdmx/io/json/sdmxjson2/messages/metadataflow.py +88 -0
  21. pysdmx/io/json/sdmxjson2/messages/mpa.py +88 -0
  22. pysdmx/io/json/sdmxjson2/messages/msd.py +241 -0
  23. pysdmx/io/json/sdmxjson2/messages/provider.py +117 -1
  24. pysdmx/io/json/sdmxjson2/messages/structure.py +25 -1
  25. pysdmx/io/json/sdmxjson2/reader/__init__.py +5 -0
  26. pysdmx/io/serde.py +5 -0
  27. pysdmx/io/writer.py +2 -4
  28. pysdmx/io/xml/__ss_aux_reader.py +1 -2
  29. pysdmx/io/xml/__structure_aux_reader.py +15 -10
  30. pysdmx/io/xml/__structure_aux_writer.py +6 -4
  31. pysdmx/io/xml/__write_data_aux.py +6 -5
  32. pysdmx/io/xml/__write_structure_specific_aux.py +6 -2
  33. pysdmx/io/xml/doc_validation.py +1 -3
  34. pysdmx/io/xml/sdmx21/writer/generic.py +5 -3
  35. pysdmx/model/__init__.py +13 -4
  36. pysdmx/model/dataflow.py +1 -0
  37. pysdmx/model/map.py +6 -6
  38. pysdmx/model/message.py +30 -6
  39. pysdmx/model/metadata.py +255 -4
  40. pysdmx/toolkit/pd/_data_utils.py +3 -4
  41. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/METADATA +2 -2
  42. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/RECORD +44 -38
  43. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/WHEEL +0 -0
  44. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,88 @@
1
+ """Collection of SDMX-JSON schemas for provision agreements."""
2
+
3
+ from typing import Sequence
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx import errors
8
+ from pysdmx.io.json.sdmxjson2.messages.core import (
9
+ JsonAnnotation,
10
+ MaintainableType,
11
+ )
12
+ from pysdmx.model import Agency, MetadataProvisionAgreement
13
+
14
+
15
+ class JsonMetadataProvisionAgreement(
16
+ MaintainableType, frozen=True, omit_defaults=True
17
+ ):
18
+ """SDMX-JSON payload for a metadata provision agreement."""
19
+
20
+ metadataflow: str = ""
21
+ metadataProvider: str = ""
22
+
23
+ def to_model(self) -> MetadataProvisionAgreement:
24
+ """Converts a FusionPA to a standard metadata provision agreement."""
25
+ return MetadataProvisionAgreement(
26
+ id=self.id,
27
+ agency=self.agency,
28
+ name=self.name,
29
+ description=self.description,
30
+ version=self.version,
31
+ valid_from=self.validFrom,
32
+ valid_to=self.validTo,
33
+ metadataflow=self.metadataflow,
34
+ metadata_provider=self.metadataProvider,
35
+ annotations=tuple([a.to_model() for a in self.annotations]),
36
+ is_external_reference=self.isExternalReference,
37
+ )
38
+
39
+ @classmethod
40
+ def from_model(
41
+ self, mpa: MetadataProvisionAgreement
42
+ ) -> "JsonMetadataProvisionAgreement":
43
+ """Converts a pysdmx metadata provision agreement to SDMX-JSON."""
44
+ if not mpa.name:
45
+ raise errors.Invalid(
46
+ "Invalid input",
47
+ "SDMX-JSON metadata provision agreements must have a name",
48
+ {"metadata_provision_agreement": mpa.id},
49
+ )
50
+ return JsonMetadataProvisionAgreement(
51
+ agency=(
52
+ mpa.agency.id if isinstance(mpa.agency, Agency) else mpa.agency
53
+ ),
54
+ id=mpa.id,
55
+ name=mpa.name,
56
+ version=mpa.version,
57
+ isExternalReference=mpa.is_external_reference,
58
+ validFrom=mpa.valid_from,
59
+ validTo=mpa.valid_to,
60
+ description=mpa.description,
61
+ annotations=tuple(
62
+ [JsonAnnotation.from_model(a) for a in mpa.annotations]
63
+ ),
64
+ metadataflow=mpa.metadataflow,
65
+ metadataProvider=mpa.metadata_provider,
66
+ )
67
+
68
+
69
+ class JsonMetadataProvisionAgreements(Struct, frozen=True, omit_defaults=True):
70
+ """SDMX-JSON payload for provision agreements."""
71
+
72
+ metadataProvisionAgreements: Sequence[JsonMetadataProvisionAgreement]
73
+
74
+ def to_model(self) -> Sequence[MetadataProvisionAgreement]:
75
+ """Returns the requested metadata provision agreements."""
76
+ return [pa.to_model() for pa in self.metadataProvisionAgreements]
77
+
78
+
79
+ class JsonMetadataProvisionAgreementsMessage(
80
+ Struct, frozen=True, omit_defaults=True
81
+ ):
82
+ """SDMX-JSON payload for /metadataprovisionagreement queries."""
83
+
84
+ data: JsonMetadataProvisionAgreements
85
+
86
+ def to_model(self) -> Sequence[MetadataProvisionAgreement]:
87
+ """Returns the requested metadata provision agreements."""
88
+ return self.data.to_model()
@@ -0,0 +1,241 @@
1
+ """Collection of SDMX-JSON schemas for SDMX-REST MSD queries."""
2
+
3
+ from typing import List, Literal, Optional, Sequence, Union
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx import errors
8
+ from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist, JsonValuelist
9
+ from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptScheme
10
+ from pysdmx.io.json.sdmxjson2.messages.core import (
11
+ JsonAnnotation,
12
+ JsonRepresentation,
13
+ MaintainableType,
14
+ )
15
+ from pysdmx.io.json.sdmxjson2.messages.dsd import (
16
+ _find_concept,
17
+ _get_concept_reference,
18
+ _get_json_representation,
19
+ _get_representation,
20
+ )
21
+ from pysdmx.model import (
22
+ Agency,
23
+ ArrayBoundaries,
24
+ Codelist,
25
+ MetadataComponent,
26
+ MetadataStructure,
27
+ )
28
+ from pysdmx.util import parse_item_urn
29
+
30
+
31
+ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
32
+ """SDMX-JSON payload for an attribute."""
33
+
34
+ id: str
35
+ conceptIdentity: str
36
+ minOccurs: int
37
+ maxOccurs: Union[int, Literal["unbounded"]]
38
+ isPresentational: bool
39
+ localRepresentation: Optional[JsonRepresentation] = None
40
+
41
+ def to_model(
42
+ self, cs: Sequence[JsonConceptScheme], cls: Sequence[Codelist]
43
+ ) -> MetadataComponent:
44
+ """Returns a metadata component."""
45
+ c = (
46
+ _find_concept(cs, self.conceptIdentity).to_model(cls)
47
+ if cs
48
+ else parse_item_urn(self.conceptIdentity)
49
+ )
50
+ dt, facets, codes, _ = _get_representation(
51
+ self.id, self.localRepresentation, cls, {}
52
+ )
53
+
54
+ if self.localRepresentation and self.localRepresentation.enumeration:
55
+ local_enum_ref = self.localRepresentation.enumeration
56
+ else:
57
+ local_enum_ref = None
58
+
59
+ if self.maxOccurs == "unbounded":
60
+ ab = ArrayBoundaries(self.minOccurs)
61
+ elif self.maxOccurs > 1:
62
+ ab = ArrayBoundaries(self.minOccurs, self.maxOccurs)
63
+ else:
64
+ ab = None
65
+
66
+ return MetadataComponent(
67
+ id=self.id,
68
+ is_presentational=self.isPresentational,
69
+ concept=c,
70
+ local_dtype=dt,
71
+ local_facets=facets,
72
+ local_codes=codes,
73
+ array_def=ab,
74
+ local_enum_ref=local_enum_ref,
75
+ components=(),
76
+ )
77
+
78
+ @classmethod
79
+ def from_model(self, cmp: MetadataComponent) -> "JsonMetadataAttribute":
80
+ """Converts a pysdmx metadata attribute to an SDMX-JSON one."""
81
+ concept = _get_concept_reference(cmp)
82
+ repr = _get_json_representation(cmp)
83
+
84
+ min_occurs = cmp.array_def.min_size if cmp.array_def else 0
85
+ if cmp.array_def is None or cmp.array_def.max_size is None:
86
+ max_occurs: Union[int, Literal["unbounded"]] = "unbounded"
87
+ else:
88
+ max_occurs = cmp.array_def.max_size
89
+
90
+ return JsonMetadataAttribute(
91
+ id=cmp.id,
92
+ conceptIdentity=concept,
93
+ localRepresentation=repr,
94
+ minOccurs=min_occurs,
95
+ maxOccurs=max_occurs,
96
+ isPresentational=cmp.is_presentational,
97
+ )
98
+
99
+
100
+ class JsonMetadataAttributes(Struct, frozen=True, omit_defaults=True):
101
+ """SDMX-JSON payload for the list of metadata attributes."""
102
+
103
+ id: Literal["MetadataAttributeDescriptor"] = "MetadataAttributeDescriptor"
104
+ metadataAttributes: Sequence[JsonMetadataAttribute] = ()
105
+
106
+ def to_model(
107
+ self, cs: Sequence[JsonConceptScheme], cls: Sequence[Codelist]
108
+ ) -> List[MetadataComponent]:
109
+ """Returns the list of metadata attributes."""
110
+ return [a.to_model(cs, cls) for a in self.metadataAttributes]
111
+
112
+ @classmethod
113
+ def from_model(
114
+ self, attributes: Sequence[MetadataComponent]
115
+ ) -> "JsonMetadataAttributes":
116
+ """Converts a pysdmx list of metadata attributes to SDMX-JSON."""
117
+ return JsonMetadataAttributes(
118
+ metadataAttributes=[
119
+ JsonMetadataAttribute.from_model(a) for a in attributes
120
+ ]
121
+ )
122
+
123
+
124
+ class JsonMetadataComponents(Struct, frozen=True, omit_defaults=True):
125
+ """SDMX-JSON payload for the list of DSD components."""
126
+
127
+ metadataAttributeList: Optional[JsonMetadataAttributes] = None
128
+
129
+ def to_model(
130
+ self,
131
+ cs: Sequence[JsonConceptScheme],
132
+ cls: Sequence[JsonCodelist],
133
+ vls: Sequence[JsonValuelist],
134
+ ) -> Sequence[MetadataComponent]:
135
+ """Returns the components for this DSD."""
136
+ enums = [cl.to_model() for cl in cls]
137
+ enums.extend([vl.to_model() for vl in vls])
138
+ comps = (
139
+ self.metadataAttributeList.to_model(cs, enums)
140
+ if self.metadataAttributeList
141
+ else []
142
+ )
143
+ return comps
144
+
145
+ @classmethod
146
+ def from_model(
147
+ self, components: Sequence[MetadataComponent]
148
+ ) -> "JsonMetadataComponents":
149
+ """Converts a pysdmx components list to an SDMX-JSON one."""
150
+ attributes = JsonMetadataAttributes.from_model(components)
151
+ return JsonMetadataComponents(metadataAttributeList=attributes)
152
+
153
+
154
+ class JsonMetadataStructure(MaintainableType, frozen=True, omit_defaults=True):
155
+ """SDMX-JSON payload for a DSD."""
156
+
157
+ metadataStructureComponents: Optional[JsonMetadataComponents] = None
158
+
159
+ def to_model(
160
+ self,
161
+ cs: Sequence[JsonConceptScheme],
162
+ cls: Sequence[JsonCodelist],
163
+ vls: Sequence[JsonValuelist],
164
+ ) -> MetadataStructure:
165
+ """Map to pysdmx model class."""
166
+ c = self.metadataStructureComponents.to_model( # type: ignore[union-attr]
167
+ cs,
168
+ cls,
169
+ vls,
170
+ )
171
+ return MetadataStructure(
172
+ id=self.id,
173
+ name=self.name,
174
+ agency=self.agency,
175
+ description=self.description,
176
+ version=self.version,
177
+ annotations=[a.to_model() for a in self.annotations],
178
+ is_external_reference=self.isExternalReference,
179
+ valid_from=self.validFrom,
180
+ valid_to=self.validTo,
181
+ components=c,
182
+ )
183
+
184
+ @classmethod
185
+ def from_model(self, msd: MetadataStructure) -> "JsonMetadataStructure":
186
+ """Converts a pysdmx MSD to an SDMX-JSON one."""
187
+ if not msd.name:
188
+ raise errors.Invalid(
189
+ "Invalid input",
190
+ "SDMX-JSON metadata structures must have a name",
191
+ {"metadata_structure": msd.id},
192
+ )
193
+
194
+ return JsonMetadataStructure(
195
+ agency=(
196
+ msd.agency.id if isinstance(msd.agency, Agency) else msd.agency
197
+ ),
198
+ id=msd.id,
199
+ name=msd.name,
200
+ version=msd.version,
201
+ isExternalReference=msd.is_external_reference,
202
+ validFrom=msd.valid_from,
203
+ validTo=msd.valid_to,
204
+ description=msd.description,
205
+ annotations=tuple(
206
+ [JsonAnnotation.from_model(a) for a in msd.annotations]
207
+ ),
208
+ metadataStructureComponents=JsonMetadataComponents.from_model(
209
+ msd.components
210
+ ),
211
+ )
212
+
213
+
214
+ class JsonMetadataStructures(Struct, frozen=True, omit_defaults=True):
215
+ """SDMX-JSON payload for metadata structures."""
216
+
217
+ metadataStructures: Sequence[JsonMetadataStructure]
218
+ conceptSchemes: Sequence[JsonConceptScheme] = ()
219
+ valuelists: Sequence[JsonValuelist] = ()
220
+ codelists: Sequence[JsonCodelist] = ()
221
+
222
+ def to_model(self) -> Sequence[MetadataStructure]:
223
+ """Returns the requested msds."""
224
+ return [
225
+ msd.to_model(
226
+ self.conceptSchemes,
227
+ self.codelists,
228
+ self.valuelists,
229
+ )
230
+ for msd in self.metadataStructures
231
+ ]
232
+
233
+
234
+ class JsonMetadataStructuresMessage(Struct, frozen=True, omit_defaults=True):
235
+ """SDMX-JSON payload for /metadatastructure queries."""
236
+
237
+ data: JsonMetadataStructures
238
+
239
+ def to_model(self) -> Sequence[MetadataStructure]:
240
+ """Returns the requested metadata structures."""
241
+ return self.data.to_model()
@@ -9,8 +9,18 @@ from pysdmx.io.json.sdmxjson2.messages.core import (
9
9
  ItemSchemeType,
10
10
  JsonAnnotation,
11
11
  )
12
+ from pysdmx.io.json.sdmxjson2.messages.mpa import (
13
+ JsonMetadataProvisionAgreement,
14
+ )
12
15
  from pysdmx.io.json.sdmxjson2.messages.pa import JsonProvisionAgreement
13
- from pysdmx.model import Agency, DataflowRef, DataProvider, DataProviderScheme
16
+ from pysdmx.model import (
17
+ Agency,
18
+ DataflowRef,
19
+ DataProvider,
20
+ DataProviderScheme,
21
+ MetadataProvider,
22
+ MetadataProviderScheme,
23
+ )
14
24
  from pysdmx.util import parse_item_urn, parse_urn
15
25
 
16
26
 
@@ -114,3 +124,109 @@ class JsonProviderMessage(Struct, frozen=True, omit_defaults=True):
114
124
  def to_model(self) -> Sequence[DataProviderScheme]:
115
125
  """Returns the requested list of data provider schemes."""
116
126
  return self.data.to_model()
127
+
128
+
129
+ class JsonMetadataProviderScheme(
130
+ ItemSchemeType, frozen=True, omit_defaults=True
131
+ ):
132
+ """SDMX-JSON payload for a metadata provider scheme."""
133
+
134
+ metadataProviders: Sequence[MetadataProvider] = ()
135
+
136
+ def __get_df_ref(self, ref: str) -> DataflowRef:
137
+ a = parse_urn(ref)
138
+ return DataflowRef(id=a.id, agency=a.agency, version=a.version)
139
+
140
+ def to_model(
141
+ self, pas: Sequence[JsonMetadataProvisionAgreement]
142
+ ) -> MetadataProviderScheme:
143
+ """Converts a JsonMetadataProviderScheme to a pysdmx one."""
144
+ if pas:
145
+ paprs: Dict[str, Set[DataflowRef]] = defaultdict(set)
146
+ for pa in pas:
147
+ df = self.__get_df_ref(pa.metadataflow)
148
+ ref = parse_item_urn(pa.metadataProvider)
149
+ paprs[f"{ref.agency}:{ref.item_id}"].add(df)
150
+ provs = [
151
+ MetadataProvider(
152
+ id=p.id,
153
+ name=p.name,
154
+ description=p.description,
155
+ contacts=p.contacts,
156
+ dataflows=list(paprs[f"{self.agency}:{p.id}"]),
157
+ annotations=tuple(
158
+ [a.to_model() for a in self.annotations]
159
+ ),
160
+ )
161
+ for p in self.metadataProviders
162
+ ]
163
+ else:
164
+ provs = [
165
+ MetadataProvider(
166
+ id=p.id,
167
+ name=p.name,
168
+ description=p.description,
169
+ contacts=p.contacts,
170
+ annotations=tuple(
171
+ [a.to_model() for a in self.annotations]
172
+ ),
173
+ )
174
+ for p in self.metadataProviders
175
+ ]
176
+ return MetadataProviderScheme(
177
+ agency=self.agency,
178
+ description=self.description,
179
+ items=provs,
180
+ annotations=[a.to_model() for a in self.annotations],
181
+ is_external_reference=self.isExternalReference,
182
+ is_partial=self.isPartial,
183
+ valid_from=self.validFrom,
184
+ valid_to=self.validTo,
185
+ )
186
+
187
+ @classmethod
188
+ def from_model(
189
+ self, dps: MetadataProviderScheme
190
+ ) -> "JsonMetadataProviderScheme":
191
+ """Converts a pysdmx metadata provider scheme to an SDMX-JSON one."""
192
+ return JsonMetadataProviderScheme(
193
+ id="METADATA_PROVIDERS",
194
+ name="METADATA_PROVIDERS",
195
+ agency=(
196
+ dps.agency.id if isinstance(dps.agency, Agency) else dps.agency
197
+ ),
198
+ description=dps.description,
199
+ version="1.0",
200
+ metadataProviders=dps.items,
201
+ annotations=tuple(
202
+ [JsonAnnotation.from_model(a) for a in dps.annotations]
203
+ ),
204
+ isExternalReference=dps.is_external_reference,
205
+ isPartial=dps.is_partial,
206
+ validFrom=dps.valid_from,
207
+ validTo=dps.valid_to,
208
+ )
209
+
210
+
211
+ class JsonMetadataProviderSchemes(Struct, frozen=True, omit_defaults=True):
212
+ """SDMX-JSON payload for the list of metadata provider schemes."""
213
+
214
+ metadataProviderSchemes: Sequence[JsonMetadataProviderScheme]
215
+ metadataProvisionAgreements: Sequence[JsonMetadataProvisionAgreement] = ()
216
+
217
+ def to_model(self) -> Sequence[MetadataProviderScheme]:
218
+ """Converts a JsonMetadataProviderSchemes to the pysdmx model."""
219
+ return [
220
+ s.to_model(self.metadataProvisionAgreements)
221
+ for s in self.metadataProviderSchemes
222
+ ]
223
+
224
+
225
+ class JsonMetadataProviderMessage(Struct, frozen=True, omit_defaults=True):
226
+ """SDMX-JSON payload for /metadataproviderscheme queries."""
227
+
228
+ data: JsonMetadataProviderSchemes
229
+
230
+ def to_model(self) -> Sequence[MetadataProviderScheme]:
231
+ """Returns the requested list of metadata provider schemes."""
232
+ return self.data.to_model()
@@ -24,8 +24,16 @@ from pysdmx.io.json.sdmxjson2.messages.map import (
24
24
  JsonRepresentationMap,
25
25
  JsonStructureMap,
26
26
  )
27
+ from pysdmx.io.json.sdmxjson2.messages.metadataflow import JsonMetadataflow
28
+ from pysdmx.io.json.sdmxjson2.messages.mpa import (
29
+ JsonMetadataProvisionAgreement,
30
+ )
31
+ from pysdmx.io.json.sdmxjson2.messages.msd import JsonMetadataStructure
27
32
  from pysdmx.io.json.sdmxjson2.messages.pa import JsonProvisionAgreement
28
- from pysdmx.io.json.sdmxjson2.messages.provider import JsonDataProviderScheme
33
+ from pysdmx.io.json.sdmxjson2.messages.provider import (
34
+ JsonDataProviderScheme,
35
+ JsonMetadataProviderScheme,
36
+ )
29
37
  from pysdmx.io.json.sdmxjson2.messages.vtl import (
30
38
  JsonCustomTypeScheme,
31
39
  JsonNamePersonalisationScheme,
@@ -50,8 +58,12 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
50
58
  hierarchyAssociations: Sequence[JsonHierarchyAssociation] = ()
51
59
  agencySchemes: Sequence[JsonAgencyScheme] = ()
52
60
  dataProviderSchemes: Sequence[JsonDataProviderScheme] = ()
61
+ metadataProviderSchemes: Sequence[JsonMetadataProviderScheme] = ()
53
62
  dataflows: Sequence[JsonDataflow] = ()
54
63
  provisionAgreements: Sequence[JsonProvisionAgreement] = ()
64
+ metadataflows: Sequence[JsonMetadataflow] = ()
65
+ metadataProvisionAgreements: Sequence[JsonMetadataProvisionAgreement] = ()
66
+ metadataStructures: Sequence[JsonMetadataStructure] = ()
55
67
  structureMaps: Sequence[JsonStructureMap] = ()
56
68
  representationMaps: Sequence[JsonRepresentationMap] = ()
57
69
  categorisations: Sequence[JsonCategorisation] = ()
@@ -89,6 +101,14 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
89
101
  i.to_model(self.provisionAgreements)
90
102
  for i in self.dataProviderSchemes
91
103
  )
104
+ structures.extend(
105
+ i.to_model(self.metadataProvisionAgreements)
106
+ for i in self.metadataProviderSchemes
107
+ )
108
+ structures.extend(
109
+ i.to_model(self.conceptSchemes, self.codelists, self.valueLists)
110
+ for i in self.metadataStructures
111
+ )
92
112
  structures.extend(
93
113
  i.to_model(
94
114
  self.dataStructures,
@@ -99,6 +119,10 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
99
119
  for i in self.dataflows
100
120
  )
101
121
  structures.extend(i.to_model() for i in self.provisionAgreements)
122
+ structures.extend(i.to_model() for i in self.metadataflows)
123
+ structures.extend(
124
+ i.to_model() for i in self.metadataProvisionAgreements
125
+ )
102
126
  structures.extend(
103
127
  i.to_model(self.representationMaps) for i in self.structureMaps
104
128
  )
@@ -20,4 +20,9 @@ deserializers = Deserializers(
20
20
  mapping=msg.JsonMappingMessage, # type: ignore[arg-type]
21
21
  code_map=msg.JsonRepresentationMapMessage, # type: ignore[arg-type]
22
22
  transformation_scheme=msg.JsonTransfoMsg, # type: ignore[arg-type]
23
+ metadataflows=msg.JsonMdfsMsg, # type: ignore[arg-type]
24
+ metadata_provision_agreement=msg.JsonMPAMsg, # type: ignore[arg-type]
25
+ metadata_providers=msg.JsonMetadataProviderMessage, # type: ignore[arg-type]
26
+ msds=msg.JsonMetadataStructuresMessage, # type: ignore[arg-type]
27
+ data_structures=msg.JsonDataStructuresMessage, # type: ignore[arg-type]
23
28
  )
pysdmx/io/serde.py CHANGED
@@ -41,6 +41,11 @@ class Deserializers:
41
41
  mapping: Deserializer
42
42
  code_map: Deserializer
43
43
  transformation_scheme: Deserializer
44
+ metadataflows: Deserializer
45
+ metadata_provision_agreement: Deserializer
46
+ metadata_providers: Deserializer
47
+ msds: Deserializer
48
+ data_structures: Deserializer
44
49
 
45
50
 
46
51
  @dataclass
pysdmx/io/writer.py CHANGED
@@ -19,11 +19,9 @@ WRITERS = {
19
19
  Format.DATA_SDMX_ML_2_1_STR: "pysdmx.io.xml.sdmx21.writer."
20
20
  "structure_specific",
21
21
  Format.STRUCTURE_SDMX_ML_2_1: "pysdmx.io.xml.sdmx21.writer.structure",
22
- Format.DATA_SDMX_ML_3_0: "pysdmx.io.xml.sdmx30.writer."
23
- "structure_specific",
22
+ Format.DATA_SDMX_ML_3_0: "pysdmx.io.xml.sdmx30.writer.structure_specific",
24
23
  Format.STRUCTURE_SDMX_ML_3_0: "pysdmx.io.xml.sdmx30.writer.structure",
25
- Format.DATA_SDMX_ML_3_1: "pysdmx.io.xml.sdmx31.writer."
26
- "structure_specific",
24
+ Format.DATA_SDMX_ML_3_1: "pysdmx.io.xml.sdmx31.writer.structure_specific",
27
25
  Format.STRUCTURE_SDMX_ML_3_1: "pysdmx.io.xml.sdmx31.writer.structure",
28
26
  Format.STRUCTURE_SDMX_JSON_2_0_0: (
29
27
  "pysdmx.io.json.sdmxjson2.writer.structure"
@@ -30,8 +30,7 @@ def _reading_str_series(dataset: Dict[str, Any]) -> pd.DataFrame:
30
30
  if OBS in data:
31
31
  del keys[OBS]
32
32
  data[OBS] = add_list(data[OBS])
33
- for j in data[OBS]:
34
- test_list.append({**keys, **j})
33
+ test_list.extend([{**keys, **j} for j in data[OBS]])
35
34
  else:
36
35
  test_list.append(keys)
37
36
  test_list, df = __process_df(test_list, df)
@@ -397,7 +397,7 @@ class StructureParser(Struct):
397
397
  """
398
398
  if json_fac is None:
399
399
  return
400
- for key, _value in json_fac.items():
400
+ for key in json_fac:
401
401
  if key == TEXT_TYPE and json_fac[TEXT_TYPE] in list(DataType):
402
402
  json_obj["dtype"] = DataType(json_fac[TEXT_TYPE])
403
403
 
@@ -907,9 +907,9 @@ class StructureParser(Struct):
907
907
  item_json_info = self.__format_name_description(item_json_info)
908
908
  if CONTACT in item_json_info and item_name_class == AGENCY:
909
909
  item_json_info[CONTACT] = add_list(item_json_info[CONTACT])
910
- contacts = []
911
- for e in item_json_info[CONTACT]:
912
- contacts.append(self.__format_contact(e))
910
+ contacts = [
911
+ self.__format_contact(e) for e in item_json_info[CONTACT]
912
+ ]
913
913
  item_json_info[CONTACT.lower() + "s"] = contacts
914
914
  del item_json_info[CONTACT]
915
915
 
@@ -949,9 +949,11 @@ class StructureParser(Struct):
949
949
  group_dimensions = [group_dimensions]
950
950
 
951
951
  group["dimensions"] = [
952
- d[DIM_REF]
953
- if isinstance(d[DIM_REF], str)
954
- else d[DIM_REF][REF][ID]
952
+ (
953
+ d[DIM_REF]
954
+ if isinstance(d[DIM_REF], str)
955
+ else d[DIM_REF][REF][ID]
956
+ )
955
957
  for d in group_dimensions
956
958
  ]
957
959
 
@@ -994,9 +996,12 @@ class StructureParser(Struct):
994
996
  items = []
995
997
  if item in element:
996
998
  element[item] = add_list(element[item])
997
- for item_elem in element[item]:
998
- # Dynamic
999
- items.append(self.__format_item(item_elem, item))
999
+ items.extend(
1000
+ [
1001
+ self.__format_item(item_elem, item)
1002
+ for item_elem in element[item]
1003
+ ]
1004
+ )
1000
1005
  del element[item]
1001
1006
  element["items"] = items
1002
1007
  element = self.__format_agency(element)
@@ -462,7 +462,7 @@ def __write_components( # noqa: C901
462
462
  )
463
463
 
464
464
  position = 1
465
- for _, comps in components.items():
465
+ for comps in components.values():
466
466
  if comps:
467
467
  role_name = ROLE_MAPPING[comps[0].role]
468
468
  if role_name == MEASURE:
@@ -1129,9 +1129,11 @@ def _write_vtl( # noqa: C901
1129
1129
  ref_codelist = (
1130
1130
  item_or_scheme.codelist
1131
1131
  if isinstance(item_or_scheme.codelist, Reference)
1132
- else parse_urn(item_or_scheme.codelist)
1133
- if isinstance(item_or_scheme.codelist, str)
1134
- else parse_short_urn(item_or_scheme.codelist.short_urn)
1132
+ else (
1133
+ parse_urn(item_or_scheme.codelist)
1134
+ if isinstance(item_or_scheme.codelist, str)
1135
+ else parse_short_urn(item_or_scheme.codelist.short_urn)
1136
+ )
1135
1137
  )
1136
1138
  if references_30:
1137
1139
  data += (
@@ -33,8 +33,7 @@ def check_dimension_at_observation(
33
33
  dimension_codes = [dim.id for dim in components.dimensions]
34
34
  if value not in dimension_codes:
35
35
  raise Invalid(
36
- f"Dimension at observation {value} "
37
- f"not found in dataset {key}."
36
+ f"Dimension at observation {value} not found in dataset {key}."
38
37
  )
39
38
  # Add the missing datasets on mapping with ALL_DIM
40
39
  for key in datasets:
@@ -54,13 +53,15 @@ def writing_validation(dataset: PandasDataset) -> None:
54
53
  for comp in dataset.structure.components
55
54
  if comp.role in (Role.DIMENSION, Role.MEASURE)
56
55
  ]
57
- for att in dataset.structure.components.attributes:
56
+ required_components.extend(
57
+ att.id
58
+ for att in dataset.structure.components.attributes
58
59
  if (
59
60
  att.required
60
61
  and att.attachment_level is not None
61
62
  and att.attachment_level != "D"
62
- ):
63
- required_components.append(att.id)
63
+ )
64
+ )
64
65
  non_required = [
65
66
  comp.id
66
67
  for comp in dataset.structure.components
@@ -198,8 +198,12 @@ def __group_processing(
198
198
  .to_dict(orient="records")
199
199
  )
200
200
 
201
- for record in grouped_data:
202
- out_list.append(__format_group_str(record, group["group_id"]))
201
+ out_list.extend(
202
+ [
203
+ __format_group_str(record, group["group_id"])
204
+ for record in grouped_data
205
+ ]
206
+ )
203
207
 
204
208
  return "".join(out_list)
205
209