pysdmx 1.3.0__py3-none-any.whl → 1.4.0rc1__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 +3 -2
- pysdmx/__init__.py +1 -1
- pysdmx/api/fmr/__init__.py +4 -4
- pysdmx/api/gds/__init__.py +328 -0
- pysdmx/api/qb/gds.py +153 -0
- pysdmx/api/qb/service.py +91 -3
- pysdmx/api/qb/structure.py +1 -0
- pysdmx/api/qb/util.py +1 -0
- pysdmx/io/__init__.py +2 -1
- pysdmx/io/csv/sdmx10/reader/__init__.py +4 -2
- pysdmx/io/csv/sdmx10/writer/__init__.py +15 -2
- pysdmx/io/csv/sdmx20/reader/__init__.py +5 -2
- pysdmx/io/csv/sdmx20/writer/__init__.py +13 -2
- pysdmx/io/format.py +4 -0
- pysdmx/io/input_processor.py +12 -3
- pysdmx/io/json/fusion/messages/core.py +2 -0
- pysdmx/io/json/fusion/messages/report.py +13 -7
- pysdmx/io/json/gds/messages/__init__.py +35 -0
- pysdmx/io/json/gds/messages/agencies.py +41 -0
- pysdmx/io/json/gds/messages/catalog.py +79 -0
- pysdmx/io/json/gds/messages/sdmx_api.py +23 -0
- pysdmx/io/json/gds/messages/services.py +49 -0
- pysdmx/io/json/gds/messages/urn_resolver.py +43 -0
- pysdmx/io/json/gds/reader/__init__.py +12 -0
- pysdmx/io/json/sdmxjson2/messages/__init__.py +12 -4
- pysdmx/io/json/sdmxjson2/messages/agency.py +72 -0
- pysdmx/io/json/sdmxjson2/messages/category.py +22 -29
- pysdmx/io/json/sdmxjson2/messages/code.py +68 -64
- pysdmx/io/json/sdmxjson2/messages/concept.py +9 -18
- pysdmx/io/json/sdmxjson2/messages/constraint.py +2 -13
- pysdmx/io/json/sdmxjson2/messages/core.py +113 -21
- pysdmx/io/json/sdmxjson2/messages/dataflow.py +51 -21
- pysdmx/io/json/sdmxjson2/messages/dsd.py +110 -36
- pysdmx/io/json/sdmxjson2/messages/map.py +61 -49
- pysdmx/io/json/sdmxjson2/messages/pa.py +9 -17
- pysdmx/io/json/sdmxjson2/messages/provider.py +88 -0
- pysdmx/io/json/sdmxjson2/messages/report.py +84 -14
- pysdmx/io/json/sdmxjson2/messages/schema.py +14 -5
- pysdmx/io/json/sdmxjson2/messages/structure.py +105 -36
- pysdmx/io/json/sdmxjson2/messages/vtl.py +42 -96
- pysdmx/io/pd.py +2 -9
- pysdmx/io/reader.py +72 -27
- pysdmx/io/serde.py +11 -0
- pysdmx/io/writer.py +134 -0
- pysdmx/io/xml/{sdmx21/reader/__data_aux.py → __data_aux.py} +9 -2
- pysdmx/io/xml/{sdmx21/reader/__parse_xml.py → __parse_xml.py} +30 -6
- pysdmx/io/xml/__ss_aux_reader.py +96 -0
- pysdmx/io/xml/__structure_aux_reader.py +1174 -0
- pysdmx/io/xml/__structure_aux_writer.py +1233 -0
- pysdmx/io/xml/{sdmx21/__tokens.py → __tokens.py} +33 -1
- pysdmx/io/xml/{sdmx21/writer/__write_aux.py → __write_aux.py} +129 -37
- pysdmx/io/xml/{sdmx21/writer/__write_data_aux.py → __write_data_aux.py} +1 -1
- pysdmx/io/xml/__write_structure_specific_aux.py +254 -0
- pysdmx/io/xml/{sdmx21/reader/doc_validation.py → doc_validation.py} +10 -2
- pysdmx/io/xml/{sdmx21/reader/header.py → header.py} +11 -3
- pysdmx/io/xml/sdmx21/reader/error.py +2 -2
- pysdmx/io/xml/sdmx21/reader/generic.py +12 -8
- pysdmx/io/xml/sdmx21/reader/structure.py +5 -840
- pysdmx/io/xml/sdmx21/reader/structure_specific.py +13 -97
- pysdmx/io/xml/sdmx21/reader/submission.py +2 -2
- pysdmx/io/xml/sdmx21/writer/error.py +1 -1
- pysdmx/io/xml/sdmx21/writer/generic.py +13 -7
- pysdmx/io/xml/sdmx21/writer/structure.py +16 -828
- pysdmx/io/xml/sdmx21/writer/structure_specific.py +13 -238
- pysdmx/io/xml/sdmx30/__init__.py +1 -0
- pysdmx/io/xml/sdmx30/reader/__init__.py +1 -0
- pysdmx/io/xml/sdmx30/reader/structure.py +39 -0
- pysdmx/io/xml/sdmx30/reader/structure_specific.py +39 -0
- pysdmx/io/xml/sdmx30/writer/__init__.py +1 -0
- pysdmx/io/xml/sdmx30/writer/structure.py +67 -0
- pysdmx/io/xml/sdmx30/writer/structure_specific.py +108 -0
- pysdmx/model/__base.py +99 -34
- pysdmx/model/__init__.py +4 -0
- pysdmx/model/category.py +20 -0
- pysdmx/model/code.py +29 -8
- pysdmx/model/concept.py +52 -11
- pysdmx/model/dataflow.py +117 -33
- pysdmx/model/dataset.py +66 -14
- pysdmx/model/gds.py +161 -0
- pysdmx/model/map.py +51 -8
- pysdmx/model/message.py +235 -55
- pysdmx/model/metadata.py +79 -16
- pysdmx/model/submission.py +12 -7
- pysdmx/model/vtl.py +30 -13
- pysdmx/toolkit/__init__.py +1 -1
- pysdmx/toolkit/pd/__init__.py +85 -0
- pysdmx/toolkit/vtl/__init__.py +2 -1
- pysdmx/toolkit/vtl/_validations.py +1 -1
- pysdmx/toolkit/vtl/{generate_vtl_script.py → script_generation.py} +30 -4
- pysdmx/toolkit/vtl/validation.py +119 -0
- pysdmx/util/_model_utils.py +1 -1
- pysdmx-1.4.0rc1.dist-info/METADATA +119 -0
- pysdmx-1.4.0rc1.dist-info/RECORD +140 -0
- pysdmx/io/json/sdmxjson2/messages/org.py +0 -140
- pysdmx/toolkit/vtl/model_validations.py +0 -50
- pysdmx-1.3.0.dist-info/METADATA +0 -76
- pysdmx-1.3.0.dist-info/RECORD +0 -116
- /pysdmx/io/xml/{sdmx21/writer/config.py → config.py} +0 -0
- {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/LICENSE +0 -0
- {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/WHEEL +0 -0
|
@@ -1,258 +1,29 @@
|
|
|
1
1
|
# mypy: disable-error-code="union-attr"
|
|
2
2
|
"""Module for writing SDMX-ML 2.1 Structure Specific data messages."""
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
import pandas as pd
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional, Sequence, Union
|
|
7
6
|
|
|
8
7
|
from pysdmx.io.format import Format
|
|
9
8
|
from pysdmx.io.pd import PandasDataset
|
|
10
|
-
from pysdmx.io.xml.
|
|
11
|
-
ABBR_MSG,
|
|
12
|
-
ALL_DIM,
|
|
13
|
-
__escape_xml,
|
|
9
|
+
from pysdmx.io.xml.__write_aux import (
|
|
14
10
|
__write_header,
|
|
15
11
|
create_namespaces,
|
|
16
12
|
get_end_message,
|
|
17
|
-
get_structure,
|
|
18
13
|
)
|
|
19
|
-
from pysdmx.io.xml.
|
|
14
|
+
from pysdmx.io.xml.__write_data_aux import (
|
|
20
15
|
check_content_dataset,
|
|
21
16
|
check_dimension_at_observation,
|
|
22
|
-
get_codes,
|
|
23
|
-
writing_validation,
|
|
24
17
|
)
|
|
25
|
-
from pysdmx.io.xml.
|
|
18
|
+
from pysdmx.io.xml.__write_structure_specific_aux import (
|
|
19
|
+
__write_data_structure_specific,
|
|
20
|
+
)
|
|
26
21
|
from pysdmx.model.message import Header
|
|
27
|
-
from pysdmx.util import parse_short_urn
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def __memory_optimization_writing(
|
|
31
|
-
dataset: PandasDataset, prettyprint: bool
|
|
32
|
-
) -> str:
|
|
33
|
-
"""Memory optimization for writing data."""
|
|
34
|
-
outfile = ""
|
|
35
|
-
length_ = len(dataset.data)
|
|
36
|
-
if len(dataset.data) > CHUNKSIZE:
|
|
37
|
-
previous = 0
|
|
38
|
-
next_ = CHUNKSIZE
|
|
39
|
-
while previous <= length_:
|
|
40
|
-
# Sliding a window for efficient access to the data
|
|
41
|
-
# and avoid memory issues
|
|
42
|
-
outfile += __obs_processing(
|
|
43
|
-
dataset.data.iloc[previous:next_], prettyprint
|
|
44
|
-
)
|
|
45
|
-
previous = next_
|
|
46
|
-
next_ += CHUNKSIZE
|
|
47
|
-
|
|
48
|
-
if next_ >= length_:
|
|
49
|
-
outfile += __obs_processing(
|
|
50
|
-
dataset.data.iloc[previous:], prettyprint
|
|
51
|
-
)
|
|
52
|
-
previous = next_
|
|
53
|
-
else:
|
|
54
|
-
outfile += __obs_processing(dataset.data, prettyprint)
|
|
55
|
-
|
|
56
|
-
return outfile
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def __write_data_structure_specific(
|
|
60
|
-
datasets: Dict[str, PandasDataset],
|
|
61
|
-
dim_mapping: Dict[str, str],
|
|
62
|
-
prettyprint: bool = True,
|
|
63
|
-
) -> str:
|
|
64
|
-
"""Write data to SDMX-ML 2.1 Structure-Specific format.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
datasets: dict. Datasets to be written.
|
|
68
|
-
dim_mapping: dict. URN-DimensionAtObservation mapping.
|
|
69
|
-
prettyprint: bool. Prettyprint or not.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
The data in SDMX-ML 2.1 Structure-Specific format, as string.
|
|
73
|
-
"""
|
|
74
|
-
outfile = ""
|
|
75
|
-
|
|
76
|
-
for i, (short_urn, dataset) in enumerate(datasets.items()):
|
|
77
|
-
dataset.data = dataset.data.fillna("").astype(str)
|
|
78
|
-
outfile += __write_data_single_dataset(
|
|
79
|
-
dataset=dataset,
|
|
80
|
-
prettyprint=prettyprint,
|
|
81
|
-
count=i + 1,
|
|
82
|
-
dim=dim_mapping[short_urn],
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
return outfile
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def __write_data_single_dataset(
|
|
89
|
-
dataset: PandasDataset,
|
|
90
|
-
prettyprint: bool = True,
|
|
91
|
-
count: int = 1,
|
|
92
|
-
dim: str = ALL_DIM,
|
|
93
|
-
) -> str:
|
|
94
|
-
"""Write data to SDMX-ML 2.1 Structure-Specific format.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
dataset: PandasDataset. Dataset to be written.
|
|
98
|
-
prettyprint: bool. Prettyprint or not.
|
|
99
|
-
count: int. Count for namespace.
|
|
100
|
-
dim: str. Dimension to be written.
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
The data in SDMX-ML 2.1 Structure-Specific format, as string.
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
def __remove_optional_attributes_empty_data(str_to_check: str) -> str:
|
|
107
|
-
"""This function removes data when optional attributes are found."""
|
|
108
|
-
for att in dataset.structure.components.attributes:
|
|
109
|
-
if not att.required:
|
|
110
|
-
str_to_check = str_to_check.replace(f"{att.id}='' ", "")
|
|
111
|
-
str_to_check = str_to_check.replace(f'{att.id}="" ', "")
|
|
112
|
-
return str_to_check
|
|
113
|
-
|
|
114
|
-
outfile = ""
|
|
115
|
-
structure_urn = get_structure(dataset)
|
|
116
|
-
id_structure = parse_short_urn(structure_urn).id
|
|
117
|
-
sdmx_type = parse_short_urn(structure_urn).id
|
|
118
|
-
|
|
119
|
-
nl = "\n" if prettyprint else ""
|
|
120
|
-
child1 = "\t" if prettyprint else ""
|
|
121
|
-
|
|
122
|
-
attached_attributes_str = ""
|
|
123
|
-
for k, v in dataset.attributes.items():
|
|
124
|
-
attached_attributes_str += f"{k}={str(v)!r} "
|
|
125
|
-
|
|
126
|
-
# Datasets
|
|
127
|
-
outfile += (
|
|
128
|
-
f"{nl}{child1}<{ABBR_MSG}:DataSet {attached_attributes_str}"
|
|
129
|
-
f"ss:structureRef={id_structure!r} "
|
|
130
|
-
f'xsi:type="ns{count}:DataSetType" '
|
|
131
|
-
f'ss:dataScope="{sdmx_type}" '
|
|
132
|
-
f'action="{dataset.action.value}">{nl}'
|
|
133
|
-
)
|
|
134
|
-
data = ""
|
|
135
|
-
if dim == ALL_DIM:
|
|
136
|
-
data += __memory_optimization_writing(dataset, prettyprint)
|
|
137
|
-
else:
|
|
138
|
-
writing_validation(dataset)
|
|
139
|
-
series_codes, obs_codes = get_codes(
|
|
140
|
-
dimension_code=dim,
|
|
141
|
-
structure=dataset.structure, # type: ignore[arg-type]
|
|
142
|
-
data=dataset.data,
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
data += __series_processing(
|
|
146
|
-
data=dataset.data,
|
|
147
|
-
series_codes=series_codes,
|
|
148
|
-
obs_codes=obs_codes,
|
|
149
|
-
prettyprint=prettyprint,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
# Remove optional attributes empty data
|
|
153
|
-
data = __remove_optional_attributes_empty_data(data)
|
|
154
|
-
|
|
155
|
-
# Adding to outfile
|
|
156
|
-
outfile += data
|
|
157
|
-
|
|
158
|
-
outfile += f"{child1}</{ABBR_MSG}:DataSet>"
|
|
159
|
-
|
|
160
|
-
return outfile.replace("'", '"')
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def __obs_processing(data: pd.DataFrame, prettyprint: bool = True) -> str:
|
|
164
|
-
def __format_obs_str(element: Dict[str, Any]) -> str:
|
|
165
|
-
"""Formats the observation as key=value pairs."""
|
|
166
|
-
nl = "\n" if prettyprint else ""
|
|
167
|
-
child2 = "\t\t" if prettyprint else ""
|
|
168
|
-
|
|
169
|
-
out = f"{child2}<Obs "
|
|
170
|
-
|
|
171
|
-
for k, v in element.items():
|
|
172
|
-
out += f"{k}={__escape_xml(str(v))!r} "
|
|
173
|
-
|
|
174
|
-
out += f"/>{nl}"
|
|
175
|
-
|
|
176
|
-
return out
|
|
177
|
-
|
|
178
|
-
parser = lambda x: __format_obs_str(x) # noqa: E731
|
|
179
|
-
|
|
180
|
-
iterator = map(parser, data.to_dict(orient="records"))
|
|
181
|
-
|
|
182
|
-
return "".join(iterator)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def __series_processing(
|
|
186
|
-
data: pd.DataFrame,
|
|
187
|
-
series_codes: List[str],
|
|
188
|
-
obs_codes: List[str],
|
|
189
|
-
prettyprint: bool = True,
|
|
190
|
-
) -> str:
|
|
191
|
-
def __generate_series_str() -> str:
|
|
192
|
-
"""Generates the series item with its observations."""
|
|
193
|
-
out_list: List[str] = []
|
|
194
|
-
data.groupby(by=series_codes)[obs_codes].apply(
|
|
195
|
-
lambda x: __format_dict_ser(out_list, x)
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
return "".join(out_list)
|
|
199
|
-
|
|
200
|
-
def __format_dict_ser(
|
|
201
|
-
output_list: List[str],
|
|
202
|
-
obs: Any,
|
|
203
|
-
) -> Any:
|
|
204
|
-
"""Formats the series as key=value pairs."""
|
|
205
|
-
# Creating the observation dict,
|
|
206
|
-
# we always get the first element on Series
|
|
207
|
-
# as we are grouping by it
|
|
208
|
-
data_dict["Series"][0]["Obs"] = obs.to_dict(orient="records")
|
|
209
|
-
output_list.append(__format_ser_str(data_dict["Series"][0]))
|
|
210
|
-
# We remove the data for series as it is no longer necessary
|
|
211
|
-
del data_dict["Series"][0]
|
|
212
|
-
|
|
213
|
-
def __format_ser_str(data_info: Dict[Any, Any]) -> str:
|
|
214
|
-
"""Formats the series as key=value pairs."""
|
|
215
|
-
child2 = "\t\t" if prettyprint else ""
|
|
216
|
-
child3 = "\t\t\t" if prettyprint else ""
|
|
217
|
-
nl = "\n" if prettyprint else ""
|
|
218
|
-
|
|
219
|
-
out_element = f"{child2}<Series "
|
|
220
|
-
|
|
221
|
-
for k, v in data_info.items():
|
|
222
|
-
if k != "Obs":
|
|
223
|
-
out_element += f"{k}={__escape_xml(str(v))!r} "
|
|
224
|
-
|
|
225
|
-
out_element += f">{nl}"
|
|
226
|
-
|
|
227
|
-
for obs in data_info["Obs"]:
|
|
228
|
-
out_element += f"{child3}<Obs "
|
|
229
|
-
|
|
230
|
-
for k, v in obs.items():
|
|
231
|
-
out_element += f"{k}={__escape_xml(str(v))!r} "
|
|
232
|
-
|
|
233
|
-
out_element += f"/>{nl}"
|
|
234
|
-
|
|
235
|
-
out_element += f"{child2}</Series>{nl}"
|
|
236
|
-
|
|
237
|
-
return out_element
|
|
238
|
-
|
|
239
|
-
# Getting each datapoint from data and creating dict
|
|
240
|
-
data = data.sort_values(series_codes, axis=0)
|
|
241
|
-
data_dict = {
|
|
242
|
-
"Series": data[series_codes]
|
|
243
|
-
.drop_duplicates()
|
|
244
|
-
.reset_index(drop=True)
|
|
245
|
-
.to_dict(orient="records")
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
out = __generate_series_str()
|
|
249
|
-
|
|
250
|
-
return out
|
|
251
22
|
|
|
252
23
|
|
|
253
24
|
def write(
|
|
254
25
|
datasets: Sequence[PandasDataset],
|
|
255
|
-
output_path: str =
|
|
26
|
+
output_path: Optional[Union[str, Path]] = None,
|
|
256
27
|
prettyprint: bool = True,
|
|
257
28
|
header: Optional[Header] = None,
|
|
258
29
|
dimension_at_observation: Optional[Dict[str, str]] = None,
|
|
@@ -309,7 +80,11 @@ def write(
|
|
|
309
80
|
|
|
310
81
|
outfile += get_end_message(type_, prettyprint)
|
|
311
82
|
|
|
312
|
-
|
|
83
|
+
output_path = (
|
|
84
|
+
str(output_path) if isinstance(output_path, Path) else output_path
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if output_path is None or output_path == "":
|
|
313
88
|
return outfile
|
|
314
89
|
|
|
315
90
|
with open(output_path, "w", encoding="UTF-8", errors="replace") as f:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDMX 3.0 XML reader and writer."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDMX 3.0 XML reader module."""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Parsers for reading metadata."""
|
|
2
|
+
|
|
3
|
+
from typing import Sequence, Union
|
|
4
|
+
|
|
5
|
+
from pysdmx.errors import Invalid
|
|
6
|
+
from pysdmx.io.xml.__parse_xml import parse_xml
|
|
7
|
+
from pysdmx.io.xml.__structure_aux_reader import StructureParser
|
|
8
|
+
from pysdmx.io.xml.__tokens import (
|
|
9
|
+
STRUCTURE,
|
|
10
|
+
STRUCTURES,
|
|
11
|
+
)
|
|
12
|
+
from pysdmx.model.__base import (
|
|
13
|
+
ItemScheme,
|
|
14
|
+
)
|
|
15
|
+
from pysdmx.model.dataflow import (
|
|
16
|
+
Dataflow,
|
|
17
|
+
DataStructureDefinition,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def read(
|
|
22
|
+
input_str: str,
|
|
23
|
+
validate: bool = True,
|
|
24
|
+
) -> Sequence[Union[ItemScheme, DataStructureDefinition, Dataflow]]:
|
|
25
|
+
"""Reads an SDMX-ML 3.0 Structure data and returns the structures.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
input_str: SDMX-ML structure message to read.
|
|
29
|
+
validate: If True, the XML data will be validated against the XSD.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
dict: Dictionary with the parsed structures.
|
|
33
|
+
"""
|
|
34
|
+
dict_info = parse_xml(input_str, validate)
|
|
35
|
+
if STRUCTURE not in dict_info:
|
|
36
|
+
raise Invalid("This SDMX document is not SDMX-ML 3.0 Structure.")
|
|
37
|
+
return StructureParser(is_sdmx_30=True).format_structures(
|
|
38
|
+
dict_info[STRUCTURE][STRUCTURES]
|
|
39
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""SDMX XML 3.0 StructureSpecificData reader module."""
|
|
2
|
+
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
from pysdmx.errors import Invalid
|
|
6
|
+
from pysdmx.io.pd import PandasDataset
|
|
7
|
+
from pysdmx.io.xml.__data_aux import (
|
|
8
|
+
get_data_objects,
|
|
9
|
+
)
|
|
10
|
+
from pysdmx.io.xml.__parse_xml import parse_xml
|
|
11
|
+
from pysdmx.io.xml.__ss_aux_reader import (
|
|
12
|
+
_parse_structure_specific_data,
|
|
13
|
+
)
|
|
14
|
+
from pysdmx.io.xml.__tokens import (
|
|
15
|
+
STR_REF,
|
|
16
|
+
STR_SPE,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def read(input_str: str, validate: bool = True) -> Sequence[PandasDataset]:
|
|
21
|
+
"""Reads an SDMX-ML 3.0 and returns a Sequence of Datasets.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
input_str: SDMX-ML data to read.
|
|
25
|
+
validate: If True, the XML data will be validated against the XSD.
|
|
26
|
+
"""
|
|
27
|
+
dict_info = parse_xml(input_str, validate=validate)
|
|
28
|
+
if STR_SPE not in dict_info:
|
|
29
|
+
raise Invalid(
|
|
30
|
+
"This SDMX document is not an SDMX-ML StructureSpecificData."
|
|
31
|
+
)
|
|
32
|
+
dataset_info, str_info = get_data_objects(dict_info[STR_SPE])
|
|
33
|
+
datasets = []
|
|
34
|
+
for dataset in dataset_info:
|
|
35
|
+
ds = _parse_structure_specific_data(
|
|
36
|
+
dataset, str_info[dataset[STR_REF]]
|
|
37
|
+
)
|
|
38
|
+
datasets.append(ds)
|
|
39
|
+
return datasets
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDMX 3.0 writer package."""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Module for writing metadata to XML files."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Optional, Sequence, Union
|
|
5
|
+
|
|
6
|
+
from pysdmx.io.format import Format
|
|
7
|
+
from pysdmx.io.xml.__structure_aux_writer import (
|
|
8
|
+
STR_DICT_TYPE_LIST_30,
|
|
9
|
+
__write_structures,
|
|
10
|
+
)
|
|
11
|
+
from pysdmx.io.xml.__write_aux import (
|
|
12
|
+
__write_header,
|
|
13
|
+
create_namespaces,
|
|
14
|
+
get_end_message,
|
|
15
|
+
)
|
|
16
|
+
from pysdmx.model.__base import MaintainableArtefact
|
|
17
|
+
from pysdmx.model.message import Header
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write(
|
|
21
|
+
structures: Sequence[MaintainableArtefact],
|
|
22
|
+
output_path: Optional[Union[str, Path]] = None,
|
|
23
|
+
prettyprint: bool = True,
|
|
24
|
+
header: Optional[Header] = None,
|
|
25
|
+
) -> Optional[str]:
|
|
26
|
+
"""This function writes a SDMX-ML file from the Message Content.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
structures: The content to be written
|
|
30
|
+
output_path: The path to save the file
|
|
31
|
+
prettyprint: Prettyprint or not
|
|
32
|
+
header: The header to be used (generated if None)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The XML string if output_path is empty, None otherwise
|
|
36
|
+
"""
|
|
37
|
+
type_ = Format.STRUCTURE_SDMX_ML_3_0
|
|
38
|
+
elements = {structure.short_urn: structure for structure in structures}
|
|
39
|
+
if header is None:
|
|
40
|
+
header = Header()
|
|
41
|
+
|
|
42
|
+
content: Dict[str, Dict[str, MaintainableArtefact]] = {}
|
|
43
|
+
for urn, element in elements.items():
|
|
44
|
+
list_ = STR_DICT_TYPE_LIST_30[type(element)]
|
|
45
|
+
if list_ not in content:
|
|
46
|
+
content[list_] = {}
|
|
47
|
+
content[list_][urn] = element
|
|
48
|
+
|
|
49
|
+
# Generating the initial tag with namespaces
|
|
50
|
+
outfile = create_namespaces(type_, prettyprint=prettyprint)
|
|
51
|
+
# Generating the header
|
|
52
|
+
outfile += __write_header(header, prettyprint, data_message=False)
|
|
53
|
+
# Writing the content
|
|
54
|
+
outfile += __write_structures(content, prettyprint, references_30=True)
|
|
55
|
+
|
|
56
|
+
outfile += get_end_message(type_, prettyprint)
|
|
57
|
+
|
|
58
|
+
output_path = (
|
|
59
|
+
str(output_path) if isinstance(output_path, Path) else output_path
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if output_path is None or output_path == "":
|
|
63
|
+
return outfile
|
|
64
|
+
|
|
65
|
+
with open(output_path, "w", encoding="UTF-8", errors="replace") as f:
|
|
66
|
+
f.write(outfile)
|
|
67
|
+
return None
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# mypy: disable-error-code="union-attr"
|
|
2
|
+
"""Module for writing SDMX-ML 3.0 Structure Specific data messages."""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional, Sequence, Union
|
|
6
|
+
|
|
7
|
+
from pysdmx.io.format import Format
|
|
8
|
+
from pysdmx.io.pd import PandasDataset
|
|
9
|
+
from pysdmx.io.xml.__tokens import (
|
|
10
|
+
DSD_LOW,
|
|
11
|
+
PROV_AGREEMENT,
|
|
12
|
+
REGISTRY_LOW,
|
|
13
|
+
)
|
|
14
|
+
from pysdmx.io.xml.__write_aux import (
|
|
15
|
+
__write_header,
|
|
16
|
+
create_namespaces,
|
|
17
|
+
get_end_message,
|
|
18
|
+
)
|
|
19
|
+
from pysdmx.io.xml.__write_data_aux import (
|
|
20
|
+
check_content_dataset,
|
|
21
|
+
check_dimension_at_observation,
|
|
22
|
+
)
|
|
23
|
+
from pysdmx.io.xml.__write_structure_specific_aux import (
|
|
24
|
+
__write_data_structure_specific,
|
|
25
|
+
)
|
|
26
|
+
from pysdmx.model.message import Header
|
|
27
|
+
from pysdmx.util import parse_short_urn
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def write(
|
|
31
|
+
datasets: Sequence[PandasDataset],
|
|
32
|
+
output_path: Optional[Union[str, Path]] = None,
|
|
33
|
+
prettyprint: bool = True,
|
|
34
|
+
header: Optional[Header] = None,
|
|
35
|
+
dimension_at_observation: Optional[Dict[str, str]] = None,
|
|
36
|
+
) -> Optional[str]:
|
|
37
|
+
"""Write data to SDMX-ML 3.0 Structure Specific format.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
datasets: The datasets to be written.
|
|
41
|
+
output_path: The path to save the file.
|
|
42
|
+
prettyprint: Prettyprint or not.
|
|
43
|
+
header: The header to be used (generated if None).
|
|
44
|
+
dimension_at_observation:
|
|
45
|
+
The mapping between the dataset and the dimension at observation.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The XML string if path is empty, None otherwise.
|
|
49
|
+
"""
|
|
50
|
+
ss_namespaces = ""
|
|
51
|
+
type_ = Format.DATA_SDMX_ML_3_0
|
|
52
|
+
|
|
53
|
+
# Checking if we have datasets,
|
|
54
|
+
# we need to ensure we can write them correctly
|
|
55
|
+
check_content_dataset(datasets)
|
|
56
|
+
content = {dataset.short_urn: dataset for dataset in datasets}
|
|
57
|
+
|
|
58
|
+
if header is None:
|
|
59
|
+
header = Header()
|
|
60
|
+
|
|
61
|
+
# Checking the dimension at observation mapping
|
|
62
|
+
dim_mapping = check_dimension_at_observation(
|
|
63
|
+
content, dimension_at_observation
|
|
64
|
+
)
|
|
65
|
+
header.structure = dim_mapping
|
|
66
|
+
add_namespace_structure = True
|
|
67
|
+
for i, (short_urn, dimension) in enumerate(header.structure.items()):
|
|
68
|
+
reference = parse_short_urn(short_urn)
|
|
69
|
+
pre_urn = (
|
|
70
|
+
REGISTRY_LOW if reference.sdmx_type == PROV_AGREEMENT else DSD_LOW
|
|
71
|
+
)
|
|
72
|
+
ss_namespaces += (
|
|
73
|
+
f'xmlns:ns{i + 1}="urn:sdmx:org.sdmx'
|
|
74
|
+
f".infomodel.{pre_urn}.{short_urn}"
|
|
75
|
+
f':ObsLevelDim:{dimension}" '
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Generating the initial tag with namespaces
|
|
79
|
+
outfile = create_namespaces(type_, ss_namespaces, prettyprint)
|
|
80
|
+
# Generating the header
|
|
81
|
+
outfile += __write_header(
|
|
82
|
+
header,
|
|
83
|
+
prettyprint,
|
|
84
|
+
add_namespace_structure,
|
|
85
|
+
data_message=True,
|
|
86
|
+
references_30=True,
|
|
87
|
+
)
|
|
88
|
+
# Writing the content
|
|
89
|
+
outfile += __write_data_structure_specific(
|
|
90
|
+
datasets=content,
|
|
91
|
+
dim_mapping=dim_mapping,
|
|
92
|
+
prettyprint=prettyprint,
|
|
93
|
+
references_30=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
outfile += get_end_message(type_, prettyprint)
|
|
97
|
+
|
|
98
|
+
output_path = (
|
|
99
|
+
str(output_path) if isinstance(output_path, Path) else output_path
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if output_path is None or output_path == "":
|
|
103
|
+
return outfile
|
|
104
|
+
|
|
105
|
+
with open(output_path, "w", encoding="UTF-8", errors="replace") as f:
|
|
106
|
+
f.write(outfile)
|
|
107
|
+
|
|
108
|
+
return None
|