pysdmx 1.8.0__py3-none-any.whl → 1.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pysdmx/__extras_check.py +14 -0
- pysdmx/__init__.py +1 -1
- pysdmx/io/json/fusion/messages/category.py +69 -41
- pysdmx/io/json/fusion/messages/msd.py +1 -1
- pysdmx/io/json/sdmxjson2/messages/category.py +76 -43
- pysdmx/io/json/sdmxjson2/messages/core.py +2 -1
- pysdmx/io/json/sdmxjson2/messages/msd.py +5 -1
- pysdmx/io/json/sdmxjson2/reader/doc_validation.py +108 -0
- pysdmx/io/json/sdmxjson2/reader/metadata.py +8 -1
- pysdmx/io/json/sdmxjson2/reader/structure.py +9 -2
- pysdmx/io/reader.py +18 -4
- pysdmx/io/xml/__data_aux.py +9 -4
- pysdmx/io/xml/__parse_xml.py +2 -0
- pysdmx/io/xml/__structure_aux_reader.py +70 -0
- pysdmx/io/xml/__structure_aux_writer.py +63 -9
- pysdmx/io/xml/__tokens.py +3 -0
- pysdmx/io/xml/__write_aux.py +35 -30
- pysdmx/io/xml/header.py +7 -1
- pysdmx/model/__base.py +1 -1
- pysdmx/model/category.py +6 -1
- pysdmx/model/message.py +22 -6
- pysdmx/util/_model_utils.py +40 -3
- {pysdmx-1.8.0.dist-info → pysdmx-1.9.0.dist-info}/METADATA +4 -1
- {pysdmx-1.8.0.dist-info → pysdmx-1.9.0.dist-info}/RECORD +26 -25
- {pysdmx-1.8.0.dist-info → pysdmx-1.9.0.dist-info}/WHEEL +0 -0
- {pysdmx-1.8.0.dist-info → pysdmx-1.9.0.dist-info}/licenses/LICENSE +0 -0
pysdmx/io/xml/__parse_xml.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import Any, Dict
|
|
|
2
2
|
|
|
3
3
|
import xmltodict
|
|
4
4
|
|
|
5
|
+
from pysdmx.__extras_check import __check_xml_extra
|
|
5
6
|
from pysdmx.io.xml.doc_validation import validate_doc
|
|
6
7
|
|
|
7
8
|
SCHEMA_ROOT = "http://www.sdmx.org/resources/sdmxml/schemas/v2_1/"
|
|
@@ -77,6 +78,7 @@ def parse_xml(
|
|
|
77
78
|
Raises:
|
|
78
79
|
Invalid: If the SDMX data cannot be parsed.
|
|
79
80
|
"""
|
|
81
|
+
__check_xml_extra()
|
|
80
82
|
if validate:
|
|
81
83
|
validate_doc(input_str)
|
|
82
84
|
if SCHEMA_ROOT_31 in input_str:
|
|
@@ -43,6 +43,7 @@ from pysdmx.io.xml.__tokens import (
|
|
|
43
43
|
CUSTOM_TYPE_SCHEME,
|
|
44
44
|
CUSTOM_TYPE_SCHEMES,
|
|
45
45
|
CUSTOM_TYPES,
|
|
46
|
+
DATA_PROV,
|
|
46
47
|
DEPARTMENT,
|
|
47
48
|
DESC,
|
|
48
49
|
DFW,
|
|
@@ -94,6 +95,8 @@ from pysdmx.io.xml.__tokens import (
|
|
|
94
95
|
ORGS,
|
|
95
96
|
PAR_ID,
|
|
96
97
|
PAR_VER,
|
|
98
|
+
PROV_AGREEMENT,
|
|
99
|
+
PROV_AGREEMENTS,
|
|
97
100
|
REF,
|
|
98
101
|
REQUIRED,
|
|
99
102
|
ROLE,
|
|
@@ -105,6 +108,7 @@ from pysdmx.io.xml.__tokens import (
|
|
|
105
108
|
SER_URL_LOW,
|
|
106
109
|
STR_URL,
|
|
107
110
|
STR_URL_LOW,
|
|
111
|
+
STR_USAGE,
|
|
108
112
|
STRUCTURE,
|
|
109
113
|
TELEPHONE,
|
|
110
114
|
TELEPHONES,
|
|
@@ -171,6 +175,7 @@ from pysdmx.model.dataflow import (
|
|
|
171
175
|
Dataflow,
|
|
172
176
|
DataStructureDefinition,
|
|
173
177
|
Group,
|
|
178
|
+
ProvisionAgreement,
|
|
174
179
|
Role,
|
|
175
180
|
)
|
|
176
181
|
from pysdmx.model.vtl import (
|
|
@@ -204,6 +209,7 @@ STRUCTURES_MAPPING = {
|
|
|
204
209
|
VTL_MAPPING_SCHEME: VtlMappingScheme,
|
|
205
210
|
NAME_PER_SCHEME: NamePersonalisationScheme,
|
|
206
211
|
CUSTOM_TYPE_SCHEME: CustomTypeScheme,
|
|
212
|
+
PROV_AGREEMENTS: ProvisionAgreement,
|
|
207
213
|
}
|
|
208
214
|
ITEMS_CLASSES = {
|
|
209
215
|
AGENCY: Agency,
|
|
@@ -808,6 +814,62 @@ class StructureParser(Struct):
|
|
|
808
814
|
|
|
809
815
|
return element
|
|
810
816
|
|
|
817
|
+
def __format_prov_agreement(
|
|
818
|
+
self, element: Dict[str, Any]
|
|
819
|
+
) -> Dict[str, Any]:
|
|
820
|
+
dfw = None
|
|
821
|
+
if STR_USAGE in element:
|
|
822
|
+
ref_dfw: Union[Reference, ItemReference]
|
|
823
|
+
if REF in element[STR_USAGE]:
|
|
824
|
+
str_usage = element[STR_USAGE][REF]
|
|
825
|
+
ref_dfw = Reference(
|
|
826
|
+
sdmx_type=str_usage[CLASS],
|
|
827
|
+
agency=str_usage[AGENCY_ID],
|
|
828
|
+
id=str_usage[ID],
|
|
829
|
+
version=str_usage[VERSION],
|
|
830
|
+
)
|
|
831
|
+
else:
|
|
832
|
+
ref_dfw = parse_urn(element[STR_USAGE][URN])
|
|
833
|
+
dfw = (
|
|
834
|
+
f"{ref_dfw.sdmx_type}={ref_dfw.agency}:"
|
|
835
|
+
f"{ref_dfw.id}({ref_dfw.version})"
|
|
836
|
+
)
|
|
837
|
+
del element[STR_USAGE]
|
|
838
|
+
|
|
839
|
+
if DFW in element:
|
|
840
|
+
ref_dfw = parse_urn(element[DFW])
|
|
841
|
+
dfw = (
|
|
842
|
+
f"{ref_dfw.sdmx_type}={ref_dfw.agency}:"
|
|
843
|
+
f"{ref_dfw.id}({ref_dfw.version})"
|
|
844
|
+
)
|
|
845
|
+
del element[DFW]
|
|
846
|
+
|
|
847
|
+
ref_data_prov: Union[Reference, ItemReference]
|
|
848
|
+
if REF in element[DATA_PROV]:
|
|
849
|
+
data_prov = element[DATA_PROV][REF]
|
|
850
|
+
ref_data_prov = ItemReference(
|
|
851
|
+
sdmx_type=data_prov[CLASS],
|
|
852
|
+
agency=data_prov[AGENCY_ID],
|
|
853
|
+
id=data_prov[PAR_ID],
|
|
854
|
+
version=data_prov[PAR_VER],
|
|
855
|
+
item_id=data_prov[ID],
|
|
856
|
+
)
|
|
857
|
+
elif URN in element[DATA_PROV]:
|
|
858
|
+
ref_data_prov = parse_urn(element[DATA_PROV][URN])
|
|
859
|
+
else:
|
|
860
|
+
ref_data_prov = parse_urn(element[DATA_PROV])
|
|
861
|
+
del element[DATA_PROV]
|
|
862
|
+
provider = (
|
|
863
|
+
f"{ref_data_prov.sdmx_type}={ref_data_prov.agency}:"
|
|
864
|
+
f"{ref_data_prov.id}({ref_data_prov.version})"
|
|
865
|
+
f".{ref_data_prov.item_id}" # type: ignore[union-attr]
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
element["dataflow"] = dfw
|
|
869
|
+
element["provider"] = provider
|
|
870
|
+
|
|
871
|
+
return element
|
|
872
|
+
|
|
811
873
|
def __format_vtl(self, json_vtl: Dict[str, Any]) -> Dict[str, Any]:
|
|
812
874
|
# VTL Scheme Handling
|
|
813
875
|
_format_lower_key("vtlVersion", json_vtl)
|
|
@@ -1054,6 +1116,8 @@ class StructureParser(Struct):
|
|
|
1054
1116
|
element = self.__format_validity(element)
|
|
1055
1117
|
element = self.__format_groups(element)
|
|
1056
1118
|
element = self.__format_components(element)
|
|
1119
|
+
if item == PROV_AGREEMENT:
|
|
1120
|
+
element = self.__format_prov_agreement(element)
|
|
1057
1121
|
|
|
1058
1122
|
if "xmlns" in element:
|
|
1059
1123
|
del element["xmlns"]
|
|
@@ -1155,6 +1219,12 @@ class StructureParser(Struct):
|
|
|
1155
1219
|
lambda data: self.__format_schema(data, DFWS, DFW),
|
|
1156
1220
|
"dataflows",
|
|
1157
1221
|
),
|
|
1222
|
+
PROV_AGREEMENTS: process_structure(
|
|
1223
|
+
PROV_AGREEMENTS,
|
|
1224
|
+
lambda data: self.__format_schema(
|
|
1225
|
+
data, PROV_AGREEMENTS, PROV_AGREEMENT
|
|
1226
|
+
),
|
|
1227
|
+
),
|
|
1158
1228
|
VTLMAPPINGS: process_structure(
|
|
1159
1229
|
VTLMAPPINGS,
|
|
1160
1230
|
lambda data: self.__format_scheme(
|
|
@@ -21,6 +21,7 @@ from pysdmx.io.xml.__tokens import (
|
|
|
21
21
|
CS,
|
|
22
22
|
CUSTOM_TYPE,
|
|
23
23
|
CUSTOM_TYPE_SCHEME,
|
|
24
|
+
DATA_PROV,
|
|
24
25
|
DEPARTMENT,
|
|
25
26
|
DFW,
|
|
26
27
|
DIM,
|
|
@@ -49,10 +50,12 @@ from pysdmx.io.xml.__tokens import (
|
|
|
49
50
|
PAR_ID,
|
|
50
51
|
PAR_VER,
|
|
51
52
|
POSITION,
|
|
53
|
+
PROV_AGREEMENT,
|
|
52
54
|
REF,
|
|
53
55
|
ROLE,
|
|
54
56
|
RULE,
|
|
55
57
|
RULE_SCHEME,
|
|
58
|
+
STR_USAGE,
|
|
56
59
|
TELEPHONE,
|
|
57
60
|
TEXT_FORMAT,
|
|
58
61
|
TEXT_TYPE,
|
|
@@ -125,10 +128,12 @@ from pysdmx.model.dataflow import (
|
|
|
125
128
|
Dataflow,
|
|
126
129
|
DataStructureDefinition,
|
|
127
130
|
Group,
|
|
131
|
+
ProvisionAgreement,
|
|
128
132
|
Role,
|
|
129
133
|
)
|
|
130
134
|
from pysdmx.util import (
|
|
131
135
|
parse_item_urn,
|
|
136
|
+
parse_short_item_urn,
|
|
132
137
|
parse_short_urn,
|
|
133
138
|
parse_urn,
|
|
134
139
|
)
|
|
@@ -174,6 +179,7 @@ STR_DICT_TYPE_LIST_21 = {
|
|
|
174
179
|
RulesetScheme: "Rulesets",
|
|
175
180
|
UserDefinedOperatorScheme: "UserDefinedOperators",
|
|
176
181
|
TransformationScheme: "Transformations",
|
|
182
|
+
ProvisionAgreement: "ProvisionAgreements",
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
|
|
@@ -189,6 +195,7 @@ STR_DICT_TYPE_LIST_30 = {
|
|
|
189
195
|
RulesetScheme: "RulesetSchemes",
|
|
190
196
|
UserDefinedOperatorScheme: "UserDefinedOperatorSchemes",
|
|
191
197
|
TransformationScheme: "TransformationSchemes",
|
|
198
|
+
ProvisionAgreement: "ProvisionAgreements",
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
|
|
@@ -790,6 +797,50 @@ def __write_structure(
|
|
|
790
797
|
return outfile
|
|
791
798
|
|
|
792
799
|
|
|
800
|
+
def __write_prov_agreement(
|
|
801
|
+
dataflow: str, provider: str, indent: str, references_30: bool = False
|
|
802
|
+
) -> str:
|
|
803
|
+
"""Writes the provision agreement structure to the XML file."""
|
|
804
|
+
ref_df = parse_short_urn(dataflow)
|
|
805
|
+
ref_pr = parse_short_item_urn(provider)
|
|
806
|
+
if references_30:
|
|
807
|
+
outfile = f"{indent}<{ABBR_STR}:{DFW}>"
|
|
808
|
+
outfile += (
|
|
809
|
+
f"urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow={ref_df.agency}:{ref_df.id}({ref_df.version})"
|
|
810
|
+
f"</{ABBR_STR}:{DFW}>"
|
|
811
|
+
)
|
|
812
|
+
outfile += f"{indent}<{ABBR_STR}:{DATA_PROV}>"
|
|
813
|
+
outfile += (
|
|
814
|
+
f"urn:sdmx:org.sdmx.infomodel.base.DataProvider={ref_pr.agency}:{ref_pr.id}({ref_pr.version}).{ref_pr.item_id}"
|
|
815
|
+
f"</{ABBR_STR}:{DATA_PROV}>"
|
|
816
|
+
)
|
|
817
|
+
else:
|
|
818
|
+
outfile = f"{indent}<{ABBR_STR}:{STR_USAGE}>"
|
|
819
|
+
outfile += (
|
|
820
|
+
f"{add_indent(indent)}<{REF} "
|
|
821
|
+
f'{PACKAGE}="datastructure" '
|
|
822
|
+
f"{AGENCY_ID}={ref_df.agency!r} "
|
|
823
|
+
f"{ID}={ref_df.id!r} "
|
|
824
|
+
f"{VERSION}={ref_df.version!r} "
|
|
825
|
+
f"{CLASS}={DFW!r}/>"
|
|
826
|
+
)
|
|
827
|
+
outfile += f"{indent}</{ABBR_STR}:{STR_USAGE}>"
|
|
828
|
+
outfile += f"{indent}<{ABBR_STR}:{DATA_PROV}>"
|
|
829
|
+
outfile += (
|
|
830
|
+
f"{add_indent(indent)}<{REF} "
|
|
831
|
+
f"{PAR_ID}={ref_pr.id!r} "
|
|
832
|
+
f'{PACKAGE}="base" '
|
|
833
|
+
f"{PAR_VER}={ref_pr.version!r} "
|
|
834
|
+
f"{AGENCY_ID}={ref_pr.agency!r} "
|
|
835
|
+
f"{ID}={ref_pr.item_id!r} "
|
|
836
|
+
f"{CLASS}={DATA_PROV!r}/>"
|
|
837
|
+
)
|
|
838
|
+
outfile += f"{indent}</{ABBR_STR}:{DATA_PROV}>"
|
|
839
|
+
|
|
840
|
+
outfile = outfile.replace("'", '"')
|
|
841
|
+
return outfile
|
|
842
|
+
|
|
843
|
+
|
|
793
844
|
def __write_scheme( # noqa: C901
|
|
794
845
|
item_scheme: Any, indent: str, scheme: str, references_30: bool = False
|
|
795
846
|
) -> str:
|
|
@@ -805,10 +856,7 @@ def __write_scheme( # noqa: C901
|
|
|
805
856
|
item_scheme, add_indent(indent), references_30
|
|
806
857
|
)
|
|
807
858
|
|
|
808
|
-
if scheme not in [
|
|
809
|
-
DSD,
|
|
810
|
-
DFW,
|
|
811
|
-
]:
|
|
859
|
+
if scheme not in [DSD, DFW, PROV_AGREEMENT]:
|
|
812
860
|
data["Attributes"] += (
|
|
813
861
|
f" isPartial={str(item_scheme.is_partial).lower()!r}"
|
|
814
862
|
)
|
|
@@ -839,6 +887,13 @@ def __write_scheme( # noqa: C901
|
|
|
839
887
|
outfile += __write_structure(
|
|
840
888
|
item_scheme.structure, add_indent(indent), references_30
|
|
841
889
|
)
|
|
890
|
+
if scheme == PROV_AGREEMENT:
|
|
891
|
+
outfile += __write_prov_agreement(
|
|
892
|
+
item_scheme.dataflow,
|
|
893
|
+
item_scheme.provider,
|
|
894
|
+
add_indent(indent),
|
|
895
|
+
references_30,
|
|
896
|
+
)
|
|
842
897
|
|
|
843
898
|
if scheme not in [
|
|
844
899
|
DSD,
|
|
@@ -849,6 +904,7 @@ def __write_scheme( # noqa: C901
|
|
|
849
904
|
VTL_MAPPING_SCHEME,
|
|
850
905
|
CUSTOM_TYPE_SCHEME,
|
|
851
906
|
NAME_PER_SCHEME,
|
|
907
|
+
PROV_AGREEMENT,
|
|
852
908
|
]:
|
|
853
909
|
for item in item_scheme.items:
|
|
854
910
|
if (
|
|
@@ -1129,11 +1185,9 @@ def _write_vtl( # noqa: C901
|
|
|
1129
1185
|
ref_codelist = (
|
|
1130
1186
|
item_or_scheme.codelist
|
|
1131
1187
|
if isinstance(item_or_scheme.codelist, Reference)
|
|
1132
|
-
else (
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
else parse_short_urn(item_or_scheme.codelist.short_urn)
|
|
1136
|
-
)
|
|
1188
|
+
else parse_urn(item_or_scheme.codelist)
|
|
1189
|
+
if isinstance(item_or_scheme.codelist, str)
|
|
1190
|
+
else parse_short_urn(item_or_scheme.codelist.short_urn)
|
|
1137
1191
|
)
|
|
1138
1192
|
if references_30:
|
|
1139
1193
|
data += (
|
pysdmx/io/xml/__tokens.py
CHANGED
|
@@ -13,6 +13,7 @@ POSITION = "position"
|
|
|
13
13
|
CLASS = "class"
|
|
14
14
|
PACKAGE = "package"
|
|
15
15
|
LINK = "Link"
|
|
16
|
+
DATA_PROV = "DataProvider"
|
|
16
17
|
|
|
17
18
|
# Structure Specific
|
|
18
19
|
STR_SPE = "StructureSpecificData"
|
|
@@ -93,6 +94,8 @@ CON_SCHEMES = "ConceptSchemes"
|
|
|
93
94
|
DSDS = "DataStructures"
|
|
94
95
|
DATAFLOWS = "Dataflows"
|
|
95
96
|
CONSTRAINTS = "Constraints"
|
|
97
|
+
PROV_AGREEMENTS = "ProvisionAgreements"
|
|
98
|
+
|
|
96
99
|
|
|
97
100
|
# Individual
|
|
98
101
|
AGENCY = "Agency"
|
pysdmx/io/xml/__write_aux.py
CHANGED
|
@@ -69,6 +69,7 @@ CONCEPTS_SCHEMES = "ConceptSchemes"
|
|
|
69
69
|
DSDS = "DataStructures"
|
|
70
70
|
DATAFLOWS = "Dataflows"
|
|
71
71
|
CONSTRAINTS = "Constraints"
|
|
72
|
+
PROV_AGREEMENTS = "ProvisionAgreements"
|
|
72
73
|
ALL_DIM = "AllDimensions"
|
|
73
74
|
|
|
74
75
|
BASE_URL_21 = "http://www.sdmx.org/resources/sdmxml/schemas/v2_1"
|
|
@@ -199,6 +200,7 @@ MSG_CONTENT_PKG_21 = OrderedDict(
|
|
|
199
200
|
[
|
|
200
201
|
(ORGS, "OrganisationSchemes"),
|
|
201
202
|
(DATAFLOWS, "Dataflows"),
|
|
203
|
+
(PROV_AGREEMENTS, "ProvisionAgreements"),
|
|
202
204
|
(CODELISTS, "Codelists"),
|
|
203
205
|
(CONCEPTS, "Concepts"),
|
|
204
206
|
(DSDS, "DataStructures"),
|
|
@@ -217,6 +219,7 @@ MSG_CONTENT_PKG_30 = OrderedDict(
|
|
|
217
219
|
[
|
|
218
220
|
(AGC, "AgencySchemes"),
|
|
219
221
|
(DATAFLOWS, "Dataflows"),
|
|
222
|
+
(PROV_AGREEMENTS, "ProvisionAgreements"),
|
|
220
223
|
(CODELISTS, "Codelists"),
|
|
221
224
|
(CONCEPTS_SCHEMES, "ConceptSchemes"),
|
|
222
225
|
(DSDS, "DataStructures"),
|
|
@@ -281,35 +284,32 @@ def __item(
|
|
|
281
284
|
name = None
|
|
282
285
|
org_id = None
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
else:
|
|
312
|
-
return ""
|
|
287
|
+
org_id = sender_receiver.id
|
|
288
|
+
name = sender_receiver.name
|
|
289
|
+
|
|
290
|
+
unexpected_keys = {
|
|
291
|
+
URI_LOW,
|
|
292
|
+
URN_LOW,
|
|
293
|
+
DESC_LOW,
|
|
294
|
+
CONTACTS_LOW,
|
|
295
|
+
DFWS_LOW,
|
|
296
|
+
ANNOTATIONS_LOW,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
unexpected_with_values = {
|
|
300
|
+
key
|
|
301
|
+
for key in unexpected_keys
|
|
302
|
+
if hasattr(sender_receiver, key)
|
|
303
|
+
and getattr(sender_receiver, key) not in (None, (), [], {})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if unexpected_with_values:
|
|
307
|
+
warnings.warn(
|
|
308
|
+
f"The following attributes will be lost: "
|
|
309
|
+
f"{', '.join(unexpected_with_values)}",
|
|
310
|
+
UserWarning,
|
|
311
|
+
stacklevel=2,
|
|
312
|
+
)
|
|
313
313
|
|
|
314
314
|
message = f"{nl}{child2}<{ABBR_MSG}:{element} id={org_id!r}"
|
|
315
315
|
if name is not None:
|
|
@@ -395,6 +395,11 @@ def __reference(
|
|
|
395
395
|
)
|
|
396
396
|
|
|
397
397
|
|
|
398
|
+
def __write_receivers(header: Header, nl: str, prettyprint: bool) -> str:
|
|
399
|
+
recs = [__item("Receiver", r, nl, prettyprint) for r in header.receiver]
|
|
400
|
+
return "".join(recs)
|
|
401
|
+
|
|
402
|
+
|
|
398
403
|
def __write_header(
|
|
399
404
|
header: Header,
|
|
400
405
|
prettyprint: bool,
|
|
@@ -467,7 +472,7 @@ def __write_header(
|
|
|
467
472
|
f"{__value('Test', test)}"
|
|
468
473
|
f"{__value('Prepared', prepared)}"
|
|
469
474
|
f"{__item('Sender', header.sender, nl, prettyprint)}"
|
|
470
|
-
f"{
|
|
475
|
+
f"{__write_receivers(header, nl, prettyprint)}"
|
|
471
476
|
f"{references_str}"
|
|
472
477
|
f"{__value('DataSetAction', action_value)}"
|
|
473
478
|
f"{__value('DataSetID', header.dataset_id)}"
|
pysdmx/io/xml/header.py
CHANGED
|
@@ -161,12 +161,18 @@ def __parse_header(header: Dict[str, Any]) -> Header:
|
|
|
161
161
|
Header: The header of the SDMX message.
|
|
162
162
|
|
|
163
163
|
"""
|
|
164
|
+
if isinstance(header.get(RECEIVER), list):
|
|
165
|
+
receiver = [
|
|
166
|
+
__parse_sender_receiver(r) for r in header.get(RECEIVER, [])
|
|
167
|
+
]
|
|
168
|
+
else:
|
|
169
|
+
receiver = __parse_sender_receiver(header.get(RECEIVER)) # type: ignore[assignment]
|
|
164
170
|
dict_header = {
|
|
165
171
|
"id": header.get(HEADER_ID),
|
|
166
172
|
"test": header.get(TEST),
|
|
167
173
|
"prepared": __parse_prepared(header.get(PREPARED)), # type: ignore[arg-type]
|
|
168
174
|
"sender": __parse_sender_receiver(header.get(SENDER)),
|
|
169
|
-
"receiver":
|
|
175
|
+
"receiver": receiver,
|
|
170
176
|
"source": __parse_source(header.get(SOURCE)),
|
|
171
177
|
"dataset_action": __parse_dataset_action(header.get(DATASET_ACTION)),
|
|
172
178
|
"structure": __parse_structure(header.get(STRUCTURE)),
|
pysdmx/model/__base.py
CHANGED
|
@@ -373,7 +373,7 @@ class DataflowRef(
|
|
|
373
373
|
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
374
374
|
|
|
375
375
|
|
|
376
|
-
class Reference(Struct, frozen=True, repr_omit_defaults=True):
|
|
376
|
+
class Reference(Struct, frozen=True, repr_omit_defaults=True, tag=True):
|
|
377
377
|
"""The coordinates of an SDMX maintainable artefact.
|
|
378
378
|
|
|
379
379
|
Attributes:
|
pysdmx/model/category.py
CHANGED
|
@@ -9,13 +9,15 @@ from typing import Iterator, Optional, Sequence, Union
|
|
|
9
9
|
from pysdmx.model.__base import (
|
|
10
10
|
DataflowRef,
|
|
11
11
|
Item,
|
|
12
|
+
ItemReference,
|
|
12
13
|
ItemScheme,
|
|
13
14
|
MaintainableArtefact,
|
|
15
|
+
Reference,
|
|
14
16
|
)
|
|
15
17
|
from pysdmx.model.dataflow import Dataflow
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class Category(Item, frozen=
|
|
20
|
+
class Category(Item, frozen=True, omit_defaults=True):
|
|
19
21
|
"""A category, ie a way to **organize and group** things.
|
|
20
22
|
|
|
21
23
|
Categories are used to organize and group other artefacts in SDMX.
|
|
@@ -34,10 +36,13 @@ class Category(Item, frozen=False, omit_defaults=True): # type: ignore[misc]
|
|
|
34
36
|
categories: The sub-categories, i.e. the categories belonging
|
|
35
37
|
to the category.
|
|
36
38
|
dataflows: The list of dataflows attached to the category.
|
|
39
|
+
other_references: References to other types artefacts (i.e. not
|
|
40
|
+
dataflows) attached to the category.
|
|
37
41
|
"""
|
|
38
42
|
|
|
39
43
|
categories: Sequence["Category"] = ()
|
|
40
44
|
dataflows: Sequence[Union[Dataflow, DataflowRef]] = ()
|
|
45
|
+
other_references: Sequence[Union[ItemReference, Reference]] = ()
|
|
41
46
|
|
|
42
47
|
def __iter__(self) -> Iterator["Category"]:
|
|
43
48
|
"""Return an iterator over the list of categories."""
|
pysdmx/model/message.py
CHANGED
|
@@ -19,7 +19,7 @@ from typing import Any, Dict, List, Optional, Sequence, Type, Union
|
|
|
19
19
|
from msgspec import Struct
|
|
20
20
|
|
|
21
21
|
from pysdmx.errors import Invalid, NotFound
|
|
22
|
-
from pysdmx.model.__base import
|
|
22
|
+
from pysdmx.model.__base import MaintainableArtefact, Organisation
|
|
23
23
|
from pysdmx.model.category import Categorisation, CategoryScheme
|
|
24
24
|
from pysdmx.model.code import Codelist, Hierarchy, HierarchyAssociation
|
|
25
25
|
from pysdmx.model.concept import ConceptScheme
|
|
@@ -87,7 +87,7 @@ class Header(Struct, repr_omit_defaults=True, kw_only=True):
|
|
|
87
87
|
test: bool = False
|
|
88
88
|
prepared: datetime = datetime.now(timezone.utc)
|
|
89
89
|
sender: Organisation = Organisation(id="ZZZ")
|
|
90
|
-
receiver:
|
|
90
|
+
receiver: Sequence[Organisation] = ()
|
|
91
91
|
source: Optional[str] = None
|
|
92
92
|
dataset_action: Optional[ActionType] = None
|
|
93
93
|
structure: Optional[Dict[str, str]] = None
|
|
@@ -99,7 +99,13 @@ class Header(Struct, repr_omit_defaults=True, kw_only=True):
|
|
|
99
99
|
self.sender = Organisation(id=self.sender)
|
|
100
100
|
|
|
101
101
|
if isinstance(self.receiver, str):
|
|
102
|
-
self.receiver = Organisation(id=self.receiver)
|
|
102
|
+
self.receiver = [Organisation(id=self.receiver)]
|
|
103
|
+
|
|
104
|
+
if isinstance(self.receiver, Organisation):
|
|
105
|
+
self.receiver = [self.receiver]
|
|
106
|
+
|
|
107
|
+
if self.receiver is None:
|
|
108
|
+
self.receiver = ()
|
|
103
109
|
|
|
104
110
|
def __str__(self) -> str:
|
|
105
111
|
"""Custom string representation without the class name."""
|
|
@@ -171,7 +177,9 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
|
171
177
|
attrs.append(f"{attr}={repr(value)}")
|
|
172
178
|
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
173
179
|
|
|
174
|
-
|
|
180
|
+
# Returns MaintainableArtefacts only, but mypy complains.
|
|
181
|
+
# As it is an internal method, it's acceptable.
|
|
182
|
+
def __get_elements(self, type_: Type[MaintainableArtefact]) -> List[Any]:
|
|
175
183
|
"""Returns a list of elements of a specific type."""
|
|
176
184
|
if self.structures is None:
|
|
177
185
|
raise NotFound(
|
|
@@ -180,6 +188,8 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
|
180
188
|
structures = [e for e in self.structures if isinstance(e, type_)]
|
|
181
189
|
return structures
|
|
182
190
|
|
|
191
|
+
# Returns Codelist or ValueList only, but mypy complains.
|
|
192
|
+
# As it is an internal method, it's acceptable.
|
|
183
193
|
def __get_enumerations(
|
|
184
194
|
self, type_: Type[Any], is_vl: bool = False
|
|
185
195
|
) -> List[Any]:
|
|
@@ -188,9 +198,11 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
|
188
198
|
t = "valuelist" if is_vl else "codelist"
|
|
189
199
|
return [e for e in enums if e.sdmx_type == t]
|
|
190
200
|
|
|
201
|
+
# Returns MaintainableArtefacts only,
|
|
202
|
+
# but mypy complains. As it is an internal method, it's acceptable.
|
|
191
203
|
def __get_single_structure(
|
|
192
204
|
self,
|
|
193
|
-
type_: Type[
|
|
205
|
+
type_: Type[MaintainableArtefact],
|
|
194
206
|
short_urn: str,
|
|
195
207
|
) -> Any:
|
|
196
208
|
"""Returns a specific element from content."""
|
|
@@ -235,7 +247,7 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
|
235
247
|
return self.__get_elements(Metadataflow)
|
|
236
248
|
|
|
237
249
|
def get_organisation_scheme(self, short_urn: str) -> AgencyScheme:
|
|
238
|
-
"""Returns a specific
|
|
250
|
+
"""Returns a specific AgencyScheme."""
|
|
239
251
|
return self.__get_single_structure(AgencyScheme, short_urn)
|
|
240
252
|
|
|
241
253
|
def get_codelist(self, short_urn: str) -> Codelist:
|
|
@@ -256,6 +268,10 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
|
256
268
|
"""Returns a specific Dataflow."""
|
|
257
269
|
return self.__get_single_structure(Dataflow, short_urn)
|
|
258
270
|
|
|
271
|
+
def get_provision_agreement(self, short_urn: str) -> ProvisionAgreement:
|
|
272
|
+
"""Returns a specific Provision Agreement."""
|
|
273
|
+
return self.__get_single_structure(ProvisionAgreement, short_urn)
|
|
274
|
+
|
|
259
275
|
def get_transformation_schemes(self) -> List[TransformationScheme]:
|
|
260
276
|
"""Returns the TransformationSchemes."""
|
|
261
277
|
return self.__get_elements(TransformationScheme)
|
pysdmx/util/_model_utils.py
CHANGED
|
@@ -5,7 +5,7 @@ from pysdmx.model.message import Message
|
|
|
5
5
|
from pysdmx.util import parse_urn
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def schema_generator(message: Message, dataset_ref: Reference) -> Schema:
|
|
8
|
+
def schema_generator(message: Message, dataset_ref: Reference) -> Schema: # noqa: C901
|
|
9
9
|
"""Generates a Schema by resolving the short_urn in the message."""
|
|
10
10
|
context = dataset_ref.sdmx_type.lower()
|
|
11
11
|
if context == "datastructure":
|
|
@@ -47,8 +47,45 @@ def schema_generator(message: Message, dataset_ref: Reference) -> Schema:
|
|
|
47
47
|
artefacts=dsd.to_schema().artefacts,
|
|
48
48
|
)
|
|
49
49
|
elif context == "provisionagreement":
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
try:
|
|
51
|
+
prov_agree = message.get_provision_agreement(str(dataset_ref))
|
|
52
|
+
except NotFound:
|
|
53
|
+
raise Invalid(
|
|
54
|
+
f"Missing Provision Agreement {dataset_ref} "
|
|
55
|
+
f"in structures message.",
|
|
56
|
+
) from None
|
|
57
|
+
if prov_agree.dataflow is None:
|
|
58
|
+
raise Invalid(
|
|
59
|
+
f"Provision Agreement {dataset_ref} does not have a "
|
|
60
|
+
f"Dataflow defined.",
|
|
61
|
+
)
|
|
62
|
+
dfw_ref = parse_urn(prov_agree.dataflow)
|
|
63
|
+
try:
|
|
64
|
+
dataflow = message.get_dataflow(str(dfw_ref))
|
|
65
|
+
except NotFound:
|
|
66
|
+
raise Invalid(
|
|
67
|
+
f"Missing Dataflow in {dataset_ref} structures message.",
|
|
68
|
+
) from None
|
|
69
|
+
if dataflow.structure is None:
|
|
70
|
+
raise Invalid(
|
|
71
|
+
f"Dataflow {dataset_ref} does not have a structure defined.",
|
|
72
|
+
)
|
|
73
|
+
dsd_ref = parse_urn(dataflow.structure) # type: ignore[arg-type]
|
|
74
|
+
try:
|
|
75
|
+
dsd = message.get_data_structure_definition(str(dsd_ref))
|
|
76
|
+
except NotFound:
|
|
77
|
+
raise Invalid(
|
|
78
|
+
f"Not found referenced DataStructure {dsd_ref}"
|
|
79
|
+
f" from Provision Agreement {dataset_ref}.",
|
|
80
|
+
) from None
|
|
81
|
+
return Schema(
|
|
82
|
+
context=context, # type: ignore[arg-type]
|
|
83
|
+
id=dataset_ref.id,
|
|
84
|
+
version=dataset_ref.version,
|
|
85
|
+
agency=dataset_ref.agency,
|
|
86
|
+
groups=dsd.groups,
|
|
87
|
+
components=dsd.components,
|
|
88
|
+
artefacts=dsd.to_schema().artefacts,
|
|
52
89
|
)
|
|
53
90
|
else:
|
|
54
91
|
raise Invalid(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysdmx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Your opinionated Python SDMX library
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -16,9 +16,11 @@ Classifier: Typing :: Typed
|
|
|
16
16
|
Provides-Extra: all
|
|
17
17
|
Provides-Extra: data
|
|
18
18
|
Provides-Extra: dc
|
|
19
|
+
Provides-Extra: json
|
|
19
20
|
Provides-Extra: vtl
|
|
20
21
|
Provides-Extra: xml
|
|
21
22
|
Requires-Dist: httpx[http2] (>=0)
|
|
23
|
+
Requires-Dist: jsonschema (>=4.10) ; extra == "json"
|
|
22
24
|
Requires-Dist: lxml (>=5.2) ; extra == "all"
|
|
23
25
|
Requires-Dist: lxml (>=5.2) ; extra == "xml"
|
|
24
26
|
Requires-Dist: msgspec (>=0)
|
|
@@ -28,6 +30,7 @@ Requires-Dist: parsy (>=2.1)
|
|
|
28
30
|
Requires-Dist: python-dateutil (>=2.8.2) ; extra == "all"
|
|
29
31
|
Requires-Dist: python-dateutil (>=2.8.2) ; extra == "dc"
|
|
30
32
|
Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "all"
|
|
33
|
+
Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "json"
|
|
31
34
|
Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "xml"
|
|
32
35
|
Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "all"
|
|
33
36
|
Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "vtl"
|