pysdmx 1.10.0rc1__py3-none-any.whl → 1.10.1__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 +6 -4
- pysdmx/api/qb/data.py +2 -0
- pysdmx/io/json/fusion/messages/dsd.py +8 -2
- pysdmx/io/json/sdmxjson2/messages/__init__.py +4 -0
- pysdmx/io/json/sdmxjson2/messages/code.py +16 -6
- pysdmx/io/json/sdmxjson2/messages/constraint.py +235 -16
- pysdmx/io/json/sdmxjson2/messages/dsd.py +67 -18
- pysdmx/io/json/sdmxjson2/messages/map.py +5 -4
- pysdmx/io/json/sdmxjson2/messages/metadataflow.py +1 -0
- pysdmx/io/json/sdmxjson2/messages/msd.py +18 -10
- pysdmx/io/json/sdmxjson2/messages/schema.py +2 -2
- pysdmx/io/json/sdmxjson2/messages/structure.py +81 -44
- pysdmx/io/json/sdmxjson2/messages/vtl.py +13 -9
- pysdmx/io/json/sdmxjson2/reader/doc_validation.py +4 -0
- pysdmx/io/xml/__write_data_aux.py +2 -0
- pysdmx/model/__base.py +46 -1
- pysdmx/model/__init__.py +18 -0
- pysdmx/model/category.py +17 -0
- pysdmx/model/constraint.py +69 -0
- pysdmx/model/dataflow.py +8 -5
- pysdmx/model/message.py +80 -71
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/METADATA +1 -1
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/RECORD +26 -25
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/WHEEL +0 -0
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/licenses/LICENSE +0 -0
pysdmx/__init__.py
CHANGED
pysdmx/api/fmr/__init__.py
CHANGED
|
@@ -39,11 +39,13 @@ from pysdmx.model import (
|
|
|
39
39
|
Dataflow,
|
|
40
40
|
DataflowInfo,
|
|
41
41
|
DataProvider,
|
|
42
|
+
DataStructureDefinition,
|
|
42
43
|
Hierarchy,
|
|
43
44
|
HierarchyAssociation,
|
|
44
45
|
Metadataflow,
|
|
45
46
|
MetadataProvisionAgreement,
|
|
46
47
|
MetadataReport,
|
|
48
|
+
MetadataStructure,
|
|
47
49
|
MultiRepresentationMap,
|
|
48
50
|
ProvisionAgreement,
|
|
49
51
|
RepresentationMap,
|
|
@@ -678,7 +680,7 @@ class RegistryClient(__BaseRegistryClient):
|
|
|
678
680
|
agency: str = "*",
|
|
679
681
|
id: str = "*",
|
|
680
682
|
version: str = "+",
|
|
681
|
-
) -> Sequence[
|
|
683
|
+
) -> Sequence[DataStructureDefinition]:
|
|
682
684
|
"""Get the data structures(s) matching the supplied parameters.
|
|
683
685
|
|
|
684
686
|
Args:
|
|
@@ -767,7 +769,7 @@ class RegistryClient(__BaseRegistryClient):
|
|
|
767
769
|
agency: str = "*",
|
|
768
770
|
id: str = "*",
|
|
769
771
|
version: str = "+",
|
|
770
|
-
) -> Sequence[
|
|
772
|
+
) -> Sequence[MetadataStructure]:
|
|
771
773
|
"""Get the metadata structures (MSD) matching the supplied parameters.
|
|
772
774
|
|
|
773
775
|
Args:
|
|
@@ -1252,7 +1254,7 @@ class AsyncRegistryClient(__BaseRegistryClient):
|
|
|
1252
1254
|
agency: str = "*",
|
|
1253
1255
|
id: str = "*",
|
|
1254
1256
|
version: str = "+",
|
|
1255
|
-
) -> Sequence[
|
|
1257
|
+
) -> Sequence[DataStructureDefinition]:
|
|
1256
1258
|
"""Get the data structures(s) matching the supplied parameters.
|
|
1257
1259
|
|
|
1258
1260
|
Args:
|
|
@@ -1341,7 +1343,7 @@ class AsyncRegistryClient(__BaseRegistryClient):
|
|
|
1341
1343
|
agency: str = "*",
|
|
1342
1344
|
id: str = "*",
|
|
1343
1345
|
version: str = "+",
|
|
1344
|
-
) -> Sequence[
|
|
1346
|
+
) -> Sequence[MetadataStructure]:
|
|
1345
1347
|
"""Get the metadata structures (MSD) matching the supplied parameters.
|
|
1346
1348
|
|
|
1347
1349
|
Args:
|
pysdmx/api/qb/data.py
CHANGED
|
@@ -329,6 +329,8 @@ class DataQuery(_CoreDataQuery, frozen=True, omit_defaults=True):
|
|
|
329
329
|
|
|
330
330
|
def __get_short_v2_qs(self, api_version: ApiVersion) -> str:
|
|
331
331
|
qs = ""
|
|
332
|
+
if self.components:
|
|
333
|
+
qs += self._create_component_filters(self.components)
|
|
332
334
|
if self.updated_after:
|
|
333
335
|
qs = super()._append_qs_param(
|
|
334
336
|
qs,
|
|
@@ -80,8 +80,14 @@ class FusionAttribute(Struct, frozen=True):
|
|
|
80
80
|
measureReferences: Optional[Sequence[str]] = None
|
|
81
81
|
|
|
82
82
|
def __derive_level(self, groups: Sequence[FusionGroup]) -> str:
|
|
83
|
-
if self.
|
|
84
|
-
|
|
83
|
+
if self.measureReferences:
|
|
84
|
+
if (
|
|
85
|
+
len(self.measureReferences) == 1
|
|
86
|
+
and self.measureReferences[0] == "OBS_VALUE"
|
|
87
|
+
):
|
|
88
|
+
return "O"
|
|
89
|
+
else:
|
|
90
|
+
return ",".join(self.measureReferences)
|
|
85
91
|
elif self.attachmentLevel == "DATA_SET":
|
|
86
92
|
return "D"
|
|
87
93
|
elif self.attachmentLevel == "GROUP":
|
|
@@ -12,6 +12,9 @@ from pysdmx.io.json.sdmxjson2.messages.code import (
|
|
|
12
12
|
JsonHierarchyMessage,
|
|
13
13
|
)
|
|
14
14
|
from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptSchemeMessage
|
|
15
|
+
from pysdmx.io.json.sdmxjson2.messages.constraint import (
|
|
16
|
+
JsonDataConstraintMessage,
|
|
17
|
+
)
|
|
15
18
|
from pysdmx.io.json.sdmxjson2.messages.dataflow import (
|
|
16
19
|
JsonDataflowMessage,
|
|
17
20
|
JsonDataflowsMessage,
|
|
@@ -50,6 +53,7 @@ __all__ = [
|
|
|
50
53
|
"JsonCategorySchemeMessage",
|
|
51
54
|
"JsonCodelistMessage",
|
|
52
55
|
"JsonConceptSchemeMessage",
|
|
56
|
+
"JsonDataConstraintMessage",
|
|
53
57
|
"JsonDataflowMessage",
|
|
54
58
|
"JsonDataflowsMessage",
|
|
55
59
|
"JsonDataStructuresMessage",
|
|
@@ -231,7 +231,7 @@ class JsonCodelists(Struct, frozen=True, omit_defaults=True):
|
|
|
231
231
|
"""SDMX-JSON payload for lists of codes."""
|
|
232
232
|
|
|
233
233
|
codelists: Sequence[JsonCodelist] = ()
|
|
234
|
-
|
|
234
|
+
valueLists: Sequence[JsonValuelist] = ()
|
|
235
235
|
|
|
236
236
|
|
|
237
237
|
class JsonCodelistMessage(Struct, frozen=True, omit_defaults=True):
|
|
@@ -244,7 +244,7 @@ class JsonCodelistMessage(Struct, frozen=True, omit_defaults=True):
|
|
|
244
244
|
if self.data.codelists:
|
|
245
245
|
return self.data.codelists[0].to_model()
|
|
246
246
|
else:
|
|
247
|
-
return self.data.
|
|
247
|
+
return self.data.valueLists[0].to_model()
|
|
248
248
|
|
|
249
249
|
|
|
250
250
|
class JsonHierarchicalCode(Struct, frozen=True, omit_defaults=True):
|
|
@@ -329,10 +329,10 @@ class JsonHierarchicalCode(Struct, frozen=True, omit_defaults=True):
|
|
|
329
329
|
code=code.urn,
|
|
330
330
|
validFrom=code.rel_valid_from,
|
|
331
331
|
validTo=code.rel_valid_to,
|
|
332
|
-
annotations=tuple(annotations),
|
|
333
|
-
hierarchicalCodes=
|
|
334
|
-
JsonHierarchicalCode.from_model(c) for c in code.codes
|
|
335
|
-
|
|
332
|
+
annotations=tuple(annotations) if annotations else None,
|
|
333
|
+
hierarchicalCodes=tuple(
|
|
334
|
+
[JsonHierarchicalCode.from_model(c) for c in code.codes]
|
|
335
|
+
),
|
|
336
336
|
)
|
|
337
337
|
|
|
338
338
|
|
|
@@ -475,6 +475,15 @@ class JsonHierarchyAssociation(
|
|
|
475
475
|
"SDMX-JSON hierarchy associations must reference a context",
|
|
476
476
|
{"hierarchy_association": ha.id},
|
|
477
477
|
)
|
|
478
|
+
lnk = (
|
|
479
|
+
JsonLink(
|
|
480
|
+
rel="UserDefinedOperator",
|
|
481
|
+
type="sdmx_artefact",
|
|
482
|
+
urn=ha.operator,
|
|
483
|
+
)
|
|
484
|
+
if ha.operator
|
|
485
|
+
else None
|
|
486
|
+
)
|
|
478
487
|
return JsonHierarchyAssociation(
|
|
479
488
|
agency=(
|
|
480
489
|
ha.agency.id if isinstance(ha.agency, Agency) else ha.agency
|
|
@@ -492,6 +501,7 @@ class JsonHierarchyAssociation(
|
|
|
492
501
|
linkedHierarchy=href,
|
|
493
502
|
linkedObject=ha.component_ref,
|
|
494
503
|
contextObject=ha.context_ref,
|
|
504
|
+
links=[lnk] if lnk else (),
|
|
495
505
|
)
|
|
496
506
|
|
|
497
507
|
|
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
"""Collection of SDMX-JSON schemas for content constraints."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Sequence
|
|
4
5
|
|
|
5
6
|
from msgspec import Struct
|
|
6
7
|
|
|
7
|
-
from pysdmx
|
|
8
|
+
from pysdmx import errors
|
|
9
|
+
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
10
|
+
JsonAnnotation,
|
|
11
|
+
MaintainableType,
|
|
12
|
+
)
|
|
13
|
+
from pysdmx.model import (
|
|
14
|
+
Agency,
|
|
15
|
+
ConstraintAttachment,
|
|
16
|
+
CubeKeyValue,
|
|
17
|
+
CubeRegion,
|
|
18
|
+
CubeValue,
|
|
19
|
+
DataConstraint,
|
|
20
|
+
DataKey,
|
|
21
|
+
DataKeyValue,
|
|
22
|
+
KeySet,
|
|
23
|
+
)
|
|
8
24
|
|
|
9
25
|
|
|
10
26
|
class JsonValue(Struct, frozen=True, omit_defaults=True):
|
|
11
|
-
"""SDMX-JSON payload for
|
|
27
|
+
"""SDMX-JSON payload for a cube value."""
|
|
12
28
|
|
|
13
29
|
value: str
|
|
30
|
+
validFrom: Optional[datetime] = None
|
|
31
|
+
validTo: Optional[datetime] = None
|
|
32
|
+
|
|
33
|
+
def to_model(self) -> CubeValue:
|
|
34
|
+
"""Converts a JsonValue to a CubeValue."""
|
|
35
|
+
return CubeValue(self.value, self.validFrom, self.validTo)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_model(self, cv: CubeValue) -> "JsonValue":
|
|
39
|
+
"""Converts a pysdmx cube value to an SDMX-JSON one."""
|
|
40
|
+
return JsonValue(cv.value, cv.valid_from, cv.valid_to)
|
|
14
41
|
|
|
15
42
|
|
|
16
43
|
class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
@@ -18,36 +45,228 @@ class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
|
18
45
|
|
|
19
46
|
id: str
|
|
20
47
|
values: Sequence[JsonValue]
|
|
48
|
+
# Additional properties are supported in the model (include,
|
|
49
|
+
# removePrefix, validFrom, validTo, timeRange) but not by the FMR.
|
|
50
|
+
# Therefore, they are ignored for now.
|
|
51
|
+
|
|
52
|
+
def to_model(self) -> CubeKeyValue:
|
|
53
|
+
"""Converts a JsonKeyValue to a CubeKeyValue."""
|
|
54
|
+
return CubeKeyValue(self.id, [v.to_model() for v in self.values])
|
|
21
55
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_model(self, key_value: CubeKeyValue) -> "JsonKeyValue":
|
|
58
|
+
"""Converts a pysdmx cube key value to an SDMX-JSON one."""
|
|
59
|
+
return JsonKeyValue(
|
|
60
|
+
key_value.id, [JsonValue.from_model(v) for v in key_value.values]
|
|
61
|
+
)
|
|
25
62
|
|
|
26
63
|
|
|
27
64
|
class JsonCubeRegion(Struct, frozen=True, omit_defaults=True):
|
|
28
65
|
"""SDMX-JSON payload for a cube region."""
|
|
29
66
|
|
|
67
|
+
# The property `components` is ignored as it's not used in the FMR`
|
|
30
68
|
keyValues: Sequence[JsonKeyValue]
|
|
69
|
+
include: bool = True
|
|
31
70
|
|
|
32
|
-
def
|
|
33
|
-
"""
|
|
34
|
-
return
|
|
71
|
+
def to_model(self) -> CubeRegion:
|
|
72
|
+
"""Converts a JsonCubeRegion to a CubeRegion."""
|
|
73
|
+
return CubeRegion(
|
|
74
|
+
[kv.to_model() for kv in self.keyValues], self.include
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_model(self, region: CubeRegion) -> "JsonCubeRegion":
|
|
79
|
+
"""Converts a pysdmx cube region to an SDMX-JSON one."""
|
|
80
|
+
return JsonCubeRegion(
|
|
81
|
+
[JsonKeyValue.from_model(kv) for kv in region.key_values],
|
|
82
|
+
region.is_included,
|
|
83
|
+
)
|
|
35
84
|
|
|
36
85
|
|
|
37
86
|
class JsonConstraintAttachment(Struct, frozen=True, omit_defaults=True):
|
|
38
87
|
"""SDMX-JSON payload for a constraint attachment."""
|
|
39
88
|
|
|
40
|
-
dataProvider: Optional[str]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
89
|
+
dataProvider: Optional[str] = None
|
|
90
|
+
dataStructures: Sequence[str] = ()
|
|
91
|
+
dataflows: Sequence[str] = ()
|
|
92
|
+
provisionAgreements: Sequence[str] = ()
|
|
93
|
+
|
|
94
|
+
def to_model(self) -> ConstraintAttachment:
|
|
95
|
+
"""Converts a JsonConstraintAttachment to a ConstraintAttachment."""
|
|
96
|
+
return ConstraintAttachment(
|
|
97
|
+
self.dataProvider,
|
|
98
|
+
self.dataStructures,
|
|
99
|
+
self.dataflows,
|
|
100
|
+
self.provisionAgreements,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_model(
|
|
105
|
+
self, attachment: ConstraintAttachment
|
|
106
|
+
) -> "JsonConstraintAttachment":
|
|
107
|
+
"""Converts a pysdmx constraint attachment to an SDMX-JSON one."""
|
|
108
|
+
ds = attachment.data_structures if attachment.data_structures else ()
|
|
109
|
+
df = attachment.dataflows if attachment.dataflows else ()
|
|
110
|
+
pa = (
|
|
111
|
+
attachment.provision_agreements
|
|
112
|
+
if attachment.provision_agreements
|
|
113
|
+
else ()
|
|
114
|
+
)
|
|
115
|
+
return JsonConstraintAttachment(attachment.data_provider, ds, df, pa)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class JsonDataKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
119
|
+
"""SDMX-JSON payload for a data key value."""
|
|
120
|
+
|
|
121
|
+
id: str
|
|
122
|
+
value: str
|
|
123
|
+
|
|
124
|
+
def to_model(self) -> DataKeyValue:
|
|
125
|
+
"""Converts a JsonDataKeyValue to a DataKeyValue."""
|
|
126
|
+
return DataKeyValue(self.id, self.value)
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def from_model(self, kv: DataKeyValue) -> "JsonDataKeyValue":
|
|
130
|
+
"""Converts a pysdmx key value to an SDMX-JSON one."""
|
|
131
|
+
return JsonDataKeyValue(kv.id, kv.value)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class JsonDataKey(Struct, frozen=True, omit_defaults=True):
|
|
135
|
+
"""SDMX-JSON payload for a data key."""
|
|
136
|
+
|
|
137
|
+
keyValues: Sequence[JsonDataKeyValue]
|
|
138
|
+
validFrom: Optional[datetime] = None
|
|
139
|
+
validTo: Optional[datetime] = None
|
|
140
|
+
|
|
141
|
+
def to_model(self) -> DataKey:
|
|
142
|
+
"""Converts a JsonDataKey to a DataKey."""
|
|
143
|
+
return DataKey(
|
|
144
|
+
[kv.to_model() for kv in self.keyValues],
|
|
145
|
+
self.validFrom,
|
|
146
|
+
self.validTo,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_model(self, kv: DataKey) -> "JsonDataKey":
|
|
151
|
+
"""Converts a pysdmx key constraint to an SDMX-JSON one."""
|
|
152
|
+
return JsonDataKey(
|
|
153
|
+
[JsonDataKeyValue.from_model(val) for val in kv.keys_values],
|
|
154
|
+
kv.valid_from,
|
|
155
|
+
kv.valid_to,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class JsonKeySet(Struct, frozen=True, omit_defaults=True):
|
|
160
|
+
"""SDMX-JSON payload for a keyset."""
|
|
161
|
+
|
|
162
|
+
keys: Sequence[JsonDataKey]
|
|
163
|
+
isIncluded: bool
|
|
164
|
+
|
|
165
|
+
def to_model(self) -> KeySet:
|
|
166
|
+
"""Converts a JsonKeySet to a KeySet."""
|
|
167
|
+
return KeySet([k.to_model() for k in self.keys], self.isIncluded)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def from_model(self, ks: KeySet) -> "JsonKeySet":
|
|
171
|
+
"""Converts a pysdmx key set constraint to an SDMX-JSON one."""
|
|
172
|
+
return JsonKeySet(
|
|
173
|
+
[JsonDataKey.from_model(k) for k in ks.keys], ks.is_included
|
|
174
|
+
)
|
|
46
175
|
|
|
47
176
|
|
|
48
177
|
class JsonDataConstraint(MaintainableType, frozen=True, omit_defaults=True):
|
|
49
178
|
"""SDMX-JSON payload for a content constraint."""
|
|
50
179
|
|
|
51
|
-
role: Optional[Literal["Allowed", "Actual"]] = None
|
|
52
180
|
constraintAttachment: Optional[JsonConstraintAttachment] = None
|
|
53
181
|
cubeRegions: Optional[Sequence[JsonCubeRegion]] = None
|
|
182
|
+
dataKeySets: Optional[Sequence[JsonKeySet]] = None
|
|
183
|
+
|
|
184
|
+
def to_model(self) -> DataConstraint:
|
|
185
|
+
"""Converts a JsonDataConstraint to a pysdmx Data Constraint."""
|
|
186
|
+
at = self.constraintAttachment.to_model() # type: ignore[union-attr]
|
|
187
|
+
return DataConstraint(
|
|
188
|
+
id=self.id,
|
|
189
|
+
name=self.name,
|
|
190
|
+
agency=self.agency,
|
|
191
|
+
description=self.description,
|
|
192
|
+
version=self.version,
|
|
193
|
+
annotations=tuple([a.to_model() for a in self.annotations]),
|
|
194
|
+
is_external_reference=self.isExternalReference,
|
|
195
|
+
valid_from=self.validFrom,
|
|
196
|
+
valid_to=self.validTo,
|
|
197
|
+
constraint_attachment=at,
|
|
198
|
+
cube_regions=[r.to_model() for r in self.cubeRegions]
|
|
199
|
+
if self.cubeRegions
|
|
200
|
+
else (),
|
|
201
|
+
key_sets=[s.to_model() for s in self.dataKeySets]
|
|
202
|
+
if self.dataKeySets
|
|
203
|
+
else (),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def from_model(self, cons: DataConstraint) -> "JsonDataConstraint":
|
|
208
|
+
"""Converts a pysdmx constraint to an SDMX-JSON one."""
|
|
209
|
+
crs = (
|
|
210
|
+
[JsonCubeRegion.from_model(r) for r in cons.cube_regions]
|
|
211
|
+
if cons.cube_regions
|
|
212
|
+
else None
|
|
213
|
+
)
|
|
214
|
+
dks = (
|
|
215
|
+
[JsonKeySet.from_model(s) for s in cons.key_sets]
|
|
216
|
+
if cons.key_sets
|
|
217
|
+
else None
|
|
218
|
+
)
|
|
219
|
+
if not cons.name:
|
|
220
|
+
raise errors.Invalid(
|
|
221
|
+
"Invalid input",
|
|
222
|
+
"SDMX-JSON data constraints must have a name",
|
|
223
|
+
{"data_constraint": cons.id},
|
|
224
|
+
)
|
|
225
|
+
if not cons.constraint_attachment:
|
|
226
|
+
raise errors.Invalid(
|
|
227
|
+
"Invalid input",
|
|
228
|
+
"SDMX-JSON data constraints must have a constraint attachment",
|
|
229
|
+
{"data_constraint": cons.id},
|
|
230
|
+
)
|
|
231
|
+
return JsonDataConstraint(
|
|
232
|
+
id=cons.id,
|
|
233
|
+
name=cons.name,
|
|
234
|
+
agency=(
|
|
235
|
+
cons.agency.id
|
|
236
|
+
if isinstance(cons.agency, Agency)
|
|
237
|
+
else cons.agency
|
|
238
|
+
),
|
|
239
|
+
description=cons.description,
|
|
240
|
+
version=cons.version,
|
|
241
|
+
annotations=tuple(
|
|
242
|
+
[JsonAnnotation.from_model(a) for a in cons.annotations]
|
|
243
|
+
),
|
|
244
|
+
isExternalReference=cons.is_external_reference,
|
|
245
|
+
validFrom=cons.valid_from,
|
|
246
|
+
validTo=cons.valid_to,
|
|
247
|
+
constraintAttachment=JsonConstraintAttachment.from_model(
|
|
248
|
+
cons.constraint_attachment
|
|
249
|
+
),
|
|
250
|
+
cubeRegions=crs,
|
|
251
|
+
dataKeySets=dks,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class JsonDataConstraints(Struct, frozen=True, omit_defaults=True):
|
|
256
|
+
"""SDMX-JSON payload for data constraints."""
|
|
257
|
+
|
|
258
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
259
|
+
|
|
260
|
+
def to_model(self) -> Sequence[DataConstraint]:
|
|
261
|
+
"""Returns the requested data constraints."""
|
|
262
|
+
return [cc.to_model() for cc in self.dataConstraints]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class JsonDataConstraintMessage(Struct, frozen=True, omit_defaults=True):
|
|
266
|
+
"""SDMX-JSON payload for /dataconstraint queries."""
|
|
267
|
+
|
|
268
|
+
data: JsonDataConstraints
|
|
269
|
+
|
|
270
|
+
def to_model(self) -> Sequence[DataConstraint]:
|
|
271
|
+
"""Returns the requested data constraints."""
|
|
272
|
+
return self.data.to_model()
|
|
@@ -126,7 +126,10 @@ class JsonAttributeRelationship(Struct, frozen=True, omit_defaults=True):
|
|
|
126
126
|
) -> str:
|
|
127
127
|
"""Returns the attachment level."""
|
|
128
128
|
if measures:
|
|
129
|
-
|
|
129
|
+
if len(measures) == 1 and measures[0] == "OBS_VALUE":
|
|
130
|
+
return "O"
|
|
131
|
+
else:
|
|
132
|
+
return ",".join(measures)
|
|
130
133
|
elif self.dimensions:
|
|
131
134
|
return ",".join(self.dimensions)
|
|
132
135
|
elif self.group:
|
|
@@ -136,15 +139,17 @@ class JsonAttributeRelationship(Struct, frozen=True, omit_defaults=True):
|
|
|
136
139
|
return "D"
|
|
137
140
|
|
|
138
141
|
@classmethod
|
|
139
|
-
def from_model(
|
|
142
|
+
def from_model(
|
|
143
|
+
self, rel: str, has_measure_rel: bool = False
|
|
144
|
+
) -> "JsonAttributeRelationship":
|
|
140
145
|
"""Converts a pysdmx attribute relationship to an SDMX-JSON one."""
|
|
141
146
|
if rel == "D":
|
|
142
147
|
return JsonAttributeRelationship(dataflow={})
|
|
143
|
-
elif rel == "O":
|
|
148
|
+
elif rel == "O" or has_measure_rel:
|
|
144
149
|
return JsonAttributeRelationship(observation={})
|
|
145
150
|
else:
|
|
146
|
-
|
|
147
|
-
return JsonAttributeRelationship(dimensions=
|
|
151
|
+
comps = rel.split(",")
|
|
152
|
+
return JsonAttributeRelationship(dimensions=comps)
|
|
148
153
|
|
|
149
154
|
|
|
150
155
|
class JsonDimension(Struct, frozen=True, omit_defaults=True):
|
|
@@ -257,20 +262,37 @@ class JsonAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
257
262
|
)
|
|
258
263
|
|
|
259
264
|
@classmethod
|
|
260
|
-
def from_model(
|
|
265
|
+
def from_model(
|
|
266
|
+
self, attribute: Component, measures: Sequence[Component]
|
|
267
|
+
) -> "JsonAttribute":
|
|
261
268
|
"""Converts a pysdmx attribute to an SDMX-JSON one."""
|
|
262
269
|
concept = _get_concept_reference(attribute)
|
|
263
270
|
usage = "mandatory" if attribute.required else "optional"
|
|
271
|
+
repr = _get_json_representation(attribute)
|
|
272
|
+
|
|
273
|
+
ids = attribute.attachment_level.split(",") # type: ignore[union-attr]
|
|
274
|
+
comps = set(ids)
|
|
275
|
+
mids = {m.id for m in measures}
|
|
276
|
+
has_measure_rel = len(comps.intersection(mids)) > 0
|
|
264
277
|
level = JsonAttributeRelationship.from_model(
|
|
265
|
-
attribute.attachment_level # type: ignore[arg-type]
|
|
278
|
+
attribute.attachment_level, # type: ignore[arg-type]
|
|
279
|
+
has_measure_rel,
|
|
266
280
|
)
|
|
267
|
-
|
|
281
|
+
|
|
282
|
+
if attribute.attachment_level == "O":
|
|
283
|
+
mr = ["OBS_VALUE"]
|
|
284
|
+
elif has_measure_rel:
|
|
285
|
+
mr = ids
|
|
286
|
+
else:
|
|
287
|
+
mr = None
|
|
288
|
+
|
|
268
289
|
return JsonAttribute(
|
|
269
290
|
id=attribute.id,
|
|
270
291
|
conceptIdentity=concept,
|
|
271
292
|
attributeRelationship=level,
|
|
272
293
|
usage=usage,
|
|
273
294
|
localRepresentation=repr,
|
|
295
|
+
measureRelationship=mr,
|
|
274
296
|
)
|
|
275
297
|
|
|
276
298
|
|
|
@@ -351,12 +373,14 @@ class JsonAttributes(Struct, frozen=True, omit_defaults=True):
|
|
|
351
373
|
|
|
352
374
|
@classmethod
|
|
353
375
|
def from_model(
|
|
354
|
-
self, attributes: Sequence[Component]
|
|
376
|
+
self, attributes: Sequence[Component], measures: Sequence[Component]
|
|
355
377
|
) -> Optional["JsonAttributes"]:
|
|
356
378
|
"""Converts a pysdmx list of attributes to an SDMX-JSON one."""
|
|
357
379
|
if len(attributes) > 0:
|
|
358
380
|
return JsonAttributes(
|
|
359
|
-
attributes=[
|
|
381
|
+
attributes=[
|
|
382
|
+
JsonAttribute.from_model(a, measures) for a in attributes
|
|
383
|
+
]
|
|
360
384
|
)
|
|
361
385
|
else:
|
|
362
386
|
return None
|
|
@@ -447,19 +471,42 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
|
|
|
447
471
|
enums = [cl.to_model() for cl in cls]
|
|
448
472
|
enums.extend([vl.to_model() for vl in vls])
|
|
449
473
|
comps = []
|
|
450
|
-
if constraints
|
|
451
|
-
|
|
474
|
+
if constraints:
|
|
475
|
+
incl_cubes = []
|
|
476
|
+
for const in constraints:
|
|
477
|
+
incl_cubes.extend(
|
|
478
|
+
[cr for cr in (const.cubeRegions or []) if cr.include]
|
|
479
|
+
)
|
|
480
|
+
if len(incl_cubes) == 1:
|
|
481
|
+
cons = {
|
|
482
|
+
kv.id: [v.value for v in kv.values]
|
|
483
|
+
for kv in incl_cubes[0].keyValues
|
|
484
|
+
}
|
|
485
|
+
else:
|
|
486
|
+
cons = {}
|
|
452
487
|
else:
|
|
453
488
|
cons = {}
|
|
454
|
-
comps.extend(
|
|
489
|
+
comps.extend(
|
|
490
|
+
self.dimensionList.to_model(
|
|
491
|
+
cs,
|
|
492
|
+
enums,
|
|
493
|
+
cons, # type: ignore[arg-type]
|
|
494
|
+
)
|
|
495
|
+
)
|
|
455
496
|
if self.measureList:
|
|
456
|
-
comps.extend(
|
|
497
|
+
comps.extend(
|
|
498
|
+
self.measureList.to_model(
|
|
499
|
+
cs,
|
|
500
|
+
enums,
|
|
501
|
+
cons, # type: ignore[arg-type]
|
|
502
|
+
)
|
|
503
|
+
)
|
|
457
504
|
if self.attributeList:
|
|
458
505
|
comps.extend(
|
|
459
506
|
self.attributeList.to_model(
|
|
460
507
|
cs,
|
|
461
508
|
enums,
|
|
462
|
-
cons,
|
|
509
|
+
cons, # type: ignore[arg-type]
|
|
463
510
|
self.groups,
|
|
464
511
|
)
|
|
465
512
|
)
|
|
@@ -474,7 +521,9 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
|
|
|
474
521
|
) -> "JsonComponents":
|
|
475
522
|
"""Converts a pysdmx components list to an SDMX-JSON one."""
|
|
476
523
|
dimensions = JsonDimensions.from_model(components.dimensions)
|
|
477
|
-
attributes = JsonAttributes.from_model(
|
|
524
|
+
attributes = JsonAttributes.from_model(
|
|
525
|
+
components.attributes, components.measures
|
|
526
|
+
)
|
|
478
527
|
measures = JsonMeasures.from_model(components.measures)
|
|
479
528
|
if grps is None:
|
|
480
529
|
groups = []
|
|
@@ -556,7 +605,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
556
605
|
conceptSchemes: Sequence[JsonConceptScheme] = ()
|
|
557
606
|
valuelists: Sequence[JsonValuelist] = ()
|
|
558
607
|
codelists: Sequence[JsonCodelist] = ()
|
|
559
|
-
|
|
608
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
560
609
|
|
|
561
610
|
def to_model(self) -> Sequence[DataStructureDefinition]:
|
|
562
611
|
"""Returns the requested dsds."""
|
|
@@ -565,7 +614,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
565
614
|
self.conceptSchemes,
|
|
566
615
|
self.codelists,
|
|
567
616
|
self.valuelists,
|
|
568
|
-
self.
|
|
617
|
+
self.dataConstraints,
|
|
569
618
|
)
|
|
570
619
|
for dsd in self.dataStructures
|
|
571
620
|
]
|
|
@@ -87,19 +87,20 @@ class JsonRepresentationMapping(Struct, frozen=True, omit_defaults=True):
|
|
|
87
87
|
self, vm: Union[MultiValueMap, ValueMap]
|
|
88
88
|
) -> "JsonRepresentationMapping":
|
|
89
89
|
"""Converts a value map to an SDMX-JSON JsonRepresentationMapping."""
|
|
90
|
+
fmt = r"%Y-%m-%dT%H:%M:%S"
|
|
90
91
|
if isinstance(vm, ValueMap):
|
|
91
92
|
return JsonRepresentationMapping(
|
|
92
93
|
[JsonSourceValue.from_model(vm.source)],
|
|
93
94
|
[vm.target],
|
|
94
|
-
vm.valid_from.strftime(
|
|
95
|
-
vm.valid_to.strftime(
|
|
95
|
+
vm.valid_from.strftime(fmt) if vm.valid_from else None,
|
|
96
|
+
vm.valid_to.strftime(fmt) if vm.valid_to else None,
|
|
96
97
|
)
|
|
97
98
|
else:
|
|
98
99
|
return JsonRepresentationMapping(
|
|
99
100
|
[JsonSourceValue.from_model(s) for s in vm.source],
|
|
100
101
|
vm.target,
|
|
101
|
-
vm.valid_from.strftime(
|
|
102
|
-
vm.valid_to.strftime(
|
|
102
|
+
vm.valid_from.strftime(fmt) if vm.valid_from else None,
|
|
103
|
+
vm.valid_to.strftime(fmt) if vm.valid_to else None,
|
|
103
104
|
)
|
|
104
105
|
|
|
105
106
|
|