pysdmx 1.8.1__py3-none-any.whl → 1.10.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/__extras_check.py +15 -1
- pysdmx/__init__.py +1 -1
- pysdmx/api/fmr/__init__.py +3 -2
- pysdmx/io/input_processor.py +9 -6
- pysdmx/io/json/fusion/messages/category.py +69 -41
- pysdmx/io/json/sdmxjson2/messages/__init__.py +4 -0
- pysdmx/io/json/sdmxjson2/messages/category.py +76 -43
- pysdmx/io/json/sdmxjson2/messages/code.py +16 -6
- pysdmx/io/json/sdmxjson2/messages/constraint.py +235 -16
- pysdmx/io/json/sdmxjson2/messages/core.py +2 -1
- pysdmx/io/json/sdmxjson2/messages/dsd.py +35 -7
- 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 +112 -0
- pysdmx/io/json/sdmxjson2/reader/metadata.py +8 -1
- pysdmx/io/json/sdmxjson2/reader/structure.py +9 -2
- pysdmx/io/reader.py +18 -4
- pysdmx/io/xml/__data_aux.py +9 -4
- pysdmx/io/xml/__parse_xml.py +2 -0
- pysdmx/io/xml/__structure_aux_reader.py +70 -0
- pysdmx/io/xml/__structure_aux_writer.py +63 -9
- pysdmx/io/xml/__tokens.py +3 -0
- pysdmx/io/xml/__write_aux.py +35 -30
- pysdmx/io/xml/header.py +48 -35
- pysdmx/model/__base.py +47 -2
- pysdmx/model/__init__.py +18 -0
- pysdmx/model/category.py +23 -1
- pysdmx/model/constraint.py +69 -0
- pysdmx/model/message.py +97 -72
- pysdmx/toolkit/vtl/__init__.py +10 -1
- pysdmx/toolkit/vtl/_validations.py +8 -12
- pysdmx/toolkit/vtl/convert.py +333 -0
- pysdmx/toolkit/vtl/script_generation.py +1 -1
- pysdmx/util/_model_utils.py +40 -3
- {pysdmx-1.8.1.dist-info → pysdmx-1.10.0.dist-info}/METADATA +6 -3
- {pysdmx-1.8.1.dist-info → pysdmx-1.10.0.dist-info}/RECORD +42 -39
- {pysdmx-1.8.1.dist-info → pysdmx-1.10.0.dist-info}/WHEEL +0 -0
- {pysdmx-1.8.1.dist-info → pysdmx-1.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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()
|
|
@@ -299,6 +299,7 @@ class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
299
299
|
test=self.test,
|
|
300
300
|
prepared=self.prepared,
|
|
301
301
|
sender=self.sender,
|
|
302
|
+
receiver=self.receivers if self.receivers else (),
|
|
302
303
|
)
|
|
303
304
|
|
|
304
305
|
@classmethod
|
|
@@ -313,7 +314,7 @@ class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
313
314
|
header.prepared,
|
|
314
315
|
header.sender,
|
|
315
316
|
header.test,
|
|
316
|
-
receivers=
|
|
317
|
+
receivers=header.receiver if header.receiver else None,
|
|
317
318
|
schema=(
|
|
318
319
|
"https://raw.githubusercontent.com/sdmx-twg/sdmx-json/"
|
|
319
320
|
"develop/structure-message/tools/schemas/2.0.0/"
|
|
@@ -265,12 +265,17 @@ class JsonAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
265
265
|
attribute.attachment_level # type: ignore[arg-type]
|
|
266
266
|
)
|
|
267
267
|
repr = _get_json_representation(attribute)
|
|
268
|
+
# The line below will need to be changed when we work on
|
|
269
|
+
# Measure Relationship (cf. issue #467)
|
|
270
|
+
mr = ["OBS_VALUE"] if attribute.attachment_level == "O" else None
|
|
271
|
+
|
|
268
272
|
return JsonAttribute(
|
|
269
273
|
id=attribute.id,
|
|
270
274
|
conceptIdentity=concept,
|
|
271
275
|
attributeRelationship=level,
|
|
272
276
|
usage=usage,
|
|
273
277
|
localRepresentation=repr,
|
|
278
|
+
measureRelationship=mr,
|
|
274
279
|
)
|
|
275
280
|
|
|
276
281
|
|
|
@@ -447,19 +452,42 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
|
|
|
447
452
|
enums = [cl.to_model() for cl in cls]
|
|
448
453
|
enums.extend([vl.to_model() for vl in vls])
|
|
449
454
|
comps = []
|
|
450
|
-
if constraints
|
|
451
|
-
|
|
455
|
+
if constraints:
|
|
456
|
+
incl_cubes = []
|
|
457
|
+
for const in constraints:
|
|
458
|
+
incl_cubes.extend(
|
|
459
|
+
[cr for cr in (const.cubeRegions or []) if cr.include]
|
|
460
|
+
)
|
|
461
|
+
if len(incl_cubes) == 1:
|
|
462
|
+
cons = {
|
|
463
|
+
kv.id: [v.value for v in kv.values]
|
|
464
|
+
for kv in incl_cubes[0].keyValues
|
|
465
|
+
}
|
|
466
|
+
else:
|
|
467
|
+
cons = {}
|
|
452
468
|
else:
|
|
453
469
|
cons = {}
|
|
454
|
-
comps.extend(
|
|
470
|
+
comps.extend(
|
|
471
|
+
self.dimensionList.to_model(
|
|
472
|
+
cs,
|
|
473
|
+
enums,
|
|
474
|
+
cons, # type: ignore[arg-type]
|
|
475
|
+
)
|
|
476
|
+
)
|
|
455
477
|
if self.measureList:
|
|
456
|
-
comps.extend(
|
|
478
|
+
comps.extend(
|
|
479
|
+
self.measureList.to_model(
|
|
480
|
+
cs,
|
|
481
|
+
enums,
|
|
482
|
+
cons, # type: ignore[arg-type]
|
|
483
|
+
)
|
|
484
|
+
)
|
|
457
485
|
if self.attributeList:
|
|
458
486
|
comps.extend(
|
|
459
487
|
self.attributeList.to_model(
|
|
460
488
|
cs,
|
|
461
489
|
enums,
|
|
462
|
-
cons,
|
|
490
|
+
cons, # type: ignore[arg-type]
|
|
463
491
|
self.groups,
|
|
464
492
|
)
|
|
465
493
|
)
|
|
@@ -556,7 +584,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
556
584
|
conceptSchemes: Sequence[JsonConceptScheme] = ()
|
|
557
585
|
valuelists: Sequence[JsonValuelist] = ()
|
|
558
586
|
codelists: Sequence[JsonCodelist] = ()
|
|
559
|
-
|
|
587
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
560
588
|
|
|
561
589
|
def to_model(self) -> Sequence[DataStructureDefinition]:
|
|
562
590
|
"""Returns the requested dsds."""
|
|
@@ -565,7 +593,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
565
593
|
self.conceptSchemes,
|
|
566
594
|
self.codelists,
|
|
567
595
|
self.valuelists,
|
|
568
|
-
self.
|
|
596
|
+
self.dataConstraints,
|
|
569
597
|
)
|
|
570
598
|
for dsd in self.dataStructures
|
|
571
599
|
]
|
|
@@ -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
|
|
|
@@ -15,7 +15,6 @@ from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
|
15
15
|
from pysdmx.io.json.sdmxjson2.messages.dsd import (
|
|
16
16
|
_find_concept,
|
|
17
17
|
_get_concept_reference,
|
|
18
|
-
_get_json_representation,
|
|
19
18
|
_get_representation,
|
|
20
19
|
)
|
|
21
20
|
from pysdmx.model import (
|
|
@@ -28,6 +27,13 @@ from pysdmx.model import (
|
|
|
28
27
|
from pysdmx.util import parse_item_urn
|
|
29
28
|
|
|
30
29
|
|
|
30
|
+
def _get_attr_repr(comp: MetadataComponent) -> Optional[JsonRepresentation]:
|
|
31
|
+
enum = comp.local_enum_ref if comp.local_enum_ref else None
|
|
32
|
+
return JsonRepresentation.from_model(
|
|
33
|
+
comp.local_dtype, enum, comp.local_facets, None
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
32
38
|
"""SDMX-JSON payload for an attribute."""
|
|
33
39
|
|
|
@@ -80,11 +86,13 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
80
86
|
def from_model(self, cmp: MetadataComponent) -> "JsonMetadataAttribute":
|
|
81
87
|
"""Converts a pysdmx metadata attribute to an SDMX-JSON one."""
|
|
82
88
|
concept = _get_concept_reference(cmp)
|
|
83
|
-
repr =
|
|
89
|
+
repr = _get_attr_repr(cmp)
|
|
84
90
|
|
|
85
91
|
min_occurs = cmp.array_def.min_size if cmp.array_def else 0
|
|
86
|
-
if cmp.array_def is None
|
|
87
|
-
max_occurs: Union[int, Literal["unbounded"]] =
|
|
92
|
+
if cmp.array_def is None:
|
|
93
|
+
max_occurs: Union[int, Literal["unbounded"]] = 1
|
|
94
|
+
elif cmp.array_def.max_size is None:
|
|
95
|
+
max_occurs = "unbounded"
|
|
88
96
|
else:
|
|
89
97
|
max_occurs = cmp.array_def.max_size
|
|
90
98
|
|
|
@@ -95,9 +103,9 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
95
103
|
minOccurs=min_occurs,
|
|
96
104
|
maxOccurs=max_occurs,
|
|
97
105
|
isPresentational=cmp.is_presentational,
|
|
98
|
-
metadataAttributes=
|
|
99
|
-
JsonMetadataAttribute.from_model(c) for c in cmp.components
|
|
100
|
-
|
|
106
|
+
metadataAttributes=tuple(
|
|
107
|
+
[JsonMetadataAttribute.from_model(c) for c in cmp.components]
|
|
108
|
+
),
|
|
101
109
|
)
|
|
102
110
|
|
|
103
111
|
|
|
@@ -119,9 +127,9 @@ class JsonMetadataAttributes(Struct, frozen=True, omit_defaults=True):
|
|
|
119
127
|
) -> "JsonMetadataAttributes":
|
|
120
128
|
"""Converts a pysdmx list of metadata attributes to SDMX-JSON."""
|
|
121
129
|
return JsonMetadataAttributes(
|
|
122
|
-
metadataAttributes=
|
|
123
|
-
JsonMetadataAttribute.from_model(a) for a in attributes
|
|
124
|
-
|
|
130
|
+
metadataAttributes=tuple(
|
|
131
|
+
[JsonMetadataAttribute.from_model(a) for a in attributes]
|
|
132
|
+
)
|
|
125
133
|
)
|
|
126
134
|
|
|
127
135
|
|
|
@@ -21,7 +21,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
21
21
|
dataStructures: Sequence[JsonDataStructure]
|
|
22
22
|
valuelists: Sequence[JsonValuelist] = ()
|
|
23
23
|
codelists: Sequence[JsonCodelist] = ()
|
|
24
|
-
|
|
24
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
25
25
|
|
|
26
26
|
def to_model(
|
|
27
27
|
self,
|
|
@@ -32,7 +32,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
32
32
|
self.conceptSchemes,
|
|
33
33
|
self.codelists,
|
|
34
34
|
self.valuelists,
|
|
35
|
-
self.
|
|
35
|
+
self.dataConstraints,
|
|
36
36
|
)
|
|
37
37
|
return comps, grps # type: ignore[return-value]
|
|
38
38
|
|