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/__base.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import Any,
|
|
2
|
+
from typing import Any, Optional, Sequence, Union
|
|
3
3
|
|
|
4
4
|
from msgspec import Struct
|
|
5
5
|
|
|
6
6
|
from pysdmx.errors import Invalid
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class Annotation(
|
|
9
|
+
class Annotation(
|
|
10
|
+
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
|
|
11
|
+
):
|
|
10
12
|
"""Annotation class.
|
|
11
13
|
|
|
12
14
|
It is used to convey extra information to describe any
|
|
13
15
|
SDMX construct.
|
|
16
|
+
|
|
14
17
|
This information may be in the form of a URL reference and/or
|
|
15
18
|
a multilingual text (represented by the association to
|
|
16
19
|
InternationalString).
|
|
@@ -18,9 +21,9 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
|
|
|
18
21
|
Attributes:
|
|
19
22
|
id: The identifier of the annotation.
|
|
20
23
|
title: The title of the annotation.
|
|
21
|
-
text: The text of the annotation.
|
|
22
|
-
url: The URL of the annotation.
|
|
23
24
|
type: The type of the annotation.
|
|
25
|
+
url: The URL of the annotation.
|
|
26
|
+
text: The value of the annotation.
|
|
24
27
|
"""
|
|
25
28
|
|
|
26
29
|
id: Optional[str] = None
|
|
@@ -29,6 +32,11 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
|
|
|
29
32
|
url: Optional[str] = None
|
|
30
33
|
type: Optional[str] = None
|
|
31
34
|
|
|
35
|
+
@property
|
|
36
|
+
def value(self) -> Optional[str]:
|
|
37
|
+
"""Alias to text."""
|
|
38
|
+
return self.text
|
|
39
|
+
|
|
32
40
|
def __post_init__(self) -> None:
|
|
33
41
|
"""Additional validation checks for Annotation."""
|
|
34
42
|
if (
|
|
@@ -47,16 +55,27 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
|
|
|
47
55
|
)
|
|
48
56
|
|
|
49
57
|
def __str__(self) -> str:
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
for
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
"""Custom string representation without the class name."""
|
|
59
|
+
processed_output = []
|
|
60
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
61
|
+
processed_output.append(f"{attr}: {value}")
|
|
62
|
+
return f"{', '.join(processed_output)}"
|
|
63
|
+
|
|
64
|
+
def __repr__(self) -> str:
|
|
65
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
66
|
+
attrs = []
|
|
67
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
68
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
69
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AnnotableArtefact(
|
|
73
|
+
Struct,
|
|
74
|
+
frozen=True,
|
|
75
|
+
omit_defaults=True,
|
|
76
|
+
repr_omit_defaults=True,
|
|
77
|
+
kw_only=True,
|
|
78
|
+
):
|
|
60
79
|
"""Annotable Artefact class.
|
|
61
80
|
|
|
62
81
|
Superclass of all SDMX artefacts.
|
|
@@ -68,22 +87,32 @@ class AnnotableArtefact(Struct, frozen=True, omit_defaults=True, kw_only=True):
|
|
|
68
87
|
|
|
69
88
|
annotations: Sequence[Annotation] = ()
|
|
70
89
|
|
|
71
|
-
@classmethod
|
|
72
|
-
def __all_annotations(cls) -> Dict[str, Any]:
|
|
73
|
-
class_attributes = {}
|
|
74
|
-
for c in cls.__mro__:
|
|
75
|
-
if "__annotations__" in c.__dict__:
|
|
76
|
-
class_attributes.update(c.__annotations__)
|
|
77
|
-
return dict(reversed(list(class_attributes.items())))
|
|
78
|
-
|
|
79
90
|
def __str__(self) -> str:
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
for
|
|
83
|
-
|
|
84
|
-
if
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
"""Custom string representation without the class name."""
|
|
92
|
+
processed_output = []
|
|
93
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
94
|
+
# str is taken as a Sequence, so we need to check it's not a str
|
|
95
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
96
|
+
# Handle non-empty lists
|
|
97
|
+
if value:
|
|
98
|
+
class_name = value[0].__class__.__name__
|
|
99
|
+
value = f"{len(value)} {class_name.lower()}s"
|
|
100
|
+
# redundant if check for python 3.9 and lower versions cov
|
|
101
|
+
if not value:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
processed_output.append(f"{attr}: {value}")
|
|
105
|
+
return f"{', '.join(processed_output)}"
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
109
|
+
attrs = []
|
|
110
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
111
|
+
# Omit empty sequences
|
|
112
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
113
|
+
continue
|
|
114
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
115
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
87
116
|
|
|
88
117
|
|
|
89
118
|
class IdentifiableArtefact(AnnotableArtefact, frozen=True, omit_defaults=True):
|
|
@@ -143,7 +172,9 @@ class Item(NameableArtefact, frozen=True, omit_defaults=True):
|
|
|
143
172
|
"""
|
|
144
173
|
|
|
145
174
|
|
|
146
|
-
class Contact(
|
|
175
|
+
class Contact(
|
|
176
|
+
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
|
|
177
|
+
):
|
|
147
178
|
"""Contact details such as the name of a contact and his email address.
|
|
148
179
|
|
|
149
180
|
Attributes:
|
|
@@ -172,6 +203,31 @@ class Contact(Struct, frozen=True, omit_defaults=True):
|
|
|
172
203
|
uris: Optional[Sequence[str]] = None
|
|
173
204
|
emails: Optional[Sequence[str]] = None
|
|
174
205
|
|
|
206
|
+
def __str__(self) -> str:
|
|
207
|
+
"""Custom string representation without the class name."""
|
|
208
|
+
processed_output = []
|
|
209
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
210
|
+
# str is taken as a Sequence, so we need to check it's not a str
|
|
211
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
212
|
+
# Handle non-empty lists
|
|
213
|
+
if not value:
|
|
214
|
+
continue
|
|
215
|
+
class_name = value[0].__class__.__name__
|
|
216
|
+
value = f"{len(value)} {class_name.lower()}s"
|
|
217
|
+
|
|
218
|
+
processed_output.append(f"{attr}: {value}")
|
|
219
|
+
return f"{', '.join(processed_output)}"
|
|
220
|
+
|
|
221
|
+
def __repr__(self) -> str:
|
|
222
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
223
|
+
attrs = []
|
|
224
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
225
|
+
# Omit empty sequences
|
|
226
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
227
|
+
continue
|
|
228
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
229
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
230
|
+
|
|
175
231
|
|
|
176
232
|
class Organisation(Item, frozen=True, omit_defaults=True):
|
|
177
233
|
"""Organisation class.
|
|
@@ -272,7 +328,9 @@ class ItemScheme(MaintainableArtefact, frozen=True, omit_defaults=True):
|
|
|
272
328
|
is_partial: bool = False
|
|
273
329
|
|
|
274
330
|
|
|
275
|
-
class DataflowRef(
|
|
331
|
+
class DataflowRef(
|
|
332
|
+
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True, tag=True
|
|
333
|
+
):
|
|
276
334
|
"""A unique reference to a dataflow.
|
|
277
335
|
|
|
278
336
|
Attributes:
|
|
@@ -304,11 +362,18 @@ class DataflowRef(Struct, frozen=True, omit_defaults=True, tag=True):
|
|
|
304
362
|
)
|
|
305
363
|
|
|
306
364
|
def __str__(self) -> str:
|
|
307
|
-
"""
|
|
365
|
+
"""Short_urn representation of the dataflow reference."""
|
|
308
366
|
return f"Dataflow={self.agency}:{self.id}({self.version})"
|
|
309
367
|
|
|
368
|
+
def __repr__(self) -> str:
|
|
369
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
370
|
+
attrs = []
|
|
371
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
372
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
373
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
374
|
+
|
|
310
375
|
|
|
311
|
-
class Reference(Struct, frozen=True):
|
|
376
|
+
class Reference(Struct, frozen=True, repr_omit_defaults=True):
|
|
312
377
|
"""The coordinates of an SDMX maintainable artefact.
|
|
313
378
|
|
|
314
379
|
Attributes:
|
|
@@ -328,7 +393,7 @@ class Reference(Struct, frozen=True):
|
|
|
328
393
|
return f"{self.sdmx_type}={self.agency}:{self.id}({self.version})"
|
|
329
394
|
|
|
330
395
|
|
|
331
|
-
class ItemReference(Struct, frozen=True, tag=True):
|
|
396
|
+
class ItemReference(Struct, frozen=True, repr_omit_defaults=True, tag=True):
|
|
332
397
|
"""The coordinates of an SDMX non-nested item.
|
|
333
398
|
|
|
334
399
|
Attributes:
|
pysdmx/model/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ import msgspec
|
|
|
10
10
|
|
|
11
11
|
from pysdmx.model.__base import (
|
|
12
12
|
Agency,
|
|
13
|
+
Annotation,
|
|
13
14
|
Contact,
|
|
14
15
|
DataConsumer,
|
|
15
16
|
DataflowRef,
|
|
@@ -34,6 +35,7 @@ from pysdmx.model.dataflow import (
|
|
|
34
35
|
Components,
|
|
35
36
|
Dataflow,
|
|
36
37
|
DataflowInfo,
|
|
38
|
+
DataStructureDefinition,
|
|
37
39
|
ProvisionAgreement,
|
|
38
40
|
Role,
|
|
39
41
|
Schema,
|
|
@@ -140,6 +142,7 @@ def decoders(type: Type, obj: Any) -> Any: # type: ignore[type-arg]
|
|
|
140
142
|
__all__ = [
|
|
141
143
|
"Agency",
|
|
142
144
|
"AgencyScheme",
|
|
145
|
+
"Annotation",
|
|
143
146
|
"ArrayBoundaries",
|
|
144
147
|
"Categorisation",
|
|
145
148
|
"Category",
|
|
@@ -163,6 +166,7 @@ __all__ = [
|
|
|
163
166
|
"DatePatternMap",
|
|
164
167
|
"DataProvider",
|
|
165
168
|
"DataProviderScheme",
|
|
169
|
+
"DataStructureDefinition",
|
|
166
170
|
"Facets",
|
|
167
171
|
"FromVtlMapping",
|
|
168
172
|
"FixedValueMap",
|
pysdmx/model/category.py
CHANGED
|
@@ -135,6 +135,26 @@ class CategoryScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
|
135
135
|
flows.extend(self.__extract_flows(sub))
|
|
136
136
|
return flows
|
|
137
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 empty lists
|
|
145
|
+
if not value:
|
|
146
|
+
continue
|
|
147
|
+
class_name = value[0].__class__.__name__
|
|
148
|
+
class_name = (
|
|
149
|
+
class_name.lower() + "s"
|
|
150
|
+
if attr != "items"
|
|
151
|
+
else "categories"
|
|
152
|
+
)
|
|
153
|
+
value = f"{len(value)} {class_name}"
|
|
154
|
+
|
|
155
|
+
processed_output.append(f"{attr}: {value}")
|
|
156
|
+
return f"{', '.join(processed_output)}"
|
|
157
|
+
|
|
138
158
|
|
|
139
159
|
class Categorisation(
|
|
140
160
|
MaintainableArtefact, frozen=True, omit_defaults=True, kw_only=True
|
pysdmx/model/code.py
CHANGED
|
@@ -17,7 +17,7 @@ representation of hierarchical relationships to hierarchies only.
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from datetime import datetime
|
|
20
|
-
from typing import Iterator, Literal, Optional, Sequence
|
|
20
|
+
from typing import Iterator, Literal, Optional, Sequence, Union
|
|
21
21
|
|
|
22
22
|
from msgspec import Struct
|
|
23
23
|
|
|
@@ -95,7 +95,9 @@ class Codelist(ItemScheme, frozen=True, omit_defaults=True, tag=True):
|
|
|
95
95
|
return bool(self.__getitem__(id_))
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
class HierarchicalCode(
|
|
98
|
+
class HierarchicalCode(
|
|
99
|
+
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
|
|
100
|
+
):
|
|
99
101
|
"""A code, as used in a hierarchy.
|
|
100
102
|
|
|
101
103
|
Hierachical codes may contain other codes.
|
|
@@ -135,11 +137,28 @@ class HierarchicalCode(Struct, frozen=True, omit_defaults=True):
|
|
|
135
137
|
yield from self.codes
|
|
136
138
|
|
|
137
139
|
def __str__(self) -> str:
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
"""Custom string representation without the class name."""
|
|
141
|
+
processed_output = []
|
|
142
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
143
|
+
# str is taken as a Sequence, so we need to check it's not a str
|
|
144
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
145
|
+
if not value:
|
|
146
|
+
continue
|
|
147
|
+
# Handle non-empty lists
|
|
148
|
+
value = f"{len(value)} hierarchical codes"
|
|
149
|
+
|
|
150
|
+
processed_output.append(f"{attr}: {value}")
|
|
151
|
+
return f"{', '.join(processed_output)}"
|
|
152
|
+
|
|
153
|
+
def __repr__(self) -> str:
|
|
154
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
155
|
+
attrs = []
|
|
156
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
157
|
+
# Omit empty sequences
|
|
158
|
+
if isinstance(value, (list, tuple, set)) and not value:
|
|
159
|
+
continue
|
|
160
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
161
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
143
162
|
|
|
144
163
|
|
|
145
164
|
class Hierarchy(
|
|
@@ -171,10 +190,12 @@ class Hierarchy(
|
|
|
171
190
|
assume that the operator property references a VTL operator
|
|
172
191
|
representing a sum. This can then be used for validation purposes,
|
|
173
192
|
to check that A = B + C.
|
|
193
|
+
is_partial: Whether the hierarchy is partial.
|
|
174
194
|
"""
|
|
175
195
|
|
|
176
196
|
codes: Sequence[HierarchicalCode] = ()
|
|
177
197
|
operator: Optional[str] = None
|
|
198
|
+
is_partial: bool = True
|
|
178
199
|
|
|
179
200
|
def __iter__(self) -> Iterator[HierarchicalCode]:
|
|
180
201
|
"""Return an iterator over the list of codes."""
|
|
@@ -287,7 +308,7 @@ class HierarchyAssociation(
|
|
|
287
308
|
):
|
|
288
309
|
"""Links a hierarchy to a component withing the context of a dataflow."""
|
|
289
310
|
|
|
290
|
-
hierarchy: Optional[Hierarchy] = None
|
|
311
|
+
hierarchy: Optional[Union[Hierarchy, str]] = None
|
|
291
312
|
component_ref: str = ""
|
|
292
313
|
context_ref: str = ""
|
|
293
314
|
operator: Optional[str] = None
|
pysdmx/model/concept.py
CHANGED
|
@@ -24,13 +24,17 @@ class DataType(str, Enum):
|
|
|
24
24
|
"""The expected data type for a concept."""
|
|
25
25
|
|
|
26
26
|
ALPHA = "Alpha"
|
|
27
|
-
"""
|
|
27
|
+
"""The alphabetic character set of A-Z, a-z.."""
|
|
28
28
|
ALPHA_NUM = "AlphaNumeric"
|
|
29
|
-
"""
|
|
29
|
+
"""The character set of A-Z, a-z and 0-9."""
|
|
30
|
+
BASIC_TIME_PERIOD = "BasicTimePeriod"
|
|
31
|
+
"""The ISO standard periods."""
|
|
30
32
|
BIG_INTEGER = "BigInteger"
|
|
31
33
|
"""Immutable arbitrary-precision signed integer."""
|
|
32
34
|
BOOLEAN = "Boolean"
|
|
33
35
|
"""True or False."""
|
|
36
|
+
COUNT = "Count"
|
|
37
|
+
"""A simple incrementing integer type."""
|
|
34
38
|
DATE = "GregorianDay"
|
|
35
39
|
"""A ISO 8601 date (e.g. ``2011-06-17``)."""
|
|
36
40
|
DATE_TIME = "DateTime"
|
|
@@ -41,8 +45,12 @@ class DataType(str, Enum):
|
|
|
41
45
|
"""Immutable arbitrary-precision signed decimal number."""
|
|
42
46
|
DOUBLE = "Double"
|
|
43
47
|
"""A decimal number (8 bytes)."""
|
|
48
|
+
DURATION = "Duration"
|
|
49
|
+
"""An ISO 8601 duration."""
|
|
44
50
|
FLOAT = "Float"
|
|
45
51
|
"""A decimal number (4 bytes)."""
|
|
52
|
+
GREGORIAN_TIME_PERIOD = "GregorianTimePeriod"
|
|
53
|
+
"""This is the union of YEAR, YEAR_MONTH, and DATE."""
|
|
46
54
|
INTEGER = "Integer"
|
|
47
55
|
"""A whole number (4 bytes)."""
|
|
48
56
|
LONG = "Long"
|
|
@@ -52,24 +60,50 @@ class DataType(str, Enum):
|
|
|
52
60
|
MONTH_DAY = "MonthDay"
|
|
53
61
|
"""A month day in the ISO 8601 calendar (e.g. ``--12-31``)."""
|
|
54
62
|
NUMERIC = "Numeric"
|
|
55
|
-
"""
|
|
63
|
+
"""The simple numeric character set of 0-9, treated as a string."""
|
|
56
64
|
PERIOD = "ObservationalTimePeriod"
|
|
57
65
|
"""A reporting period. The format varies with the frequency."""
|
|
66
|
+
REP_DAY = "ReportingDay"
|
|
67
|
+
"""The SDMX reporting period for daily frequency (e.g. 2025-D001)."""
|
|
68
|
+
REP_MONTH = "ReportingMonth"
|
|
69
|
+
"""The SDMX reporting period for monthly frequency (e.g. 2025-M01)."""
|
|
70
|
+
REP_QUARTER = "ReportingQuarter"
|
|
71
|
+
"""The SDMX reporting period for quarterly frequency (e.g. 2025-Q1)."""
|
|
72
|
+
REP_SEMESTER = "ReportingSemester"
|
|
73
|
+
"""The SDMX reporting period for half-yearly frequency (e.g. 2025-S1)."""
|
|
74
|
+
REP_TRIMESTER = "ReportingTrimester"
|
|
75
|
+
"""The SDMX reporting period for trimestrial frequency (e.g. 2025-T1)."""
|
|
76
|
+
REP_WEEK = "ReportingWeek"
|
|
77
|
+
"""The SDMX reporting period for weekly frequency (e.g. 2025-W01)."""
|
|
78
|
+
REP_YEAR = "ReportingYear"
|
|
79
|
+
"""The SDMX reporting period for yearly frequency (e.g. 2025-A1)."""
|
|
58
80
|
SHORT = "Short"
|
|
59
81
|
"""A whole number (2 bytes)."""
|
|
82
|
+
STD_TIME_PERIOD = "StandardTimePeriod"
|
|
83
|
+
"""The ISO standard periods and the SDMX reporting periods."""
|
|
60
84
|
STRING = "String"
|
|
61
85
|
"""A string (as immutable sequence of Unicode code points)."""
|
|
62
86
|
TIME = "Time"
|
|
63
87
|
"""An ISO 8601 time (e.g. ``12:50:42``)."""
|
|
64
88
|
URI = "URI"
|
|
65
89
|
"""A uniform resource identifier, such as a URL."""
|
|
90
|
+
XHTML = "XHTML"
|
|
91
|
+
"""XHTML (or HTML) markup."""
|
|
66
92
|
YEAR = "GregorianYear"
|
|
67
93
|
"""An ISO 8601 year (e.g. ``2000``)."""
|
|
68
94
|
YEAR_MONTH = "GregorianYearMonth"
|
|
69
95
|
"""An ISO 8601 year and month (e.g. ``2000-01``)."""
|
|
70
96
|
|
|
97
|
+
def __str__(self) -> str:
|
|
98
|
+
"""Data Type String representation."""
|
|
99
|
+
return self.value
|
|
100
|
+
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
"""Data Type String representation."""
|
|
103
|
+
return f"{self.__class__.__name__}.{self._name_}"
|
|
104
|
+
|
|
71
105
|
|
|
72
|
-
class Facets(Struct, frozen=True, omit_defaults=True):
|
|
106
|
+
class Facets(Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True):
|
|
73
107
|
"""Additional information about the concept expected values.
|
|
74
108
|
|
|
75
109
|
The facets that apply vary with the type. For example,
|
|
@@ -104,15 +138,22 @@ class Facets(Struct, frozen=True, omit_defaults=True):
|
|
|
104
138
|
"""Indicates the ending point of a sequence."""
|
|
105
139
|
is_sequence: bool = False
|
|
106
140
|
"""Whether the values are intended to be ordered."""
|
|
141
|
+
is_multilingual: bool = False
|
|
142
|
+
"""Whether the text can be in multiple languages."""
|
|
107
143
|
|
|
108
144
|
def __str__(self) -> str:
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
for
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
145
|
+
"""Custom string representation without the class name."""
|
|
146
|
+
processed_output = []
|
|
147
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
148
|
+
processed_output.append(f"{attr}: {value}")
|
|
149
|
+
return f"{', '.join(processed_output)}"
|
|
150
|
+
|
|
151
|
+
def __repr__(self) -> str:
|
|
152
|
+
"""Custom __repr__ that omits empty sequences."""
|
|
153
|
+
attrs = []
|
|
154
|
+
for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
|
|
155
|
+
attrs.append(f"{attr}={repr(value)}")
|
|
156
|
+
return f"{self.__class__.__name__}({', '.join(attrs)})"
|
|
116
157
|
|
|
117
158
|
|
|
118
159
|
class Concept(Item, frozen=True, omit_defaults=True, tag=True):
|