pysdmx 1.4.0rc1__py3-none-any.whl → 1.5.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/qb/service.py +3 -34
- pysdmx/io/format.py +13 -1
- pysdmx/io/input_processor.py +41 -12
- pysdmx/io/reader.py +33 -3
- pysdmx/io/writer.py +4 -0
- pysdmx/io/xml/__parse_xml.py +24 -1
- pysdmx/io/xml/__structure_aux_reader.py +94 -25
- pysdmx/io/xml/__structure_aux_writer.py +134 -43
- pysdmx/io/xml/__tokens.py +15 -3
- pysdmx/io/xml/__write_aux.py +33 -1
- pysdmx/io/xml/__write_data_aux.py +29 -4
- pysdmx/io/xml/__write_structure_specific_aux.py +42 -2
- pysdmx/io/xml/doc_validation.py +6 -2
- pysdmx/io/xml/sdmx21/reader/generic.py +29 -0
- pysdmx/io/xml/sdmx21/writer/generic.py +67 -1
- pysdmx/io/xml/sdmx31/__init__.py +1 -0
- pysdmx/io/xml/sdmx31/reader/__init__.py +1 -0
- pysdmx/io/xml/sdmx31/reader/structure.py +39 -0
- pysdmx/io/xml/sdmx31/reader/structure_specific.py +39 -0
- pysdmx/io/xml/sdmx31/writer/__init__.py +1 -0
- pysdmx/io/xml/sdmx31/writer/structure.py +67 -0
- pysdmx/io/xml/sdmx31/writer/structure_specific.py +108 -0
- pysdmx/model/code.py +19 -1
- pysdmx/model/dataflow.py +12 -0
- pysdmx/util/__init__.py +2 -0
- pysdmx/util/_model_utils.py +1 -0
- pysdmx/util/_net_utils.py +39 -0
- {pysdmx-1.4.0rc1.dist-info → pysdmx-1.5.0.dist-info}/METADATA +3 -3
- {pysdmx-1.4.0rc1.dist-info → pysdmx-1.5.0.dist-info}/RECORD +32 -24
- {pysdmx-1.4.0rc1.dist-info → pysdmx-1.5.0.dist-info}/LICENSE +0 -0
- {pysdmx-1.4.0rc1.dist-info → pysdmx-1.5.0.dist-info}/WHEEL +0 -0
|
@@ -193,7 +193,7 @@ def __write_data_single_dataset(
|
|
|
193
193
|
prettyprint=prettyprint,
|
|
194
194
|
)
|
|
195
195
|
else:
|
|
196
|
-
series_codes, obs_codes = get_codes(
|
|
196
|
+
series_codes, obs_codes, group_codes = get_codes(
|
|
197
197
|
dimension_code=dim,
|
|
198
198
|
structure=dataset.structure, # type: ignore[arg-type]
|
|
199
199
|
data=dataset.data,
|
|
@@ -205,6 +205,13 @@ def __write_data_single_dataset(
|
|
|
205
205
|
series_codes = [x for x in series_codes if x not in series_att_codes]
|
|
206
206
|
obs_codes = [x for x in obs_codes if x not in obs_att_codes]
|
|
207
207
|
|
|
208
|
+
if group_codes:
|
|
209
|
+
data += __group_processing(
|
|
210
|
+
data=dataset.data,
|
|
211
|
+
group_codes=group_codes,
|
|
212
|
+
prettyprint=prettyprint,
|
|
213
|
+
)
|
|
214
|
+
|
|
208
215
|
data += __series_processing(
|
|
209
216
|
data=dataset.data,
|
|
210
217
|
series_codes=series_codes,
|
|
@@ -269,6 +276,65 @@ def __obs_processing(
|
|
|
269
276
|
return "".join(iterator)
|
|
270
277
|
|
|
271
278
|
|
|
279
|
+
def __group_processing(
|
|
280
|
+
data: pd.DataFrame,
|
|
281
|
+
group_codes: List[Dict[str, Any]],
|
|
282
|
+
prettyprint: bool = True,
|
|
283
|
+
) -> str:
|
|
284
|
+
def __format_group_str(
|
|
285
|
+
data_info: Dict[Any, Any],
|
|
286
|
+
group_id: str,
|
|
287
|
+
dimensions: List[str],
|
|
288
|
+
attribute: str,
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Formats a generic SDMX group using __value()."""
|
|
291
|
+
child2 = "\t\t" if prettyprint else ""
|
|
292
|
+
nl = "\n" if prettyprint else ""
|
|
293
|
+
|
|
294
|
+
out_element = f'{child2}<{ABBR_GEN}:Group type="{group_id}">{nl}'
|
|
295
|
+
|
|
296
|
+
# GroupKey block
|
|
297
|
+
out_element += f"{child2}\t<{ABBR_GEN}:GroupKey>{nl}"
|
|
298
|
+
for dim in dimensions:
|
|
299
|
+
out_element += (
|
|
300
|
+
f"{child2}\t\t{__value(dim, data_info.get(dim, ''))}{nl}"
|
|
301
|
+
)
|
|
302
|
+
out_element += f"{child2}\t</{ABBR_GEN}:GroupKey>{nl}"
|
|
303
|
+
|
|
304
|
+
# Attributes block
|
|
305
|
+
out_element += f"{child2}\t<{ABBR_GEN}:Attributes>{nl}"
|
|
306
|
+
out_element += (
|
|
307
|
+
f"{child2}\t\t{__value(attribute, data_info.get(attribute, ''))}"
|
|
308
|
+
f"{nl}"
|
|
309
|
+
)
|
|
310
|
+
out_element += f"{child2}\t</{ABBR_GEN}:Attributes>{nl}"
|
|
311
|
+
|
|
312
|
+
out_element += f"{child2}</{ABBR_GEN}:Group>{nl}"
|
|
313
|
+
return out_element
|
|
314
|
+
|
|
315
|
+
out_list: List[str] = []
|
|
316
|
+
|
|
317
|
+
for group in group_codes:
|
|
318
|
+
group_id = group["group_id"]
|
|
319
|
+
dimensions = group["dimensions"]
|
|
320
|
+
attribute = group["attribute"]
|
|
321
|
+
group_keys = dimensions + [attribute]
|
|
322
|
+
|
|
323
|
+
grouped_data = (
|
|
324
|
+
data[group_keys]
|
|
325
|
+
.drop_duplicates()
|
|
326
|
+
.reset_index(drop=True)
|
|
327
|
+
.to_dict(orient="records")
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
for record in grouped_data:
|
|
331
|
+
out_list.append(
|
|
332
|
+
__format_group_str(record, group_id, dimensions, attribute)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return "".join(out_list)
|
|
336
|
+
|
|
337
|
+
|
|
272
338
|
def __series_processing(
|
|
273
339
|
data: pd.DataFrame,
|
|
274
340
|
series_codes: List[str],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDMX 3.1 XML reader and writer."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDMX 3.1 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.1 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.1 Structure.")
|
|
37
|
+
return StructureParser(is_sdmx_30=True).format_structures(
|
|
38
|
+
dict_info[STRUCTURE][STRUCTURES]
|
|
39
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""SDMX XML 3.1 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.1 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.1 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_1
|
|
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.1 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.1 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_1
|
|
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
|
pysdmx/model/code.py
CHANGED
|
@@ -21,7 +21,7 @@ from typing import Iterator, Literal, Optional, Sequence, Union
|
|
|
21
21
|
|
|
22
22
|
from msgspec import Struct
|
|
23
23
|
|
|
24
|
-
from pysdmx.model.__base import Item, ItemScheme, MaintainableArtefact
|
|
24
|
+
from pysdmx.model.__base import Agency, Item, ItemScheme, MaintainableArtefact
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class Code(Item, frozen=True, omit_defaults=True):
|
|
@@ -74,6 +74,24 @@ class Codelist(ItemScheme, frozen=True, omit_defaults=True, tag=True):
|
|
|
74
74
|
"""Extract the items in the Codelist."""
|
|
75
75
|
return self.items
|
|
76
76
|
|
|
77
|
+
@property
|
|
78
|
+
def short_urn(self) -> str:
|
|
79
|
+
"""Returns the short URN for Codelist.
|
|
80
|
+
|
|
81
|
+
A short URN follows the syntax: Type=Agency:Id(Version). For example:
|
|
82
|
+
Codelist=SDMX:CL_FREQ(1.0)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The short URN for the Codelist .
|
|
86
|
+
"""
|
|
87
|
+
agency = (
|
|
88
|
+
self.agency.id if isinstance(self.agency, Agency) else self.agency
|
|
89
|
+
)
|
|
90
|
+
# Value lists are represented as Codelist, but their short URN uses
|
|
91
|
+
# ValueList instead of Codelist.
|
|
92
|
+
typ = "ValueList" if self.sdmx_type == "valuelist" else "Codelist"
|
|
93
|
+
return f"{typ}={agency}:{self.id}({self.version})"
|
|
94
|
+
|
|
77
95
|
def __iter__(self) -> Iterator[Code]:
|
|
78
96
|
"""Return an iterator over the list of codes."""
|
|
79
97
|
yield from self.codes
|
pysdmx/model/dataflow.py
CHANGED
|
@@ -18,6 +18,7 @@ from pysdmx.errors import Invalid
|
|
|
18
18
|
from pysdmx.model.__base import (
|
|
19
19
|
Agency,
|
|
20
20
|
DataProvider,
|
|
21
|
+
IdentifiableArtefact,
|
|
21
22
|
ItemReference,
|
|
22
23
|
MaintainableArtefact,
|
|
23
24
|
)
|
|
@@ -231,6 +232,14 @@ class Component(
|
|
|
231
232
|
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
232
233
|
|
|
233
234
|
|
|
235
|
+
class GroupDimension(
|
|
236
|
+
IdentifiableArtefact, frozen=True, omit_defaults=True, kw_only=True
|
|
237
|
+
):
|
|
238
|
+
"""A group of dimensions that can be used to identify a group."""
|
|
239
|
+
|
|
240
|
+
dimensions: Sequence[str]
|
|
241
|
+
|
|
242
|
+
|
|
234
243
|
class Components(UserList[Component]):
|
|
235
244
|
"""A collection of components describing the data."""
|
|
236
245
|
|
|
@@ -463,6 +472,7 @@ class Schema(Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True):
|
|
|
463
472
|
version: str = "1.0"
|
|
464
473
|
artefacts: Sequence[str] = ()
|
|
465
474
|
generated: datetime = datetime.now(timezone.utc)
|
|
475
|
+
groups: Optional[Sequence[GroupDimension]] = None
|
|
466
476
|
|
|
467
477
|
def __str__(self) -> str:
|
|
468
478
|
"""Custom string representation without the class name."""
|
|
@@ -528,6 +538,7 @@ class DataStructureDefinition(MaintainableArtefact, frozen=True, kw_only=True):
|
|
|
528
538
|
"""
|
|
529
539
|
|
|
530
540
|
components: Components
|
|
541
|
+
groups: Optional[Sequence[GroupDimension]] = None
|
|
531
542
|
evolving_structure: bool = False
|
|
532
543
|
|
|
533
544
|
def __extract_artefacts(self) -> Sequence[str]:
|
|
@@ -557,6 +568,7 @@ class DataStructureDefinition(MaintainableArtefact, frozen=True, kw_only=True):
|
|
|
557
568
|
else self.agency
|
|
558
569
|
),
|
|
559
570
|
id=self.id,
|
|
571
|
+
groups=self.groups,
|
|
560
572
|
components=self.components,
|
|
561
573
|
version=self.version,
|
|
562
574
|
artefacts=self.__extract_artefacts(),
|
pysdmx/util/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Any, Sequence, Union
|
|
|
6
6
|
from pysdmx.errors import Invalid, NotFound
|
|
7
7
|
from pysdmx.model import Agency, ItemReference, Reference
|
|
8
8
|
from pysdmx.util._date_pattern_map import convert_dpm
|
|
9
|
+
from pysdmx.util._net_utils import map_httpx_errors
|
|
9
10
|
|
|
10
11
|
NF = "Not found"
|
|
11
12
|
|
|
@@ -134,4 +135,5 @@ __all__ = [
|
|
|
134
135
|
"parse_short_item_urn",
|
|
135
136
|
"ItemReference",
|
|
136
137
|
"Reference",
|
|
138
|
+
"map_httpx_errors",
|
|
137
139
|
]
|
pysdmx/util/_model_utils.py
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import NoReturn, Union
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from pysdmx import errors
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def map_httpx_errors(
|
|
9
|
+
e: Union[httpx.RequestError, httpx.HTTPStatusError],
|
|
10
|
+
) -> NoReturn:
|
|
11
|
+
"""Map httpx errors to pysdmx errors."""
|
|
12
|
+
q = e.request.url
|
|
13
|
+
if isinstance(e, httpx.HTTPStatusError):
|
|
14
|
+
s = e.response.status_code
|
|
15
|
+
t = e.response.text
|
|
16
|
+
if s == 404:
|
|
17
|
+
msg = (
|
|
18
|
+
"The requested resource(s) could not be found in the "
|
|
19
|
+
f"targeted service. The query was `{q}`"
|
|
20
|
+
)
|
|
21
|
+
raise errors.NotFound("Not found", msg) from e
|
|
22
|
+
elif s < 500:
|
|
23
|
+
msg = (
|
|
24
|
+
f"The query returned a {s} error code. The query "
|
|
25
|
+
f"was `{q}`. The error message was: `{t}`."
|
|
26
|
+
)
|
|
27
|
+
raise errors.Invalid(f"Client error {s}", msg) from e
|
|
28
|
+
else:
|
|
29
|
+
msg = (
|
|
30
|
+
f"The service returned a {s} error code. The query "
|
|
31
|
+
f"was `{q}`. The error message was: `{t}`."
|
|
32
|
+
)
|
|
33
|
+
raise errors.InternalError(f"Service error {s}", msg) from e
|
|
34
|
+
else:
|
|
35
|
+
msg = (
|
|
36
|
+
f"There was an issue connecting to the targeted service. "
|
|
37
|
+
f"The query was `{q}`. The error message was: `{e}`."
|
|
38
|
+
)
|
|
39
|
+
raise errors.Unavailable("Connection error", msg) from e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pysdmx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Your opinionated Python SDMX library
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: sdmx,data discovery,data retrieval,metadata,fmr
|
|
@@ -26,8 +26,8 @@ Requires-Dist: pandas (>=2.1.4) ; extra == "data"
|
|
|
26
26
|
Requires-Dist: parsy (>=2.1)
|
|
27
27
|
Requires-Dist: python-dateutil (>=2.8.2) ; extra == "all"
|
|
28
28
|
Requires-Dist: python-dateutil (>=2.8.2) ; extra == "dc"
|
|
29
|
-
Requires-Dist: sdmxschemas (>=0.
|
|
30
|
-
Requires-Dist: sdmxschemas (>=0.
|
|
29
|
+
Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "all"
|
|
30
|
+
Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "xml"
|
|
31
31
|
Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "all"
|
|
32
32
|
Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "vtl"
|
|
33
33
|
Requires-Dist: xmltodict (>=0.13) ; extra == "all"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
pysdmx/__extras_check.py,sha256=I39OaM1lAPBnyzHVJ7kA_ZA_tMeDAQLr6ZKqEQ9MK1Q,1659
|
|
2
|
-
pysdmx/__init__.py,sha256=
|
|
2
|
+
pysdmx/__init__.py,sha256=5z10nwG-grwF4ljX4qnTW3mCl2fdff-9iqmtKljWHL0,67
|
|
3
3
|
pysdmx/api/__init__.py,sha256=8lRaF6kEO51ehl0fmW_pHLvkN_34TtEhqhr3oKo6E6g,26
|
|
4
4
|
pysdmx/api/dc/__init__.py,sha256=oPU32X8CRZy4T1to9mO5KMqMwxQsVI424dPqai-I8zI,121
|
|
5
5
|
pysdmx/api/dc/_api.py,sha256=poy1FYFXnF6maBGy5lpOodf32-7QQjH8PCBNDkuOXxQ,7747
|
|
@@ -19,7 +19,7 @@ pysdmx/api/qb/gds.py,sha256=Z2KhP6m09_oWI2CbYRhlTsx8VLC-_UZaQEOEqX94SOw,4975
|
|
|
19
19
|
pysdmx/api/qb/refmeta.py,sha256=rWhMcUkcQ0zYoiEVAVFOnykLLhMWFFZssFZh8z3kRng,10677
|
|
20
20
|
pysdmx/api/qb/registration.py,sha256=IURlmXcXQi8e-w5YXCgRNs07EQJZJ2bOdZb7M_k5iZ8,7132
|
|
21
21
|
pysdmx/api/qb/schema.py,sha256=f0V-fQl9GQ2YWhnZqUF_wogB2L9dRIdY_vT4XvYkqoI,5747
|
|
22
|
-
pysdmx/api/qb/service.py,sha256=
|
|
22
|
+
pysdmx/api/qb/service.py,sha256=ieLgOR27NNRD2P4FGhFmWVkJweGngAx-mWkTDfsVCyI,14001
|
|
23
23
|
pysdmx/api/qb/structure.py,sha256=NA4-Xlnj55xNLFlAC2qmaSRgKYbzFjQTKXA5G-SYlBo,17783
|
|
24
24
|
pysdmx/api/qb/util.py,sha256=Meysdjad-ZyE2WZ1xwMGYrVQPapCKG5QdT4LkdF5CSw,3651
|
|
25
25
|
pysdmx/errors.py,sha256=9bfujYykzfGMa1TuUOmH9QqghnZGOo556fvbKH2jFa8,3295
|
|
@@ -31,8 +31,8 @@ pysdmx/io/csv/sdmx10/writer/__init__.py,sha256=c7WvEufF8UBu6Lf417a4ujY8cQ7QOiQ9_
|
|
|
31
31
|
pysdmx/io/csv/sdmx20/__init__.py,sha256=mNfMJKSpmMfk4CbbHhjnBOTQ2IZFY15cNUhHtJPcs90,231
|
|
32
32
|
pysdmx/io/csv/sdmx20/reader/__init__.py,sha256=HSSUE3iL9g2Bzb_vKw97-Uw_3H3yUQxN8ssAyr1EJkI,4925
|
|
33
33
|
pysdmx/io/csv/sdmx20/writer/__init__.py,sha256=xqNg2L2BdCkKVIkMrBpkbR6wq2gZ3r1K7oZRkZ1aBoU,2062
|
|
34
|
-
pysdmx/io/format.py,sha256=
|
|
35
|
-
pysdmx/io/input_processor.py,sha256=
|
|
34
|
+
pysdmx/io/format.py,sha256=BJghvHaXNvsfqJCWvB8jhjGMaLPv7zi7N12O0qRwChc,4749
|
|
35
|
+
pysdmx/io/input_processor.py,sha256=MALwyWI26KoxIlRARgIsXxHvEHH9tZ9TTHExtYAPCk8,6406
|
|
36
36
|
pysdmx/io/json/fusion/messages/__init__.py,sha256=C0LAG6UfIZcQdlCv5d_D1ZNIcCq04RxZYLAp6tLDqwY,1573
|
|
37
37
|
pysdmx/io/json/fusion/messages/category.py,sha256=E9jrzXenEpqDNUhT1JJLYxty37PSGegRtx45mB3-COg,4589
|
|
38
38
|
pysdmx/io/json/fusion/messages/code.py,sha256=kB3RJiSA75G4IJYObYR0hdiXYcRs-vNMKqUeA6F4SSo,7529
|
|
@@ -73,33 +73,33 @@ pysdmx/io/json/sdmxjson2/messages/structure.py,sha256=WpQttjSdGK1AuWfR69gpe4E7HP
|
|
|
73
73
|
pysdmx/io/json/sdmxjson2/messages/vtl.py,sha256=UrcNP5p_R2R_bFrY-vbOKCGYXkD5N4yBAwZlCWetDMQ,14700
|
|
74
74
|
pysdmx/io/json/sdmxjson2/reader/__init__.py,sha256=f9vTKscFIAQpAKh3pZ4ZKlZSKyGfj1yaZS9w7S0_hIc,1264
|
|
75
75
|
pysdmx/io/pd.py,sha256=1C5UnAT25EADOpNsRBooEdWNaJEGdmcwh6_R9O5MOrc,464
|
|
76
|
-
pysdmx/io/reader.py,sha256=
|
|
76
|
+
pysdmx/io/reader.py,sha256=saAQ71aRKqze2XeOSV5xcSEB056h8OTKbvm_qwRSfuk,9695
|
|
77
77
|
pysdmx/io/serde.py,sha256=l8z9qulrUkRNZq63YOUqTTs7zpl1hdYZe3UAZW1aEpM,1062
|
|
78
|
-
pysdmx/io/writer.py,sha256=
|
|
78
|
+
pysdmx/io/writer.py,sha256=I7n0yW4c2Hu7eUSWbyHcmWd2VtPkiTUlVE4zTNmdxIg,4759
|
|
79
79
|
pysdmx/io/xml/__allowed_lxml_errors.py,sha256=PdIK2i6JwwGRh1Ogc5JA0hRySO7QXJKN-DI8EYtdjA0,225
|
|
80
80
|
pysdmx/io/xml/__data_aux.py,sha256=ztivjZANN1PJ63IvoMfx-5TA2AiPQTVPC_0YYHOT9Ns,4069
|
|
81
81
|
pysdmx/io/xml/__init__.py,sha256=tcUsSEiM3nBA7lDbZPwGZ7Vu_K9gQd8oliASUMTGjFE,105
|
|
82
|
-
pysdmx/io/xml/__parse_xml.py,sha256=
|
|
82
|
+
pysdmx/io/xml/__parse_xml.py,sha256=SITC7Yptq_qY_exetRez90lJCukH1WsKDMqlk7Q1RaY,2899
|
|
83
83
|
pysdmx/io/xml/__ss_aux_reader.py,sha256=Tfp7oSb_0-AVEVdu7YFtj-SlBuEKp7fpg3-SxCmOHW0,2868
|
|
84
|
-
pysdmx/io/xml/__structure_aux_reader.py,sha256=
|
|
85
|
-
pysdmx/io/xml/__structure_aux_writer.py,sha256=
|
|
86
|
-
pysdmx/io/xml/__tokens.py,sha256=
|
|
87
|
-
pysdmx/io/xml/__write_aux.py,sha256=
|
|
88
|
-
pysdmx/io/xml/__write_data_aux.py,sha256=
|
|
89
|
-
pysdmx/io/xml/__write_structure_specific_aux.py,sha256=
|
|
84
|
+
pysdmx/io/xml/__structure_aux_reader.py,sha256=guK6hEi2TN-6csc6JMeXbW4JDHKA6pKy6GoFzQ6FNJE,40276
|
|
85
|
+
pysdmx/io/xml/__structure_aux_writer.py,sha256=mXsURMFJuaku6Ac-7nWOrerxfPfTVtQxzBwjRgZeQu8,43588
|
|
86
|
+
pysdmx/io/xml/__tokens.py,sha256=Y_SlFWYIXolsgjHoFq7pm3TYlEu07CR9TmJKk2jHfQ0,6823
|
|
87
|
+
pysdmx/io/xml/__write_aux.py,sha256=KHZxEhGSjZPRW93OvniZ0kHmiAJtKJu-2aLtMhsKt90,15396
|
|
88
|
+
pysdmx/io/xml/__write_data_aux.py,sha256=DlFwR1KmVMJmwID_G7Tl9zasah9iiQKHuuHKJpd0huk,5052
|
|
89
|
+
pysdmx/io/xml/__write_structure_specific_aux.py,sha256=Z5CIiy30TxK8igzcnwcruhGdohFxJaFyYyCojMU8nnY,8825
|
|
90
90
|
pysdmx/io/xml/config.py,sha256=R24cczVkzkhjVLXpv-qfEm88W3_QTqVt2Qofi8IvJ5Y,93
|
|
91
|
-
pysdmx/io/xml/doc_validation.py,sha256=
|
|
91
|
+
pysdmx/io/xml/doc_validation.py,sha256=qS-riLo3fMNLBrsQWzizeVNePuYT9W8wp9q-xE3QXto,1842
|
|
92
92
|
pysdmx/io/xml/header.py,sha256=_XiruPAfMtr4wudCpiuhFXDvRgWITGQ9GZVG3lMvWcc,5535
|
|
93
93
|
pysdmx/io/xml/sdmx21/__init__.py,sha256=_rh_dz1d-LmUw4iVbsZQkTXANTJeR8ov8z_4jNRGhtg,38
|
|
94
94
|
pysdmx/io/xml/sdmx21/reader/__init__.py,sha256=2jMsySFbbq-So3ymqzKVi5gOPRwO0qasBBwntD03rTw,34
|
|
95
95
|
pysdmx/io/xml/sdmx21/reader/error.py,sha256=ySY3dBPcOaF_lm8yX1Khc92R5XgyD1u-djjiOrgZJP8,885
|
|
96
|
-
pysdmx/io/xml/sdmx21/reader/generic.py,sha256=
|
|
96
|
+
pysdmx/io/xml/sdmx21/reader/generic.py,sha256=IrdWLVsBsfa8lCG9jtzWc0WzsIemTUWEDLjiwu6P-NA,6086
|
|
97
97
|
pysdmx/io/xml/sdmx21/reader/structure.py,sha256=bR3rCfY4x5xxkcyLhDSI-6Vw7b52RJY2qCcqr5MBuD4,1088
|
|
98
98
|
pysdmx/io/xml/sdmx21/reader/structure_specific.py,sha256=S3-gLmaBFjBRIr25qQtlraonKm9XAlvHgUVtdFHEFdM,1158
|
|
99
99
|
pysdmx/io/xml/sdmx21/reader/submission.py,sha256=8daiBW-sIVGaB6lYwHqJNkLI7IixMSydCK-0ZO8ri4I,1711
|
|
100
100
|
pysdmx/io/xml/sdmx21/writer/__init__.py,sha256=QQGFAss26njCC4eKYxhBcI9LYm5NHuJaAJGKCrIrL80,31
|
|
101
101
|
pysdmx/io/xml/sdmx21/writer/error.py,sha256=0wkX7K_n2oZNkOKg_zpl9Id82qP72Lqof-T-ZLGoZ1M,353
|
|
102
|
-
pysdmx/io/xml/sdmx21/writer/generic.py,sha256=
|
|
102
|
+
pysdmx/io/xml/sdmx21/writer/generic.py,sha256=I5PQCJvoRMHtbr7i2h27NinW4-to6x6jB0ZNIAh9r9g,15722
|
|
103
103
|
pysdmx/io/xml/sdmx21/writer/structure.py,sha256=S3qoNgXxrakn2V4NLdL5U5mAA16XisI0PpJDuxqalFE,2084
|
|
104
104
|
pysdmx/io/xml/sdmx21/writer/structure_specific.py,sha256=iXc1J-RzoKyRznvgGgdTSeUfyqZLouI8CtSq2YhGBWI,2877
|
|
105
105
|
pysdmx/io/xml/sdmx30/__init__.py,sha256=8BScJFEgLy8DoUreu2RBUtxjGjKyClkKBI_Qtarbk-Y,38
|
|
@@ -109,13 +109,20 @@ pysdmx/io/xml/sdmx30/reader/structure_specific.py,sha256=BYZmNjGKGTMZLoE28XuYof8
|
|
|
109
109
|
pysdmx/io/xml/sdmx30/writer/__init__.py,sha256=cfCVmf82PNRe9A5RYmuKfvxDTSNcsySlJwCBbKfY_O0,31
|
|
110
110
|
pysdmx/io/xml/sdmx30/writer/structure.py,sha256=T4Ety741N0P648lSN0WszhX9CztboAnWTtahSaeBts4,2105
|
|
111
111
|
pysdmx/io/xml/sdmx30/writer/structure_specific.py,sha256=ftNFO4RIJnyC-ZmU7SrIhjbJUJPzebDHMUMKyw9QPc4,3236
|
|
112
|
+
pysdmx/io/xml/sdmx31/__init__.py,sha256=OpvPM3qa8v5X6iKP7wqSBOH10R0W6lL5X4rKiJNAWUg,38
|
|
113
|
+
pysdmx/io/xml/sdmx31/reader/__init__.py,sha256=DPY_uGHiMECGIK4fKSQKneltqlbFGGVxpO_tllX0yOQ,34
|
|
114
|
+
pysdmx/io/xml/sdmx31/reader/structure.py,sha256=PJ50N_2cH6YtlADRL8zlzMm_rRSI0bJDaVJDorFvWFU,1103
|
|
115
|
+
pysdmx/io/xml/sdmx31/reader/structure_specific.py,sha256=hX9WSfAlGTvUu4i-MokyMkg9y0r6F8GeiQrj87IcgIs,1158
|
|
116
|
+
pysdmx/io/xml/sdmx31/writer/__init__.py,sha256=wSWTfbD6bBh-gsdn1Z2kyIa5YXq3WEfn-u7ZYxJvjT0,31
|
|
117
|
+
pysdmx/io/xml/sdmx31/writer/structure.py,sha256=hK-BYOsyyuqLTqxe6qC3PxeDJDp3_qyBTqGUfpVjTl8,2105
|
|
118
|
+
pysdmx/io/xml/sdmx31/writer/structure_specific.py,sha256=OtP_uxn-y9tVB9eNRP6ov3zBL1wtr4SRef9llB4A3yk,3236
|
|
112
119
|
pysdmx/io/xml/utils.py,sha256=ljrocQwPTYycg3I68sqYI5pF8qukNccSCMM9Xf_Tag0,583
|
|
113
120
|
pysdmx/model/__base.py,sha256=H5OpCpi6fGxB30vFn-lCstTczSdOB4YJiBynThovQs4,13810
|
|
114
121
|
pysdmx/model/__init__.py,sha256=gmY8uGK0_SK9Ca5TqtYJqPSXvO9godlDKu2rhYouPSU,4984
|
|
115
122
|
pysdmx/model/category.py,sha256=WWWysaTdlcU8KZpQyZgbZkrOelNVCGOfVUk_APJxm-w,6031
|
|
116
|
-
pysdmx/model/code.py,sha256=
|
|
123
|
+
pysdmx/model/code.py,sha256=KEG2D74UHbZuOplSrztLWCNTzFTOg22imRe_eQvqAM8,12600
|
|
117
124
|
pysdmx/model/concept.py,sha256=mQfqJdtWc10WdTKX_Mw7Znw65cN3QO-kCar9MWYeWO4,9645
|
|
118
|
-
pysdmx/model/dataflow.py,sha256=
|
|
125
|
+
pysdmx/model/dataflow.py,sha256=9SgbQ9n2j5N_TqvN9eQtshL4Lom4kDnapP97cREP-e4,22999
|
|
119
126
|
pysdmx/model/dataset.py,sha256=Lbr7tYonGHD3NZUD-M9hK2puaEAluOVPG2DbkOohzMM,4861
|
|
120
127
|
pysdmx/model/gds.py,sha256=QrnmI8Hn--C95gGXCeUeWwhn-Ur7DuT08Cg7oPJIEVI,4976
|
|
121
128
|
pysdmx/model/map.py,sha256=IYoxKYpt0-9kYAgU24wryrSchR-lrc3NMacI5Vt5las,17213
|
|
@@ -131,10 +138,11 @@ pysdmx/toolkit/vtl/__init__.py,sha256=nVwtISeFiT4tr2gNjb2A-P_IRAdqv6Mn7EOgFkaRle
|
|
|
131
138
|
pysdmx/toolkit/vtl/_validations.py,sha256=seDf83sodg9Jn7Y-L4vo2Zmfqyh_ivBLdH7tz1DjyO4,5677
|
|
132
139
|
pysdmx/toolkit/vtl/script_generation.py,sha256=YOP_-MMcIdDIHYa8HIMuVCN9N-S9CQD8bH5xpF1kc2E,3235
|
|
133
140
|
pysdmx/toolkit/vtl/validation.py,sha256=UieJUYwxkw9McHZwixKFQdgYKFsgFwFo5XCLIIDcr7Q,3594
|
|
134
|
-
pysdmx/util/__init__.py,sha256=
|
|
141
|
+
pysdmx/util/__init__.py,sha256=m_XWRAmVJ7F6ai4Ckrj_YuPbhg3cJZAXeZrEThrL88k,3997
|
|
135
142
|
pysdmx/util/_date_pattern_map.py,sha256=IS1qONwVHbTBNIFCT0Rqbijj2a9mYvs7onXSK6GeQAQ,1620
|
|
136
|
-
pysdmx/util/_model_utils.py,sha256=
|
|
137
|
-
pysdmx
|
|
138
|
-
pysdmx-1.
|
|
139
|
-
pysdmx-1.
|
|
140
|
-
pysdmx-1.
|
|
143
|
+
pysdmx/util/_model_utils.py,sha256=d0XY8cnxvviQtkJJInVik7LWeuqX-eb4-zikFortL58,2335
|
|
144
|
+
pysdmx/util/_net_utils.py,sha256=nOTz_x3FgFrwKh42_J70IqYXz9duQkMFJWtejZXcLHs,1326
|
|
145
|
+
pysdmx-1.5.0.dist-info/LICENSE,sha256=3XTNDPtv2RxDUNkQzn9MIWit2u7_Ob5daMLEq-4pBJs,649
|
|
146
|
+
pysdmx-1.5.0.dist-info/METADATA,sha256=K7v8g6s1ooDheCBRC4DaAg68MA3mixhIGlw3cnAP2gc,4690
|
|
147
|
+
pysdmx-1.5.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
148
|
+
pysdmx-1.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|