sdmxlib 0.8.0__tar.gz → 0.16.2__tar.gz
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.
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/PKG-INFO +15 -8
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/README.md +8 -1
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/pyproject.toml +10 -9
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/__init__.py +44 -2
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/client.py +7 -3
- sdmxlib-0.16.2/src/sdmxlib/api/federated.py +166 -0
- sdmxlib-0.16.2/src/sdmxlib/api/policy.py +39 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/providers.py +17 -7
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/query.py +2 -2
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/registry.py +181 -31
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/session.py +1 -1
- sdmxlib-0.16.2/src/sdmxlib/catalog.py +299 -0
- sdmxlib-0.16.2/src/sdmxlib/data_store.py +64 -0
- sdmxlib-0.16.2/src/sdmxlib/formats/__init__.py +211 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_csv/reader.py +1 -1
- sdmxlib-0.16.2/src/sdmxlib/formats/sdmx_json/metadata.py +192 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_json/reader.py +119 -11
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_json/writer.py +92 -4
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml21/reader.py +6 -6
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml21/writer.py +3 -3
- sdmxlib-0.16.2/src/sdmxlib/formats/sdmx_ml30/metadata.py +320 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +3 -1
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml30/reader.py +158 -10
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml30/writer.py +76 -3
- sdmxlib-0.16.2/src/sdmxlib/local/__init__.py +6 -0
- sdmxlib-0.16.2/src/sdmxlib/local/_data_store.py +168 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/local/_registry.py +104 -128
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/__init__.py +29 -5
- sdmxlib-0.16.2/src/sdmxlib/model/binding.py +234 -0
- sdmxlib-0.16.2/src/sdmxlib/model/code_query.py +893 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/codelist.py +74 -6
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/constraint.py +4 -4
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/dataset.py +24 -15
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/datastructure.py +37 -74
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/hierarchy.py +32 -1
- sdmxlib-0.8.0/src/sdmxlib/model/registry.py → sdmxlib-0.16.2/src/sdmxlib/model/in_memory_registry.py +24 -11
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/message.py +47 -7
- sdmxlib-0.16.2/src/sdmxlib/model/metadataflow.py +55 -0
- sdmxlib-0.16.2/src/sdmxlib/model/metadataset.py +165 -0
- sdmxlib-0.16.2/src/sdmxlib/model/metadatastructure.py +126 -0
- sdmxlib-0.16.2/src/sdmxlib/model/registry.py +59 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/polars.py +99 -18
- sdmxlib-0.16.2/src/sdmxlib/storage/__init__.py +28 -0
- sdmxlib-0.16.2/src/sdmxlib/storage/lazy.py +220 -0
- sdmxlib-0.16.2/src/sdmxlib/storage/readers.py +1366 -0
- sdmxlib-0.16.2/src/sdmxlib/storage/schema.py +561 -0
- sdmxlib-0.16.2/src/sdmxlib/storage/writers.py +1408 -0
- sdmxlib-0.8.0/src/sdmxlib/formats/__init__.py +0 -166
- sdmxlib-0.8.0/src/sdmxlib/local/__init__.py +0 -6
- sdmxlib-0.8.0/src/sdmxlib/storage/__init__.py +0 -28
- sdmxlib-0.8.0/src/sdmxlib/storage/lazy.py +0 -329
- sdmxlib-0.8.0/src/sdmxlib/storage/readers.py +0 -310
- sdmxlib-0.8.0/src/sdmxlib/storage/schema.py +0 -289
- sdmxlib-0.8.0/src/sdmxlib/storage/writers.py +0 -503
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/__init__.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/api/filters.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/annotations.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/base.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/category.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/collections.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/concept.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/convert.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/dataflow.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/expr.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/istring.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/mapping.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/organisation.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/provision.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/ref.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/representation.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/urn.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/model/validation.py +0 -0
- {sdmxlib-0.8.0 → sdmxlib-0.16.2}/src/sdmxlib/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sdmxlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: SDMX structural metadata library for Python
|
|
5
5
|
Keywords: sdmx,statistics,metadata,datastructure
|
|
6
6
|
Author: gabrielgellner
|
|
@@ -10,18 +10,18 @@ Classifier: Development Status :: 4 - Beta
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Intended Audience :: Science/Research
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
15
|
Requires-Dist: attrs>=26.1.0
|
|
16
16
|
Requires-Dist: duckdb>=1.0.0
|
|
17
17
|
Requires-Dist: httpx>=0.28.1
|
|
18
18
|
Requires-Dist: lxml>=6.0.2
|
|
19
19
|
Requires-Dist: polars>=1.39.3
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
-
Project-URL: Homepage, https://gitlab.com/
|
|
22
|
-
Project-URL: Repository, https://gitlab.com/
|
|
23
|
-
Project-URL: Documentation, https://
|
|
24
|
-
Project-URL: Changelog, https://gitlab.com/
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Project-URL: Homepage, https://gitlab.com/pinax-suite/sdmxlib
|
|
22
|
+
Project-URL: Repository, https://gitlab.com/pinax-suite/sdmxlib
|
|
23
|
+
Project-URL: Documentation, https://pinax-suite.gitlab.io/sdmxlib
|
|
24
|
+
Project-URL: Changelog, https://gitlab.com/pinax-suite/sdmxlib/-/blob/main/CHANGELOG.md
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# sdmxlib
|
|
@@ -74,7 +74,14 @@ uv add "sdmxlib @ git+https://gitlab.com/gabrielgellner/sdmxlib.git"
|
|
|
74
74
|
- Build SDMX artefacts locally and serialise to SDMX-ML or SDMX-JSON
|
|
75
75
|
- `LocalRegistry` -- a DuckDB-backed persistent store for offline access
|
|
76
76
|
with lazy codelist loading, automatic Ref resolution, and direct SQL
|
|
77
|
-
code maps; supports shared connections and
|
|
77
|
+
code maps; supports shared connections and any-language queries
|
|
78
|
+
- Reference metadata via `MetadataStructure` (MSD), `Metadataflow`, and
|
|
79
|
+
`MetadataSet` — attach typed footnotes, methodology notes, or quality
|
|
80
|
+
reports to any identifiable artefact. Definitions ride in
|
|
81
|
+
`StructureMessage`; populated payloads ride in `MetadataMessage`
|
|
82
|
+
(`<mes:GenericMetadata>` in SDMX-ML 3.0; metadata-schema envelope in
|
|
83
|
+
SDMX-JSON 2.0). Both message kinds round-trip through
|
|
84
|
+
`LocalRegistry`, JSON, and XML.
|
|
78
85
|
- Integrates with [Polars](https://pola.rs) for schema generation and data
|
|
79
86
|
validation
|
|
80
87
|
- Fully typed — IDE completion and static analysis work throughout
|
|
@@ -48,7 +48,14 @@ uv add "sdmxlib @ git+https://gitlab.com/gabrielgellner/sdmxlib.git"
|
|
|
48
48
|
- Build SDMX artefacts locally and serialise to SDMX-ML or SDMX-JSON
|
|
49
49
|
- `LocalRegistry` -- a DuckDB-backed persistent store for offline access
|
|
50
50
|
with lazy codelist loading, automatic Ref resolution, and direct SQL
|
|
51
|
-
code maps; supports shared connections and
|
|
51
|
+
code maps; supports shared connections and any-language queries
|
|
52
|
+
- Reference metadata via `MetadataStructure` (MSD), `Metadataflow`, and
|
|
53
|
+
`MetadataSet` — attach typed footnotes, methodology notes, or quality
|
|
54
|
+
reports to any identifiable artefact. Definitions ride in
|
|
55
|
+
`StructureMessage`; populated payloads ride in `MetadataMessage`
|
|
56
|
+
(`<mes:GenericMetadata>` in SDMX-ML 3.0; metadata-schema envelope in
|
|
57
|
+
SDMX-JSON 2.0). Both message kinds round-trip through
|
|
58
|
+
`LocalRegistry`, JSON, and XML.
|
|
52
59
|
- Integrates with [Polars](https://pola.rs) for schema generation and data
|
|
53
60
|
validation
|
|
54
61
|
- Fully typed — IDE completion and static analysis work throughout
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sdmxlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.2"
|
|
4
4
|
description = "SDMX structural metadata library for Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Apache-2.0" }
|
|
7
7
|
authors = [{ name = "gabrielgellner", email = "gabrielgellner@gmail.com" }]
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.13"
|
|
9
9
|
keywords = ["sdmx", "statistics", "metadata", "datastructure"]
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Development Status :: 4 - Beta",
|
|
12
12
|
"Intended Audience :: Developers",
|
|
13
13
|
"Intended Audience :: Science/Research",
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
|
-
"Programming Language :: Python :: 3.
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
16
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
17
17
|
]
|
|
18
18
|
dependencies = [
|
|
@@ -24,10 +24,10 @@ dependencies = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
|
-
Homepage = "https://gitlab.com/
|
|
28
|
-
Repository = "https://gitlab.com/
|
|
29
|
-
Documentation = "https://
|
|
30
|
-
Changelog = "https://gitlab.com/
|
|
27
|
+
Homepage = "https://gitlab.com/pinax-suite/sdmxlib"
|
|
28
|
+
Repository = "https://gitlab.com/pinax-suite/sdmxlib"
|
|
29
|
+
Documentation = "https://pinax-suite.gitlab.io/sdmxlib"
|
|
30
|
+
Changelog = "https://gitlab.com/pinax-suite/sdmxlib/-/blob/main/CHANGELOG.md"
|
|
31
31
|
|
|
32
32
|
[build-system]
|
|
33
33
|
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
@@ -38,7 +38,8 @@ testpaths = ["tests"]
|
|
|
38
38
|
markers = [
|
|
39
39
|
"unit: fast unit tests (no I/O)",
|
|
40
40
|
"docs: tests that verify documentation examples",
|
|
41
|
-
"integration:
|
|
41
|
+
"integration: tests that use real DuckDB and sdmxlib but no network",
|
|
42
|
+
"live: tests that make real HTTP calls to external SDMX endpoints",
|
|
42
43
|
]
|
|
43
44
|
|
|
44
45
|
[tool.coverage.run]
|
|
@@ -57,7 +58,7 @@ exclude_lines = [
|
|
|
57
58
|
]
|
|
58
59
|
|
|
59
60
|
[tool.basedpyright]
|
|
60
|
-
pythonVersion = "3.
|
|
61
|
+
pythonVersion = "3.13"
|
|
61
62
|
pythonPlatform = "Linux"
|
|
62
63
|
venvPath = "."
|
|
63
64
|
venv = ".venv"
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"""sdmxlib — SDMX structural metadata library for Python."""
|
|
2
2
|
|
|
3
|
+
from sdmxlib.api.federated import FederatedRegistry
|
|
3
4
|
from sdmxlib.api.filters import DimFilter, dim
|
|
5
|
+
from sdmxlib.api.policy import FailurePolicy, RegistryUnavailable
|
|
4
6
|
from sdmxlib.api.providers import Provider
|
|
5
7
|
from sdmxlib.api.query import DataQuery
|
|
6
8
|
from sdmxlib.api.registry import BearerToken, FmrRegistry, RestRegistry
|
|
9
|
+
from sdmxlib.catalog import DataflowEntry, FederatedCatalog
|
|
10
|
+
from sdmxlib.data_store import DataStore
|
|
7
11
|
from sdmxlib.formats import parse, read, serialize, to_json, to_xml
|
|
8
12
|
from sdmxlib.local import LazyCodelist, LocalRegistry
|
|
13
|
+
from sdmxlib.local._data_store import DuckDBDataStore
|
|
9
14
|
from sdmxlib.model.convert import from_dict, to_dict
|
|
10
15
|
from sdmxlib.model.validation import ReferenceIssue, ValidationIssue, validate, validate_references
|
|
11
16
|
from sdmxlib.model import (
|
|
@@ -20,6 +25,7 @@ from sdmxlib.model import (
|
|
|
20
25
|
Category,
|
|
21
26
|
CategoryScheme,
|
|
22
27
|
Code,
|
|
28
|
+
CodeQuery,
|
|
23
29
|
Codelist,
|
|
24
30
|
Concept,
|
|
25
31
|
ConceptScheme,
|
|
@@ -32,7 +38,7 @@ from sdmxlib.model import (
|
|
|
32
38
|
ConceptSchemeMap,
|
|
33
39
|
ConstraintAttachment,
|
|
34
40
|
Contact,
|
|
35
|
-
|
|
41
|
+
DataConstraint,
|
|
36
42
|
CubeRegion,
|
|
37
43
|
ReferencePeriod,
|
|
38
44
|
ReleaseCalendar,
|
|
@@ -42,22 +48,33 @@ from sdmxlib.model import (
|
|
|
42
48
|
Dataset,
|
|
43
49
|
DataStructureMap,
|
|
44
50
|
Dataflow,
|
|
51
|
+
DataflowBinding,
|
|
45
52
|
DataflowMap,
|
|
46
53
|
Dimension,
|
|
54
|
+
DriftIssue,
|
|
47
55
|
Hierarchy,
|
|
48
56
|
HierarchicalCode,
|
|
49
57
|
Group,
|
|
58
|
+
InMemoryRegistry,
|
|
50
59
|
InternationalString,
|
|
51
60
|
ItemExpr,
|
|
52
61
|
ItemList,
|
|
53
62
|
ItemPredicate,
|
|
54
63
|
KeyValue,
|
|
64
|
+
Level,
|
|
55
65
|
item,
|
|
56
66
|
Maintainable,
|
|
57
67
|
MaintainableArtefact,
|
|
58
68
|
Measure,
|
|
59
69
|
MeasureDimension,
|
|
70
|
+
MetadataAttribute,
|
|
71
|
+
MetadataMessage,
|
|
72
|
+
MetadataSet,
|
|
73
|
+
MetadataStructure,
|
|
74
|
+
Metadataflow,
|
|
60
75
|
ProvisionAgreement,
|
|
76
|
+
ReportedAttribute,
|
|
77
|
+
ReportedAttributeValue,
|
|
61
78
|
DataType,
|
|
62
79
|
Facet,
|
|
63
80
|
Ref,
|
|
@@ -69,6 +86,9 @@ from sdmxlib.model import (
|
|
|
69
86
|
StructureSet,
|
|
70
87
|
TimeDimension,
|
|
71
88
|
UnresolvedRef,
|
|
89
|
+
artefact_urn,
|
|
90
|
+
code,
|
|
91
|
+
validate_binding,
|
|
72
92
|
)
|
|
73
93
|
|
|
74
94
|
__all__ = [
|
|
@@ -87,6 +107,7 @@ __all__ = [
|
|
|
87
107
|
"CategorySchemeMap",
|
|
88
108
|
"Code",
|
|
89
109
|
"CodeMap",
|
|
110
|
+
"CodeQuery",
|
|
90
111
|
"Codelist",
|
|
91
112
|
"CodelistMap",
|
|
92
113
|
"ComponentMap",
|
|
@@ -96,42 +117,60 @@ __all__ = [
|
|
|
96
117
|
"ConceptSchemeMap",
|
|
97
118
|
"ConstraintAttachment",
|
|
98
119
|
"Contact",
|
|
99
|
-
"ContentConstraint",
|
|
100
120
|
"CubeRegion",
|
|
121
|
+
"DataConstraint",
|
|
101
122
|
"DataProvider",
|
|
102
123
|
"DataProviderScheme",
|
|
103
124
|
"DataQuery",
|
|
125
|
+
"DataStore",
|
|
104
126
|
"DataStructure",
|
|
105
127
|
"DataStructureMap",
|
|
106
128
|
"DataType",
|
|
107
129
|
"Dataflow",
|
|
130
|
+
"DataflowBinding",
|
|
131
|
+
"DataflowEntry",
|
|
108
132
|
"DataflowMap",
|
|
109
133
|
"Dataset",
|
|
110
134
|
"DimFilter",
|
|
111
135
|
"Dimension",
|
|
136
|
+
"DriftIssue",
|
|
137
|
+
"DuckDBDataStore",
|
|
112
138
|
"Facet",
|
|
139
|
+
"FailurePolicy",
|
|
140
|
+
"FederatedCatalog",
|
|
141
|
+
"FederatedRegistry",
|
|
113
142
|
"FmrRegistry",
|
|
114
143
|
"Group",
|
|
115
144
|
"HierarchicalCode",
|
|
116
145
|
"Hierarchy",
|
|
146
|
+
"InMemoryRegistry",
|
|
117
147
|
"InternationalString",
|
|
118
148
|
"ItemExpr",
|
|
119
149
|
"ItemList",
|
|
120
150
|
"ItemPredicate",
|
|
121
151
|
"KeyValue",
|
|
122
152
|
"LazyCodelist",
|
|
153
|
+
"Level",
|
|
123
154
|
"LocalRegistry",
|
|
124
155
|
"Maintainable",
|
|
125
156
|
"MaintainableArtefact",
|
|
126
157
|
"Measure",
|
|
127
158
|
"MeasureDimension",
|
|
159
|
+
"MetadataAttribute",
|
|
160
|
+
"MetadataMessage",
|
|
161
|
+
"MetadataSet",
|
|
162
|
+
"MetadataStructure",
|
|
163
|
+
"Metadataflow",
|
|
128
164
|
"Provider",
|
|
129
165
|
"ProvisionAgreement",
|
|
130
166
|
"Ref",
|
|
131
167
|
"ReferenceIssue",
|
|
132
168
|
"ReferencePeriod",
|
|
133
169
|
"Registry",
|
|
170
|
+
"RegistryUnavailable",
|
|
134
171
|
"ReleaseCalendar",
|
|
172
|
+
"ReportedAttribute",
|
|
173
|
+
"ReportedAttributeValue",
|
|
135
174
|
"Representation",
|
|
136
175
|
"RestRegistry",
|
|
137
176
|
"SdmxRef",
|
|
@@ -141,6 +180,8 @@ __all__ = [
|
|
|
141
180
|
"TimeDimension",
|
|
142
181
|
"UnresolvedRef",
|
|
143
182
|
"ValidationIssue",
|
|
183
|
+
"artefact_urn",
|
|
184
|
+
"code",
|
|
144
185
|
"dim",
|
|
145
186
|
"from_dict",
|
|
146
187
|
"item",
|
|
@@ -151,5 +192,6 @@ __all__ = [
|
|
|
151
192
|
"to_json",
|
|
152
193
|
"to_xml",
|
|
153
194
|
"validate",
|
|
195
|
+
"validate_binding",
|
|
154
196
|
"validate_references",
|
|
155
197
|
]
|
|
@@ -17,7 +17,7 @@ _RESOURCE_PATH: dict[str, str] = {
|
|
|
17
17
|
"ProvisionAgreement": "provisionagreement",
|
|
18
18
|
"CategoryScheme": "categoryscheme",
|
|
19
19
|
"Categorisation": "categorisation",
|
|
20
|
-
"
|
|
20
|
+
"DataConstraint": "dataconstraint",
|
|
21
21
|
"AgencyScheme": "agencyscheme",
|
|
22
22
|
"DataProviderScheme": "dataproviderorganisationscheme",
|
|
23
23
|
"Hierarchy": "hierarchy",
|
|
@@ -133,7 +133,9 @@ class SdmxClient:
|
|
|
133
133
|
params["references"] = references
|
|
134
134
|
response = self._client.get(url, params=params)
|
|
135
135
|
response.raise_for_status()
|
|
136
|
-
|
|
136
|
+
msg = parse(response.content, version=self._parse_version)
|
|
137
|
+
assert isinstance(msg, StructureMessage) # noqa: S101 # structure endpoint only
|
|
138
|
+
return msg
|
|
137
139
|
|
|
138
140
|
def get_all(
|
|
139
141
|
self,
|
|
@@ -150,7 +152,9 @@ class SdmxClient:
|
|
|
150
152
|
params["references"] = references
|
|
151
153
|
response = self._client.get(url, params=params)
|
|
152
154
|
response.raise_for_status()
|
|
153
|
-
|
|
155
|
+
msg = parse(response.content, version=self._parse_version)
|
|
156
|
+
assert isinstance(msg, StructureMessage) # noqa: S101 # structure endpoint only
|
|
157
|
+
return msg
|
|
154
158
|
|
|
155
159
|
def _data_url(
|
|
156
160
|
self,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""FederatedRegistry — agency-routed dispatch across multiple sources.
|
|
2
|
+
|
|
3
|
+
The headline of #46. Composes any number of :class:`~sdmxlib.model.registry.Registry`
|
|
4
|
+
instances (in-memory, local DuckDB, REST) and dispatches each ``get(urn)``
|
|
5
|
+
call to the source responsible for the URN's maintenance agency. The user
|
|
6
|
+
sees a single registry that resolves URNs from many backends.
|
|
7
|
+
|
|
8
|
+
Cache-mode v1 — each agency has exactly one source. Peer-mode (multiple
|
|
9
|
+
sources cooperating to answer a single URN) is intentionally out of
|
|
10
|
+
scope; see #46 for that conversation.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
local = sl.LocalRegistry("cache.duckdb", source_registry="local")
|
|
14
|
+
estat = sl.RestRegistry(sl.Provider.ESTAT, materialise_to=local)
|
|
15
|
+
sdmx_global = sl.RestRegistry(sl.Provider.SDMX_GLOBAL, materialise_to=local)
|
|
16
|
+
|
|
17
|
+
fed = sl.FederatedRegistry(
|
|
18
|
+
routing={"ESTAT": estat, "SDMX": sdmx_global},
|
|
19
|
+
default=local, # locally-authored agencies dispatch here
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Routes by maintenance agency:
|
|
23
|
+
dsd = fed.get(sl.DataStructure.urn(agency="ESTAT", id="X", version="1.0"))
|
|
24
|
+
# → estat → HTTP fetch → materialise to local
|
|
25
|
+
|
|
26
|
+
cl = fed.get(sl.Codelist.urn(agency="SDMX", id="CL_FREQ", version="2.1"))
|
|
27
|
+
# → sdmx_global → HTTP fetch → materialise to local
|
|
28
|
+
|
|
29
|
+
own = fed.get(sl.Codelist.urn(agency="MY_ORG", id="CL_X", version="1.0"))
|
|
30
|
+
# → falls through to local (no routing entry)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from typing import TYPE_CHECKING
|
|
34
|
+
|
|
35
|
+
import httpx
|
|
36
|
+
|
|
37
|
+
from sdmxlib.api.policy import FailurePolicy, RegistryUnavailable
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from sdmxlib.model.registry import Registry
|
|
41
|
+
from sdmxlib.model.urn import SdmxUrn
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FederatedRegistry:
|
|
45
|
+
"""Read-side dispatcher across multiple Registry sources.
|
|
46
|
+
|
|
47
|
+
Routes each URN-keyed lookup by maintenance agency. Configure with a
|
|
48
|
+
``routing`` map from agency id to a :class:`Registry` source, plus
|
|
49
|
+
an optional ``default`` source for agencies absent from the map.
|
|
50
|
+
|
|
51
|
+
Satisfies the :class:`~sdmxlib.model.registry.Registry` Protocol —
|
|
52
|
+
a ``FederatedRegistry`` can itself be a source in another
|
|
53
|
+
``FederatedRegistry`` (composition).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
routing: Mapping of agency id (e.g. ``"ESTAT"``, ``"SDMX"``) to a
|
|
57
|
+
:class:`Registry` source. Lookup keys are matched against
|
|
58
|
+
``SdmxUrn.agency``.
|
|
59
|
+
default: Optional fallback source for URNs whose agency is not
|
|
60
|
+
in ``routing``. ``None`` (the default) means "no fallback":
|
|
61
|
+
unrouted URNs raise :class:`KeyError`.
|
|
62
|
+
failure: How to treat exceptions raised by a routed source.
|
|
63
|
+
Defaults to :attr:`FailurePolicy.HARD_FAIL` — propagate the
|
|
64
|
+
underlying error wrapped in :class:`RegistryUnavailable`.
|
|
65
|
+
|
|
66
|
+
Note:
|
|
67
|
+
FederatedRegistry is a *router*, not a *store*. It has no
|
|
68
|
+
``add()`` method — write to specific concrete registries
|
|
69
|
+
(typically the local one in your routing chain).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
routing: "dict[str, Registry]",
|
|
76
|
+
default: "Registry | None" = None,
|
|
77
|
+
failure: FailurePolicy = FailurePolicy.HARD_FAIL,
|
|
78
|
+
) -> None:
|
|
79
|
+
self._routing = dict(routing)
|
|
80
|
+
self._default = default
|
|
81
|
+
self._failure = failure
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def routing(self) -> "dict[str, Registry]":
|
|
85
|
+
"""Read-only view of the agency → source map."""
|
|
86
|
+
return dict(self._routing)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def default(self) -> "Registry | None":
|
|
90
|
+
"""The fallback source used when no routing entry matches."""
|
|
91
|
+
return self._default
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def failure(self) -> FailurePolicy:
|
|
95
|
+
"""The configured failure policy."""
|
|
96
|
+
return self._failure
|
|
97
|
+
|
|
98
|
+
# ── Routing ──────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
def _route(self, urn: "SdmxUrn[object]") -> "Registry | None":
|
|
101
|
+
"""Pick the source responsible for ``urn``'s agency."""
|
|
102
|
+
return self._routing.get(urn.agency, self._default)
|
|
103
|
+
|
|
104
|
+
def source_for[T](self, urn: "SdmxUrn[T]") -> "Registry | None":
|
|
105
|
+
"""Public version of the routing decision — useful for diagnostics."""
|
|
106
|
+
return self._route(urn)
|
|
107
|
+
|
|
108
|
+
# ── Registry Protocol ────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
def get[T](self, urn: "SdmxUrn[T]") -> "T | None":
|
|
111
|
+
"""Resolve ``urn`` via the routed source.
|
|
112
|
+
|
|
113
|
+
Routing rules:
|
|
114
|
+
|
|
115
|
+
- URN agency in ``routing`` → dispatch to that source.
|
|
116
|
+
- URN agency absent and ``default`` is set → dispatch to default.
|
|
117
|
+
- URN agency absent and no default → raise ``KeyError``.
|
|
118
|
+
|
|
119
|
+
Errors from the routed source are wrapped in
|
|
120
|
+
:class:`RegistryUnavailable` when ``failure`` is
|
|
121
|
+
:attr:`FailurePolicy.HARD_FAIL` (default), or swallowed (returning
|
|
122
|
+
``None``) when ``failure`` is :attr:`FailurePolicy.RETURN_NONE`.
|
|
123
|
+
"""
|
|
124
|
+
source = self._route(urn)
|
|
125
|
+
if source is None:
|
|
126
|
+
err = (
|
|
127
|
+
f"No source registered for agency {urn.agency!r} and no default — "
|
|
128
|
+
f"add an entry to routing or supply default=..."
|
|
129
|
+
)
|
|
130
|
+
raise KeyError(err)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
return source.get(urn) # type: ignore[return-value]
|
|
134
|
+
except (httpx.HTTPError, OSError, RegistryUnavailable) as exc:
|
|
135
|
+
return self._on_failure(source, exc)
|
|
136
|
+
|
|
137
|
+
def __contains__(self, urn: object) -> bool:
|
|
138
|
+
"""True if the routed source already has ``urn`` cached.
|
|
139
|
+
|
|
140
|
+
Like the underlying Registry membership: never triggers a network
|
|
141
|
+
round-trip. ``False`` if no source is routed for the URN's agency.
|
|
142
|
+
"""
|
|
143
|
+
from sdmxlib.model.urn import SdmxUrn # noqa: PLC0415
|
|
144
|
+
|
|
145
|
+
if not isinstance(urn, SdmxUrn):
|
|
146
|
+
return False
|
|
147
|
+
source = self._route(urn)
|
|
148
|
+
return source is not None and urn in source
|
|
149
|
+
|
|
150
|
+
# ── Failure handling ─────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
def _on_failure[T](self, source: "Registry", exc: Exception) -> "T | None":
|
|
153
|
+
"""Apply the configured ``FailurePolicy`` to an upstream error."""
|
|
154
|
+
source_id = getattr(source, "id", None)
|
|
155
|
+
if self._failure is FailurePolicy.RETURN_NONE:
|
|
156
|
+
return None
|
|
157
|
+
# HARD_FAIL — wrap and re-raise
|
|
158
|
+
raise RegistryUnavailable(source_id, exc) from exc
|
|
159
|
+
|
|
160
|
+
def __repr__(self) -> str:
|
|
161
|
+
agencies = ", ".join(sorted(self._routing))
|
|
162
|
+
default = " +default" if self._default is not None else ""
|
|
163
|
+
return f"FederatedRegistry(routes=[{agencies}]{default}, failure={self._failure.name})"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = ["FederatedRegistry"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""FailurePolicy — how a federated read should behave when a source errors.
|
|
2
|
+
|
|
3
|
+
Per #46's pitfall list ("Partial failure handling"), there are several
|
|
4
|
+
valid behaviours when a routed source is unreachable: hard-fail, return
|
|
5
|
+
``None`` (treat as not-found), or fall through to a stale local cache.
|
|
6
|
+
The policy is configurable per source or globally on
|
|
7
|
+
:class:`~sdmxlib.api.federated.FederatedRegistry`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FailurePolicy(Enum):
|
|
14
|
+
"""How a federated registry handles errors from a routed source.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
HARD_FAIL: Re-raise the underlying error (default). The caller
|
|
18
|
+
sees the network/HTTP exception unchanged.
|
|
19
|
+
RETURN_NONE: Catch errors that look like "source unreachable"
|
|
20
|
+
and return ``None``. The caller cannot distinguish "agency
|
|
21
|
+
doesn't have this artefact" from "source was down."
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
HARD_FAIL = "hard_fail"
|
|
25
|
+
RETURN_NONE = "return_none"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RegistryUnavailable(Exception): # noqa: N818 # name matches #46 vocabulary
|
|
29
|
+
"""Raised when a routed source cannot be reached and the policy is HARD_FAIL.
|
|
30
|
+
|
|
31
|
+
Wraps the underlying httpx / connection error so callers can catch a
|
|
32
|
+
single exception type without depending on the HTTP layer.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, source_id: str | None, cause: Exception) -> None:
|
|
36
|
+
self.source_id = source_id
|
|
37
|
+
self.cause = cause
|
|
38
|
+
sid = f" (source={source_id!r})" if source_id else ""
|
|
39
|
+
super().__init__(f"Registry unavailable{sid}: {cause}")
|
|
@@ -13,10 +13,20 @@ from attrs import frozen
|
|
|
13
13
|
|
|
14
14
|
@frozen
|
|
15
15
|
class ProviderInfo:
|
|
16
|
-
"""URL and REST API version for a known SDMX endpoint.
|
|
16
|
+
"""URL and REST API version for a known SDMX endpoint.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
url: Base REST endpoint URL.
|
|
20
|
+
api: SDMX REST API version (``"2.1"`` or ``"3.0"``).
|
|
21
|
+
id: Stable identifier used as the ``source_registry`` provenance
|
|
22
|
+
tag when a :class:`RestRegistry` materialises through to a
|
|
23
|
+
local store. Required for federation; ``None`` for ad-hoc
|
|
24
|
+
URLs without a known label.
|
|
25
|
+
"""
|
|
17
26
|
|
|
18
27
|
url: str
|
|
19
28
|
api: str = "2.1"
|
|
29
|
+
id: str | None = None
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
class Provider:
|
|
@@ -39,17 +49,17 @@ class Provider:
|
|
|
39
49
|
"""
|
|
40
50
|
|
|
41
51
|
#: European Central Bank
|
|
42
|
-
ECB = ProviderInfo("https://data-api.ecb.europa.eu/service", api="2.1")
|
|
52
|
+
ECB = ProviderInfo("https://data-api.ecb.europa.eu/service", api="2.1", id="ECB")
|
|
43
53
|
#: Bank for International Settlements
|
|
44
|
-
BIS = ProviderInfo("https://stats.bis.org/api/v1", api="2.1")
|
|
54
|
+
BIS = ProviderInfo("https://stats.bis.org/api/v1", api="2.1", id="BIS")
|
|
45
55
|
#: Eurostat
|
|
46
|
-
ESTAT = ProviderInfo("https://ec.europa.eu/eurostat/api/dissemination/sdmx/2.1", api="2.1")
|
|
56
|
+
ESTAT = ProviderInfo("https://ec.europa.eu/eurostat/api/dissemination/sdmx/2.1", api="2.1", id="ESTAT")
|
|
47
57
|
#: International Monetary Fund
|
|
48
|
-
IMF = ProviderInfo("https://sdmxcentral.imf.org/ws/public/sdmxapi/rest", api="2.1")
|
|
58
|
+
IMF = ProviderInfo("https://sdmxcentral.imf.org/ws/public/sdmxapi/rest", api="2.1", id="IMF")
|
|
49
59
|
#: OECD
|
|
50
|
-
OECD = ProviderInfo("https://sdmx.oecd.org/public/rest", api="2.1")
|
|
60
|
+
OECD = ProviderInfo("https://sdmx.oecd.org/public/rest", api="2.1", id="OECD")
|
|
51
61
|
#: SDMX Global Registry (FMR — SDMX 3.0 endpoint)
|
|
52
|
-
SDMX_GLOBAL = ProviderInfo("https://registry.sdmx.org/sdmx/v2", api="3.0")
|
|
62
|
+
SDMX_GLOBAL = ProviderInfo("https://registry.sdmx.org/sdmx/v2", api="3.0", id="SDMX_GLOBAL")
|
|
53
63
|
|
|
54
64
|
def __class_getitem__(cls, key: str) -> ProviderInfo:
|
|
55
65
|
val = getattr(cls, key, None)
|
|
@@ -66,7 +66,7 @@ class GetQuery[T]:
|
|
|
66
66
|
reference_type: If given, resolve only Refs of this type.
|
|
67
67
|
If ``None`` (default), resolve all references.
|
|
68
68
|
partial: If ``True`` (default), referenced ItemSchemes are trimmed
|
|
69
|
-
to items allowed by
|
|
69
|
+
to items allowed by DataConstraints
|
|
70
70
|
(``detail=referencepartial``). Produces smaller, more
|
|
71
71
|
useful responses for the common case. Set to ``False``
|
|
72
72
|
for full metadata audits (``detail=full``).
|
|
@@ -204,7 +204,7 @@ class Query[T]:
|
|
|
204
204
|
Args:
|
|
205
205
|
reference_type: If given, resolve only Refs of this type.
|
|
206
206
|
partial: Controls whether referenced ItemSchemes are trimmed by
|
|
207
|
-
|
|
207
|
+
DataConstraints. ``True`` (default) is faster and
|
|
208
208
|
more useful for data queries; ``False`` for full audits.
|
|
209
209
|
"""
|
|
210
210
|
ref_str = "descendants" if reference_type is None else _to_references(reference_type)
|