sdmxlib 0.65.0__tar.gz → 0.67.0__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.65.0 → sdmxlib-0.67.0}/PKG-INFO +1 -1
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/pyproject.toml +4 -47
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/pyproject.toml.orig +7 -131
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/filters.py +1 -1
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/registry.py +1 -1
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/session.py +2 -2
- sdmxlib-0.67.0/src/sdmxlib/formats/_arrow.py +28 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_csv/writer.py +4 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/_data_common.py +3 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/_resolve.py +23 -3
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/_structure_ingest.py +17 -17
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/writer.py +13 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml/_common.py +4 -4
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/local/registry.py +14 -29
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/annotations.py +1 -1
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/base.py +4 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/binding.py +4 -4
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/category.py +5 -4
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/codelist.py +4 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/collections.py +20 -34
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/concept.py +2 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/constraint.py +5 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/dataflow.py +2 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/dataset.py +14 -7
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/datastructure.py +33 -19
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/expr.py +2 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/hierarchy.py +10 -10
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/in_memory_registry.py +5 -6
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/mapping.py +18 -12
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/message.py +21 -35
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/metadata_provision.py +12 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/metadataflow.py +2 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/metadataset.py +5 -3
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/metadatastructure.py +8 -6
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/organisation.py +4 -4
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/provision.py +6 -5
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/ref.py +67 -12
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/urn.py +79 -26
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/validation.py +37 -28
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/version.py +13 -3
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_expr.py +8 -8
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_plan.py +20 -1
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/query.py +9 -12
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/lazy.py +3 -3
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/readers.py +156 -195
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/resolve.py +3 -4
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/writers.py +10 -2
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/README.md +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/_duckdb.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/_singleflight.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/_time.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/_freshness.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/client.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/federated.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/policy.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/providers.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/api/query.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/builders/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/builders/contentconstraint.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/builders/metadata_annotation.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/data_store.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/_coerce.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/_index.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/data_format.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/gaps.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_csv/reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/_dto.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/_structure_dto.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/data_v1.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/data_v2.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/metadata.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json/structure_values.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json_1_0/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_json_1_0/writer.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml/_v3_reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml/_v3_writer.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml21/_synthetic.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml21/reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml21/writer.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml30/metadata.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml30/reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml30/writer.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml31/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml31/namespaces.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml31/reader.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/formats/sdmx_ml31/writer.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/local/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/local/data_store.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/local/sql.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/_interning.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/_items.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/_label_match.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/_refshape.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/convert.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/errors.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/istring.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/registry.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/representation.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/rest_api.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/model/rollup.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/polars.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/py.typed +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_closure.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_common.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_compile.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_edges.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_execute.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_joins.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_pushdown.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_rebase.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/_rest.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/accessors.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/observations.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/raw.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/query/schema.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/rest.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/session.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/sql.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/__init__.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/_kinds.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/_projections.py +0 -0
- {sdmxlib-0.65.0 → sdmxlib-0.67.0}/src/sdmxlib/storage/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sdmxlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.67.0"
|
|
4
4
|
description = "SDMX structural metadata library for Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -72,51 +72,6 @@ exclude_lines = [
|
|
|
72
72
|
"@overload",
|
|
73
73
|
]
|
|
74
74
|
|
|
75
|
-
[tool.basedpyright]
|
|
76
|
-
pythonVersion = "3.13"
|
|
77
|
-
pythonPlatform = "Linux"
|
|
78
|
-
venvPath = "."
|
|
79
|
-
venv = ".venv"
|
|
80
|
-
typeCheckingMode = "standard"
|
|
81
|
-
reportUnusedCallResult = false
|
|
82
|
-
reportMissingTypeArgument = "error"
|
|
83
|
-
reportMissingParameterType = "error"
|
|
84
|
-
reportUnnecessaryCast = "error"
|
|
85
|
-
reportPropertyTypeMismatch = "error"
|
|
86
|
-
reportInconsistentConstructor = "error"
|
|
87
|
-
reportCallInDefaultInitializer = "error"
|
|
88
|
-
reportUnnecessaryContains = "error"
|
|
89
|
-
reportUnnecessaryTypeIgnoreComment = "error"
|
|
90
|
-
|
|
91
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
92
|
-
root = "tests/integration"
|
|
93
|
-
extraPaths = ["."]
|
|
94
|
-
reportMissingParameterType = false
|
|
95
|
-
reportOptionalMemberAccess = false
|
|
96
|
-
|
|
97
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
98
|
-
root = "tests/docs"
|
|
99
|
-
extraPaths = ["."]
|
|
100
|
-
reportMissingParameterType = false
|
|
101
|
-
reportOptionalMemberAccess = false
|
|
102
|
-
|
|
103
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
104
|
-
root = "tests/unit"
|
|
105
|
-
extraPaths = ["."]
|
|
106
|
-
reportMissingParameterType = false
|
|
107
|
-
reportOptionalMemberAccess = false
|
|
108
|
-
|
|
109
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
110
|
-
root = "tests/bench"
|
|
111
|
-
extraPaths = ["."]
|
|
112
|
-
reportMissingParameterType = false
|
|
113
|
-
reportOptionalMemberAccess = false
|
|
114
|
-
reportInvalidCast = "error"
|
|
115
|
-
reportDeprecated = "error"
|
|
116
|
-
reportUnsafeMultipleInheritance = "error"
|
|
117
|
-
reportTypeCommentUsage = "error"
|
|
118
|
-
reportSelfClsParameterName = "error"
|
|
119
|
-
|
|
120
75
|
[tool.mutmut]
|
|
121
76
|
source_paths = ["src"]
|
|
122
77
|
only_mutate = [
|
|
@@ -140,9 +95,10 @@ mutate_only_covered_lines = false
|
|
|
140
95
|
|
|
141
96
|
[dependency-groups]
|
|
142
97
|
dev = [
|
|
143
|
-
"basedpyright==1.39.9",
|
|
144
98
|
"catppuccin[pygments]>=2.5.0",
|
|
145
99
|
"types-lxml>=2025.3.30",
|
|
100
|
+
"types-jsonschema>=4.21.0",
|
|
101
|
+
"types-PyYAML>=6.0.12",
|
|
146
102
|
"mkdocs-material>=9.7.6",
|
|
147
103
|
"mkdocstrings[python]>=1.0.3",
|
|
148
104
|
"polars>=1.39.3",
|
|
@@ -150,6 +106,7 @@ dev = [
|
|
|
150
106
|
"jsonschema>=4.21.0",
|
|
151
107
|
"pytest-benchmark>=4.0.0",
|
|
152
108
|
"pytest-cov>=7.1.0",
|
|
109
|
+
"pytest-xdist>=3.8.0",
|
|
153
110
|
"pytest-memray>=1.7.0",
|
|
154
111
|
"pytest-recording>=0.13.4",
|
|
155
112
|
"pydoclint>=0.9.1,<0.10",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sdmxlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.67.0"
|
|
4
4
|
description = "SDMX structural metadata library for Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Apache-2.0" }
|
|
@@ -92,126 +92,6 @@ exclude_lines = [
|
|
|
92
92
|
"@overload",
|
|
93
93
|
]
|
|
94
94
|
|
|
95
|
-
[tool.basedpyright]
|
|
96
|
-
pythonVersion = "3.13"
|
|
97
|
-
pythonPlatform = "Linux"
|
|
98
|
-
venvPath = "."
|
|
99
|
-
venv = ".venv"
|
|
100
|
-
# "standard" rather than "strict" because the strict-only rules
|
|
101
|
-
# (reportUnknownMemberType, reportUnknownArgumentType,
|
|
102
|
-
# reportUnknownVariableType) fire on every duckdb / lxml / pyarrow
|
|
103
|
-
# touch — third-party stubs we don't control. Standard mode still
|
|
104
|
-
# catches the bugs we care about (assignments, attribute access,
|
|
105
|
-
# return types, missing arguments). Strict-mode rules that *are*
|
|
106
|
-
# enforceable on this codebase are turned on manually via
|
|
107
|
-
# `# pyright: ignore[...]` opt-outs at the call sites that need them.
|
|
108
|
-
typeCheckingMode = "standard"
|
|
109
|
-
# `pytest.raises(...)` doesn't bind a name — without this every
|
|
110
|
-
# `with pytest.raises(...):` block would need `# pyright: ignore`.
|
|
111
|
-
# Off project-wide since the only places that consume return values
|
|
112
|
-
# we *don't* care about are tests.
|
|
113
|
-
reportUnusedCallResult = false
|
|
114
|
-
|
|
115
|
-
# Strict-mode rules that ARE clean on this codebase (0 findings), turned on
|
|
116
|
-
# to catch regressions the flood-prone `reportUnknown*` rules would bury.
|
|
117
|
-
# The first two are basedpyright's Any-degradation catchers — the closest it
|
|
118
|
-
# gets to pyrefly's `implicit-any` family without the duckdb/pyarrow noise:
|
|
119
|
-
# * reportMissingTypeArgument — a bare generic (`list` not `list[str]`) → Any.
|
|
120
|
-
# * reportMissingParameterType — an untyped parameter → Any.
|
|
121
|
-
# The rest are pure hardening: dead casts (aligns with "narrow, don't cast"),
|
|
122
|
-
# getter/setter type drift, __init__/__new__ disagreement, mutable-default
|
|
123
|
-
# gotchas, and always-true/false `in` checks.
|
|
124
|
-
reportMissingTypeArgument = "error"
|
|
125
|
-
reportMissingParameterType = "error"
|
|
126
|
-
reportUnnecessaryCast = "error"
|
|
127
|
-
reportPropertyTypeMismatch = "error"
|
|
128
|
-
reportInconsistentConstructor = "error"
|
|
129
|
-
reportCallInDefaultInitializer = "error"
|
|
130
|
-
reportUnnecessaryContains = "error"
|
|
131
|
-
|
|
132
|
-
# Suppression hygiene. This is the rule that keeps the "suppressions require
|
|
133
|
-
# approval" policy honest in the other direction: an ignore that outlived its
|
|
134
|
-
# defect is a lie the next reader believes. It found 10 dead ones the day it
|
|
135
|
-
# went in. Note that neither checker can adjudicate a shared `# type: ignore`
|
|
136
|
-
# alone — pyrefly honours the same comment — which is why checker-specific
|
|
137
|
-
# suppressions are written as `# pyright: ignore[rule]` and
|
|
138
|
-
# `# pyrefly: ignore[kind]`, and why pyrefly's `unused-type-ignore` is off.
|
|
139
|
-
reportUnnecessaryTypeIgnoreComment = "error"
|
|
140
|
-
|
|
141
|
-
# ── Test directories: two rules subtracted, with reasons (#347) ────────────
|
|
142
|
-
#
|
|
143
|
-
# `tests/` was outside both checkers, which is what let 59 dead suppressions
|
|
144
|
-
# accumulate there — a suppression written against a checker that never runs
|
|
145
|
-
# can be neither validated nor invalidated. It is all in now: !433 brought
|
|
146
|
-
# `tests/support`, !469 `tests/integration` / `tests/docs` / `tests/bench`,
|
|
147
|
-
# and this pass `tests/unit`, which was the last holdout at 1,050 findings.
|
|
148
|
-
#
|
|
149
|
-
# Two rules are off for these paths, and only these paths. Both are measured
|
|
150
|
-
# across the whole of `tests/` and both are *style*, not safety:
|
|
151
|
-
#
|
|
152
|
-
# * reportMissingParameterType (919) — pytest injects fixtures by name, so
|
|
153
|
-
# `def test_x(bundle_conn)` is how every test in the suite is written.
|
|
154
|
-
# Annotating them all would be mechanical, would bind each test to its
|
|
155
|
-
# fixture's concrete type, and would buy nothing: the fixture's own return
|
|
156
|
-
# type is checked where it is defined. Where a fixture's type *does* carry
|
|
157
|
-
# through to many call sites it is annotated anyway — `tests/bench` proved
|
|
158
|
-
# the cost of not doing so, hiding four `str` URNs from `just check` until
|
|
159
|
-
# the bundle tier caught them.
|
|
160
|
-
# * reportOptionalMemberAccess (227) — `reg.get(urn).id` in a test that added
|
|
161
|
-
# `urn` three lines earlier. The Optional is real and the test is the proof
|
|
162
|
-
# it is not None; asserting that first in 227 places is noise. It covers
|
|
163
|
-
# attribute access only, so `len(x)`, `x in y` and passing the value on
|
|
164
|
-
# still need the assert spelled out, and do have it.
|
|
165
|
-
#
|
|
166
|
-
# `extraPaths = ["."]` appears in all four because `root` re-bases import
|
|
167
|
-
# resolution: without it, `from tests.support …` stops resolving.
|
|
168
|
-
#
|
|
169
|
-
# Everything else is on, which is the point.
|
|
170
|
-
|
|
171
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
172
|
-
root = "tests/integration"
|
|
173
|
-
extraPaths = ["."]
|
|
174
|
-
reportMissingParameterType = false
|
|
175
|
-
reportOptionalMemberAccess = false
|
|
176
|
-
|
|
177
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
178
|
-
root = "tests/docs"
|
|
179
|
-
extraPaths = ["."]
|
|
180
|
-
reportMissingParameterType = false
|
|
181
|
-
reportOptionalMemberAccess = false
|
|
182
|
-
|
|
183
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
184
|
-
root = "tests/unit"
|
|
185
|
-
extraPaths = ["."]
|
|
186
|
-
reportMissingParameterType = false
|
|
187
|
-
reportOptionalMemberAccess = false
|
|
188
|
-
|
|
189
|
-
[[tool.basedpyright.executionEnvironments]]
|
|
190
|
-
root = "tests/bench"
|
|
191
|
-
extraPaths = ["."]
|
|
192
|
-
reportMissingParameterType = false
|
|
193
|
-
reportOptionalMemberAccess = false
|
|
194
|
-
|
|
195
|
-
# Zero findings when enabled; on as regression guards. reportInvalidCast in
|
|
196
|
-
# particular backs the "narrow, don't cast" rule — it fires when a cast asserts
|
|
197
|
-
# something the checker knows to be false.
|
|
198
|
-
reportInvalidCast = "error"
|
|
199
|
-
reportDeprecated = "error"
|
|
200
|
-
reportUnsafeMultipleInheritance = "error"
|
|
201
|
-
reportTypeCommentUsage = "error"
|
|
202
|
-
reportSelfClsParameterName = "error"
|
|
203
|
-
|
|
204
|
-
# NOT enabled, measured rather than assumed:
|
|
205
|
-
# * reportImplicitOverride (26) — after pyrefly's `missing-override-decorator`
|
|
206
|
-
# took the 29 real base-class overrides, every one left is a `__repr__` /
|
|
207
|
-
# `__eq__` / `__hash__` / `__str__` overriding `object`. Decorating those
|
|
208
|
-
# adds ceremony, not safety. One owner per check; pyrefly owns this one.
|
|
209
|
-
# * reportPrivateUsage (340) — same population as the SLF001 noqas.
|
|
210
|
-
# * reportUnnecessaryIsInstance (14) — half are pyright generic artifacts
|
|
211
|
-
# ("Ref[Code] is always an instance of Ref[Unknown]").
|
|
212
|
-
# * reportUnnecessaryComparison (7) — all `str`/`None` guards in `model/`
|
|
213
|
-
# that validate untyped caller input at runtime. Deliberate.
|
|
214
|
-
|
|
215
95
|
[dependency-groups]
|
|
216
96
|
dev = [
|
|
217
97
|
# Checker versions carry UPPER bounds. uv.lock is gitignored (published
|
|
@@ -220,18 +100,13 @@ dev = [
|
|
|
220
100
|
# ruff on `select = ["ALL"]` that is not hypothetical: ruff 0.16.0
|
|
221
101
|
# stabilised 12 preview rules and would have turned CI red with no commit
|
|
222
102
|
# of ours in between. Bump these deliberately, in their own MR.
|
|
223
|
-
#
|
|
224
|
-
# basedpyright is pinned *exactly*, because the bound above was not enough:
|
|
225
|
-
# 1.39.10 is a patch release built on pyright 1.1.412, which stopped solving
|
|
226
|
-
# a generic class passed as a `Callable[[], R]` factory from the declared
|
|
227
|
-
# type — `x: Box[int] = make(Box)` is an error there and fine on 1.1.411.
|
|
228
|
-
# Every `field(factory=ItemList, ...)` in the model is that shape, so a fresh
|
|
229
|
-
# environment failed `just check` with 44 errors and no commit of ours in
|
|
230
|
-
# between. Still broken on pyright 1.1.414 / basedpyright 1.40.2. The pin
|
|
231
|
-
# goes with basedpyright itself in the pyrefly-only migration.
|
|
232
|
-
"basedpyright==1.39.9",
|
|
233
103
|
"catppuccin[pygments]>=2.5.0",
|
|
234
104
|
"types-lxml>=2025.3.30",
|
|
105
|
+
# Stubs for the two untyped test-only imports, so pyrefly checks tests/ without
|
|
106
|
+
# `untyped-import` findings (#425): jsonschema (tests/support/json_schema.py)
|
|
107
|
+
# and PyYAML (test_query_closure). Dev group only — nothing published needs them.
|
|
108
|
+
"types-jsonschema>=4.21.0",
|
|
109
|
+
"types-PyYAML>=6.0.12",
|
|
235
110
|
"mkdocs-material>=9.7.6",
|
|
236
111
|
"mkdocstrings[python]>=1.0.3",
|
|
237
112
|
"polars>=1.39.3",
|
|
@@ -239,6 +114,7 @@ dev = [
|
|
|
239
114
|
"jsonschema>=4.21.0",
|
|
240
115
|
"pytest-benchmark>=4.0.0",
|
|
241
116
|
"pytest-cov>=7.1.0",
|
|
117
|
+
"pytest-xdist>=3.8.0",
|
|
242
118
|
"pytest-memray>=1.7.0",
|
|
243
119
|
"pytest-recording>=0.13.4",
|
|
244
120
|
"pydoclint>=0.9.1,<0.10",
|
|
@@ -26,7 +26,7 @@ class _DimExpr:
|
|
|
26
26
|
def __init__(self, id: str) -> None:
|
|
27
27
|
self._id = id
|
|
28
28
|
|
|
29
|
-
def __eq__(self, other: object) -> DimFilter: #
|
|
29
|
+
def __eq__(self, other: object) -> DimFilter: # pyrefly: ignore[bad-override]
|
|
30
30
|
if not isinstance(other, str):
|
|
31
31
|
msg = f"Expected a code string, got {type(other).__name__!r}"
|
|
32
32
|
raise TypeError(msg)
|
|
@@ -137,7 +137,7 @@ class Session:
|
|
|
137
137
|
"""
|
|
138
138
|
hit = self._map.get(str(urn))
|
|
139
139
|
if hit is not None:
|
|
140
|
-
return hit #
|
|
140
|
+
return hit # pyrefly: ignore[bad-return]
|
|
141
141
|
return None
|
|
142
142
|
|
|
143
143
|
def get_at_least[T](self, urn: SdmxUrn[T], completeness: Completeness) -> T | None:
|
|
@@ -154,7 +154,7 @@ class Session:
|
|
|
154
154
|
return None
|
|
155
155
|
hit = self._map.get(key)
|
|
156
156
|
if hit is not None:
|
|
157
|
-
return hit #
|
|
157
|
+
return hit # pyrefly: ignore[bad-return]
|
|
158
158
|
return None
|
|
159
159
|
|
|
160
160
|
def completeness_for(self, urn: SdmxUrn[object]) -> Completeness:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Arrow record batch → polars frame, on the fast path (#427).
|
|
2
|
+
|
|
3
|
+
The data writers convert every batch they encode, so the conversion is on the
|
|
4
|
+
hot path of every bulk download. `pl.from_arrow(batch)` is the zero-copy
|
|
5
|
+
conversion; the `pl.DataFrame(batch)` constructor takes a slower general path —
|
|
6
|
+
2.4x slower to build the frame on polars 1.39, and 1.33x slower for SDMX-CSV's
|
|
7
|
+
whole per-batch encode. 4f65fd7 switched to the constructor for its return type
|
|
8
|
+
and cost a consumer ~17% on large CSV downloads.
|
|
9
|
+
|
|
10
|
+
`from_arrow` is typed `DataFrame | Series`, and since polars 2.0 really returns
|
|
11
|
+
a `Series` for stream-only inputs. A `cast` would pass that through unchecked,
|
|
12
|
+
so the result is narrowed at runtime instead: a record batch takes the fast
|
|
13
|
+
branch, and anything that ever comes back as a `Series` falls back to the
|
|
14
|
+
constructor rather than being mislabelled.
|
|
15
|
+
|
|
16
|
+
Shared by the SDMX-CSV and SDMX-JSON data writers, hence `sdmxlib.formats`.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import polars as pl
|
|
20
|
+
import pyarrow as pa
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def frame_of(batch: pa.RecordBatch) -> pl.DataFrame:
|
|
24
|
+
"""The batch as a polars frame, zero-copy where polars allows it."""
|
|
25
|
+
df = pl.from_arrow(batch)
|
|
26
|
+
if isinstance(df, pl.DataFrame):
|
|
27
|
+
return df
|
|
28
|
+
return pl.DataFrame(batch)
|
|
@@ -23,7 +23,7 @@ column maps in their dict-insertion order, which matches the order in
|
|
|
23
23
|
which they were declared on the structure.
|
|
24
24
|
|
|
25
25
|
Performance: rows are serialised via Polars' Rust CSV writer
|
|
26
|
-
(``pl.
|
|
26
|
+
(``pl.from_arrow(batch).write_csv``) rather than Python's `csv`,
|
|
27
27
|
which makes the writer fast enough that bulk SDMX-CSV downloads are
|
|
28
28
|
network-bound rather than writer-bound (#61).
|
|
29
29
|
|
|
@@ -47,6 +47,7 @@ from typing import TYPE_CHECKING
|
|
|
47
47
|
|
|
48
48
|
import polars as pl
|
|
49
49
|
|
|
50
|
+
from sdmxlib.formats._arrow import frame_of
|
|
50
51
|
from sdmxlib.formats.sdmx_csv.reader import STRUCTURE_KINDS
|
|
51
52
|
from sdmxlib.model.dataflow import Dataflow
|
|
52
53
|
from sdmxlib.model.datastructure import DataStructure
|
|
@@ -143,11 +144,9 @@ def write_sdmx_csv(
|
|
|
143
144
|
)
|
|
144
145
|
raise KeyError(msg)
|
|
145
146
|
|
|
146
|
-
#
|
|
147
|
-
# `DataFrame | Series` and since polars 2.0 really does return a Series
|
|
148
|
-
# for stream-only inputs — a `cast` here would have hidden that.
|
|
147
|
+
# `frame_of`, not the `pl.DataFrame` constructor: 1.33x per batch (#427).
|
|
149
148
|
df = (
|
|
150
|
-
|
|
149
|
+
frame_of(batch)
|
|
151
150
|
.select(data_cols)
|
|
152
151
|
.with_columns(
|
|
153
152
|
pl.lit(structure).alias("STRUCTURE"),
|
|
@@ -28,6 +28,7 @@ if TYPE_CHECKING:
|
|
|
28
28
|
from sdmxlib.model.datastructure import Component, DataStructure, Dimension
|
|
29
29
|
from sdmxlib.model.istring import InternationalString
|
|
30
30
|
|
|
31
|
+
from sdmxlib.formats._arrow import frame_of
|
|
31
32
|
from sdmxlib.formats._coerce import to_int
|
|
32
33
|
from sdmxlib.model.errors import SdmxParseError
|
|
33
34
|
from sdmxlib.model.ref import Ref
|
|
@@ -640,11 +641,8 @@ def encode_batch(
|
|
|
640
641
|
)
|
|
641
642
|
raise KeyError(msg)
|
|
642
643
|
|
|
643
|
-
#
|
|
644
|
-
|
|
645
|
-
# `from_arrow` return a Series for stream-only inputs, which a `cast` would
|
|
646
|
-
# have passed through unchecked.
|
|
647
|
-
df = pl.DataFrame(batch)
|
|
644
|
+
# `frame_of`, not the `pl.DataFrame` constructor: 2.4x on the conversion (#427).
|
|
645
|
+
df = frame_of(batch)
|
|
648
646
|
n = df.height
|
|
649
647
|
if n == 0:
|
|
650
648
|
return b""
|
|
@@ -12,10 +12,30 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
from sdmxlib.formats._index import lookup
|
|
14
14
|
from sdmxlib.model.ref import Ref
|
|
15
|
-
from sdmxlib.model.urn import SdmxUrn
|
|
15
|
+
from sdmxlib.model.urn import ParsesUrn, SdmxUrn
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def resolve_any_urn(urn_val: object, index: dict[str, object]) -> Ref[object] | None:
|
|
19
|
+
"""`resolve_urn` for a reference that may name any artefact.
|
|
20
|
+
|
|
21
|
+
A categorisation's source and target, or a constraint's attachment, can be
|
|
22
|
+
any maintainable, so there is no class to parse the URN as or to guard the
|
|
23
|
+
resolved object with. Split from `resolve_urn` when `SdmxUrn.parse` became
|
|
24
|
+
`SdmxUrn[object]` (#399): the typed path parses on the class
|
|
25
|
+
(`cls.parse_urn`), which `object` has not got, and saying "any" by name
|
|
26
|
+
here is clearer than an `object` witness there.
|
|
27
|
+
"""
|
|
28
|
+
if not isinstance(urn_val, str) or not urn_val.startswith("urn:"):
|
|
29
|
+
return None
|
|
30
|
+
try:
|
|
31
|
+
urn = SdmxUrn.parse(urn_val)
|
|
32
|
+
except (ValueError, TypeError):
|
|
33
|
+
return None
|
|
34
|
+
obj = lookup(index, urn)
|
|
35
|
+
return Ref.of(urn, obj) if obj is not None else Ref.unresolved(urn)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def resolve_urn[T: ParsesUrn](urn_val: object, index: dict[str, object], cls: type[T]) -> Ref[T] | None:
|
|
19
39
|
"""Parse a URN string and resolve it against the artefact index.
|
|
20
40
|
|
|
21
41
|
*cls* is both a type witness and a runtime guard: the indexed object is
|
|
@@ -27,7 +47,7 @@ def resolve_urn[T](urn_val: object, index: dict[str, object], cls: type[T]) -> R
|
|
|
27
47
|
if not isinstance(urn_val, str) or not urn_val.startswith("urn:"):
|
|
28
48
|
return None
|
|
29
49
|
try:
|
|
30
|
-
urn =
|
|
50
|
+
urn = cls.parse_urn(urn_val)
|
|
31
51
|
except (ValueError, TypeError):
|
|
32
52
|
return None
|
|
33
53
|
obj = lookup(index, urn)
|
|
@@ -19,7 +19,7 @@ from datetime import date, datetime
|
|
|
19
19
|
|
|
20
20
|
from sdmxlib.formats._coerce import to_date, to_datetime, to_int
|
|
21
21
|
from sdmxlib.formats.sdmx_json._dto import ingest_annotations, ingest_istr
|
|
22
|
-
from sdmxlib.formats.sdmx_json._resolve import resolve_urn
|
|
22
|
+
from sdmxlib.formats.sdmx_json._resolve import resolve_any_urn, resolve_urn
|
|
23
23
|
from sdmxlib.formats.sdmx_json._structure_dto import (
|
|
24
24
|
AgencySchemeJson,
|
|
25
25
|
AttributeJson,
|
|
@@ -552,8 +552,8 @@ def from_categorisation(cat: CategorisationJson, maintainer: Agency, index: dict
|
|
|
552
552
|
description=ingest_istr(cat.description, cat.descriptions),
|
|
553
553
|
annotations=ingest_annotations(cat.annotations),
|
|
554
554
|
uri=read_uri(cat.links),
|
|
555
|
-
source=
|
|
556
|
-
target=
|
|
555
|
+
source=resolve_any_urn(cat.source, index),
|
|
556
|
+
target=resolve_any_urn(cat.target, index),
|
|
557
557
|
valid_from=parse_dt(cat.valid_from),
|
|
558
558
|
valid_to=parse_dt(cat.valid_to),
|
|
559
559
|
)
|
|
@@ -571,7 +571,7 @@ def from_provision_agreement(
|
|
|
571
571
|
annotations=ingest_annotations(pa.annotations),
|
|
572
572
|
uri=read_uri(pa.links),
|
|
573
573
|
dataflow=resolve_urn(pa.dataflow, index, Dataflow),
|
|
574
|
-
data_provider=
|
|
574
|
+
data_provider=resolve_any_urn(pa.data_provider, index),
|
|
575
575
|
valid_from=parse_dt(pa.valid_from),
|
|
576
576
|
valid_to=parse_dt(pa.valid_to),
|
|
577
577
|
)
|
|
@@ -585,13 +585,13 @@ def from_constraint_attachment(ca: ConstraintAttachmentJson | None, index: dict[
|
|
|
585
585
|
if ca is None:
|
|
586
586
|
return ConstraintAttachment()
|
|
587
587
|
if ca.dataflows:
|
|
588
|
-
return ConstraintAttachment(dataflow=
|
|
588
|
+
return ConstraintAttachment(dataflow=resolve_any_urn(ca.dataflows[0], index))
|
|
589
589
|
if ca.data_structures:
|
|
590
|
-
return ConstraintAttachment(data_structure=
|
|
590
|
+
return ConstraintAttachment(data_structure=resolve_any_urn(ca.data_structures[0], index))
|
|
591
591
|
if ca.provision_agreements:
|
|
592
|
-
return ConstraintAttachment(provision_agreement=
|
|
592
|
+
return ConstraintAttachment(provision_agreement=resolve_any_urn(ca.provision_agreements[0], index))
|
|
593
593
|
if ca.data_provider is not None:
|
|
594
|
-
return ConstraintAttachment(data_provider=
|
|
594
|
+
return ConstraintAttachment(data_provider=resolve_any_urn(ca.data_provider, index))
|
|
595
595
|
return ConstraintAttachment()
|
|
596
596
|
|
|
597
597
|
|
|
@@ -637,7 +637,7 @@ def _hcode_ref(urn_val: str | None) -> Ref[Code] | None:
|
|
|
637
637
|
if not isinstance(urn_val, str) or not urn_val.startswith("urn:"):
|
|
638
638
|
return None
|
|
639
639
|
try:
|
|
640
|
-
return Ref[Code].unresolved(
|
|
640
|
+
return Ref[Code].unresolved(Code.parse_urn(urn_val))
|
|
641
641
|
except (ValueError, TypeError) as exc:
|
|
642
642
|
warnings.warn(
|
|
643
643
|
f"Skipping unparseable code URN in hierarchical code: {urn_val!r} ({exc})",
|
|
@@ -719,8 +719,8 @@ def from_hierarchy_association(
|
|
|
719
719
|
valid_from=parse_dt(ha.valid_from),
|
|
720
720
|
valid_to=parse_dt(ha.valid_to),
|
|
721
721
|
linked_hierarchy=resolve_urn(ha.linked_hierarchy, index, Hierarchy),
|
|
722
|
-
linked_object=
|
|
723
|
-
context_object=
|
|
722
|
+
linked_object=resolve_any_urn(ha.linked_object, index),
|
|
723
|
+
context_object=resolve_any_urn(ha.context_object, index),
|
|
724
724
|
)
|
|
725
725
|
|
|
726
726
|
|
|
@@ -888,8 +888,8 @@ def from_concept_scheme_map(
|
|
|
888
888
|
version=csm.version,
|
|
889
889
|
name=ingest_istr(csm.name, csm.names),
|
|
890
890
|
description=ingest_istr(csm.description, csm.descriptions),
|
|
891
|
-
source=
|
|
892
|
-
target=
|
|
891
|
+
source=resolve_any_urn(csm.source, index),
|
|
892
|
+
target=resolve_any_urn(csm.target, index),
|
|
893
893
|
maps=tuple(maps),
|
|
894
894
|
annotations=ingest_annotations(csm.annotations),
|
|
895
895
|
uri=read_uri(csm.links),
|
|
@@ -908,8 +908,8 @@ def from_category_scheme_map(
|
|
|
908
908
|
version=catm.version,
|
|
909
909
|
name=ingest_istr(catm.name, catm.names),
|
|
910
910
|
description=ingest_istr(catm.description, catm.descriptions),
|
|
911
|
-
source=
|
|
912
|
-
target=
|
|
911
|
+
source=resolve_any_urn(catm.source, index),
|
|
912
|
+
target=resolve_any_urn(catm.target, index),
|
|
913
913
|
maps=tuple(maps),
|
|
914
914
|
annotations=ingest_annotations(catm.annotations),
|
|
915
915
|
uri=read_uri(catm.links),
|
|
@@ -926,8 +926,8 @@ def from_structure_map(sm: StructureMapJson, maintainer: Agency, index: dict[str
|
|
|
926
926
|
version=sm.version,
|
|
927
927
|
name=ingest_istr(sm.name, sm.names),
|
|
928
928
|
description=ingest_istr(sm.description, sm.descriptions),
|
|
929
|
-
source=
|
|
930
|
-
target=
|
|
929
|
+
source=resolve_any_urn(sm.source, index),
|
|
930
|
+
target=resolve_any_urn(sm.target, index),
|
|
931
931
|
maps=tuple(maps),
|
|
932
932
|
annotations=ingest_annotations(sm.annotations),
|
|
933
933
|
uri=read_uri(sm.links),
|
|
@@ -15,10 +15,12 @@ from typing import Any, NamedTuple
|
|
|
15
15
|
|
|
16
16
|
import msgspec
|
|
17
17
|
|
|
18
|
+
from sdmxlib.model.base import MaintainableArtefact
|
|
18
19
|
from sdmxlib.model.constraint import DataConstraint
|
|
19
20
|
from sdmxlib.model.message import StructureMessage
|
|
20
21
|
from sdmxlib.model.ref import artefact_urn
|
|
21
22
|
from sdmxlib.model.urn import SdmxUrn
|
|
23
|
+
from sdmxlib.model.version import check_writable
|
|
22
24
|
|
|
23
25
|
from ._dto import ENCODER, Link, Meta, Sender, WireStruct
|
|
24
26
|
from ._structure_dto import (
|
|
@@ -187,6 +189,8 @@ def write(
|
|
|
187
189
|
|
|
188
190
|
Raises:
|
|
189
191
|
ValueError: If ``metadata_links`` is given without ``base_url``.
|
|
192
|
+
VersionFormatError: If an artefact's version is one the structure
|
|
193
|
+
schema's `VersionType` (SDMX-ML 3.0's grammar) does not accept.
|
|
190
194
|
"""
|
|
191
195
|
link_base: str | None = None
|
|
192
196
|
if metadata_links:
|
|
@@ -195,6 +199,15 @@ def write(
|
|
|
195
199
|
raise ValueError(msg_err)
|
|
196
200
|
link_base = base_url
|
|
197
201
|
|
|
202
|
+
# The structure schema's `VersionType` is SDMX-ML 3.0's, so a version 3.0
|
|
203
|
+
# refuses would be written here schema-invalid (#417). Checked over every
|
|
204
|
+
# maintainable before anything is encoded, as the SDMX-ML writers do.
|
|
205
|
+
for artefact in msg.all_artefacts():
|
|
206
|
+
if isinstance(artefact, MaintainableArtefact):
|
|
207
|
+
check_writable(
|
|
208
|
+
artefact.version, "json", artefact=f"{type(artefact).__name__} {artefact.agency_id}:{artefact.id}"
|
|
209
|
+
)
|
|
210
|
+
|
|
198
211
|
data: dict[str, object] = {}
|
|
199
212
|
|
|
200
213
|
for sec in _SECTIONS:
|
|
@@ -51,7 +51,7 @@ from sdmxlib.model.organisation import Agency
|
|
|
51
51
|
from sdmxlib.model.provision import ProvisionAgreement
|
|
52
52
|
from sdmxlib.model.ref import Ref, SupportsUrn
|
|
53
53
|
from sdmxlib.model.representation import Facet
|
|
54
|
-
from sdmxlib.model.urn import SdmxUrn
|
|
54
|
+
from sdmxlib.model.urn import ParsesUrn, SdmxUrn
|
|
55
55
|
from sdmxlib.model.validation import SdmxConformanceError
|
|
56
56
|
from sdmxlib.model.version import check_writable
|
|
57
57
|
|
|
@@ -506,7 +506,7 @@ class ReaderHelpers:
|
|
|
506
506
|
el = local_rep_el.find("str:EnumerationFormat", namespaces=self._ns)
|
|
507
507
|
return self.read_text_format(el) if el is not None else None
|
|
508
508
|
|
|
509
|
-
def read_representation[T](
|
|
509
|
+
def read_representation[T: ParsesUrn](
|
|
510
510
|
self,
|
|
511
511
|
local_rep_el: etree._Element,
|
|
512
512
|
index: dict[str, object] | None = None,
|
|
@@ -542,7 +542,7 @@ class ReaderHelpers:
|
|
|
542
542
|
return Ref[T].unresolved(urn)
|
|
543
543
|
text = (enum_el.text or "").strip()
|
|
544
544
|
if text.startswith("urn:"):
|
|
545
|
-
urn =
|
|
545
|
+
urn = enum_cls.parse_urn(text)
|
|
546
546
|
_require_urn_class(urn, enum_cls, tag)
|
|
547
547
|
return Ref[T].unresolved(urn)
|
|
548
548
|
tf_el = local_rep_el.find("str:TextFormat", namespaces=self._ns)
|
|
@@ -998,7 +998,7 @@ def _read_codelist_extensions(cl_el: etree._Element, ns: dict[str, str]) -> tupl
|
|
|
998
998
|
urn_text = cl_ref_el.text.strip()
|
|
999
999
|
if not urn_text.startswith("urn:"):
|
|
1000
1000
|
continue
|
|
1001
|
-
codelist_ref = Ref[Codelist].unresolved(
|
|
1001
|
+
codelist_ref = Ref[Codelist].unresolved(Codelist.parse_urn(urn_text))
|
|
1002
1002
|
|
|
1003
1003
|
selection: CodeSelection | None = None
|
|
1004
1004
|
sel_el = ext_el.find("str:InclusiveCodeSelection", namespaces=ns)
|
|
@@ -945,33 +945,18 @@ class LocalRegistry(SessionFactory):
|
|
|
945
945
|
For Codelists, returns a ``LazyCodelist`` by default (codes not
|
|
946
946
|
materialized). Pass ``eager=True`` to get a real ``Codelist``.
|
|
947
947
|
|
|
948
|
-
**
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
and the explicit spelling is the common one. **Narrowing
|
|
961
|
-
`SdmxUrn.parse` to `SdmxUrn[object]`** — #399's option 2 — measures at
|
|
962
|
-
52 basedpyright + 27 pyrefly findings, every one the same fact: a
|
|
963
|
-
`Ref[object]` does not go where a `Ref[Codelist]` is wanted. `Ref` is
|
|
964
|
-
*covariant* (see `Ref`'s own class comment and #289), so the assignment
|
|
965
|
-
that works is the other direction; narrowing `parse` asks for this one,
|
|
966
|
-
which covariance does not give. They are sites of correct code, not
|
|
967
|
-
defects.
|
|
968
|
-
|
|
969
|
-
Pass a typed URN where you have one; narrow with `isinstance` where you
|
|
970
|
-
do not. `tests/typing/probe_urn_any.py` pins the declared types and
|
|
971
|
-
`tests/unit/test_overload_any_ambiguity.py` the disagreement, so this
|
|
972
|
-
caveat comes due for deletion on its own when basedpyright conforms.
|
|
973
|
-
|
|
974
|
-
**`expect=` is the way out that does not wait on any of that (#406).**
|
|
948
|
+
**An untyped URN answers `object | None`.** `SdmxUrn.parse(text)` and
|
|
949
|
+
`artefact_urn(x)` are `SdmxUrn[object]` (#399): a URN string can name
|
|
950
|
+
anything, so this cannot say which class comes back, and the caller
|
|
951
|
+
narrows. A typed URN — `Codelist.urn_for(...)`, `Codelist.parse_urn(text)`,
|
|
952
|
+
`artefact.urn` — picks the matching overload and its class.
|
|
953
|
+
|
|
954
|
+
Until #399 those were `SdmxUrn[Any]`, which made the call ambiguous
|
|
955
|
+
across these overloads; basedpyright then answered a confident, wrong
|
|
956
|
+
`LazyCodelist | None` and pyrefly an unchecked `Any | None`.
|
|
957
|
+
`tests/typing/probe_urn_any.py` pins what is declared now.
|
|
958
|
+
|
|
959
|
+
**`expect=` names the class when the URN does not carry it (#406).**
|
|
975
960
|
It names the class, so `T` comes from the argument rather than from the
|
|
976
961
|
URN's phantom, and the ambiguity above never arises:
|
|
977
962
|
|
|
@@ -1336,7 +1321,7 @@ class LocalRegistry(SessionFactory):
|
|
|
1336
1321
|
`tests/unit/test_query_applicability.py` compares them.
|
|
1337
1322
|
"""
|
|
1338
1323
|
rows = self._applicability(dataflow, dim_id)
|
|
1339
|
-
return [Ref[Hierarchy].unresolved(
|
|
1324
|
+
return [Ref[Hierarchy].unresolved(Hierarchy.parse_urn(urn)) for urn, _ha, _scoped in rows]
|
|
1340
1325
|
|
|
1341
1326
|
def unique_hierarchy(
|
|
1342
1327
|
self,
|
|
@@ -1378,7 +1363,7 @@ class LocalRegistry(SessionFactory):
|
|
|
1378
1363
|
dataflow_urn=str(artefact_urn(dataflow)),
|
|
1379
1364
|
ha_urns=sorted(association for _h, association, _s in chosen),
|
|
1380
1365
|
)
|
|
1381
|
-
return Ref[Hierarchy].unresolved(
|
|
1366
|
+
return Ref[Hierarchy].unresolved(Hierarchy.parse_urn(rows[0][0]))
|
|
1382
1367
|
|
|
1383
1368
|
# ── The data plane ────────────────────────────────────────────────────
|
|
1384
1369
|
|