pysdmx 1.10.0rc1__py3-none-any.whl → 1.10.1__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/fmr/__init__.py +6 -4
- pysdmx/api/qb/data.py +2 -0
- pysdmx/io/json/fusion/messages/dsd.py +8 -2
- pysdmx/io/json/sdmxjson2/messages/__init__.py +4 -0
- pysdmx/io/json/sdmxjson2/messages/code.py +16 -6
- pysdmx/io/json/sdmxjson2/messages/constraint.py +235 -16
- pysdmx/io/json/sdmxjson2/messages/dsd.py +67 -18
- pysdmx/io/json/sdmxjson2/messages/map.py +5 -4
- pysdmx/io/json/sdmxjson2/messages/metadataflow.py +1 -0
- pysdmx/io/json/sdmxjson2/messages/msd.py +18 -10
- pysdmx/io/json/sdmxjson2/messages/schema.py +2 -2
- pysdmx/io/json/sdmxjson2/messages/structure.py +81 -44
- pysdmx/io/json/sdmxjson2/messages/vtl.py +13 -9
- pysdmx/io/json/sdmxjson2/reader/doc_validation.py +4 -0
- pysdmx/io/xml/__write_data_aux.py +2 -0
- pysdmx/model/__base.py +46 -1
- pysdmx/model/__init__.py +18 -0
- pysdmx/model/category.py +17 -0
- pysdmx/model/constraint.py +69 -0
- pysdmx/model/dataflow.py +8 -5
- pysdmx/model/message.py +80 -71
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/METADATA +1 -1
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/RECORD +26 -25
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/WHEEL +0 -0
- {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,7 +15,6 @@ from pysdmx.io.json.sdmxjson2.messages.core import (
|
|
|
15
15
|
from pysdmx.io.json.sdmxjson2.messages.dsd import (
|
|
16
16
|
_find_concept,
|
|
17
17
|
_get_concept_reference,
|
|
18
|
-
_get_json_representation,
|
|
19
18
|
_get_representation,
|
|
20
19
|
)
|
|
21
20
|
from pysdmx.model import (
|
|
@@ -28,6 +27,13 @@ from pysdmx.model import (
|
|
|
28
27
|
from pysdmx.util import parse_item_urn
|
|
29
28
|
|
|
30
29
|
|
|
30
|
+
def _get_attr_repr(comp: MetadataComponent) -> Optional[JsonRepresentation]:
|
|
31
|
+
enum = comp.local_enum_ref if comp.local_enum_ref else None
|
|
32
|
+
return JsonRepresentation.from_model(
|
|
33
|
+
comp.local_dtype, enum, comp.local_facets, None
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
32
38
|
"""SDMX-JSON payload for an attribute."""
|
|
33
39
|
|
|
@@ -80,11 +86,13 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
80
86
|
def from_model(self, cmp: MetadataComponent) -> "JsonMetadataAttribute":
|
|
81
87
|
"""Converts a pysdmx metadata attribute to an SDMX-JSON one."""
|
|
82
88
|
concept = _get_concept_reference(cmp)
|
|
83
|
-
repr =
|
|
89
|
+
repr = _get_attr_repr(cmp)
|
|
84
90
|
|
|
85
91
|
min_occurs = cmp.array_def.min_size if cmp.array_def else 0
|
|
86
|
-
if cmp.array_def is None
|
|
87
|
-
max_occurs: Union[int, Literal["unbounded"]] =
|
|
92
|
+
if cmp.array_def is None:
|
|
93
|
+
max_occurs: Union[int, Literal["unbounded"]] = 1
|
|
94
|
+
elif cmp.array_def.max_size is None:
|
|
95
|
+
max_occurs = "unbounded"
|
|
88
96
|
else:
|
|
89
97
|
max_occurs = cmp.array_def.max_size
|
|
90
98
|
|
|
@@ -95,9 +103,9 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
|
|
|
95
103
|
minOccurs=min_occurs,
|
|
96
104
|
maxOccurs=max_occurs,
|
|
97
105
|
isPresentational=cmp.is_presentational,
|
|
98
|
-
metadataAttributes=
|
|
99
|
-
JsonMetadataAttribute.from_model(c) for c in cmp.components
|
|
100
|
-
|
|
106
|
+
metadataAttributes=tuple(
|
|
107
|
+
[JsonMetadataAttribute.from_model(c) for c in cmp.components]
|
|
108
|
+
),
|
|
101
109
|
)
|
|
102
110
|
|
|
103
111
|
|
|
@@ -119,9 +127,9 @@ class JsonMetadataAttributes(Struct, frozen=True, omit_defaults=True):
|
|
|
119
127
|
) -> "JsonMetadataAttributes":
|
|
120
128
|
"""Converts a pysdmx list of metadata attributes to SDMX-JSON."""
|
|
121
129
|
return JsonMetadataAttributes(
|
|
122
|
-
metadataAttributes=
|
|
123
|
-
JsonMetadataAttribute.from_model(a) for a in attributes
|
|
124
|
-
|
|
130
|
+
metadataAttributes=tuple(
|
|
131
|
+
[JsonMetadataAttribute.from_model(a) for a in attributes]
|
|
132
|
+
)
|
|
125
133
|
)
|
|
126
134
|
|
|
127
135
|
|
|
@@ -21,7 +21,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
21
21
|
dataStructures: Sequence[JsonDataStructure]
|
|
22
22
|
valuelists: Sequence[JsonValuelist] = ()
|
|
23
23
|
codelists: Sequence[JsonCodelist] = ()
|
|
24
|
-
|
|
24
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
25
25
|
|
|
26
26
|
def to_model(
|
|
27
27
|
self,
|
|
@@ -32,7 +32,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
|
|
|
32
32
|
self.conceptSchemes,
|
|
33
33
|
self.codelists,
|
|
34
34
|
self.valuelists,
|
|
35
|
-
self.
|
|
35
|
+
self.dataConstraints,
|
|
36
36
|
)
|
|
37
37
|
return comps, grps # type: ignore[return-value]
|
|
38
38
|
|
|
@@ -17,6 +17,7 @@ from pysdmx.io.json.sdmxjson2.messages.code import (
|
|
|
17
17
|
JsonValuelist,
|
|
18
18
|
)
|
|
19
19
|
from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptScheme
|
|
20
|
+
from pysdmx.io.json.sdmxjson2.messages.constraint import JsonDataConstraint
|
|
20
21
|
from pysdmx.io.json.sdmxjson2.messages.core import JsonHeader
|
|
21
22
|
from pysdmx.io.json.sdmxjson2.messages.dataflow import JsonDataflow
|
|
22
23
|
from pysdmx.io.json.sdmxjson2.messages.dsd import JsonDataStructure
|
|
@@ -49,90 +50,95 @@ from pysdmx.model.message import StructureMessage
|
|
|
49
50
|
class JsonStructures(Struct, frozen=True, omit_defaults=True):
|
|
50
51
|
"""The allowed strutures."""
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
agencySchemes: Sequence[JsonAgencyScheme] = ()
|
|
54
|
+
categorisations: Sequence[JsonCategorisation] = ()
|
|
53
55
|
categorySchemes: Sequence[JsonCategoryScheme] = ()
|
|
54
|
-
conceptSchemes: Sequence[JsonConceptScheme] = ()
|
|
55
56
|
codelists: Sequence[JsonCodelist] = ()
|
|
56
|
-
|
|
57
|
+
conceptSchemes: Sequence[JsonConceptScheme] = ()
|
|
58
|
+
customTypeSchemes: Sequence[JsonCustomTypeScheme] = ()
|
|
59
|
+
dataConstraints: Sequence[JsonDataConstraint] = ()
|
|
60
|
+
dataflows: Sequence[JsonDataflow] = ()
|
|
61
|
+
dataProviderSchemes: Sequence[JsonDataProviderScheme] = ()
|
|
62
|
+
dataStructures: Sequence[JsonDataStructure] = ()
|
|
57
63
|
hierarchies: Sequence[JsonHierarchy] = ()
|
|
58
64
|
hierarchyAssociations: Sequence[JsonHierarchyAssociation] = ()
|
|
59
|
-
agencySchemes: Sequence[JsonAgencyScheme] = ()
|
|
60
|
-
dataProviderSchemes: Sequence[JsonDataProviderScheme] = ()
|
|
61
|
-
metadataProviderSchemes: Sequence[JsonMetadataProviderScheme] = ()
|
|
62
|
-
dataflows: Sequence[JsonDataflow] = ()
|
|
63
|
-
provisionAgreements: Sequence[JsonProvisionAgreement] = ()
|
|
64
65
|
metadataflows: Sequence[JsonMetadataflow] = ()
|
|
66
|
+
metadataProviderSchemes: Sequence[JsonMetadataProviderScheme] = ()
|
|
65
67
|
metadataProvisionAgreements: Sequence[JsonMetadataProvisionAgreement] = ()
|
|
66
68
|
metadataStructures: Sequence[JsonMetadataStructure] = ()
|
|
67
|
-
structureMaps: Sequence[JsonStructureMap] = ()
|
|
68
|
-
representationMaps: Sequence[JsonRepresentationMap] = ()
|
|
69
|
-
categorisations: Sequence[JsonCategorisation] = ()
|
|
70
|
-
customTypeSchemes: Sequence[JsonCustomTypeScheme] = ()
|
|
71
|
-
vtlMappingSchemes: Sequence[JsonVtlMappingScheme] = ()
|
|
72
69
|
namePersonalisationSchemes: Sequence[JsonNamePersonalisationScheme] = ()
|
|
70
|
+
provisionAgreements: Sequence[JsonProvisionAgreement] = ()
|
|
71
|
+
representationMaps: Sequence[JsonRepresentationMap] = ()
|
|
73
72
|
rulesetSchemes: Sequence[JsonRulesetScheme] = ()
|
|
73
|
+
structureMaps: Sequence[JsonStructureMap] = ()
|
|
74
74
|
transformationSchemes: Sequence[JsonTransformationScheme] = ()
|
|
75
75
|
userDefinedOperatorSchemes: Sequence[JsonUserDefinedOperatorScheme] = ()
|
|
76
|
+
valueLists: Sequence[JsonValuelist] = ()
|
|
77
|
+
vtlMappingSchemes: Sequence[JsonVtlMappingScheme] = ()
|
|
76
78
|
|
|
77
79
|
def to_model(self) -> Sequence[MaintainableArtefact]:
|
|
78
80
|
"""Map to pysdmx artefacts."""
|
|
79
81
|
structures = [] # type: ignore[var-annotated]
|
|
80
82
|
structures.extend(
|
|
81
|
-
i.to_model(
|
|
82
|
-
self.conceptSchemes, self.codelists, self.valueLists, ()
|
|
83
|
-
)
|
|
84
|
-
for i in self.dataStructures
|
|
83
|
+
i.to_model(self.dataflows) for i in self.agencySchemes
|
|
85
84
|
)
|
|
85
|
+
structures.extend(i.to_model() for i in self.categorisations)
|
|
86
86
|
structures.extend(i.to_model() for i in self.categorySchemes)
|
|
87
|
-
structures.extend(
|
|
88
|
-
i.to_model(self.codelists) for i in self.conceptSchemes
|
|
89
|
-
)
|
|
90
87
|
structures.extend(i.to_model() for i in self.codelists)
|
|
91
|
-
structures.extend(i.to_model() for i in self.valueLists)
|
|
92
|
-
structures.extend(i.to_model(self.codelists) for i in self.hierarchies)
|
|
93
88
|
structures.extend(
|
|
94
|
-
i.to_model(self.
|
|
95
|
-
for i in self.hierarchyAssociations
|
|
89
|
+
i.to_model(self.codelists) for i in self.conceptSchemes
|
|
96
90
|
)
|
|
91
|
+
structures.extend(i.to_model() for i in self.customTypeSchemes)
|
|
92
|
+
structures.extend(i.to_model() for i in self.dataConstraints)
|
|
97
93
|
structures.extend(
|
|
98
|
-
i.to_model(
|
|
94
|
+
i.to_model(
|
|
95
|
+
self.dataStructures,
|
|
96
|
+
self.conceptSchemes,
|
|
97
|
+
self.valueLists,
|
|
98
|
+
self.codelists,
|
|
99
|
+
)
|
|
100
|
+
for i in self.dataflows
|
|
99
101
|
)
|
|
100
102
|
structures.extend(
|
|
101
103
|
i.to_model(self.provisionAgreements)
|
|
102
104
|
for i in self.dataProviderSchemes
|
|
103
105
|
)
|
|
106
|
+
structures.extend(
|
|
107
|
+
i.to_model(
|
|
108
|
+
self.conceptSchemes, self.codelists, self.valueLists, ()
|
|
109
|
+
)
|
|
110
|
+
for i in self.dataStructures
|
|
111
|
+
)
|
|
112
|
+
structures.extend(i.to_model(self.codelists) for i in self.hierarchies)
|
|
113
|
+
structures.extend(
|
|
114
|
+
i.to_model(self.hierarchies, self.codelists)
|
|
115
|
+
for i in self.hierarchyAssociations
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
structures.extend(i.to_model() for i in self.metadataflows)
|
|
104
119
|
structures.extend(
|
|
105
120
|
i.to_model(self.metadataProvisionAgreements)
|
|
106
121
|
for i in self.metadataProviderSchemes
|
|
107
122
|
)
|
|
123
|
+
structures.extend(
|
|
124
|
+
i.to_model() for i in self.metadataProvisionAgreements
|
|
125
|
+
)
|
|
108
126
|
structures.extend(
|
|
109
127
|
i.to_model(self.conceptSchemes, self.codelists, self.valueLists)
|
|
110
128
|
for i in self.metadataStructures
|
|
111
129
|
)
|
|
112
130
|
structures.extend(
|
|
113
|
-
i.to_model(
|
|
114
|
-
self.dataStructures,
|
|
115
|
-
self.conceptSchemes,
|
|
116
|
-
self.valueLists,
|
|
117
|
-
self.codelists,
|
|
118
|
-
)
|
|
119
|
-
for i in self.dataflows
|
|
131
|
+
i.to_model() for i in self.namePersonalisationSchemes
|
|
120
132
|
)
|
|
121
133
|
structures.extend(i.to_model() for i in self.provisionAgreements)
|
|
122
|
-
structures.extend(i.to_model() for i in self.metadataflows)
|
|
123
134
|
structures.extend(
|
|
124
|
-
i.to_model()
|
|
135
|
+
i.to_model(bool(len(i.source) > 1 or len(i.target) > 1))
|
|
136
|
+
for i in self.representationMaps
|
|
125
137
|
)
|
|
138
|
+
structures.extend(i.to_model() for i in self.rulesetSchemes)
|
|
126
139
|
structures.extend(
|
|
127
140
|
i.to_model(self.representationMaps) for i in self.structureMaps
|
|
128
141
|
)
|
|
129
|
-
structures.extend(i.to_model() for i in self.categorisations)
|
|
130
|
-
structures.extend(i.to_model() for i in self.customTypeSchemes)
|
|
131
|
-
structures.extend(i.to_model() for i in self.vtlMappingSchemes)
|
|
132
|
-
structures.extend(
|
|
133
|
-
i.to_model() for i in self.namePersonalisationSchemes
|
|
134
|
-
)
|
|
135
|
-
structures.extend(i.to_model() for i in self.rulesetSchemes)
|
|
136
142
|
structures.extend(
|
|
137
143
|
i.to_model(
|
|
138
144
|
self.customTypeSchemes,
|
|
@@ -146,9 +152,8 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
146
152
|
structures.extend(
|
|
147
153
|
i.to_model() for i in self.userDefinedOperatorSchemes
|
|
148
154
|
)
|
|
149
|
-
for
|
|
150
|
-
|
|
151
|
-
structures.append(rm.to_model(multi))
|
|
155
|
+
structures.extend(i.to_model() for i in self.valueLists)
|
|
156
|
+
structures.extend(i.to_model() for i in self.vtlMappingSchemes)
|
|
152
157
|
return structures
|
|
153
158
|
|
|
154
159
|
@classmethod
|
|
@@ -264,6 +269,33 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
264
269
|
hierarchies = tuple(
|
|
265
270
|
[JsonHierarchy.from_model(h) for h in msg.get_hierarchies()]
|
|
266
271
|
)
|
|
272
|
+
constraints = tuple(
|
|
273
|
+
[
|
|
274
|
+
JsonDataConstraint.from_model(c)
|
|
275
|
+
for c in msg.get_data_constraints()
|
|
276
|
+
]
|
|
277
|
+
)
|
|
278
|
+
mpas = tuple(
|
|
279
|
+
[
|
|
280
|
+
JsonMetadataProvisionAgreement.from_model(c)
|
|
281
|
+
for c in msg.get_metadata_provision_agreements()
|
|
282
|
+
]
|
|
283
|
+
)
|
|
284
|
+
mprvs = tuple(
|
|
285
|
+
[
|
|
286
|
+
JsonMetadataProviderScheme.from_model(c)
|
|
287
|
+
for c in msg.get_metadata_provider_schemes()
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
mdfs = tuple(
|
|
291
|
+
[JsonMetadataflow.from_model(c) for c in msg.get_metadataflows()]
|
|
292
|
+
)
|
|
293
|
+
msds = tuple(
|
|
294
|
+
[
|
|
295
|
+
JsonMetadataStructure.from_model(c)
|
|
296
|
+
for c in msg.get_metadata_structures()
|
|
297
|
+
]
|
|
298
|
+
)
|
|
267
299
|
return JsonStructures(
|
|
268
300
|
agencySchemes=agencies,
|
|
269
301
|
categorisations=categorisations,
|
|
@@ -271,11 +303,16 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
|
|
|
271
303
|
codelists=codelists,
|
|
272
304
|
conceptSchemes=concept_schemes,
|
|
273
305
|
customTypeSchemes=custom_types,
|
|
306
|
+
dataConstraints=constraints,
|
|
274
307
|
dataflows=dataflows,
|
|
275
308
|
dataProviderSchemes=data_providers,
|
|
276
309
|
dataStructures=data_structures,
|
|
277
310
|
hierarchies=hierarchies,
|
|
278
311
|
hierarchyAssociations=hier_associations,
|
|
312
|
+
metadataflows=mdfs,
|
|
313
|
+
metadataProviderSchemes=mprvs,
|
|
314
|
+
metadataProvisionAgreements=mpas,
|
|
315
|
+
metadataStructures=msds,
|
|
279
316
|
namePersonalisationSchemes=name_personalisations,
|
|
280
317
|
provisionAgreements=agreements,
|
|
281
318
|
representationMaps=representations_maps,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Collection of SDMX-JSON schemas for VTL artefacts."""
|
|
2
2
|
|
|
3
|
-
from typing import Literal, Optional, Sequence
|
|
3
|
+
from typing import Dict, Literal, Optional, Sequence
|
|
4
4
|
|
|
5
5
|
from msgspec import Struct
|
|
6
6
|
|
|
@@ -506,33 +506,37 @@ class JsonRulesetScheme(ItemSchemeType, frozen=True, omit_defaults=True):
|
|
|
506
506
|
class JsonToVtlMapping(Struct, frozen=True, omit_defaults=True):
|
|
507
507
|
"""SDMX-JSON payload for To VTL mappings."""
|
|
508
508
|
|
|
509
|
-
toVtlSubSpace: Sequence[str]
|
|
510
|
-
|
|
509
|
+
toVtlSubSpace: Dict[str, Sequence[str]]
|
|
510
|
+
method: Optional[str] = None
|
|
511
511
|
|
|
512
512
|
def to_model(self) -> ToVtlMapping:
|
|
513
513
|
"""Converts deserialized class to pysdmx model class."""
|
|
514
|
-
return ToVtlMapping(self.toVtlSubSpace, self.
|
|
514
|
+
return ToVtlMapping(self.toVtlSubSpace["keys"], self.method)
|
|
515
515
|
|
|
516
516
|
@classmethod
|
|
517
517
|
def from_model(cls, mapping: ToVtlMapping) -> "JsonToVtlMapping":
|
|
518
518
|
"""Converts a pysdmx "to VTL" mapping to an SDMX-JSON one."""
|
|
519
|
-
return JsonToVtlMapping(
|
|
519
|
+
return JsonToVtlMapping(
|
|
520
|
+
{"keys": mapping.to_vtl_sub_space}, mapping.method
|
|
521
|
+
)
|
|
520
522
|
|
|
521
523
|
|
|
522
524
|
class JsonFromVtlMapping(Struct, frozen=True, omit_defaults=True):
|
|
523
525
|
"""SDMX-JSON payload for from VTL mappings."""
|
|
524
526
|
|
|
525
|
-
fromVtlSuperSpace: Sequence[str]
|
|
526
|
-
|
|
527
|
+
fromVtlSuperSpace: Dict[str, Sequence[str]]
|
|
528
|
+
method: Optional[str] = None
|
|
527
529
|
|
|
528
530
|
def to_model(self) -> FromVtlMapping:
|
|
529
531
|
"""Converts deserialized class to pysdmx model class."""
|
|
530
|
-
return FromVtlMapping(self.fromVtlSuperSpace, self.
|
|
532
|
+
return FromVtlMapping(self.fromVtlSuperSpace["keys"], self.method)
|
|
531
533
|
|
|
532
534
|
@classmethod
|
|
533
535
|
def from_model(cls, mapping: FromVtlMapping) -> "JsonFromVtlMapping":
|
|
534
536
|
"""Converts a pysdmx "from VTL" mapping to an SDMX-JSON one."""
|
|
535
|
-
return JsonFromVtlMapping(
|
|
537
|
+
return JsonFromVtlMapping(
|
|
538
|
+
{"keys": mapping.from_vtl_sub_space}, mapping.method
|
|
539
|
+
)
|
|
536
540
|
|
|
537
541
|
|
|
538
542
|
class JsonVtlMapping(NameableType, frozen=True, omit_defaults=True):
|
|
@@ -26,6 +26,8 @@ def check_dimension_at_observation(
|
|
|
26
26
|
for key, value in dimension_at_observation.items():
|
|
27
27
|
if key not in datasets:
|
|
28
28
|
raise Invalid(f"Dataset {key} not found in Message content.")
|
|
29
|
+
if value == ALL_DIM:
|
|
30
|
+
continue
|
|
29
31
|
writing_validation(datasets[key])
|
|
30
32
|
dataset = datasets[key]
|
|
31
33
|
components = dataset.structure.components # type: ignore[union-attr]
|
pysdmx/model/__base.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from datetime import datetime
|
|
2
|
-
from typing import Any, Optional, Sequence, Union
|
|
3
|
+
from typing import Any, Literal, Optional, Sequence, Union
|
|
3
4
|
|
|
4
5
|
from msgspec import Struct
|
|
5
6
|
|
|
@@ -327,6 +328,50 @@ class ItemScheme(MaintainableArtefact, frozen=True, omit_defaults=True):
|
|
|
327
328
|
items: Sequence[Item] = ()
|
|
328
329
|
is_partial: bool = False
|
|
329
330
|
|
|
331
|
+
def search(
|
|
332
|
+
self,
|
|
333
|
+
query: str,
|
|
334
|
+
use_regex: bool = False,
|
|
335
|
+
fields: Literal["name", "description", "all"] = "all",
|
|
336
|
+
) -> Sequence[Item]:
|
|
337
|
+
"""Search for items matching the query.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
query: The substring or regex pattern to search for.
|
|
341
|
+
use_regex: Whether to treat the query as a regex (default: False).
|
|
342
|
+
fields: The fields to search in (default: all textual fields).
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Items that match the query.
|
|
346
|
+
"""
|
|
347
|
+
if not query:
|
|
348
|
+
raise Invalid(
|
|
349
|
+
"Invalid search", "The query string cannot be empty."
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Determine which fields to search in
|
|
353
|
+
search_fields = (
|
|
354
|
+
["name", "description"] if fields == "all" else [fields]
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Transform plain text queries into a regex
|
|
358
|
+
if not use_regex:
|
|
359
|
+
query = re.escape(query)
|
|
360
|
+
|
|
361
|
+
pattern = re.compile(query, re.IGNORECASE if not use_regex else 0)
|
|
362
|
+
|
|
363
|
+
all_items = getattr(self, "all_items", "")
|
|
364
|
+
items = all_items if all_items else self.items
|
|
365
|
+
|
|
366
|
+
return [
|
|
367
|
+
item # type: ignore[misc]
|
|
368
|
+
for item in items
|
|
369
|
+
if any(
|
|
370
|
+
pattern.search(str(getattr(item, field, "")))
|
|
371
|
+
for field in search_fields
|
|
372
|
+
)
|
|
373
|
+
]
|
|
374
|
+
|
|
330
375
|
|
|
331
376
|
class DataflowRef(
|
|
332
377
|
Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True, tag=True
|
pysdmx/model/__init__.py
CHANGED
|
@@ -29,6 +29,16 @@ from pysdmx.model.code import (
|
|
|
29
29
|
HierarchyAssociation,
|
|
30
30
|
)
|
|
31
31
|
from pysdmx.model.concept import Concept, ConceptScheme, DataType, Facets
|
|
32
|
+
from pysdmx.model.constraint import (
|
|
33
|
+
ConstraintAttachment,
|
|
34
|
+
CubeKeyValue,
|
|
35
|
+
CubeRegion,
|
|
36
|
+
CubeValue,
|
|
37
|
+
DataConstraint,
|
|
38
|
+
DataKey,
|
|
39
|
+
DataKeyValue,
|
|
40
|
+
KeySet,
|
|
41
|
+
)
|
|
32
42
|
from pysdmx.model.dataflow import (
|
|
33
43
|
ArrayBoundaries,
|
|
34
44
|
Component,
|
|
@@ -161,9 +171,16 @@ __all__ = [
|
|
|
161
171
|
"ComponentMap",
|
|
162
172
|
"Concept",
|
|
163
173
|
"ConceptScheme",
|
|
174
|
+
"ConstraintAttachment",
|
|
164
175
|
"Contact",
|
|
176
|
+
"CubeKeyValue",
|
|
177
|
+
"CubeRegion",
|
|
178
|
+
"CubeValue",
|
|
165
179
|
"DataConsumer",
|
|
166
180
|
"DataConsumerScheme",
|
|
181
|
+
"DataConstraint",
|
|
182
|
+
"DataKey",
|
|
183
|
+
"DataKeyValue",
|
|
167
184
|
"Dataflow",
|
|
168
185
|
"DataflowInfo",
|
|
169
186
|
"DataflowRef",
|
|
@@ -180,6 +197,7 @@ __all__ = [
|
|
|
180
197
|
"HierarchyAssociation",
|
|
181
198
|
"ImplicitComponentMap",
|
|
182
199
|
"ItemReference",
|
|
200
|
+
"KeySet",
|
|
183
201
|
"MetadataAttribute",
|
|
184
202
|
"MetadataComponent",
|
|
185
203
|
"Metadataflow",
|
pysdmx/model/category.py
CHANGED
|
@@ -91,6 +91,15 @@ class CategoryScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
|
91
91
|
flows.update(self.__extract_flows(cat))
|
|
92
92
|
return list(flows)
|
|
93
93
|
|
|
94
|
+
@property
|
|
95
|
+
def all_items(self) -> Sequence[Category]:
|
|
96
|
+
"""Get all the categories in the category scheme as a flat list.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A flat list of all the categories present in the category scheme.
|
|
100
|
+
"""
|
|
101
|
+
return self.__get_categories(self.categories)
|
|
102
|
+
|
|
94
103
|
def __iter__(self) -> Iterator[Category]:
|
|
95
104
|
"""Return an iterator over the list of categories."""
|
|
96
105
|
yield from self.categories
|
|
@@ -160,6 +169,14 @@ class CategoryScheme(ItemScheme, frozen=True, omit_defaults=True):
|
|
|
160
169
|
processed_output.append(f"{attr}: {value}")
|
|
161
170
|
return f"{', '.join(processed_output)}"
|
|
162
171
|
|
|
172
|
+
def __get_categories(self, cats: Sequence[Category]) -> Sequence[Category]:
|
|
173
|
+
out = []
|
|
174
|
+
for cat in cats:
|
|
175
|
+
out.append(cat)
|
|
176
|
+
if cat.categories:
|
|
177
|
+
out.extend(self.__get_categories(cat.categories))
|
|
178
|
+
return out
|
|
179
|
+
|
|
163
180
|
|
|
164
181
|
class Categorisation(
|
|
165
182
|
MaintainableArtefact, frozen=True, omit_defaults=True, kw_only=True
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Model for SDMX Data Constraints."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Sequence
|
|
5
|
+
|
|
6
|
+
from msgspec import Struct
|
|
7
|
+
|
|
8
|
+
from pysdmx.model.__base import MaintainableArtefact
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CubeValue(Struct, frozen=True, omit_defaults=True):
|
|
12
|
+
"""A value of the cube, with optional business validity."""
|
|
13
|
+
|
|
14
|
+
value: str
|
|
15
|
+
valid_from: Optional[datetime] = None
|
|
16
|
+
valid_to: Optional[datetime] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CubeKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
20
|
+
"""The list of values for a cube's component."""
|
|
21
|
+
|
|
22
|
+
id: str
|
|
23
|
+
values: Sequence[CubeValue]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CubeRegion(Struct, frozen=True, omit_defaults=True):
|
|
27
|
+
"""A cube region, with its associated values (by default, included)."""
|
|
28
|
+
|
|
29
|
+
key_values: Sequence[CubeKeyValue]
|
|
30
|
+
is_included: bool = True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConstraintAttachment(Struct, frozen=True, omit_defaults=True):
|
|
34
|
+
"""The artefacts to which the data constraint is attached."""
|
|
35
|
+
|
|
36
|
+
data_provider: Optional[str]
|
|
37
|
+
data_structures: Optional[Sequence[str]] = None
|
|
38
|
+
dataflows: Optional[Sequence[str]] = None
|
|
39
|
+
provision_agreements: Optional[Sequence[str]] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DataKeyValue(Struct, frozen=True, omit_defaults=True):
|
|
43
|
+
"""A key value, i.e. a component of the key (e.g. FREQ=M)."""
|
|
44
|
+
|
|
45
|
+
id: str
|
|
46
|
+
value: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DataKey(Struct, frozen=True, omit_defaults=True):
|
|
50
|
+
"""A data key, i.e. one value per dimension in the data key."""
|
|
51
|
+
|
|
52
|
+
keys_values: Sequence[DataKeyValue]
|
|
53
|
+
valid_from: Optional[datetime] = None
|
|
54
|
+
valid_to: Optional[datetime] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class KeySet(Struct, frozen=True, omit_defaults=True):
|
|
58
|
+
"""A set of keys, inluded by default."""
|
|
59
|
+
|
|
60
|
+
keys: Sequence[DataKey]
|
|
61
|
+
is_included: bool
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DataConstraint(MaintainableArtefact, frozen=True, omit_defaults=True):
|
|
65
|
+
"""A data constraint, defining the allowed or available values."""
|
|
66
|
+
|
|
67
|
+
constraint_attachment: Optional[ConstraintAttachment] = None
|
|
68
|
+
cube_regions: Sequence[CubeRegion] = ()
|
|
69
|
+
key_sets: Sequence[KeySet] = ()
|
pysdmx/model/dataflow.py
CHANGED
|
@@ -101,10 +101,11 @@ class Component(
|
|
|
101
101
|
one of: *D* (for Dataset), *O* (for Observation), any string identifying a
|
|
102
102
|
component ID (FREQ) or comma-separated list of component IDs
|
|
103
103
|
(FREQ,REF_AREA). The latter can be used to identify the dimension, group
|
|
104
|
-
or series to which the attribute is attached.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
or series to which the attribute is attached. It can also be used to
|
|
105
|
+
identify the measure(s) to which the attribute relates, in case multiple
|
|
106
|
+
measures are defined. The attachment level of a component may vary with the
|
|
107
|
+
statistical domain, i.e. a component attached to a series in a particular
|
|
108
|
+
domain may be attached to, say, the dataset in another domain.
|
|
108
109
|
|
|
109
110
|
The *codes* field indicates the expected (i.e. allowed) set of values a
|
|
110
111
|
component can take within a particular domain. In addition to
|
|
@@ -128,7 +129,9 @@ class Component(
|
|
|
128
129
|
Attributes can be attached at different levels such as
|
|
129
130
|
D (for dataset-level attributes), O (for observation-level
|
|
130
131
|
attributes) or a combination of dimension IDs, separated by
|
|
131
|
-
commas, for series- and group-level attributes
|
|
132
|
+
commas, for series- and group-level attributes, as well as for
|
|
133
|
+
attributes attached to one or more measures, when multiple
|
|
134
|
+
measures are defined).
|
|
132
135
|
A post_init check makes this attribute mandatory for attributes.
|
|
133
136
|
array_def: Any additional constraints for array types.
|
|
134
137
|
urn: The URN of the component.
|