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
pysdmx/model/message.py
CHANGED
|
@@ -19,22 +19,60 @@ 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 import
|
|
23
|
-
|
|
22
|
+
from pysdmx.model.__base import ItemScheme, MaintainableArtefact, Organisation
|
|
23
|
+
from pysdmx.model.category import Categorisation, CategoryScheme
|
|
24
|
+
from pysdmx.model.code import Codelist, Hierarchy, HierarchyAssociation
|
|
25
|
+
from pysdmx.model.concept import ConceptScheme
|
|
26
|
+
from pysdmx.model.dataflow import (
|
|
27
|
+
Dataflow,
|
|
28
|
+
DataStructureDefinition,
|
|
29
|
+
ProvisionAgreement,
|
|
30
|
+
)
|
|
31
|
+
from pysdmx.model.dataset import ActionType, Dataset
|
|
32
|
+
from pysdmx.model.map import (
|
|
33
|
+
MultiRepresentationMap,
|
|
34
|
+
RepresentationMap,
|
|
35
|
+
StructureMap,
|
|
36
|
+
)
|
|
37
|
+
from pysdmx.model.metadata import MetadataReport
|
|
38
|
+
from pysdmx.model.organisation import AgencyScheme, DataProviderScheme
|
|
39
|
+
from pysdmx.model.submission import SubmissionResult
|
|
40
|
+
from pysdmx.model.vtl import (
|
|
41
|
+
CustomTypeScheme,
|
|
42
|
+
NamePersonalisationScheme,
|
|
24
43
|
RulesetScheme,
|
|
25
44
|
TransformationScheme,
|
|
26
45
|
UserDefinedOperatorScheme,
|
|
46
|
+
VtlMappingScheme,
|
|
27
47
|
)
|
|
28
|
-
from pysdmx.model.__base import ItemScheme, Organisation
|
|
29
|
-
from pysdmx.model.code import Codelist
|
|
30
|
-
from pysdmx.model.concept import ConceptScheme
|
|
31
|
-
from pysdmx.model.dataflow import Dataflow, DataStructureDefinition
|
|
32
|
-
from pysdmx.model.dataset import ActionType, Dataset
|
|
33
|
-
from pysdmx.model.submission import SubmissionResult
|
|
34
48
|
|
|
35
49
|
|
|
36
|
-
class Header(Struct, kw_only=True):
|
|
37
|
-
"""Header for the SDMX messages.
|
|
50
|
+
class Header(Struct, repr_omit_defaults=True, kw_only=True):
|
|
51
|
+
"""Header for the SDMX messages.
|
|
52
|
+
|
|
53
|
+
Represents the Header of an SDMX message, containing metadata about the
|
|
54
|
+
message such as the sender, receiver, and other relevant information.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
id: Unique identifier for the message. (default: generated UUID)
|
|
58
|
+
test: Indicates if the message is a test message. (default: False)
|
|
59
|
+
prepared: Timestamp when the message was prepared.
|
|
60
|
+
(default: current UTC time)
|
|
61
|
+
sender: Organisation that sent the message.
|
|
62
|
+
(default: Organisation with id "ZZZ")
|
|
63
|
+
receiver: Optional Organisation that received the message.
|
|
64
|
+
(default: None)
|
|
65
|
+
source: Optional source of the message. (default: None)
|
|
66
|
+
dataset_action: Optional action for the dataset
|
|
67
|
+
(only for SDMX Data messages). (default: None)
|
|
68
|
+
structure: Dimension at observation mapping
|
|
69
|
+
(dict with short URN as key and Dimension ID as value)
|
|
70
|
+
(only for SDMX Data Messages)
|
|
71
|
+
(Overridden by dimension_at_observation argument in writers).
|
|
72
|
+
(default: None)
|
|
73
|
+
dataset_id: DatasetID defined at SDMX-ML
|
|
74
|
+
(only for SDMX-ML Data messages). (default: None)
|
|
75
|
+
"""
|
|
38
76
|
|
|
39
77
|
id: str = str(uuid.uuid4())
|
|
40
78
|
test: bool = False
|
|
@@ -46,53 +84,84 @@ class Header(Struct, kw_only=True):
|
|
|
46
84
|
structure: Optional[Dict[str, str]] = None
|
|
47
85
|
dataset_id: Optional[str] = None
|
|
48
86
|
|
|
87
|
+
def __post_init__(self) -> None:
|
|
88
|
+
"""Header post-initialization."""
|
|
89
|
+
if isinstance(self.sender, str):
|
|
90
|
+
self.sender = Organisation(id=self.sender)
|
|
49
91
|
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
if isinstance(self.receiver, str):
|
|
93
|
+
self.receiver = Organisation(id=self.receiver)
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
"""Custom string representation without the class name."""
|
|
97
|
+
processed_output = []
|
|
98
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
99
|
+
processed_output.append(f"{attr}: {value}")
|
|
100
|
+
return f"{', '.join(processed_output)}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
|
|
104
|
+
"""Message class holds the content of an SDMX Structure Message.
|
|
52
105
|
|
|
53
106
|
Attributes:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
SDMX Data Message in any format.
|
|
58
|
-
submission: Sequence of SubmissionResult objects. They represent the
|
|
59
|
-
contents of a SDMX Submission Message.
|
|
107
|
+
header: The header of the SDMX message.
|
|
108
|
+
structures: Sequence of MaintainableArtefact objects.
|
|
109
|
+
They represent the contents of a Structure Message.
|
|
60
110
|
"""
|
|
61
111
|
|
|
62
112
|
header: Optional[Header] = None
|
|
63
|
-
structures: Optional[
|
|
64
|
-
Sequence[
|
|
65
|
-
Union[
|
|
66
|
-
ItemScheme,
|
|
67
|
-
DataStructureDefinition,
|
|
68
|
-
Dataflow,
|
|
69
|
-
]
|
|
70
|
-
]
|
|
71
|
-
] = None
|
|
72
|
-
data: Optional[Sequence[Dataset]] = None
|
|
73
|
-
submission: Optional[Sequence[SubmissionResult]] = None
|
|
113
|
+
structures: Optional[Sequence[MaintainableArtefact]] = None
|
|
74
114
|
|
|
75
115
|
def __post_init__(self) -> None:
|
|
76
116
|
"""Checks if the content is valid."""
|
|
77
117
|
if self.structures is not None:
|
|
78
118
|
for obj_ in self.structures:
|
|
79
|
-
if not isinstance(
|
|
80
|
-
obj_, (ItemScheme, DataStructureDefinition, Dataflow)
|
|
81
|
-
):
|
|
119
|
+
if not isinstance(obj_, (MaintainableArtefact)):
|
|
82
120
|
raise Invalid(
|
|
83
121
|
f"Invalid structure: {type(obj_).__name__} ",
|
|
84
122
|
"Check the docs on structures.",
|
|
85
123
|
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
|
|
125
|
+
def __str__(self) -> str:
|
|
126
|
+
"""Custom string representation with detailed structure counts."""
|
|
127
|
+
processed_output = []
|
|
128
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
129
|
+
if attr in ["data", "structures"] and value:
|
|
130
|
+
# Count occurrences of each class in structures
|
|
131
|
+
class_counts: Dict[str, int] = {}
|
|
132
|
+
for obj in value:
|
|
133
|
+
class_name = obj.__class__.__name__
|
|
134
|
+
class_counts[class_name] = (
|
|
135
|
+
class_counts.get(class_name, 0) + 1
|
|
94
136
|
)
|
|
95
137
|
|
|
138
|
+
# Format the counts
|
|
139
|
+
value = ", ".join(
|
|
140
|
+
f"{count} {class_name.lower()}"
|
|
141
|
+
for class_name, count in class_counts.items()
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Handle sequences and omit empty ones
|
|
145
|
+
if (
|
|
146
|
+
isinstance(value, Sequence)
|
|
147
|
+
and not isinstance(value, str)
|
|
148
|
+
and not value
|
|
149
|
+
):
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
processed_output.append(f"{attr}: {value}")
|
|
153
|
+
return f"{', '.join(processed_output)}"
|
|
154
|
+
|
|
155
|
+
def __repr__(self) -> str:
|
|
156
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
157
|
+
attrs = []
|
|
158
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
159
|
+
# Omit empty sequences
|
|
160
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
161
|
+
continue
|
|
162
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
163
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
164
|
+
|
|
96
165
|
def __get_elements(self, type_: Type[Any]) -> List[Any]:
|
|
97
166
|
"""Returns a list of elements of a specific type."""
|
|
98
167
|
if self.structures is None:
|
|
@@ -105,6 +174,14 @@ class Message(Struct, frozen=True):
|
|
|
105
174
|
structures.append(element)
|
|
106
175
|
return structures
|
|
107
176
|
|
|
177
|
+
def __get_enumerations(
|
|
178
|
+
self, type_: Type[Any], is_vl: bool = False
|
|
179
|
+
) -> List[Any]:
|
|
180
|
+
"""Returns a list of elements of a specific type."""
|
|
181
|
+
enums = self.__get_elements(type_)
|
|
182
|
+
t = "valuelist" if is_vl else "codelist"
|
|
183
|
+
return [e for e in enums if e.sdmx_type == t]
|
|
184
|
+
|
|
108
185
|
def __get_single_structure(
|
|
109
186
|
self,
|
|
110
187
|
type_: Type[Union[ItemScheme, DataStructureDefinition, Dataflow]],
|
|
@@ -131,7 +208,7 @@ class Message(Struct, frozen=True):
|
|
|
131
208
|
|
|
132
209
|
def get_codelists(self) -> List[Codelist]:
|
|
133
210
|
"""Returns the Codelists."""
|
|
134
|
-
return self.
|
|
211
|
+
return self.__get_enumerations(Codelist, False)
|
|
135
212
|
|
|
136
213
|
def get_concept_schemes(self) -> List[ConceptScheme]:
|
|
137
214
|
"""Returns the Concept Schemes."""
|
|
@@ -169,6 +246,123 @@ class Message(Struct, frozen=True):
|
|
|
169
246
|
"""Returns a specific Dataflow."""
|
|
170
247
|
return self.__get_single_structure(Dataflow, short_urn)
|
|
171
248
|
|
|
249
|
+
def get_transformation_schemes(self) -> List[TransformationScheme]:
|
|
250
|
+
"""Returns the TransformationSchemes."""
|
|
251
|
+
return self.__get_elements(TransformationScheme)
|
|
252
|
+
|
|
253
|
+
def get_user_defined_operator_schemes(
|
|
254
|
+
self,
|
|
255
|
+
) -> List[UserDefinedOperatorScheme]:
|
|
256
|
+
"""Returns the UserDefinedOperatorSchemes."""
|
|
257
|
+
return self.__get_elements(UserDefinedOperatorScheme)
|
|
258
|
+
|
|
259
|
+
def get_ruleset_schemes(self) -> List[RulesetScheme]:
|
|
260
|
+
"""Returns the RulesetSchemes."""
|
|
261
|
+
return self.__get_elements(RulesetScheme)
|
|
262
|
+
|
|
263
|
+
def get_category_schemes(self) -> List[CategoryScheme]:
|
|
264
|
+
"""Returns the CategorySchemes."""
|
|
265
|
+
return self.__get_elements(CategoryScheme)
|
|
266
|
+
|
|
267
|
+
def get_value_lists(self) -> List[Codelist]:
|
|
268
|
+
"""Returns the Codelists."""
|
|
269
|
+
return self.__get_enumerations(Codelist, True)
|
|
270
|
+
|
|
271
|
+
def get_hierarchies(self) -> List[Hierarchy]:
|
|
272
|
+
"""Returns the HierarchyCodelists."""
|
|
273
|
+
return self.__get_elements(Hierarchy)
|
|
274
|
+
|
|
275
|
+
def get_hierarchy_associations(self) -> List[HierarchyAssociation]:
|
|
276
|
+
"""Returns the HierarchyAssociations."""
|
|
277
|
+
return self.__get_elements(HierarchyAssociation)
|
|
278
|
+
|
|
279
|
+
def get_data_provider_schemes(self) -> List[DataProviderScheme]:
|
|
280
|
+
"""Returns the DataProviderSchemes."""
|
|
281
|
+
return self.__get_elements(DataProviderScheme)
|
|
282
|
+
|
|
283
|
+
def get_provision_agreements(self) -> List[ProvisionAgreement]:
|
|
284
|
+
"""Returns the ProvisionAgreements."""
|
|
285
|
+
return self.__get_elements(ProvisionAgreement)
|
|
286
|
+
|
|
287
|
+
def get_structure_maps(self) -> List[StructureMap]:
|
|
288
|
+
"""Returns the StructureMaps."""
|
|
289
|
+
return self.__get_elements(StructureMap)
|
|
290
|
+
|
|
291
|
+
def get_representation_maps(
|
|
292
|
+
self,
|
|
293
|
+
) -> List[Union[MultiRepresentationMap, RepresentationMap]]:
|
|
294
|
+
"""Returns the RepresentationMaps."""
|
|
295
|
+
out = []
|
|
296
|
+
out.extend(self.__get_elements(RepresentationMap))
|
|
297
|
+
out.extend(self.__get_elements(MultiRepresentationMap))
|
|
298
|
+
return out
|
|
299
|
+
|
|
300
|
+
def get_categorisations(self) -> List[Categorisation]:
|
|
301
|
+
"""Returns the Categorisations."""
|
|
302
|
+
return self.__get_elements(Categorisation)
|
|
303
|
+
|
|
304
|
+
def get_custom_type_schemes(self) -> List[CustomTypeScheme]:
|
|
305
|
+
"""Returns the CustomType Schemes."""
|
|
306
|
+
return self.__get_elements(CustomTypeScheme)
|
|
307
|
+
|
|
308
|
+
def get_vtl_mapping_schemes(self) -> List[VtlMappingScheme]:
|
|
309
|
+
"""Returns the VTL Mapping Schemes."""
|
|
310
|
+
return self.__get_elements(VtlMappingScheme)
|
|
311
|
+
|
|
312
|
+
def get_name_personalisation_schemes(
|
|
313
|
+
self,
|
|
314
|
+
) -> List[NamePersonalisationScheme]:
|
|
315
|
+
"""Returns the NamePersonalisationSchemes."""
|
|
316
|
+
return self.__get_elements(NamePersonalisationScheme)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class MetadataMessage(Struct, frozen=True):
|
|
320
|
+
"""Message class holds the content of an SDMX Reference Metadata Message.
|
|
321
|
+
|
|
322
|
+
Attributes:
|
|
323
|
+
header: The header of the SDMX message.
|
|
324
|
+
reports: Sequence of metadata reports.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
header: Optional[Header] = None
|
|
328
|
+
reports: Optional[Sequence[MetadataReport]] = None
|
|
329
|
+
|
|
330
|
+
def get_reports(self) -> Sequence[MetadataReport]:
|
|
331
|
+
"""Returns the metadata reports."""
|
|
332
|
+
if self.reports:
|
|
333
|
+
return self.reports
|
|
334
|
+
else:
|
|
335
|
+
raise NotFound("No metadata reports werefound in the message.")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class Message(StructureMessage, frozen=True):
|
|
339
|
+
"""Message class holds the content of SDMX Message.
|
|
340
|
+
|
|
341
|
+
Attributes:
|
|
342
|
+
header: The header of the SDMX message.
|
|
343
|
+
structures: Sequence of MaintainableArtefact objects.
|
|
344
|
+
data: Sequence of Dataset objects. They represent the contents of a
|
|
345
|
+
SDMX Data Message in any format.
|
|
346
|
+
submission: Sequence of SubmissionResult objects. They represent the
|
|
347
|
+
contents of a SDMX Submission Message.
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
data: Optional[Sequence[Dataset]] = None
|
|
351
|
+
submission: Optional[Sequence[SubmissionResult]] = None
|
|
352
|
+
|
|
353
|
+
def __post_init__(self) -> None:
|
|
354
|
+
"""Checks if the content is valid."""
|
|
355
|
+
super().__post_init__()
|
|
356
|
+
if self.data is not None:
|
|
357
|
+
for data_value in self.data:
|
|
358
|
+
if not isinstance(data_value, Dataset):
|
|
359
|
+
raise Invalid(
|
|
360
|
+
f"Invalid data type: "
|
|
361
|
+
f"{type(data_value).__name__} "
|
|
362
|
+
f"for Data Message, requires a Dataset object.",
|
|
363
|
+
"Check the docs for the proper structure on data.",
|
|
364
|
+
)
|
|
365
|
+
|
|
172
366
|
def get_datasets(self) -> Sequence[Dataset]:
|
|
173
367
|
"""Returns the Datasets."""
|
|
174
368
|
if self.data is not None:
|
|
@@ -188,17 +382,3 @@ class Message(Struct, frozen=True):
|
|
|
188
382
|
f"No Dataset with Short URN {short_urn} found in data.",
|
|
189
383
|
"Could not find the requested Dataset.",
|
|
190
384
|
)
|
|
191
|
-
|
|
192
|
-
def get_transformation_schemes(self) -> List[TransformationScheme]:
|
|
193
|
-
"""Returns the TransformationSchemes."""
|
|
194
|
-
return self.__get_elements(TransformationScheme)
|
|
195
|
-
|
|
196
|
-
def get_user_defined_operator_schemes(
|
|
197
|
-
self,
|
|
198
|
-
) -> List[UserDefinedOperatorScheme]:
|
|
199
|
-
"""Returns the UserDefinedOperatorSchemes."""
|
|
200
|
-
return self.__get_elements(UserDefinedOperatorScheme)
|
|
201
|
-
|
|
202
|
-
def get_ruleset_schemes(self) -> List[RulesetScheme]:
|
|
203
|
-
"""Returns the RulesetSchemes."""
|
|
204
|
-
return self.__get_elements(RulesetScheme)
|
pysdmx/model/metadata.py
CHANGED
|
@@ -13,8 +13,14 @@ from typing import Any, Dict, Iterator, List, Optional, Sequence
|
|
|
13
13
|
|
|
14
14
|
from msgspec import Struct
|
|
15
15
|
|
|
16
|
+
from pysdmx.model.__base import Annotation, MaintainableArtefact
|
|
17
|
+
from pysdmx.model.concept import Facets
|
|
18
|
+
from pysdmx.model.dataset import ActionType
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
class MetadataAttribute(
|
|
22
|
+
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
|
|
23
|
+
):
|
|
18
24
|
"""An entry in a metadata report.
|
|
19
25
|
|
|
20
26
|
An attribute is iterable, as it may contain other attributes.
|
|
@@ -29,40 +35,70 @@ class MetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
29
35
|
id: str
|
|
30
36
|
value: Optional[Any] = None
|
|
31
37
|
attributes: Sequence["MetadataAttribute"] = ()
|
|
38
|
+
annotations: Sequence[Annotation] = ()
|
|
39
|
+
format: Optional[Facets] = None
|
|
32
40
|
|
|
33
41
|
def __iter__(self) -> Iterator["MetadataAttribute"]:
|
|
34
42
|
"""Return an iterator over the list of attributes."""
|
|
35
43
|
yield from self.attributes
|
|
36
44
|
|
|
37
45
|
def __str__(self) -> str:
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
"""Custom string representation without the class name."""
|
|
47
|
+
processed_output = []
|
|
48
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
49
|
+
# str is taken as a Sequence, so we need to check it's not a str
|
|
50
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
51
|
+
# Handle non-empty lists
|
|
52
|
+
if not value:
|
|
53
|
+
continue
|
|
54
|
+
class_name = value[0].__class__.__name__
|
|
55
|
+
value = f"{len(value)} {class_name.lower()}s"
|
|
56
|
+
|
|
57
|
+
processed_output.append(f"{attr}: {value}")
|
|
58
|
+
return f"{', '.join(processed_output)}"
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
62
|
+
attrs = []
|
|
63
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
64
|
+
# Omit empty sequences
|
|
65
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
66
|
+
continue
|
|
67
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
68
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MetadataReport(MaintainableArtefact, frozen=True, omit_defaults=True):
|
|
43
72
|
"""An organized collection of metadata.
|
|
44
73
|
|
|
45
74
|
A metadata report is iterable and it is also possible to directly
|
|
46
75
|
retrieve an attribute using its ID.
|
|
47
76
|
|
|
48
77
|
Attributes:
|
|
49
|
-
id: The identifier of the report (e.g. DTI_MACRO).
|
|
50
|
-
name: The name of the report (e.g. "Configuration metadata for
|
|
51
|
-
the MACRO dataflow").
|
|
52
78
|
metadataflow: The URN of the dataflow for which the report
|
|
53
79
|
belongs.
|
|
54
80
|
targets: The URN(s) of SDMX artefact(s) to which the report relates.
|
|
55
81
|
attributes: The list of metadata attributes included in the report.
|
|
56
82
|
Attributes may contain other attributes.
|
|
57
|
-
|
|
83
|
+
metadataProvisionAgreement: reference to a metadata provision
|
|
84
|
+
agreement
|
|
85
|
+
publicationPeriod: The reporting period to which the metadata report
|
|
86
|
+
relates
|
|
87
|
+
publicationYear: The year when the report was published
|
|
88
|
+
reportingBegin: The oldest period to which the report relates.
|
|
89
|
+
reportingEnd: The most recent period to which the report relates.
|
|
90
|
+
action: The action to be performed by the receiver.
|
|
58
91
|
"""
|
|
59
92
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
metadataflow: str = ""
|
|
94
|
+
targets: Sequence[str] = ()
|
|
95
|
+
attributes: Sequence[MetadataAttribute] = ()
|
|
96
|
+
metadataProvisionAgreement: Optional[str] = None
|
|
97
|
+
publicationPeriod: Optional[str] = None
|
|
98
|
+
publicationYear: Optional[str] = None
|
|
99
|
+
reportingBegin: Optional[str] = None
|
|
100
|
+
reportingEnd: Optional[str] = None
|
|
101
|
+
action: Optional[ActionType] = None
|
|
66
102
|
|
|
67
103
|
def __iter__(self) -> Iterator[MetadataAttribute]:
|
|
68
104
|
"""Return an iterator over the list of report attributes."""
|
|
@@ -99,6 +135,33 @@ class MetadataReport(Struct, frozen=True, omit_defaults=True):
|
|
|
99
135
|
return out[0]
|
|
100
136
|
return None
|
|
101
137
|
|
|
138
|
+
def __str__(self) -> str:
|
|
139
|
+
"""Custom string representation without the class name."""
|
|
140
|
+
processed_output = []
|
|
141
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
142
|
+
# str is taken as a Sequence, so we need to check it's not a str
|
|
143
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
144
|
+
# Handle non-empty lists
|
|
145
|
+
if not value:
|
|
146
|
+
continue
|
|
147
|
+
class_name = value[0].__class__.__name__
|
|
148
|
+
if class_name == "MetadataAttribute":
|
|
149
|
+
class_name = "Metadata Attribute"
|
|
150
|
+
value = f"{len(value)} {class_name.lower()}s"
|
|
151
|
+
|
|
152
|
+
processed_output.append(f"{attr}: {value}")
|
|
153
|
+
return f"{', '.join(processed_output)}"
|
|
154
|
+
|
|
155
|
+
def __repr__(self) -> str:
|
|
156
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
157
|
+
attrs = []
|
|
158
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
159
|
+
# Omit empty sequences
|
|
160
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
161
|
+
continue
|
|
162
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
163
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
164
|
+
|
|
102
165
|
|
|
103
166
|
def merge_attributes(
|
|
104
167
|
attrs: Sequence[MetadataAttribute],
|
pysdmx/model/submission.py
CHANGED
|
@@ -11,10 +11,15 @@ class SubmissionResult(Struct, frozen=True):
|
|
|
11
11
|
status: str
|
|
12
12
|
|
|
13
13
|
def __str__(self) -> str:
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
f"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
"""Custom string representation without the class name."""
|
|
15
|
+
processed_output = []
|
|
16
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
17
|
+
processed_output.append(f"{attr}: {value}")
|
|
18
|
+
return f"{', '.join(processed_output)}"
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
22
|
+
attrs = []
|
|
23
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
24
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
25
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
pysdmx/model/vtl.py
CHANGED
|
@@ -6,7 +6,14 @@ from typing import Literal, Optional, Sequence, Union
|
|
|
6
6
|
from msgspec import Struct
|
|
7
7
|
|
|
8
8
|
from pysdmx.errors import Invalid
|
|
9
|
-
from pysdmx.model
|
|
9
|
+
from pysdmx.model import Codelist, Concept
|
|
10
|
+
from pysdmx.model.__base import (
|
|
11
|
+
DataflowRef,
|
|
12
|
+
Item,
|
|
13
|
+
ItemReference,
|
|
14
|
+
ItemScheme,
|
|
15
|
+
Reference,
|
|
16
|
+
)
|
|
10
17
|
from pysdmx.model.dataflow import Dataflow
|
|
11
18
|
|
|
12
19
|
|
|
@@ -92,14 +99,14 @@ class VtlDataflowMapping(
|
|
|
92
99
|
class VtlCodelistMapping(VtlMapping, frozen=True, omit_defaults=True):
|
|
93
100
|
"""Single mapping with a codelist."""
|
|
94
101
|
|
|
95
|
-
codelist: str = ""
|
|
102
|
+
codelist: Union[str, Codelist, Reference] = ""
|
|
96
103
|
codelist_alias: str = ""
|
|
97
104
|
|
|
98
105
|
|
|
99
106
|
class VtlConceptMapping(VtlMapping, frozen=True, omit_defaults=True):
|
|
100
107
|
"""Single mapping with a concept."""
|
|
101
108
|
|
|
102
|
-
concept: str = ""
|
|
109
|
+
concept: Union[str, Concept, ItemReference] = ""
|
|
103
110
|
concept_alias: str = ""
|
|
104
111
|
|
|
105
112
|
|
|
@@ -141,30 +148,38 @@ class VtlScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
|
141
148
|
class CustomTypeScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
142
149
|
"""A collection of custom specifications for VTL basic scalar types."""
|
|
143
150
|
|
|
151
|
+
items: Sequence[CustomType] = ()
|
|
152
|
+
|
|
144
153
|
|
|
145
154
|
class NamePersonalisationScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
146
155
|
"""A collection of name personalisations."""
|
|
147
156
|
|
|
157
|
+
items: Sequence[NamePersonalisation] = ()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class VtlMappingScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
161
|
+
"""A collection of VTL mappings."""
|
|
162
|
+
|
|
148
163
|
|
|
149
164
|
class RulesetScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
150
165
|
"""A collection of rulesets."""
|
|
151
166
|
|
|
152
|
-
vtl_mapping_scheme: Optional[str] =
|
|
167
|
+
vtl_mapping_scheme: Optional[Union[str, VtlMappingScheme, Reference]] = (
|
|
168
|
+
None
|
|
169
|
+
)
|
|
153
170
|
items: Sequence[Ruleset] = ()
|
|
154
171
|
|
|
155
172
|
|
|
156
173
|
class UserDefinedOperatorScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
157
174
|
"""A collection of user-defined operators."""
|
|
158
175
|
|
|
159
|
-
vtl_mapping_scheme: Optional[str] =
|
|
160
|
-
|
|
176
|
+
vtl_mapping_scheme: Optional[Union[str, VtlMappingScheme, Reference]] = (
|
|
177
|
+
None
|
|
178
|
+
)
|
|
179
|
+
ruleset_schemes: Sequence[Union[str, RulesetScheme, Reference]] = ()
|
|
161
180
|
items: Sequence[UserDefinedOperator] = ()
|
|
162
181
|
|
|
163
182
|
|
|
164
|
-
class VtlMappingScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
165
|
-
"""A collection of VTL mappings."""
|
|
166
|
-
|
|
167
|
-
|
|
168
183
|
class TransformationScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
169
184
|
"""VTL Transformation Scheme class.
|
|
170
185
|
|
|
@@ -195,9 +210,11 @@ class TransformationScheme(VtlScheme, frozen=True, omit_defaults=True):
|
|
|
195
210
|
The user-defined operator schemes.
|
|
196
211
|
"""
|
|
197
212
|
|
|
198
|
-
vtl_mapping_scheme: Optional[VtlMappingScheme] = None
|
|
199
|
-
name_personalisation_scheme: Optional[
|
|
200
|
-
|
|
213
|
+
vtl_mapping_scheme: Optional[Union[VtlMappingScheme, Reference]] = None
|
|
214
|
+
name_personalisation_scheme: Optional[
|
|
215
|
+
Union[NamePersonalisationScheme, Reference]
|
|
216
|
+
] = None
|
|
217
|
+
custom_type_scheme: Optional[Union[CustomTypeScheme, Reference]] = None
|
|
201
218
|
ruleset_schemes: Sequence[Union[RulesetScheme, Reference]] = ()
|
|
202
219
|
user_defined_operator_schemes: Sequence[
|
|
203
220
|
Union[UserDefinedOperatorScheme, Reference]
|
pysdmx/toolkit/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Toolkit for PySDMX."""
|