pysdmx 1.5.1__py3-none-any.whl → 1.6.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 +8 -3
- pysdmx/api/fmr/maintenance.py +158 -0
- pysdmx/api/qb/structure.py +1 -0
- pysdmx/api/qb/util.py +1 -0
- pysdmx/io/csv/__csv_aux_reader.py +99 -0
- pysdmx/io/csv/__csv_aux_writer.py +118 -0
- pysdmx/io/csv/sdmx10/reader/__init__.py +9 -14
- pysdmx/io/csv/sdmx10/writer/__init__.py +28 -2
- pysdmx/io/csv/sdmx20/__init__.py +0 -9
- pysdmx/io/csv/sdmx20/reader/__init__.py +8 -61
- pysdmx/io/csv/sdmx20/writer/__init__.py +32 -25
- pysdmx/io/csv/sdmx21/__init__.py +1 -0
- pysdmx/io/csv/sdmx21/reader/__init__.py +86 -0
- pysdmx/io/csv/sdmx21/writer/__init__.py +70 -0
- pysdmx/io/format.py +8 -0
- pysdmx/io/input_processor.py +16 -2
- pysdmx/io/json/fusion/messages/code.py +21 -4
- pysdmx/io/json/fusion/messages/concept.py +16 -8
- pysdmx/io/json/fusion/messages/dataflow.py +8 -1
- pysdmx/io/json/fusion/messages/dsd.py +15 -0
- pysdmx/io/json/fusion/messages/schema.py +8 -1
- pysdmx/io/json/sdmxjson2/messages/agency.py +43 -7
- pysdmx/io/json/sdmxjson2/messages/category.py +92 -7
- pysdmx/io/json/sdmxjson2/messages/code.py +239 -18
- pysdmx/io/json/sdmxjson2/messages/concept.py +78 -13
- pysdmx/io/json/sdmxjson2/messages/constraint.py +5 -5
- pysdmx/io/json/sdmxjson2/messages/core.py +121 -14
- pysdmx/io/json/sdmxjson2/messages/dataflow.py +63 -8
- pysdmx/io/json/sdmxjson2/messages/dsd.py +215 -20
- pysdmx/io/json/sdmxjson2/messages/map.py +200 -24
- pysdmx/io/json/sdmxjson2/messages/pa.py +36 -5
- pysdmx/io/json/sdmxjson2/messages/provider.py +35 -7
- pysdmx/io/json/sdmxjson2/messages/report.py +85 -7
- pysdmx/io/json/sdmxjson2/messages/schema.py +11 -12
- pysdmx/io/json/sdmxjson2/messages/structure.py +150 -2
- pysdmx/io/json/sdmxjson2/messages/vtl.py +547 -17
- pysdmx/io/json/sdmxjson2/reader/metadata.py +32 -0
- pysdmx/io/json/sdmxjson2/reader/structure.py +32 -0
- pysdmx/io/json/sdmxjson2/writer/__init__.py +9 -0
- pysdmx/io/json/sdmxjson2/writer/metadata.py +60 -0
- pysdmx/io/json/sdmxjson2/writer/structure.py +61 -0
- pysdmx/io/reader.py +28 -9
- pysdmx/io/serde.py +17 -0
- pysdmx/io/writer.py +45 -9
- pysdmx/io/xml/__structure_aux_reader.py +2 -2
- pysdmx/io/xml/__structure_aux_writer.py +5 -5
- pysdmx/io/xml/__write_data_aux.py +1 -54
- pysdmx/io/xml/__write_structure_specific_aux.py +1 -1
- pysdmx/io/xml/sdmx21/writer/generic.py +1 -1
- pysdmx/model/code.py +11 -1
- pysdmx/model/dataflow.py +26 -3
- pysdmx/model/map.py +12 -4
- pysdmx/model/message.py +9 -1
- pysdmx/toolkit/pd/_data_utils.py +100 -0
- pysdmx/toolkit/vtl/_validations.py +2 -3
- {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/METADATA +3 -2
- {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/RECORD +60 -48
- {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/WHEEL +1 -1
- {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional, Sequence
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import msgspec
|
|
6
6
|
|
|
7
|
+
from pysdmx import errors
|
|
7
8
|
from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist
|
|
8
9
|
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
9
10
|
ItemSchemeType,
|
|
11
|
+
JsonAnnotation,
|
|
10
12
|
JsonRepresentation,
|
|
11
13
|
NameableType,
|
|
12
14
|
)
|
|
13
|
-
from pysdmx.model import Codelist, Concept, ConceptScheme, DataType
|
|
15
|
+
from pysdmx.model import Agency, Codelist, Concept, ConceptScheme, DataType
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
class IsoConceptReference(Struct, frozen=True):
|
|
18
|
+
class IsoConceptReference(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
17
19
|
"""Payload for a reference to an ISO 11179 concept."""
|
|
18
20
|
|
|
19
21
|
conceptAgency: str
|
|
@@ -21,7 +23,7 @@ class IsoConceptReference(Struct, frozen=True):
|
|
|
21
23
|
conceptID: str
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
class JsonConcept(NameableType, frozen=True):
|
|
26
|
+
class JsonConcept(NameableType, frozen=True, omit_defaults=True):
|
|
25
27
|
"""SDMX-JSON payload for concepts."""
|
|
26
28
|
|
|
27
29
|
coreRepresentation: Optional[JsonRepresentation] = None
|
|
@@ -56,22 +58,62 @@ class JsonConcept(NameableType, frozen=True):
|
|
|
56
58
|
enum_ref=cl_ref,
|
|
57
59
|
)
|
|
58
60
|
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_model(self, concept: Concept) -> "JsonConcept":
|
|
63
|
+
"""Converts a pysdmx concept to an SDMX-JSON one."""
|
|
64
|
+
if not concept.name:
|
|
65
|
+
raise errors.Invalid(
|
|
66
|
+
"Invalid input",
|
|
67
|
+
"SDMX-JSON concepts must have a name",
|
|
68
|
+
{"codelist": concept.id},
|
|
69
|
+
)
|
|
70
|
+
if concept.codes:
|
|
71
|
+
enum_ref = (
|
|
72
|
+
"urn:sdmx:org.sdmx.infomodel.codelist."
|
|
73
|
+
f"{concept.codes.short_urn}"
|
|
74
|
+
)
|
|
75
|
+
elif concept.enum_ref:
|
|
76
|
+
enum_ref = concept.enum_ref
|
|
77
|
+
else:
|
|
78
|
+
enum_ref = None
|
|
79
|
+
|
|
80
|
+
return JsonConcept(
|
|
81
|
+
id=concept.id,
|
|
82
|
+
name=concept.name,
|
|
83
|
+
description=concept.description,
|
|
84
|
+
annotations=tuple(
|
|
85
|
+
[JsonAnnotation.from_model(a) for a in concept.annotations]
|
|
86
|
+
),
|
|
87
|
+
coreRepresentation=JsonRepresentation.from_model(
|
|
88
|
+
concept.dtype, enum_ref, concept.facets, None
|
|
89
|
+
),
|
|
90
|
+
)
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
|
|
93
|
+
class JsonConceptScheme(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
61
94
|
"""SDMX-JSON payload for a concept scheme."""
|
|
62
95
|
|
|
63
96
|
concepts: Sequence[JsonConcept] = ()
|
|
64
97
|
|
|
98
|
+
def __set_urn(self, concept: Concept) -> Concept:
|
|
99
|
+
urn = (
|
|
100
|
+
"urn:sdmx:org.sdmx.infomodel.conceptscheme.Concept="
|
|
101
|
+
f"{self.agency}:{self.id}({self.version}).{concept.id}"
|
|
102
|
+
)
|
|
103
|
+
return msgspec.structs.replace(concept, urn=urn)
|
|
104
|
+
|
|
65
105
|
def to_model(self, codelists: Sequence[JsonCodelist]) -> ConceptScheme:
|
|
66
106
|
"""Converts a JsonConceptScheme to a standard concept scheme."""
|
|
67
|
-
cls = [
|
|
107
|
+
cls = [cl.to_model() for cl in codelists]
|
|
108
|
+
concepts = [c.to_model(cls) for c in self.concepts]
|
|
109
|
+
concepts = [self.__set_urn(c) for c in concepts]
|
|
68
110
|
return ConceptScheme(
|
|
69
111
|
id=self.id,
|
|
70
112
|
name=self.name,
|
|
71
113
|
agency=self.agency,
|
|
72
114
|
description=self.description,
|
|
73
115
|
version=self.version,
|
|
74
|
-
items=
|
|
116
|
+
items=concepts,
|
|
75
117
|
annotations=[a.to_model() for a in self.annotations],
|
|
76
118
|
is_external_reference=self.isExternalReference,
|
|
77
119
|
is_partial=self.isPartial,
|
|
@@ -79,11 +121,35 @@ class JsonConceptScheme(ItemSchemeType, frozen=True):
|
|
|
79
121
|
valid_to=self.validTo,
|
|
80
122
|
)
|
|
81
123
|
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_model(self, cs: ConceptScheme) -> "JsonConceptScheme":
|
|
126
|
+
"""Converts a pysdmx concept scheme to an SDMX-JSON one."""
|
|
127
|
+
if not cs.name:
|
|
128
|
+
raise errors.Invalid(
|
|
129
|
+
"Invalid input",
|
|
130
|
+
"SDMX-JSON concept schemes must have a name",
|
|
131
|
+
{"concept_scheme": cs.id},
|
|
132
|
+
)
|
|
133
|
+
return JsonConceptScheme(
|
|
134
|
+
agency=(
|
|
135
|
+
cs.agency.id if isinstance(cs.agency, Agency) else cs.agency
|
|
136
|
+
),
|
|
137
|
+
id=cs.id,
|
|
138
|
+
name=cs.name,
|
|
139
|
+
version=cs.version,
|
|
140
|
+
isExternalReference=cs.is_external_reference,
|
|
141
|
+
validFrom=cs.valid_from,
|
|
142
|
+
validTo=cs.valid_to,
|
|
143
|
+
description=cs.description,
|
|
144
|
+
annotations=tuple(
|
|
145
|
+
[JsonAnnotation.from_model(a) for a in cs.annotations]
|
|
146
|
+
),
|
|
147
|
+
isPartial=cs.is_partial,
|
|
148
|
+
concepts=tuple([JsonConcept.from_model(c) for c in cs.concepts]),
|
|
149
|
+
)
|
|
150
|
+
|
|
82
151
|
|
|
83
|
-
class JsonConceptSchemes(
|
|
84
|
-
Struct,
|
|
85
|
-
frozen=True,
|
|
86
|
-
):
|
|
152
|
+
class JsonConceptSchemes(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
87
153
|
"""SDMX-JSON payload for the list of concept schemes."""
|
|
88
154
|
|
|
89
155
|
conceptSchemes: Sequence[JsonConceptScheme]
|
|
@@ -91,8 +157,7 @@ class JsonConceptSchemes(
|
|
|
91
157
|
|
|
92
158
|
|
|
93
159
|
class JsonConceptSchemeMessage(
|
|
94
|
-
Struct,
|
|
95
|
-
frozen=True,
|
|
160
|
+
msgspec.Struct, frozen=True, omit_defaults=True
|
|
96
161
|
):
|
|
97
162
|
"""SDMX-JSON payload for /conceptscheme queries."""
|
|
98
163
|
|
|
@@ -7,13 +7,13 @@ from msgspec import Struct
|
|
|
7
7
|
from pysdmx.io.json.sdmxjson2.messages.core import MaintainableType
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class JsonValue(Struct, frozen=True):
|
|
10
|
+
class JsonValue(Struct, frozen=True, omit_defaults=True):
|
|
11
11
|
"""SDMX-JSON payload for an allowed value."""
|
|
12
12
|
|
|
13
13
|
value: str
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class JsonKeyValue(Struct, frozen=True):
|
|
16
|
+
class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
17
17
|
"""SDMX-JSON payload for the list of allowed values per component."""
|
|
18
18
|
|
|
19
19
|
id: str
|
|
@@ -24,7 +24,7 @@ class JsonKeyValue(Struct, frozen=True):
|
|
|
24
24
|
return [v.value for v in self.values]
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class JsonCubeRegion(Struct, frozen=True):
|
|
27
|
+
class JsonCubeRegion(Struct, frozen=True, omit_defaults=True):
|
|
28
28
|
"""SDMX-JSON payload for a cube region."""
|
|
29
29
|
|
|
30
30
|
keyValues: Sequence[JsonKeyValue]
|
|
@@ -34,7 +34,7 @@ class JsonCubeRegion(Struct, frozen=True):
|
|
|
34
34
|
return {kv.id: kv.to_model() for kv in self.keyValues}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class JsonConstraintAttachment(Struct, frozen=True):
|
|
37
|
+
class JsonConstraintAttachment(Struct, frozen=True, omit_defaults=True):
|
|
38
38
|
"""SDMX-JSON payload for a constraint attachment."""
|
|
39
39
|
|
|
40
40
|
dataProvider: Optional[str]
|
|
@@ -45,7 +45,7 @@ class JsonConstraintAttachment(Struct, frozen=True):
|
|
|
45
45
|
queryableDataSources: Optional[Sequence[str]] = None
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class JsonDataConstraint(MaintainableType, frozen=True):
|
|
48
|
+
class JsonDataConstraint(MaintainableType, frozen=True, omit_defaults=True):
|
|
49
49
|
"""SDMX-JSON payload for a content constraint."""
|
|
50
50
|
|
|
51
51
|
role: Optional[Literal["Allowed", "Actual"]] = None
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Collection of SDMX-JSON schemas for common artefacts."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Optional, Sequence, Union
|
|
4
|
+
from typing import Literal, Optional, Sequence, Union
|
|
5
5
|
|
|
6
6
|
import msgspec
|
|
7
7
|
|
|
@@ -10,6 +10,7 @@ from pysdmx.model import (
|
|
|
10
10
|
Annotation,
|
|
11
11
|
ArrayBoundaries,
|
|
12
12
|
Codelist,
|
|
13
|
+
DataType,
|
|
13
14
|
Facets,
|
|
14
15
|
Organisation,
|
|
15
16
|
)
|
|
@@ -17,7 +18,7 @@ from pysdmx.model.message import Header
|
|
|
17
18
|
from pysdmx.util import find_by_urn
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class JsonLink(msgspec.Struct, frozen=True):
|
|
21
|
+
class JsonLink(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
21
22
|
"""SDMX-JSON payload for link objects."""
|
|
22
23
|
|
|
23
24
|
href: Optional[str] = None
|
|
@@ -29,7 +30,7 @@ class JsonLink(msgspec.Struct, frozen=True):
|
|
|
29
30
|
hreflang: Optional[str] = None
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
class JsonAnnotation(msgspec.Struct, frozen=True):
|
|
33
|
+
class JsonAnnotation(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
33
34
|
"""SDMX-JSON payload for annotations."""
|
|
34
35
|
|
|
35
36
|
id: Optional[str] = None
|
|
@@ -57,15 +58,31 @@ class JsonAnnotation(msgspec.Struct, frozen=True):
|
|
|
57
58
|
text=self.value if self.value else self.text,
|
|
58
59
|
)
|
|
59
60
|
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_model(self, annotation: Annotation) -> "JsonAnnotation":
|
|
63
|
+
"""Converts a pysdmx annotation to an SDMX-JSON one."""
|
|
64
|
+
if annotation.url:
|
|
65
|
+
links = (JsonLink(rel="self", href=annotation.url),)
|
|
66
|
+
else:
|
|
67
|
+
links = () # type: ignore[assignment]
|
|
68
|
+
|
|
69
|
+
return JsonAnnotation(
|
|
70
|
+
id=annotation.id,
|
|
71
|
+
title=annotation.title,
|
|
72
|
+
type=annotation.type,
|
|
73
|
+
text=annotation.text,
|
|
74
|
+
links=links,
|
|
75
|
+
)
|
|
60
76
|
|
|
61
|
-
|
|
77
|
+
|
|
78
|
+
class IdentifiableType(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
62
79
|
"""An abstract base type used for all nameable artefacts."""
|
|
63
80
|
|
|
64
81
|
id: str
|
|
65
82
|
annotations: Sequence[JsonAnnotation] = ()
|
|
66
83
|
|
|
67
84
|
|
|
68
|
-
class NameableType(msgspec.Struct, frozen=True):
|
|
85
|
+
class NameableType(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
69
86
|
"""An abstract base type used for all nameable artefacts."""
|
|
70
87
|
|
|
71
88
|
id: str
|
|
@@ -75,7 +92,10 @@ class NameableType(msgspec.Struct, frozen=True):
|
|
|
75
92
|
|
|
76
93
|
|
|
77
94
|
class MaintainableType(
|
|
78
|
-
msgspec.Struct,
|
|
95
|
+
msgspec.Struct,
|
|
96
|
+
frozen=True,
|
|
97
|
+
rename={"agency": "agencyID"},
|
|
98
|
+
omit_defaults=True,
|
|
79
99
|
):
|
|
80
100
|
"""An abstract base type used for all maintainable artefacts."""
|
|
81
101
|
|
|
@@ -90,16 +110,16 @@ class MaintainableType(
|
|
|
90
110
|
annotations: Sequence[JsonAnnotation] = ()
|
|
91
111
|
|
|
92
112
|
|
|
93
|
-
class ItemSchemeType(MaintainableType, frozen=True):
|
|
113
|
+
class ItemSchemeType(MaintainableType, frozen=True, omit_defaults=True):
|
|
94
114
|
"""An abstract base type used for all item schemes."""
|
|
95
115
|
|
|
96
116
|
isPartial: bool = False
|
|
97
117
|
|
|
98
118
|
|
|
99
|
-
class JsonTextFormat(msgspec.Struct, frozen=True):
|
|
119
|
+
class JsonTextFormat(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
100
120
|
"""SDMX-JSON payload for TextFormat."""
|
|
101
121
|
|
|
102
|
-
dataType: str
|
|
122
|
+
dataType: Optional[str] = None
|
|
103
123
|
minLength: Optional[int] = None
|
|
104
124
|
maxLength: Optional[int] = None
|
|
105
125
|
minValue: Optional[Union[int, float]] = None
|
|
@@ -114,7 +134,37 @@ class JsonTextFormat(msgspec.Struct, frozen=True):
|
|
|
114
134
|
isMultilingual: bool = False
|
|
115
135
|
sentinelValues: Optional[Sequence[str]] = None
|
|
116
136
|
timeInterval: Optional[str] = None
|
|
117
|
-
interval: Optional[int] = None
|
|
137
|
+
interval: Optional[Union[int, float]] = None
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_model(
|
|
141
|
+
self, dtype: Optional[DataType], facets: Optional[Facets]
|
|
142
|
+
) -> Optional["JsonTextFormat"]:
|
|
143
|
+
"""Converts pysdmx format details to an SDMX-JSON JsonTextFormat."""
|
|
144
|
+
if dtype is None and facets is None:
|
|
145
|
+
return None
|
|
146
|
+
else:
|
|
147
|
+
typ = dtype.value if dtype else None
|
|
148
|
+
if facets is None:
|
|
149
|
+
return JsonTextFormat(typ)
|
|
150
|
+
else:
|
|
151
|
+
return JsonTextFormat(
|
|
152
|
+
typ,
|
|
153
|
+
facets.min_length,
|
|
154
|
+
facets.max_length,
|
|
155
|
+
facets.min_value,
|
|
156
|
+
facets.max_value,
|
|
157
|
+
facets.start_value,
|
|
158
|
+
facets.end_value,
|
|
159
|
+
facets.decimals,
|
|
160
|
+
facets.pattern,
|
|
161
|
+
facets.start_time,
|
|
162
|
+
facets.end_time,
|
|
163
|
+
facets.is_sequence,
|
|
164
|
+
facets.is_multilingual,
|
|
165
|
+
timeInterval=facets.time_interval,
|
|
166
|
+
interval=facets.interval,
|
|
167
|
+
)
|
|
118
168
|
|
|
119
169
|
|
|
120
170
|
def get_facets(input: JsonTextFormat) -> Facets:
|
|
@@ -135,7 +185,7 @@ def get_facets(input: JsonTextFormat) -> Facets:
|
|
|
135
185
|
)
|
|
136
186
|
|
|
137
187
|
|
|
138
|
-
class JsonRepresentation(msgspec.Struct, frozen=True):
|
|
188
|
+
class JsonRepresentation(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
139
189
|
"""SDMX-JSON payload for core representation."""
|
|
140
190
|
|
|
141
191
|
enumerationFormat: Optional[JsonTextFormat] = None
|
|
@@ -192,8 +242,44 @@ class JsonRepresentation(msgspec.Struct, frozen=True):
|
|
|
192
242
|
else:
|
|
193
243
|
return None
|
|
194
244
|
|
|
195
|
-
|
|
196
|
-
|
|
245
|
+
@classmethod
|
|
246
|
+
def from_model(
|
|
247
|
+
self,
|
|
248
|
+
dtype: Optional[DataType],
|
|
249
|
+
enumeration: Optional[str],
|
|
250
|
+
facets: Optional[Facets],
|
|
251
|
+
array_def: Optional[ArrayBoundaries],
|
|
252
|
+
) -> Optional["JsonRepresentation"]:
|
|
253
|
+
"""Converts pysdmx representation details to an SDMX-JSON one."""
|
|
254
|
+
if (
|
|
255
|
+
dtype is None
|
|
256
|
+
and enumeration is None
|
|
257
|
+
and facets is None
|
|
258
|
+
and array_def is None
|
|
259
|
+
):
|
|
260
|
+
return None
|
|
261
|
+
else:
|
|
262
|
+
fmt = JsonTextFormat.from_model(dtype, facets)
|
|
263
|
+
if array_def:
|
|
264
|
+
mino = array_def.min_size
|
|
265
|
+
maxo = array_def.max_size
|
|
266
|
+
else:
|
|
267
|
+
mino = None
|
|
268
|
+
maxo = None
|
|
269
|
+
if enumeration:
|
|
270
|
+
return JsonRepresentation(
|
|
271
|
+
enumerationFormat=fmt,
|
|
272
|
+
enumeration=enumeration,
|
|
273
|
+
minOccurs=mino,
|
|
274
|
+
maxOccurs=maxo,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
return JsonRepresentation(
|
|
278
|
+
format=fmt, minOccurs=mino, maxOccurs=maxo
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
197
283
|
"""SDMX-JSON payload for message header."""
|
|
198
284
|
|
|
199
285
|
id: str
|
|
@@ -202,8 +288,9 @@ class JsonHeader(msgspec.Struct, frozen=True):
|
|
|
202
288
|
test: bool = False
|
|
203
289
|
contentLanguages: Sequence[str] = ()
|
|
204
290
|
name: Optional[str] = None
|
|
205
|
-
receivers: Optional[Organisation] = None
|
|
291
|
+
receivers: Optional[Sequence[Organisation]] = None
|
|
206
292
|
links: Sequence[JsonLink] = ()
|
|
293
|
+
schema: Optional[str] = None
|
|
207
294
|
|
|
208
295
|
def to_model(self) -> Header:
|
|
209
296
|
"""Map to pysdmx header class."""
|
|
@@ -213,3 +300,23 @@ class JsonHeader(msgspec.Struct, frozen=True):
|
|
|
213
300
|
prepared=self.prepared,
|
|
214
301
|
sender=self.sender,
|
|
215
302
|
)
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def from_model(
|
|
306
|
+
self,
|
|
307
|
+
header: Header,
|
|
308
|
+
msg_type: Literal["structure", "metadata"] = "structure",
|
|
309
|
+
) -> "JsonHeader":
|
|
310
|
+
"""Create an SDMX-JSON header from a pysdmx Header."""
|
|
311
|
+
return JsonHeader(
|
|
312
|
+
header.id,
|
|
313
|
+
header.prepared,
|
|
314
|
+
header.sender,
|
|
315
|
+
header.test,
|
|
316
|
+
receivers=(header.receiver,) if header.receiver else None,
|
|
317
|
+
schema=(
|
|
318
|
+
"https://raw.githubusercontent.com/sdmx-twg/sdmx-json/"
|
|
319
|
+
"develop/structure-message/tools/schemas/2.0.0/"
|
|
320
|
+
f"sdmx-json-{msg_type}-schema.json"
|
|
321
|
+
),
|
|
322
|
+
)
|
|
@@ -4,9 +4,13 @@ from typing import List, Optional, Sequence, Union
|
|
|
4
4
|
|
|
5
5
|
from msgspec import Struct
|
|
6
6
|
|
|
7
|
+
from pysdmx import errors
|
|
7
8
|
from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist, JsonValuelist
|
|
8
9
|
from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptScheme
|
|
9
|
-
from pysdmx.io.json.sdmxjson2.messages.core import
|
|
10
|
+
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
11
|
+
JsonAnnotation,
|
|
12
|
+
MaintainableType,
|
|
13
|
+
)
|
|
10
14
|
from pysdmx.io.json.sdmxjson2.messages.dsd import JsonDataStructure
|
|
11
15
|
from pysdmx.io.json.sdmxjson2.messages.provider import JsonDataProviderScheme
|
|
12
16
|
from pysdmx.model import (
|
|
@@ -17,10 +21,11 @@ from pysdmx.model import (
|
|
|
17
21
|
DataProvider,
|
|
18
22
|
DataStructureDefinition,
|
|
19
23
|
)
|
|
24
|
+
from pysdmx.model.dataflow import Group
|
|
20
25
|
from pysdmx.util import parse_urn
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
class JsonDataflow(MaintainableType, frozen=True):
|
|
28
|
+
class JsonDataflow(MaintainableType, frozen=True, omit_defaults=True):
|
|
24
29
|
"""SDMX-JSON payload for a dataflow."""
|
|
25
30
|
|
|
26
31
|
structure: str = ""
|
|
@@ -59,8 +64,47 @@ class JsonDataflow(MaintainableType, frozen=True):
|
|
|
59
64
|
valid_to=self.validTo,
|
|
60
65
|
)
|
|
61
66
|
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_model(self, df: Dataflow) -> "JsonDataflow":
|
|
69
|
+
"""Converts a pysdmx dataflow to an SDMX-JSON one."""
|
|
70
|
+
if not df.name:
|
|
71
|
+
raise errors.Invalid(
|
|
72
|
+
"Invalid input",
|
|
73
|
+
"SDMX-JSON dataflows must have a name",
|
|
74
|
+
{"dataflow": df.id},
|
|
75
|
+
)
|
|
76
|
+
if not df.structure:
|
|
77
|
+
raise errors.Invalid(
|
|
78
|
+
"Invalid input",
|
|
79
|
+
"SDMX-JSON dataflows must reference a DSD.",
|
|
80
|
+
{"dataflow": df.id},
|
|
81
|
+
)
|
|
82
|
+
if isinstance(df.structure, DataStructureDefinition):
|
|
83
|
+
dsdref = (
|
|
84
|
+
"urn:sdmx:org.sdmx.infomodel.datastructure.DataStructure="
|
|
85
|
+
f"{df.structure.agency}:{df.structure.id}({df.structure.version})"
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
dsdref = df.structure
|
|
89
|
+
return JsonDataflow(
|
|
90
|
+
agency=(
|
|
91
|
+
df.agency.id if isinstance(df.agency, Agency) else df.agency
|
|
92
|
+
),
|
|
93
|
+
id=df.id,
|
|
94
|
+
name=df.name,
|
|
95
|
+
version=df.version,
|
|
96
|
+
isExternalReference=df.is_external_reference,
|
|
97
|
+
validFrom=df.valid_from,
|
|
98
|
+
validTo=df.valid_to,
|
|
99
|
+
description=df.description,
|
|
100
|
+
annotations=tuple(
|
|
101
|
+
[JsonAnnotation.from_model(a) for a in df.annotations]
|
|
102
|
+
),
|
|
103
|
+
structure=dsdref,
|
|
104
|
+
)
|
|
62
105
|
|
|
63
|
-
|
|
106
|
+
|
|
107
|
+
class JsonDataflows(Struct, frozen=True, omit_defaults=True):
|
|
64
108
|
"""SDMX-JSON payload for the list of dataflows."""
|
|
65
109
|
|
|
66
110
|
dataflows: Sequence[JsonDataflow]
|
|
@@ -81,7 +125,12 @@ class JsonDataflows(Struct, frozen=True):
|
|
|
81
125
|
return df.agency == agency and df.id == id_
|
|
82
126
|
|
|
83
127
|
def to_model(
|
|
84
|
-
self,
|
|
128
|
+
self,
|
|
129
|
+
components: Components,
|
|
130
|
+
grps: Optional[Sequence[Group]],
|
|
131
|
+
agency: str,
|
|
132
|
+
id_: str,
|
|
133
|
+
version: str,
|
|
85
134
|
) -> DataflowInfo:
|
|
86
135
|
"""Returns the requested dataflow details."""
|
|
87
136
|
prvs: List[DataProvider] = []
|
|
@@ -102,6 +151,7 @@ class JsonDataflows(Struct, frozen=True):
|
|
|
102
151
|
version=df.version,
|
|
103
152
|
providers=prvs,
|
|
104
153
|
dsd_ref=df.structure,
|
|
154
|
+
groups=grps,
|
|
105
155
|
)
|
|
106
156
|
|
|
107
157
|
def to_generic_model(self) -> Sequence[Dataflow]:
|
|
@@ -117,19 +167,24 @@ class JsonDataflows(Struct, frozen=True):
|
|
|
117
167
|
]
|
|
118
168
|
|
|
119
169
|
|
|
120
|
-
class JsonDataflowMessage(Struct, frozen=True):
|
|
170
|
+
class JsonDataflowMessage(Struct, frozen=True, omit_defaults=True):
|
|
121
171
|
"""SDMX-JSON payload for /dataflow queries (with details)."""
|
|
122
172
|
|
|
123
173
|
data: JsonDataflows
|
|
124
174
|
|
|
125
175
|
def to_model(
|
|
126
|
-
self,
|
|
176
|
+
self,
|
|
177
|
+
components: Components,
|
|
178
|
+
grps: Optional[Sequence[Group]],
|
|
179
|
+
agency: str,
|
|
180
|
+
id_: str,
|
|
181
|
+
version: str,
|
|
127
182
|
) -> DataflowInfo:
|
|
128
183
|
"""Returns the requested dataflow details."""
|
|
129
|
-
return self.data.to_model(components, agency, id_, version)
|
|
184
|
+
return self.data.to_model(components, grps, agency, id_, version)
|
|
130
185
|
|
|
131
186
|
|
|
132
|
-
class JsonDataflowsMessage(Struct, frozen=True):
|
|
187
|
+
class JsonDataflowsMessage(Struct, frozen=True, omit_defaults=True):
|
|
133
188
|
"""SDMX-JSON payload for /dataflow queries."""
|
|
134
189
|
|
|
135
190
|
data: JsonDataflows
|