pysdmx 1.9.0__py3-none-any.whl → 1.10.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 +1 -1
- pysdmx/__init__.py +1 -1
- pysdmx/api/fmr/__init__.py +3 -2
- pysdmx/io/input_processor.py +9 -6
- 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 +35 -7
- 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/header.py +41 -34
- 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/message.py +80 -71
- pysdmx/toolkit/vtl/__init__.py +10 -1
- pysdmx/toolkit/vtl/_validations.py +8 -12
- pysdmx/toolkit/vtl/convert.py +333 -0
- pysdmx/toolkit/vtl/script_generation.py +1 -1
- {pysdmx-1.9.0.dist-info → pysdmx-1.10.0.dist-info}/METADATA +3 -3
- {pysdmx-1.9.0.dist-info → pysdmx-1.10.0.dist-info}/RECORD +29 -27
- {pysdmx-1.9.0.dist-info → pysdmx-1.10.0.dist-info}/WHEEL +0 -0
- {pysdmx-1.9.0.dist-info → pysdmx-1.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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):
|
pysdmx/io/xml/header.py
CHANGED
|
@@ -31,6 +31,7 @@ from pysdmx.io.xml.__tokens import (
|
|
|
31
31
|
URN,
|
|
32
32
|
VERSION,
|
|
33
33
|
)
|
|
34
|
+
from pysdmx.io.xml.utils import add_list
|
|
34
35
|
from pysdmx.model import Organisation, Reference
|
|
35
36
|
from pysdmx.model.dataset import ActionType
|
|
36
37
|
from pysdmx.model.message import Header
|
|
@@ -87,43 +88,49 @@ def __parse_sender_receiver(
|
|
|
87
88
|
def __parse_structure(
|
|
88
89
|
structure: Union[Dict[str, Any], None],
|
|
89
90
|
) -> Union[Dict[str, str], None]:
|
|
90
|
-
"""Parses the structure of the SDMX header."""
|
|
91
|
+
"""Parses the structure/s of the SDMX header."""
|
|
91
92
|
if structure is None:
|
|
92
93
|
return None
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
95
|
+
result = {}
|
|
96
|
+
|
|
97
|
+
for struct in add_list(structure):
|
|
98
|
+
dim_at_obs = struct.get(DIM_OBS, "AllDimensions")
|
|
99
|
+
|
|
100
|
+
if STRUCTURE in struct:
|
|
101
|
+
structure_info = struct[STRUCTURE]
|
|
102
|
+
sdmx_type = DSD
|
|
103
|
+
elif STR_USAGE in struct:
|
|
104
|
+
structure_info = struct[STR_USAGE]
|
|
105
|
+
sdmx_type = DFW
|
|
106
|
+
elif PROV_AGREEMENT in struct:
|
|
107
|
+
structure_info = struct[PROV_AGREEMENT]
|
|
108
|
+
sdmx_type = PROV_AGREEMENT
|
|
109
|
+
else:
|
|
110
|
+
# Provision Agrement is a typo in the SDMX 2.1 schema,
|
|
111
|
+
# and it is later solved in SDMX 3.0
|
|
112
|
+
structure_info = struct[PROV_AGREMENT]
|
|
113
|
+
sdmx_type = PROV_AGREMENT
|
|
114
|
+
|
|
115
|
+
if REF in structure_info:
|
|
116
|
+
reference = structure_info[REF]
|
|
117
|
+
agency_id = reference[AGENCY_ID]
|
|
118
|
+
structure_id = reference[ID]
|
|
119
|
+
version = reference[VERSION]
|
|
120
|
+
ref_obj = Reference(
|
|
121
|
+
sdmx_type=sdmx_type,
|
|
122
|
+
agency=agency_id,
|
|
123
|
+
id=structure_id,
|
|
124
|
+
version=version,
|
|
125
|
+
)
|
|
126
|
+
elif URN in structure_info:
|
|
127
|
+
ref_obj = parse_maintainable_urn(structure_info[URN])
|
|
128
|
+
else:
|
|
129
|
+
ref_obj = parse_maintainable_urn(structure_info)
|
|
130
|
+
|
|
131
|
+
result[str(ref_obj)] = dim_at_obs
|
|
132
|
+
|
|
133
|
+
return result
|
|
127
134
|
|
|
128
135
|
|
|
129
136
|
def __parse_source(source: Optional[Dict[str, Any]]) -> Optional[str]:
|
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] = ()
|