sdmxlib 0.44.0__tar.gz → 0.45.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.44.0 → sdmxlib-0.45.0}/PKG-INFO +1 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/pyproject.toml +1 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/__init__.py +3 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/metadata.py +7 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/reader.py +95 -14
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/structure_values.py +37 -3
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/writer.py +124 -67
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml/_common.py +3 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml21/reader.py +53 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml21/writer.py +97 -9
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml30/metadata.py +8 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml30/reader.py +2 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml30/writer.py +36 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/local/__init__.py +2 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/local/data_store.py +38 -32
- sdmxlib-0.45.0/src/sdmxlib/local/sql.py +166 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/code_query.py +2 -2
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/istring.py +9 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/lazy.py +29 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/readers.py +80 -6
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/schema.py +14 -1
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/writers.py +40 -4
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/README.md +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/_duckdb.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/client.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/federated.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/filters.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/policy.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/providers.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/query.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/registry.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/api/session.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/builders/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/builders/contentconstraint.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/catalog.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/data_store.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_csv/reader.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_csv/writer.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/_data_common.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/data_v1.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json/data_v2.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json_1_0/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_json_1_0/writer.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml31/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml31/namespaces.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml31/reader.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/formats/sdmx_ml31/writer.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/local/registry.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/annotations.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/base.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/binding.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/category.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/codelist.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/collections.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/concept.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/constraint.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/convert.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/dataflow.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/dataset.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/datastructure.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/expr.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/hierarchy.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/hierarchy_query.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/in_memory_registry.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/mapping.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/message.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/metadataflow.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/metadataset.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/metadatastructure.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/organisation.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/provision.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/ref.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/registry.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/representation.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/urn.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/model/validation.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/polars.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/py.typed +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/rest.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/sql.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/__init__.py +0 -0
- {sdmxlib-0.44.0 → sdmxlib-0.45.0}/src/sdmxlib/storage/_kinds.py +0 -0
|
@@ -10,7 +10,7 @@ from sdmxlib.catalog import DataflowEntry, FederatedCatalog
|
|
|
10
10
|
from sdmxlib.data_store import DataStore
|
|
11
11
|
from sdmxlib.sql import CompileContext, Predicate, SqlRelation
|
|
12
12
|
from sdmxlib.formats import parse, read, serialize, to_json, to_xml
|
|
13
|
-
from sdmxlib.local import LazyCodelist, LazyHierarchy, Registry
|
|
13
|
+
from sdmxlib.local import LazyCodelist, LazyHierarchy, QueryPlan, Registry, SchemaInspector
|
|
14
14
|
from sdmxlib.local.data_store import DuckDBDataStore, DuckLakeVersionError
|
|
15
15
|
from sdmxlib.rest import ComponentFilter, parse_components
|
|
16
16
|
from sdmxlib.model.convert import from_dict, to_dict
|
|
@@ -206,6 +206,7 @@ __all__ = [
|
|
|
206
206
|
"Predicate",
|
|
207
207
|
"Provider",
|
|
208
208
|
"ProvisionAgreement",
|
|
209
|
+
"QueryPlan",
|
|
209
210
|
"Ref",
|
|
210
211
|
"ReferenceIssue",
|
|
211
212
|
"ReferencePeriod",
|
|
@@ -220,6 +221,7 @@ __all__ = [
|
|
|
220
221
|
"Representation",
|
|
221
222
|
"RepresentationMap",
|
|
222
223
|
"RestRegistry",
|
|
224
|
+
"SchemaInspector",
|
|
223
225
|
"SdmxRef",
|
|
224
226
|
"SdmxUrn",
|
|
225
227
|
"SqlRelation",
|
|
@@ -111,6 +111,10 @@ def _read_metadata_set(obj: dict[str, Any]) -> MetadataSet:
|
|
|
111
111
|
targets = [Ref[Any].unresolved(SdmxUrn.parse(t)) for t in obj.get("targets") or []]
|
|
112
112
|
attributes = [_read_reported_attribute(a) for a in obj.get("attributes") or []]
|
|
113
113
|
|
|
114
|
+
def _dt(key: str) -> datetime | None:
|
|
115
|
+
val = obj.get(key)
|
|
116
|
+
return datetime.fromisoformat(val) if isinstance(val, str) and val else None
|
|
117
|
+
|
|
114
118
|
return MetadataSet(
|
|
115
119
|
id=obj["id"],
|
|
116
120
|
maintainer=Agency(id=obj.get("agencyID", "")),
|
|
@@ -119,6 +123,9 @@ def _read_metadata_set(obj: dict[str, Any]) -> MetadataSet:
|
|
|
119
123
|
name=_read_istring(obj, "name"),
|
|
120
124
|
description=_read_istring(obj, "description"),
|
|
121
125
|
is_final=bool(obj.get("isFinal", False)),
|
|
126
|
+
# Writer emits validFrom/validTo; parse them back so they round-trip (#159).
|
|
127
|
+
valid_from=_dt("validFrom"),
|
|
128
|
+
valid_to=_dt("validTo"),
|
|
122
129
|
targets=targets,
|
|
123
130
|
attributes=ItemList(attributes),
|
|
124
131
|
)
|
|
@@ -5,7 +5,7 @@ All references within the message are auto-resolved where possible.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import warnings
|
|
8
|
-
from datetime import datetime
|
|
8
|
+
from datetime import date, datetime
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
import orjson
|
|
@@ -45,11 +45,14 @@ from sdmxlib.model.mapping import (
|
|
|
45
45
|
CategorySchemeMap,
|
|
46
46
|
ComponentMap,
|
|
47
47
|
ConceptSchemeMap,
|
|
48
|
+
IndexValueMap,
|
|
48
49
|
ItemMap,
|
|
49
50
|
LiteralValueMap,
|
|
50
51
|
MapDataType,
|
|
52
|
+
RegexValueMap,
|
|
51
53
|
RepresentationMap,
|
|
52
54
|
StructureMap,
|
|
55
|
+
ValueMap,
|
|
53
56
|
)
|
|
54
57
|
from sdmxlib.model.message import StructureMessage
|
|
55
58
|
from sdmxlib.model.metadataflow import Metadataflow
|
|
@@ -90,25 +93,43 @@ def _read_datetime_str(obj: dict[str, Any], key: str) -> datetime | None:
|
|
|
90
93
|
return datetime.fromisoformat(val)
|
|
91
94
|
|
|
92
95
|
|
|
96
|
+
def _annotation_url(ann: dict[str, Any]) -> str | None:
|
|
97
|
+
"""Extract the annotation URL from its ``links`` entry (``rel: "self"``).
|
|
98
|
+
|
|
99
|
+
Per the SDMX-JSON 2.0 structure schema an annotation's URL rides in
|
|
100
|
+
the ``links`` array under the ``self`` relationship. Fall back to a
|
|
101
|
+
bare ``url`` key for lenience toward non-conformant producers.
|
|
102
|
+
"""
|
|
103
|
+
links = ann.get("links")
|
|
104
|
+
if isinstance(links, list):
|
|
105
|
+
for link in links:
|
|
106
|
+
if isinstance(link, dict) and link.get("rel") == "self":
|
|
107
|
+
href = link.get("href")
|
|
108
|
+
if isinstance(href, str):
|
|
109
|
+
return href
|
|
110
|
+
url = ann.get("url")
|
|
111
|
+
return url if isinstance(url, str) else None
|
|
112
|
+
|
|
113
|
+
|
|
93
114
|
def _read_annotations(obj: dict[str, Any]) -> Annotations:
|
|
94
115
|
"""Parse the optional ``annotations`` array on any artefact."""
|
|
95
116
|
raw = obj.get("annotations")
|
|
96
117
|
if not raw:
|
|
97
118
|
return Annotations()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
text_raw = ann.get("text")
|
|
101
|
-
text = InternationalString.of(text_raw) if isinstance(text_raw, dict) else InternationalString()
|
|
102
|
-
items.append(
|
|
119
|
+
return Annotations(
|
|
120
|
+
[
|
|
103
121
|
Annotation(
|
|
104
122
|
id=ann.get("id"),
|
|
105
123
|
title=ann.get("title"),
|
|
106
124
|
type=ann.get("type"),
|
|
107
|
-
url=ann
|
|
108
|
-
text
|
|
125
|
+
url=_annotation_url(ann),
|
|
126
|
+
# SDMX-JSON 2.0: localised body splits into ``text`` (best
|
|
127
|
+
# match) + ``texts`` (lang map), same shape as name/desc.
|
|
128
|
+
text=_istring(ann, "text"),
|
|
109
129
|
)
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
for ann in raw
|
|
131
|
+
]
|
|
132
|
+
)
|
|
112
133
|
|
|
113
134
|
|
|
114
135
|
# ── Ref / URN helpers ─────────────────────────────────────────────────────────
|
|
@@ -459,6 +480,21 @@ def _read_attachment(
|
|
|
459
480
|
return AttachmentLevel.DATASET, None, None, None
|
|
460
481
|
|
|
461
482
|
|
|
483
|
+
def _read_concept_roles(obj: dict[str, Any]) -> tuple[str, ...]:
|
|
484
|
+
"""Parse ``conceptRoles`` URN strings → tuple of role ids (item id)."""
|
|
485
|
+
roles: list[str] = []
|
|
486
|
+
for raw in obj.get("conceptRoles") or []:
|
|
487
|
+
if not isinstance(raw, str):
|
|
488
|
+
continue
|
|
489
|
+
try:
|
|
490
|
+
urn = SdmxUrn.parse(raw)
|
|
491
|
+
except (ValueError, TypeError):
|
|
492
|
+
continue
|
|
493
|
+
if urn.item_id is not None:
|
|
494
|
+
roles.append(urn.item_id)
|
|
495
|
+
return tuple(roles)
|
|
496
|
+
|
|
497
|
+
|
|
462
498
|
def _read_dimension(dim: dict[str, Any], index: dict[str, Any]) -> Dimension:
|
|
463
499
|
concept_ref = _resolve_urn(dim.get("conceptIdentity"), index)
|
|
464
500
|
return Dimension(
|
|
@@ -466,6 +502,7 @@ def _read_dimension(dim: dict[str, Any], index: dict[str, Any]) -> Dimension:
|
|
|
466
502
|
concept=concept_ref,
|
|
467
503
|
representation=_read_representation(dim.get("localRepresentation"), index),
|
|
468
504
|
annotations=_read_annotations(dim),
|
|
505
|
+
concept_role=_read_concept_roles(dim),
|
|
469
506
|
)
|
|
470
507
|
|
|
471
508
|
|
|
@@ -499,6 +536,7 @@ def _read_attribute(attr: dict[str, Any], index: dict[str, Any]) -> Attribute:
|
|
|
499
536
|
concept=concept_ref,
|
|
500
537
|
representation=_read_representation(attr.get("localRepresentation"), index),
|
|
501
538
|
annotations=_read_annotations(attr),
|
|
539
|
+
concept_role=_read_concept_roles(attr),
|
|
502
540
|
)
|
|
503
541
|
|
|
504
542
|
|
|
@@ -509,6 +547,7 @@ def _read_measure(meas: dict[str, Any], index: dict[str, Any]) -> Measure:
|
|
|
509
547
|
concept=concept_ref,
|
|
510
548
|
representation=_read_representation(meas.get("localRepresentation"), index),
|
|
511
549
|
annotations=_read_annotations(meas),
|
|
550
|
+
concept_role=_read_concept_roles(meas),
|
|
512
551
|
)
|
|
513
552
|
|
|
514
553
|
|
|
@@ -742,6 +781,37 @@ def _read_mapped_value(arr: Any) -> str | None:
|
|
|
742
781
|
return str(entry)
|
|
743
782
|
|
|
744
783
|
|
|
784
|
+
def _read_value_map(m: dict[str, Any]) -> ValueMap | None:
|
|
785
|
+
"""Reconstruct the correct ``ValueMap`` variant from a representationMapping.
|
|
786
|
+
|
|
787
|
+
The ``sourceValues`` MappedValueType entry discriminates the operation
|
|
788
|
+
via ``isRegEx`` (regex) or ``startIndex``/``endIndex`` (substring
|
|
789
|
+
slice); absent both it is a literal equality map. Reading only the
|
|
790
|
+
``value`` collapses all three to literal (#159).
|
|
791
|
+
"""
|
|
792
|
+
source_arr = m.get("sourceValues")
|
|
793
|
+
s_val = _read_mapped_value(source_arr)
|
|
794
|
+
t_val = _read_mapped_value(m.get("targetValues"))
|
|
795
|
+
if s_val is None or t_val is None:
|
|
796
|
+
return None
|
|
797
|
+
entry = source_arr[0] if isinstance(source_arr, list) and source_arr else {}
|
|
798
|
+
entry = entry if isinstance(entry, dict) else {}
|
|
799
|
+
|
|
800
|
+
def _d(key: str) -> date | None:
|
|
801
|
+
val = m.get(key)
|
|
802
|
+
return date.fromisoformat(val) if isinstance(val, str) and val else None
|
|
803
|
+
|
|
804
|
+
validity = {"valid_from": _d("validFrom"), "valid_to": _d("validTo")}
|
|
805
|
+
if entry.get("isRegEx") is True:
|
|
806
|
+
return RegexValueMap(source_value=s_val, target_value=t_val, **validity)
|
|
807
|
+
start, end = entry.get("startIndex"), entry.get("endIndex")
|
|
808
|
+
if isinstance(start, int) and isinstance(end, int):
|
|
809
|
+
return IndexValueMap(
|
|
810
|
+
source_value=s_val, target_value=t_val, source_start_index=start, source_end_index=end, **validity
|
|
811
|
+
)
|
|
812
|
+
return LiteralValueMap(source_value=s_val, target_value=t_val, **validity)
|
|
813
|
+
|
|
814
|
+
|
|
745
815
|
def _read_structure_sets(
|
|
746
816
|
data: dict[str, Any],
|
|
747
817
|
index: dict[str, Any],
|
|
@@ -772,10 +842,9 @@ def _read_structure_sets(
|
|
|
772
842
|
for rm in data.get("representationMaps") or []:
|
|
773
843
|
value_maps = []
|
|
774
844
|
for m in rm.get("representationMappings") or []:
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
value_maps.append(LiteralValueMap(source_value=s_val, target_value=t_val))
|
|
845
|
+
vm = _read_value_map(m)
|
|
846
|
+
if vm is not None:
|
|
847
|
+
value_maps.append(vm)
|
|
779
848
|
rep_maps.append(
|
|
780
849
|
RepresentationMap(
|
|
781
850
|
id=rm.get("id", ""),
|
|
@@ -786,6 +855,7 @@ def _read_structure_sets(
|
|
|
786
855
|
source=_read_map_endpoint(rm.get("source"), index),
|
|
787
856
|
target=_read_map_endpoint(rm.get("target"), index),
|
|
788
857
|
maps=tuple(value_maps),
|
|
858
|
+
annotations=_read_annotations(rm),
|
|
789
859
|
)
|
|
790
860
|
)
|
|
791
861
|
for csm in data.get("conceptSchemeMaps") or []:
|
|
@@ -804,6 +874,7 @@ def _read_structure_sets(
|
|
|
804
874
|
source=_resolve_urn(csm.get("source"), index),
|
|
805
875
|
target=_resolve_urn(csm.get("target"), index),
|
|
806
876
|
maps=tuple(item_maps),
|
|
877
|
+
annotations=_read_annotations(csm),
|
|
807
878
|
)
|
|
808
879
|
)
|
|
809
880
|
for catm in data.get("categorySchemeMaps") or []:
|
|
@@ -822,6 +893,7 @@ def _read_structure_sets(
|
|
|
822
893
|
source=_resolve_urn(catm.get("source"), index),
|
|
823
894
|
target=_resolve_urn(catm.get("target"), index),
|
|
824
895
|
maps=tuple(item_maps),
|
|
896
|
+
annotations=_read_annotations(catm),
|
|
825
897
|
)
|
|
826
898
|
)
|
|
827
899
|
for sm in data.get("structureMaps") or []:
|
|
@@ -840,6 +912,7 @@ def _read_structure_sets(
|
|
|
840
912
|
source=_resolve_urn(sm.get("source"), index),
|
|
841
913
|
target=_resolve_urn(sm.get("target"), index),
|
|
842
914
|
maps=tuple(comp_maps),
|
|
915
|
+
annotations=_read_annotations(sm),
|
|
843
916
|
)
|
|
844
917
|
)
|
|
845
918
|
|
|
@@ -883,6 +956,8 @@ def _read_hierarchical_code(
|
|
|
883
956
|
children=children,
|
|
884
957
|
annotations=_read_annotations(obj),
|
|
885
958
|
level=level,
|
|
959
|
+
valid_from=_read_datetime_str(obj, "validFrom"),
|
|
960
|
+
valid_to=_read_datetime_str(obj, "validTo"),
|
|
886
961
|
)
|
|
887
962
|
|
|
888
963
|
|
|
@@ -899,10 +974,15 @@ def _read_hierarchies(
|
|
|
899
974
|
levels_by_id: dict[str, Level] = {}
|
|
900
975
|
node = h.get("level")
|
|
901
976
|
while isinstance(node, dict):
|
|
977
|
+
coding = node.get("codingFormat")
|
|
978
|
+
# codingFormat is always a text format (never an enumeration), so
|
|
979
|
+
# the resolution index is unused — pass an empty one.
|
|
980
|
+
coding_facet = _read_representation({"format": coding}, {}) if isinstance(coding, dict) else None
|
|
902
981
|
lv = Level(
|
|
903
982
|
id=node["id"],
|
|
904
983
|
name=_istring(node, "name"),
|
|
905
984
|
description=_istring(node, "description"),
|
|
985
|
+
coding_format=coding_facet if isinstance(coding_facet, Facet) else None,
|
|
906
986
|
annotations=_read_annotations(node),
|
|
907
987
|
)
|
|
908
988
|
levels.append(lv)
|
|
@@ -918,6 +998,7 @@ def _read_hierarchies(
|
|
|
918
998
|
name=_istring(h, "name"),
|
|
919
999
|
description=_istring(h, "description"),
|
|
920
1000
|
is_final=bool(h.get("isFinal", False)),
|
|
1001
|
+
has_formal_levels=bool(h.get("hasFormalLevels", False)),
|
|
921
1002
|
levels=levels,
|
|
922
1003
|
tree=tree,
|
|
923
1004
|
annotations=_read_annotations(h),
|
|
@@ -20,6 +20,7 @@ What it does NOT own:
|
|
|
20
20
|
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
|
+
from sdmxlib.model.annotations import Annotation
|
|
23
24
|
from sdmxlib.model.codelist import Code, Codelist
|
|
24
25
|
from sdmxlib.model.concept import Concept, ConceptScheme
|
|
25
26
|
from sdmxlib.model.istring import InternationalString
|
|
@@ -37,13 +38,44 @@ def _emit_istring(obj: dict[str, Any], key: str, value: InternationalString | No
|
|
|
37
38
|
"""
|
|
38
39
|
if not value:
|
|
39
40
|
return
|
|
40
|
-
pairs =
|
|
41
|
-
if not pairs:
|
|
42
|
-
return
|
|
41
|
+
pairs = value.as_dict()
|
|
43
42
|
obj[key] = next(iter(pairs.values()))
|
|
44
43
|
obj[key + "s"] = pairs
|
|
45
44
|
|
|
46
45
|
|
|
46
|
+
def _annotation_dict(ann: Annotation) -> dict[str, Any]:
|
|
47
|
+
"""Render one ``Annotation`` as a SDMX-JSON 2.0 ``annotations[*]`` entry.
|
|
48
|
+
|
|
49
|
+
Follows the structure-schema shape: ``id`` / ``title`` / ``type``
|
|
50
|
+
scalars (omitted when None), ``text`` + ``texts`` for the localised
|
|
51
|
+
body, and the annotation ``url`` as a ``links`` entry with
|
|
52
|
+
``rel: "self"`` (per the schema's Annotation.links contract).
|
|
53
|
+
"""
|
|
54
|
+
out: dict[str, Any] = {}
|
|
55
|
+
if ann.id is not None:
|
|
56
|
+
out["id"] = ann.id
|
|
57
|
+
if ann.title is not None:
|
|
58
|
+
out["title"] = ann.title
|
|
59
|
+
if ann.type is not None:
|
|
60
|
+
out["type"] = ann.type
|
|
61
|
+
_emit_istring(out, "text", ann.text)
|
|
62
|
+
if ann.url is not None:
|
|
63
|
+
out["links"] = [{"rel": "self", "href": ann.url}]
|
|
64
|
+
return out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def emit_annotations(obj: dict[str, Any], artefact: Any) -> None:
|
|
68
|
+
"""Set ``obj["annotations"]`` from ``artefact.annotations`` when present.
|
|
69
|
+
|
|
70
|
+
Shared by every artefact- and item-level SDMX-JSON writer so annotations
|
|
71
|
+
round-trip through the reader. No-op when the artefact carries none.
|
|
72
|
+
"""
|
|
73
|
+
annotations = getattr(artefact, "annotations", None)
|
|
74
|
+
if not annotations:
|
|
75
|
+
return
|
|
76
|
+
obj["annotations"] = [_annotation_dict(a) for a in annotations]
|
|
77
|
+
|
|
78
|
+
|
|
47
79
|
def serialize_code(code: Code) -> dict[str, Any]:
|
|
48
80
|
"""Render one ``Code`` as a SDMX-JSON ``codelists[*].codes[*]`` entry.
|
|
49
81
|
|
|
@@ -59,6 +91,7 @@ def serialize_code(code: Code) -> dict[str, Any]:
|
|
|
59
91
|
_emit_istring(obj, "description", code.description)
|
|
60
92
|
if code.parent_id is not None:
|
|
61
93
|
obj["parent"] = code.parent_id
|
|
94
|
+
emit_annotations(obj, code)
|
|
62
95
|
return obj
|
|
63
96
|
|
|
64
97
|
|
|
@@ -100,6 +133,7 @@ def serialize_concept(concept: Concept) -> dict[str, Any]:
|
|
|
100
133
|
tf = {k: v for k, v in pairs if v is not None}
|
|
101
134
|
if tf:
|
|
102
135
|
obj["coreRepresentation"] = {"format": tf}
|
|
136
|
+
emit_annotations(obj, concept)
|
|
103
137
|
return obj
|
|
104
138
|
|
|
105
139
|
|