pysdmx 1.5.2__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/__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 +23 -0
- 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.2.dist-info → pysdmx-1.6.0.dist-info}/METADATA +3 -2
- {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info}/RECORD +58 -46
- {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info}/WHEEL +1 -1
- {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -3,14 +3,25 @@
|
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from typing import Dict, Sequence, Set
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import msgspec
|
|
7
7
|
|
|
8
|
-
from pysdmx.io.json.sdmxjson2.messages.core import
|
|
8
|
+
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
9
|
+
ItemSchemeType,
|
|
10
|
+
JsonAnnotation,
|
|
11
|
+
)
|
|
9
12
|
from pysdmx.io.json.sdmxjson2.messages.dataflow import JsonDataflow
|
|
10
13
|
from pysdmx.model import Agency, AgencyScheme, DataflowRef
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
def _sanitize_agency(agency: Agency, is_sdmx_scheme: bool) -> Agency:
|
|
17
|
+
if is_sdmx_scheme:
|
|
18
|
+
nid = agency.id
|
|
19
|
+
else:
|
|
20
|
+
nid = agency.id[agency.id.rindex(".") + 1 :]
|
|
21
|
+
return msgspec.structs.replace(agency, id=nid, dataflows=())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JsonAgencyScheme(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
14
25
|
"""SDMX-JSON payload for an agency scheme."""
|
|
15
26
|
|
|
16
27
|
agencies: Sequence[Agency] = ()
|
|
@@ -26,7 +37,7 @@ class JsonAgencyScheme(ItemSchemeType, frozen=True):
|
|
|
26
37
|
description=a.description,
|
|
27
38
|
contacts=a.contacts,
|
|
28
39
|
dataflows=flows,
|
|
29
|
-
annotations=[a.to_model() for a in self.annotations],
|
|
40
|
+
annotations=tuple([a.to_model() for a in self.annotations]),
|
|
30
41
|
)
|
|
31
42
|
|
|
32
43
|
def to_model(self, dataflows: Sequence[JsonDataflow]) -> AgencyScheme:
|
|
@@ -43,15 +54,40 @@ class JsonAgencyScheme(ItemSchemeType, frozen=True):
|
|
|
43
54
|
description=self.description,
|
|
44
55
|
agency=self.agency,
|
|
45
56
|
items=agencies,
|
|
46
|
-
annotations=[a.to_model() for a in self.annotations],
|
|
57
|
+
annotations=tuple([a.to_model() for a in self.annotations]),
|
|
47
58
|
is_external_reference=self.isExternalReference,
|
|
48
59
|
is_partial=self.isPartial,
|
|
49
60
|
valid_from=self.validFrom,
|
|
50
61
|
valid_to=self.validTo,
|
|
51
62
|
)
|
|
52
63
|
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_model(self, asc: AgencyScheme) -> "JsonAgencyScheme":
|
|
66
|
+
"""Converts a pysdmx agency scheme to an SDMX-JSON one."""
|
|
67
|
+
agency = (
|
|
68
|
+
asc.agency.id if isinstance(asc.agency, Agency) else asc.agency
|
|
69
|
+
)
|
|
70
|
+
is_sdmx_scheme = agency == "SDMX"
|
|
71
|
+
children = [_sanitize_agency(a, is_sdmx_scheme) for a in asc.items]
|
|
72
|
+
|
|
73
|
+
return JsonAgencyScheme(
|
|
74
|
+
id="AGENCIES",
|
|
75
|
+
name="AGENCIES",
|
|
76
|
+
agency=agency,
|
|
77
|
+
description=asc.description,
|
|
78
|
+
version="1.0",
|
|
79
|
+
agencies=children,
|
|
80
|
+
annotations=tuple(
|
|
81
|
+
[JsonAnnotation.from_model(a) for a in asc.annotations]
|
|
82
|
+
),
|
|
83
|
+
isExternalReference=asc.is_external_reference,
|
|
84
|
+
isPartial=asc.is_partial,
|
|
85
|
+
validFrom=asc.valid_from,
|
|
86
|
+
validTo=asc.valid_to,
|
|
87
|
+
)
|
|
88
|
+
|
|
53
89
|
|
|
54
|
-
class JsonAgencySchemes(Struct, frozen=True):
|
|
90
|
+
class JsonAgencySchemes(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
55
91
|
"""SDMX-JSON payload for the list of agency schemes."""
|
|
56
92
|
|
|
57
93
|
agencySchemes: Sequence[JsonAgencyScheme]
|
|
@@ -62,7 +98,7 @@ class JsonAgencySchemes(Struct, frozen=True):
|
|
|
62
98
|
return [a.to_model(self.dataflows) for a in self.agencySchemes]
|
|
63
99
|
|
|
64
100
|
|
|
65
|
-
class JsonAgencyMessage(Struct, frozen=True):
|
|
101
|
+
class JsonAgencyMessage(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
66
102
|
"""SDMX-JSON payload for /agencyscheme queries."""
|
|
67
103
|
|
|
68
104
|
data: JsonAgencySchemes
|
|
@@ -5,8 +5,10 @@ from typing import Dict, Sequence
|
|
|
5
5
|
|
|
6
6
|
from msgspec import Struct
|
|
7
7
|
|
|
8
|
+
from pysdmx import errors
|
|
8
9
|
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
9
10
|
ItemSchemeType,
|
|
11
|
+
JsonAnnotation,
|
|
10
12
|
MaintainableType,
|
|
11
13
|
NameableType,
|
|
12
14
|
)
|
|
@@ -23,7 +25,10 @@ from pysdmx.util import find_by_urn
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
class JsonCategorisation(
|
|
26
|
-
MaintainableType,
|
|
28
|
+
MaintainableType,
|
|
29
|
+
frozen=True,
|
|
30
|
+
rename={"agency": "agencyID"},
|
|
31
|
+
omit_defaults=True,
|
|
27
32
|
):
|
|
28
33
|
"""SDMX-JSON payload for a categorisation."""
|
|
29
34
|
|
|
@@ -46,8 +51,35 @@ class JsonCategorisation(
|
|
|
46
51
|
annotations=[a.to_model() for a in self.annotations],
|
|
47
52
|
)
|
|
48
53
|
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_model(self, cat: Categorisation) -> "JsonCategorisation":
|
|
56
|
+
"""Converts a pysdmx categorisation to an SDMX-JSON one."""
|
|
57
|
+
if not cat.name:
|
|
58
|
+
raise errors.Invalid(
|
|
59
|
+
"Invalid input",
|
|
60
|
+
"SDMX-JSON categorisations must have a name",
|
|
61
|
+
{"categorisation": cat.id},
|
|
62
|
+
)
|
|
63
|
+
return JsonCategorisation(
|
|
64
|
+
agency=(
|
|
65
|
+
cat.agency.id if isinstance(cat.agency, Agency) else cat.agency
|
|
66
|
+
),
|
|
67
|
+
id=cat.id,
|
|
68
|
+
name=cat.name,
|
|
69
|
+
version=cat.version,
|
|
70
|
+
isExternalReference=cat.is_external_reference,
|
|
71
|
+
validFrom=cat.valid_from,
|
|
72
|
+
validTo=cat.valid_to,
|
|
73
|
+
description=cat.description,
|
|
74
|
+
annotations=tuple(
|
|
75
|
+
[JsonAnnotation.from_model(a) for a in cat.annotations]
|
|
76
|
+
),
|
|
77
|
+
source=cat.source,
|
|
78
|
+
target=cat.target,
|
|
79
|
+
)
|
|
80
|
+
|
|
49
81
|
|
|
50
|
-
class JsonCategory(NameableType, frozen=True):
|
|
82
|
+
class JsonCategory(NameableType, frozen=True, omit_defaults=True):
|
|
51
83
|
"""SDMX-JSON payload for a category."""
|
|
52
84
|
|
|
53
85
|
categories: Sequence["JsonCategory"] = ()
|
|
@@ -62,9 +94,33 @@ class JsonCategory(NameableType, frozen=True):
|
|
|
62
94
|
annotations=[a.to_model() for a in self.annotations],
|
|
63
95
|
)
|
|
64
96
|
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_model(self, cat: Category) -> "JsonCategory":
|
|
99
|
+
"""Converts a pysdmx category to an SDMX-JSON one."""
|
|
100
|
+
if not cat.name:
|
|
101
|
+
raise errors.Invalid(
|
|
102
|
+
"Invalid input",
|
|
103
|
+
"SDMX-JSON category must have a name",
|
|
104
|
+
{"category": cat.id},
|
|
105
|
+
)
|
|
106
|
+
return JsonCategory(
|
|
107
|
+
id=cat.id,
|
|
108
|
+
name=cat.name,
|
|
109
|
+
description=cat.description,
|
|
110
|
+
annotations=tuple(
|
|
111
|
+
[JsonAnnotation.from_model(a) for a in cat.annotations]
|
|
112
|
+
),
|
|
113
|
+
categories=tuple(
|
|
114
|
+
[JsonCategory.from_model(c) for c in cat.categories]
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
65
118
|
|
|
66
119
|
class JsonCategoryScheme(
|
|
67
|
-
ItemSchemeType,
|
|
120
|
+
ItemSchemeType,
|
|
121
|
+
frozen=True,
|
|
122
|
+
rename={"agency": "agencyID"},
|
|
123
|
+
omit_defaults=True,
|
|
68
124
|
):
|
|
69
125
|
"""SDMX-JSON payload for a category scheme."""
|
|
70
126
|
|
|
@@ -86,8 +142,37 @@ class JsonCategoryScheme(
|
|
|
86
142
|
annotations=[a.to_model() for a in self.annotations],
|
|
87
143
|
)
|
|
88
144
|
|
|
145
|
+
@classmethod
|
|
146
|
+
def from_model(self, cs: CategoryScheme) -> "JsonCategoryScheme":
|
|
147
|
+
"""Converts a pysdmx category scheme to an SDMX-JSON one."""
|
|
148
|
+
if not cs.name:
|
|
149
|
+
raise errors.Invalid(
|
|
150
|
+
"Invalid input",
|
|
151
|
+
"SDMX-JSON category schemes must have a name",
|
|
152
|
+
{"category_scheme": cs.id},
|
|
153
|
+
)
|
|
154
|
+
return JsonCategoryScheme(
|
|
155
|
+
agency=(
|
|
156
|
+
cs.agency.id if isinstance(cs.agency, Agency) else cs.agency
|
|
157
|
+
),
|
|
158
|
+
id=cs.id,
|
|
159
|
+
name=cs.name,
|
|
160
|
+
version=cs.version,
|
|
161
|
+
isExternalReference=cs.is_external_reference,
|
|
162
|
+
validFrom=cs.valid_from,
|
|
163
|
+
validTo=cs.valid_to,
|
|
164
|
+
description=cs.description,
|
|
165
|
+
annotations=tuple(
|
|
166
|
+
[JsonAnnotation.from_model(a) for a in cs.annotations]
|
|
167
|
+
),
|
|
168
|
+
isPartial=cs.is_partial,
|
|
169
|
+
categories=tuple(
|
|
170
|
+
[JsonCategory.from_model(c) for c in cs.categories]
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
89
174
|
|
|
90
|
-
class JsonCategorySchemes(Struct, frozen=True):
|
|
175
|
+
class JsonCategorySchemes(Struct, frozen=True, omit_defaults=True):
|
|
91
176
|
"""SDMX-JSON payload for the list of category schemes."""
|
|
92
177
|
|
|
93
178
|
categorySchemes: Sequence[JsonCategoryScheme]
|
|
@@ -95,7 +180,7 @@ class JsonCategorySchemes(Struct, frozen=True):
|
|
|
95
180
|
dataflows: Sequence[JsonDataflow] = ()
|
|
96
181
|
|
|
97
182
|
|
|
98
|
-
class JsonCategorySchemeMessage(Struct, frozen=True):
|
|
183
|
+
class JsonCategorySchemeMessage(Struct, frozen=True, omit_defaults=True):
|
|
99
184
|
"""SDMX-JSON payload for /categoryscheme queries."""
|
|
100
185
|
|
|
101
186
|
data: JsonCategorySchemes
|
|
@@ -139,13 +224,13 @@ class JsonCategorySchemeMessage(Struct, frozen=True):
|
|
|
139
224
|
return cs
|
|
140
225
|
|
|
141
226
|
|
|
142
|
-
class JsonCategorisations(Struct, frozen=True):
|
|
227
|
+
class JsonCategorisations(Struct, frozen=True, omit_defaults=True):
|
|
143
228
|
"""SDMX-JSON payload for the list of categorisations."""
|
|
144
229
|
|
|
145
230
|
categorisations: Sequence[JsonCategorisation]
|
|
146
231
|
|
|
147
232
|
|
|
148
|
-
class JsonCategorisationMessage(Struct, frozen=True):
|
|
233
|
+
class JsonCategorisationMessage(Struct, frozen=True, omit_defaults=True):
|
|
149
234
|
"""SDMX-JSON payload for /categorisation queries."""
|
|
150
235
|
|
|
151
236
|
data: JsonCategorisations
|
|
@@ -6,6 +6,7 @@ from typing import Optional, Sequence, Tuple
|
|
|
6
6
|
|
|
7
7
|
from msgspec import Struct
|
|
8
8
|
|
|
9
|
+
from pysdmx import errors
|
|
9
10
|
from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
10
11
|
ItemSchemeType,
|
|
11
12
|
JsonAnnotation,
|
|
@@ -14,6 +15,8 @@ from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
|
14
15
|
NameableType,
|
|
15
16
|
)
|
|
16
17
|
from pysdmx.model import (
|
|
18
|
+
Agency,
|
|
19
|
+
Annotation,
|
|
17
20
|
Code,
|
|
18
21
|
Codelist,
|
|
19
22
|
HierarchicalCode,
|
|
@@ -22,14 +25,16 @@ from pysdmx.model import (
|
|
|
22
25
|
)
|
|
23
26
|
from pysdmx.util import find_by_urn, parse_item_urn
|
|
24
27
|
|
|
28
|
+
_VAL_FMT = "%Y-%m-%dT%H:%M:%S%z"
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
|
|
31
|
+
class JsonCode(NameableType, frozen=True, omit_defaults=True):
|
|
27
32
|
"""SDMX-JSON payload for codes."""
|
|
28
33
|
|
|
29
34
|
parent: Optional[str] = None
|
|
30
35
|
|
|
31
36
|
def __handle_date(self, datestr: str) -> datetime:
|
|
32
|
-
return datetime.strptime(datestr,
|
|
37
|
+
return datetime.strptime(datestr, _VAL_FMT)
|
|
33
38
|
|
|
34
39
|
def __get_val(
|
|
35
40
|
self, a: JsonAnnotation
|
|
@@ -57,11 +62,51 @@ class JsonCode(NameableType, frozen=True):
|
|
|
57
62
|
description=self.description,
|
|
58
63
|
valid_from=vf,
|
|
59
64
|
valid_to=vt,
|
|
60
|
-
annotations=
|
|
65
|
+
annotations=tuple(
|
|
66
|
+
[
|
|
67
|
+
a.to_model()
|
|
68
|
+
for a in self.annotations
|
|
69
|
+
if a.type != "FR_VALIDITY_PERIOD"
|
|
70
|
+
]
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_model(self, code: Code) -> "JsonCode":
|
|
76
|
+
"""Converts a pysdmx code to an SDMX-JSON one."""
|
|
77
|
+
if not code.name:
|
|
78
|
+
raise errors.Invalid(
|
|
79
|
+
"Invalid input",
|
|
80
|
+
"SDMX-JSON codes must have a name",
|
|
81
|
+
{"code": code.id},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
annotations = [JsonAnnotation.from_model(a) for a in code.annotations]
|
|
85
|
+
if code.valid_from and code.valid_to:
|
|
86
|
+
vp = (
|
|
87
|
+
f"{datetime.strftime(code.valid_from, _VAL_FMT)}/"
|
|
88
|
+
f"{datetime.strftime(code.valid_to, _VAL_FMT)}"
|
|
89
|
+
)
|
|
90
|
+
elif code.valid_from:
|
|
91
|
+
vp = f"{datetime.strftime(code.valid_from, _VAL_FMT)}/"
|
|
92
|
+
elif code.valid_to:
|
|
93
|
+
vp = f"/{datetime.strftime(code.valid_to, _VAL_FMT)}"
|
|
94
|
+
else:
|
|
95
|
+
vp = ""
|
|
96
|
+
if vp:
|
|
97
|
+
annotations.append(
|
|
98
|
+
JsonAnnotation(title=vp, type="FR_VALIDITY_PERIOD")
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return JsonCode(
|
|
102
|
+
id=code.id,
|
|
103
|
+
name=code.name,
|
|
104
|
+
description=code.description,
|
|
105
|
+
annotations=tuple(annotations),
|
|
61
106
|
)
|
|
62
107
|
|
|
63
108
|
|
|
64
|
-
class JsonCodelist(ItemSchemeType, frozen=True):
|
|
109
|
+
class JsonCodelist(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
65
110
|
"""SDMX-JSON payload for a codelist."""
|
|
66
111
|
|
|
67
112
|
codes: Sequence[JsonCode] = ()
|
|
@@ -74,16 +119,43 @@ class JsonCodelist(ItemSchemeType, frozen=True):
|
|
|
74
119
|
agency=self.agency,
|
|
75
120
|
description=self.description,
|
|
76
121
|
version=self.version,
|
|
77
|
-
items=[i.to_model() for i in self.codes],
|
|
78
|
-
annotations=[a.to_model() for a in self.annotations],
|
|
122
|
+
items=tuple([i.to_model() for i in self.codes]),
|
|
123
|
+
annotations=tuple([a.to_model() for a in self.annotations]),
|
|
79
124
|
is_external_reference=self.isExternalReference,
|
|
80
125
|
is_partial=self.isPartial,
|
|
81
126
|
valid_from=self.validFrom,
|
|
82
127
|
valid_to=self.validTo,
|
|
83
128
|
)
|
|
84
129
|
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_model(self, cl: Codelist) -> "JsonCodelist":
|
|
132
|
+
"""Converts a pysdmx codelist to an SDMX-JSON one."""
|
|
133
|
+
if not cl.name:
|
|
134
|
+
raise errors.Invalid(
|
|
135
|
+
"Invalid input",
|
|
136
|
+
"SDMX-JSON codelists must have a name",
|
|
137
|
+
{"codelist": cl.id},
|
|
138
|
+
)
|
|
139
|
+
return JsonCodelist(
|
|
140
|
+
id=cl.id,
|
|
141
|
+
name=cl.name,
|
|
142
|
+
agency=(
|
|
143
|
+
cl.agency.id if isinstance(cl.agency, Agency) else cl.agency
|
|
144
|
+
),
|
|
145
|
+
description=cl.description,
|
|
146
|
+
version=cl.version,
|
|
147
|
+
codes=tuple([JsonCode.from_model(i) for i in cl.items]),
|
|
148
|
+
annotations=tuple(
|
|
149
|
+
[JsonAnnotation.from_model(a) for a in cl.annotations]
|
|
150
|
+
),
|
|
151
|
+
isExternalReference=cl.is_external_reference,
|
|
152
|
+
isPartial=cl.is_partial,
|
|
153
|
+
validFrom=cl.valid_from,
|
|
154
|
+
validTo=cl.valid_to,
|
|
155
|
+
)
|
|
156
|
+
|
|
85
157
|
|
|
86
|
-
class JsonValuelist(ItemSchemeType, frozen=True):
|
|
158
|
+
class JsonValuelist(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
87
159
|
"""SDMX-JSON payload for a valuelist."""
|
|
88
160
|
|
|
89
161
|
valueItems: Sequence[JsonCode] = ()
|
|
@@ -105,15 +177,42 @@ class JsonValuelist(ItemSchemeType, frozen=True):
|
|
|
105
177
|
sdmx_type="valuelist",
|
|
106
178
|
)
|
|
107
179
|
|
|
180
|
+
@classmethod
|
|
181
|
+
def from_model(self, cl: Codelist) -> "JsonValuelist":
|
|
182
|
+
"""Converts a pysdmx codelist to an SDMX-JSON valuelist."""
|
|
183
|
+
if not cl.name:
|
|
184
|
+
raise errors.Invalid(
|
|
185
|
+
"Invalid input",
|
|
186
|
+
"SDMX-JSON valuelists must have a name",
|
|
187
|
+
{"valuelist": cl.id},
|
|
188
|
+
)
|
|
189
|
+
return JsonValuelist(
|
|
190
|
+
id=cl.id,
|
|
191
|
+
name=cl.name,
|
|
192
|
+
agency=(
|
|
193
|
+
cl.agency.id if isinstance(cl.agency, Agency) else cl.agency
|
|
194
|
+
),
|
|
195
|
+
description=cl.description,
|
|
196
|
+
version=cl.version,
|
|
197
|
+
valueItems=tuple([JsonCode.from_model(i) for i in cl.items]),
|
|
198
|
+
annotations=tuple(
|
|
199
|
+
[JsonAnnotation.from_model(a) for a in cl.annotations]
|
|
200
|
+
),
|
|
201
|
+
isExternalReference=cl.is_external_reference,
|
|
202
|
+
isPartial=cl.is_partial,
|
|
203
|
+
validFrom=cl.valid_from,
|
|
204
|
+
validTo=cl.valid_to,
|
|
205
|
+
)
|
|
108
206
|
|
|
109
|
-
|
|
207
|
+
|
|
208
|
+
class JsonCodelists(Struct, frozen=True, omit_defaults=True):
|
|
110
209
|
"""SDMX-JSON payload for lists of codes."""
|
|
111
210
|
|
|
112
211
|
codelists: Sequence[JsonCodelist] = ()
|
|
113
212
|
valuelists: Sequence[JsonValuelist] = ()
|
|
114
213
|
|
|
115
214
|
|
|
116
|
-
class JsonCodelistMessage(Struct, frozen=True):
|
|
215
|
+
class JsonCodelistMessage(Struct, frozen=True, omit_defaults=True):
|
|
117
216
|
"""SDMX-JSON payload for /codelist queries."""
|
|
118
217
|
|
|
119
218
|
data: JsonCodelists
|
|
@@ -126,7 +225,7 @@ class JsonCodelistMessage(Struct, frozen=True):
|
|
|
126
225
|
return self.data.valuelists[0].to_model()
|
|
127
226
|
|
|
128
227
|
|
|
129
|
-
class JsonHierarchicalCode(Struct, frozen=True):
|
|
228
|
+
class JsonHierarchicalCode(Struct, frozen=True, omit_defaults=True):
|
|
130
229
|
"""Fusion-JSON payload for hierarchical codes."""
|
|
131
230
|
|
|
132
231
|
id: str
|
|
@@ -163,6 +262,11 @@ class JsonHierarchicalCode(Struct, frozen=True):
|
|
|
163
262
|
codes = [c.to_model(codelists) for c in self.hierarchicalCodes]
|
|
164
263
|
vf = self.validFrom.replace(tzinfo=tz.utc) if self.validFrom else None
|
|
165
264
|
vt = self.validTo.replace(tzinfo=tz.utc) if self.validTo else None
|
|
265
|
+
if self.id != code.id:
|
|
266
|
+
a = Annotation(id="hcode", type="pysdmx", text=self.id)
|
|
267
|
+
annotations = [a]
|
|
268
|
+
else:
|
|
269
|
+
annotations = []
|
|
166
270
|
return HierarchicalCode(
|
|
167
271
|
code.id,
|
|
168
272
|
name,
|
|
@@ -172,10 +276,45 @@ class JsonHierarchicalCode(Struct, frozen=True):
|
|
|
172
276
|
vf,
|
|
173
277
|
vt,
|
|
174
278
|
codes,
|
|
279
|
+
tuple(annotations),
|
|
280
|
+
self.code,
|
|
175
281
|
)
|
|
176
282
|
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_model(self, code: HierarchicalCode) -> "JsonHierarchicalCode":
|
|
285
|
+
"""Converts a pysdmx hierarchical code to an SDMX-JSON one."""
|
|
286
|
+
if not code.urn:
|
|
287
|
+
raise errors.Invalid(
|
|
288
|
+
"Invalid input",
|
|
289
|
+
"SDMX-JSON hierarchical codes must have the code urn.",
|
|
290
|
+
{"code": code.id},
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
annotations = [
|
|
294
|
+
JsonAnnotation.from_model(a)
|
|
295
|
+
for a in code.annotations
|
|
296
|
+
if a.type != "pysdmx"
|
|
297
|
+
]
|
|
298
|
+
id_ano = [
|
|
299
|
+
a
|
|
300
|
+
for a in code.annotations
|
|
301
|
+
if a.type == "pysdmx" and a.id == "hcode"
|
|
302
|
+
]
|
|
303
|
+
hid = id_ano[0].value if len(id_ano) > 0 else code.id
|
|
304
|
+
|
|
305
|
+
return JsonHierarchicalCode(
|
|
306
|
+
id=hid, # type: ignore[arg-type]
|
|
307
|
+
code=code.urn,
|
|
308
|
+
validFrom=code.rel_valid_from,
|
|
309
|
+
validTo=code.rel_valid_to,
|
|
310
|
+
annotations=tuple(annotations),
|
|
311
|
+
hierarchicalCodes=[
|
|
312
|
+
JsonHierarchicalCode.from_model(c) for c in code.codes
|
|
313
|
+
],
|
|
314
|
+
)
|
|
177
315
|
|
|
178
|
-
|
|
316
|
+
|
|
317
|
+
class JsonHierarchy(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
179
318
|
"""SDMX-JSON payload for a hierarchy."""
|
|
180
319
|
|
|
181
320
|
hierarchicalCodes: Sequence[JsonHierarchicalCode] = ()
|
|
@@ -189,7 +328,7 @@ class JsonHierarchy(ItemSchemeType, frozen=True):
|
|
|
189
328
|
agency=self.agency,
|
|
190
329
|
description=self.description,
|
|
191
330
|
version=self.version,
|
|
192
|
-
annotations=[a.to_model() for a in self.annotations],
|
|
331
|
+
annotations=tuple([a.to_model() for a in self.annotations]),
|
|
193
332
|
is_external_reference=self.isExternalReference,
|
|
194
333
|
is_partial=self.isPartial,
|
|
195
334
|
valid_from=self.validFrom,
|
|
@@ -197,8 +336,35 @@ class JsonHierarchy(ItemSchemeType, frozen=True):
|
|
|
197
336
|
codes=[i.to_model(cls) for i in self.hierarchicalCodes],
|
|
198
337
|
)
|
|
199
338
|
|
|
339
|
+
@classmethod
|
|
340
|
+
def from_model(self, h: Hierarchy) -> "JsonHierarchy":
|
|
341
|
+
"""Converts a pysdmx hierarchy to an SDMX-JSON one."""
|
|
342
|
+
if not h.name:
|
|
343
|
+
raise errors.Invalid(
|
|
344
|
+
"Invalid input",
|
|
345
|
+
"SDMX-JSON hierarchy must have a name",
|
|
346
|
+
{"hierarchy": h.id},
|
|
347
|
+
)
|
|
348
|
+
return JsonHierarchy(
|
|
349
|
+
id=h.id,
|
|
350
|
+
name=h.name,
|
|
351
|
+
agency=(h.agency.id if isinstance(h.agency, Agency) else h.agency),
|
|
352
|
+
description=h.description,
|
|
353
|
+
version=h.version,
|
|
354
|
+
hierarchicalCodes=tuple(
|
|
355
|
+
[JsonHierarchicalCode.from_model(i) for i in h.codes]
|
|
356
|
+
),
|
|
357
|
+
annotations=tuple(
|
|
358
|
+
[JsonAnnotation.from_model(a) for a in h.annotations]
|
|
359
|
+
),
|
|
360
|
+
isExternalReference=h.is_external_reference,
|
|
361
|
+
isPartial=h.is_partial,
|
|
362
|
+
validFrom=h.valid_from,
|
|
363
|
+
validTo=h.valid_to,
|
|
364
|
+
)
|
|
200
365
|
|
|
201
|
-
|
|
366
|
+
|
|
367
|
+
class JsonHierarchies(Struct, frozen=True, omit_defaults=True):
|
|
202
368
|
"""SDMX-JSON payload for hierarchies."""
|
|
203
369
|
|
|
204
370
|
codelists: Sequence[JsonCodelist] = ()
|
|
@@ -209,7 +375,9 @@ class JsonHierarchies(Struct, frozen=True):
|
|
|
209
375
|
return [h.to_model(self.codelists) for h in self.hierarchies]
|
|
210
376
|
|
|
211
377
|
|
|
212
|
-
class JsonHierarchyAssociation(
|
|
378
|
+
class JsonHierarchyAssociation(
|
|
379
|
+
MaintainableType, frozen=True, omit_defaults=True
|
|
380
|
+
):
|
|
213
381
|
"""SDMX-JSON payload for a hierarchy association."""
|
|
214
382
|
|
|
215
383
|
linkedHierarchy: str = ""
|
|
@@ -251,8 +419,61 @@ class JsonHierarchyAssociation(MaintainableType, frozen=True):
|
|
|
251
419
|
operator=lnk[0].urn if lnk else None,
|
|
252
420
|
)
|
|
253
421
|
|
|
422
|
+
@classmethod
|
|
423
|
+
def from_model(
|
|
424
|
+
self, ha: HierarchyAssociation
|
|
425
|
+
) -> "JsonHierarchyAssociation":
|
|
426
|
+
"""Converts a pysdmx hierarchy association to an SDMX-JSON one."""
|
|
427
|
+
if not ha.name:
|
|
428
|
+
raise errors.Invalid(
|
|
429
|
+
"Invalid input",
|
|
430
|
+
"SDMX-JSON hierarchy associations must have a name",
|
|
431
|
+
{"hierarchy_association": ha.id},
|
|
432
|
+
)
|
|
433
|
+
if ha.hierarchy is None:
|
|
434
|
+
raise errors.Invalid(
|
|
435
|
+
"Invalid input",
|
|
436
|
+
"SDMX-JSON hierarchy associations must reference a hierarchy",
|
|
437
|
+
{"hierarchy_association": ha.id},
|
|
438
|
+
)
|
|
439
|
+
if not ha.component_ref:
|
|
440
|
+
raise errors.Invalid(
|
|
441
|
+
"Invalid input",
|
|
442
|
+
"SDMX-JSON hierarchy associations must reference a component",
|
|
443
|
+
{"hierarchy_association": ha.id},
|
|
444
|
+
)
|
|
445
|
+
if isinstance(ha.hierarchy, Hierarchy):
|
|
446
|
+
base = "urn:sdmx:org.sdmx.infomodel.codelist."
|
|
447
|
+
href = f"{base}{ha.hierarchy.short_urn}"
|
|
448
|
+
else:
|
|
449
|
+
href = ha.hierarchy
|
|
450
|
+
if not ha.context_ref:
|
|
451
|
+
raise errors.Invalid(
|
|
452
|
+
"Invalid input",
|
|
453
|
+
"SDMX-JSON hierarchy associations must reference a context",
|
|
454
|
+
{"hierarchy_association": ha.id},
|
|
455
|
+
)
|
|
456
|
+
return JsonHierarchyAssociation(
|
|
457
|
+
agency=(
|
|
458
|
+
ha.agency.id if isinstance(ha.agency, Agency) else ha.agency
|
|
459
|
+
),
|
|
460
|
+
id=ha.id,
|
|
461
|
+
name=ha.name,
|
|
462
|
+
version=ha.version,
|
|
463
|
+
isExternalReference=ha.is_external_reference,
|
|
464
|
+
validFrom=ha.valid_from,
|
|
465
|
+
validTo=ha.valid_to,
|
|
466
|
+
description=ha.description,
|
|
467
|
+
annotations=tuple(
|
|
468
|
+
[JsonAnnotation.from_model(a) for a in ha.annotations]
|
|
469
|
+
),
|
|
470
|
+
linkedHierarchy=href,
|
|
471
|
+
linkedObject=ha.component_ref,
|
|
472
|
+
contextObject=ha.context_ref,
|
|
473
|
+
)
|
|
474
|
+
|
|
254
475
|
|
|
255
|
-
class JsonHierarchyMessage(Struct, frozen=True):
|
|
476
|
+
class JsonHierarchyMessage(Struct, frozen=True, omit_defaults=True):
|
|
256
477
|
"""SDMX-JSON payload for /hierarchy queries."""
|
|
257
478
|
|
|
258
479
|
data: JsonHierarchies
|
|
@@ -262,7 +483,7 @@ class JsonHierarchyMessage(Struct, frozen=True):
|
|
|
262
483
|
return self.data.to_model()[0]
|
|
263
484
|
|
|
264
485
|
|
|
265
|
-
class JsonHierarchiesMessage(Struct, frozen=True):
|
|
486
|
+
class JsonHierarchiesMessage(Struct, frozen=True, omit_defaults=True):
|
|
266
487
|
"""SDMX-JSON payload for /hierarchy queries."""
|
|
267
488
|
|
|
268
489
|
data: JsonHierarchies
|
|
@@ -272,7 +493,7 @@ class JsonHierarchiesMessage(Struct, frozen=True):
|
|
|
272
493
|
return self.data.to_model()
|
|
273
494
|
|
|
274
495
|
|
|
275
|
-
class JsonHierarchyAssociations(Struct, frozen=True):
|
|
496
|
+
class JsonHierarchyAssociations(Struct, frozen=True, omit_defaults=True):
|
|
276
497
|
"""SDMX-JSON payload for hierarchy associations."""
|
|
277
498
|
|
|
278
499
|
codelists: Sequence[JsonCodelist] = ()
|
|
@@ -287,7 +508,7 @@ class JsonHierarchyAssociations(Struct, frozen=True):
|
|
|
287
508
|
]
|
|
288
509
|
|
|
289
510
|
|
|
290
|
-
class JsonHierarchyAssociationMessage(Struct, frozen=True):
|
|
511
|
+
class JsonHierarchyAssociationMessage(Struct, frozen=True, omit_defaults=True):
|
|
291
512
|
"""SDMX-JSON payload for hierarchy associations messages."""
|
|
292
513
|
|
|
293
514
|
data: JsonHierarchyAssociations
|