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.
- pysdmx/__init__.py +1 -1
- pysdmx/api/fmr/__init__.py +266 -0
- pysdmx/api/qb/refmeta.py +1 -1
- pysdmx/api/qb/schema.py +4 -10
- pysdmx/api/qb/service.py +26 -4
- pysdmx/api/qb/structure.py +2 -2
- pysdmx/io/input_processor.py +4 -4
- pysdmx/io/json/fusion/messages/__init__.py +14 -0
- pysdmx/io/json/fusion/messages/concept.py +2 -8
- pysdmx/io/json/fusion/messages/dsd.py +52 -1
- pysdmx/io/json/fusion/messages/metadataflow.py +44 -0
- pysdmx/io/json/fusion/messages/mpa.py +45 -0
- pysdmx/io/json/fusion/messages/msd.py +121 -0
- pysdmx/io/json/fusion/messages/org.py +90 -0
- pysdmx/io/json/fusion/reader/__init__.py +5 -0
- pysdmx/io/json/sdmxjson2/messages/__init__.py +15 -1
- pysdmx/io/json/sdmxjson2/messages/code.py +35 -13
- pysdmx/io/json/sdmxjson2/messages/concept.py +5 -8
- pysdmx/io/json/sdmxjson2/messages/dsd.py +9 -6
- pysdmx/io/json/sdmxjson2/messages/metadataflow.py +88 -0
- pysdmx/io/json/sdmxjson2/messages/mpa.py +88 -0
- pysdmx/io/json/sdmxjson2/messages/msd.py +241 -0
- pysdmx/io/json/sdmxjson2/messages/provider.py +117 -1
- pysdmx/io/json/sdmxjson2/messages/structure.py +25 -1
- pysdmx/io/json/sdmxjson2/reader/__init__.py +5 -0
- pysdmx/io/serde.py +5 -0
- pysdmx/io/writer.py +2 -4
- pysdmx/io/xml/__ss_aux_reader.py +1 -2
- pysdmx/io/xml/__structure_aux_reader.py +15 -10
- pysdmx/io/xml/__structure_aux_writer.py +6 -4
- pysdmx/io/xml/__write_data_aux.py +6 -5
- pysdmx/io/xml/__write_structure_specific_aux.py +6 -2
- pysdmx/io/xml/doc_validation.py +1 -3
- pysdmx/io/xml/sdmx21/writer/generic.py +5 -3
- pysdmx/model/__init__.py +13 -4
- pysdmx/model/dataflow.py +1 -0
- pysdmx/model/map.py +6 -6
- pysdmx/model/message.py +30 -6
- pysdmx/model/metadata.py +255 -4
- pysdmx/toolkit/pd/_data_utils.py +3 -4
- {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/METADATA +2 -2
- {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/RECORD +44 -38
- {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
|
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"
|
pysdmx/io/xml/__ss_aux_reader.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
912
|
-
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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
|
|
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
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
|