sdmxlib 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdmxlib/__init__.py +155 -0
- sdmxlib/api/__init__.py +7 -0
- sdmxlib/api/client.py +246 -0
- sdmxlib/api/filters.py +67 -0
- sdmxlib/api/providers.py +66 -0
- sdmxlib/api/query.py +428 -0
- sdmxlib/api/registry.py +666 -0
- sdmxlib/api/session.py +124 -0
- sdmxlib/formats/__init__.py +166 -0
- sdmxlib/formats/sdmx_csv/__init__.py +5 -0
- sdmxlib/formats/sdmx_csv/reader.py +147 -0
- sdmxlib/formats/sdmx_json/__init__.py +1 -0
- sdmxlib/formats/sdmx_json/reader.py +897 -0
- sdmxlib/formats/sdmx_json/writer.py +698 -0
- sdmxlib/formats/sdmx_ml21/__init__.py +5 -0
- sdmxlib/formats/sdmx_ml21/namespaces.py +12 -0
- sdmxlib/formats/sdmx_ml21/reader.py +1147 -0
- sdmxlib/formats/sdmx_ml21/writer.py +709 -0
- sdmxlib/formats/sdmx_ml30/__init__.py +1 -0
- sdmxlib/formats/sdmx_ml30/namespaces.py +12 -0
- sdmxlib/formats/sdmx_ml30/reader.py +1138 -0
- sdmxlib/formats/sdmx_ml30/writer.py +730 -0
- sdmxlib/local/__init__.py +6 -0
- sdmxlib/local/_registry.py +368 -0
- sdmxlib/model/__init__.py +122 -0
- sdmxlib/model/annotations.py +103 -0
- sdmxlib/model/base.py +61 -0
- sdmxlib/model/category.py +88 -0
- sdmxlib/model/codelist.py +91 -0
- sdmxlib/model/collections.py +382 -0
- sdmxlib/model/concept.py +121 -0
- sdmxlib/model/constraint.py +92 -0
- sdmxlib/model/convert.py +268 -0
- sdmxlib/model/dataflow.py +49 -0
- sdmxlib/model/dataset.py +337 -0
- sdmxlib/model/datastructure.py +390 -0
- sdmxlib/model/expr.py +120 -0
- sdmxlib/model/hierarchy.py +111 -0
- sdmxlib/model/istring.py +81 -0
- sdmxlib/model/mapping.py +134 -0
- sdmxlib/model/message.py +70 -0
- sdmxlib/model/organisation.py +149 -0
- sdmxlib/model/provision.py +45 -0
- sdmxlib/model/ref.py +195 -0
- sdmxlib/model/registry.py +191 -0
- sdmxlib/model/representation.py +99 -0
- sdmxlib/model/urn.py +130 -0
- sdmxlib/model/validation.py +338 -0
- sdmxlib/polars.py +392 -0
- sdmxlib/py.typed +0 -0
- sdmxlib/storage/__init__.py +28 -0
- sdmxlib/storage/lazy.py +329 -0
- sdmxlib/storage/readers.py +310 -0
- sdmxlib/storage/schema.py +289 -0
- sdmxlib/storage/writers.py +503 -0
- sdmxlib-0.8.0.dist-info/METADATA +102 -0
- sdmxlib-0.8.0.dist-info/RECORD +58 -0
- sdmxlib-0.8.0.dist-info/WHEEL +4 -0
sdmxlib/polars.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""Polars integration for sdmxlib.
|
|
2
|
+
|
|
3
|
+
Low-level helpers for schema generation, validation, and label views.
|
|
4
|
+
Used internally by :class:`~sdmxlib.model.dataset.Dataset`; also importable
|
|
5
|
+
directly for standalone use::
|
|
6
|
+
|
|
7
|
+
import sdmxlib.polars as slpl
|
|
8
|
+
|
|
9
|
+
schema = slpl.schema(dsd)
|
|
10
|
+
df = pl.read_csv("data.csv", schema_overrides=schema)
|
|
11
|
+
report = slpl.validation_report(dsd, df)
|
|
12
|
+
labelled = slpl.label(df.lazy(), dsd).collect()
|
|
13
|
+
meta = slpl.structure_frame(dsd)
|
|
14
|
+
|
|
15
|
+
# Higher-level Dataset API
|
|
16
|
+
ds = slpl.scan_csv("data.csv", dsd)
|
|
17
|
+
df = ds.label().collect()
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
import polars as pl
|
|
25
|
+
|
|
26
|
+
from sdmxlib.model.ref import Ref
|
|
27
|
+
from sdmxlib.model.representation import Facet
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from sdmxlib.model.dataset import Dataset
|
|
33
|
+
from sdmxlib.model.datastructure import Attribute, DataStructure, Dimension, Measure, MeasureDimension
|
|
34
|
+
|
|
35
|
+
# ── Facet → Polars dtype mapping ─────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
_TEXT_TYPE_MAP: dict[str, pl.DataType] = {
|
|
38
|
+
"String": pl.String(),
|
|
39
|
+
"Alpha": pl.String(),
|
|
40
|
+
"AlphaNumeric": pl.String(),
|
|
41
|
+
"Numeric": pl.Float64(),
|
|
42
|
+
"Integer": pl.Int64(),
|
|
43
|
+
"Long": pl.Int64(),
|
|
44
|
+
"Short": pl.Int32(),
|
|
45
|
+
"Float": pl.Float64(),
|
|
46
|
+
"Double": pl.Float64(),
|
|
47
|
+
"Decimal": pl.Float64(),
|
|
48
|
+
"Boolean": pl.Boolean(),
|
|
49
|
+
"Date": pl.Date(),
|
|
50
|
+
"DateTime": pl.Datetime(),
|
|
51
|
+
"Time": pl.Time(),
|
|
52
|
+
# All SDMX period types are represented as strings
|
|
53
|
+
"ObservationalTimePeriod": pl.String(),
|
|
54
|
+
"StandardTimePeriod": pl.String(),
|
|
55
|
+
"BasicTimePeriod": pl.String(),
|
|
56
|
+
"GregorianTimePeriod": pl.String(),
|
|
57
|
+
"GregorianYear": pl.String(),
|
|
58
|
+
"GregorianYearMonth": pl.String(),
|
|
59
|
+
"GregorianDay": pl.Date(),
|
|
60
|
+
"ReportingTimePeriod": pl.String(),
|
|
61
|
+
"ReportingYear": pl.String(),
|
|
62
|
+
"ReportingSemester": pl.String(),
|
|
63
|
+
"ReportingTrimester": pl.String(),
|
|
64
|
+
"ReportingQuarter": pl.String(),
|
|
65
|
+
"ReportingMonth": pl.String(),
|
|
66
|
+
"ReportingWeek": pl.String(),
|
|
67
|
+
"ReportingDay": pl.String(),
|
|
68
|
+
"Year": pl.String(),
|
|
69
|
+
"Month": pl.String(),
|
|
70
|
+
"YearMonth": pl.String(),
|
|
71
|
+
"Duration": pl.String(),
|
|
72
|
+
"URI": pl.String(),
|
|
73
|
+
"XHTML": pl.String(),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _dtype_for(component: Dimension | MeasureDimension | Attribute | Measure) -> pl.DataType:
|
|
78
|
+
"""Infer the Polars dtype for a single DSD component."""
|
|
79
|
+
if isinstance(component.representation, Ref) and component.representation.resolved:
|
|
80
|
+
code_ids = sorted(c.id for c in component.representation().codes) # type: ignore[union-attr]
|
|
81
|
+
return pl.Enum(code_ids)
|
|
82
|
+
match component.representation:
|
|
83
|
+
case Facet(type=dtype) if dtype is not None:
|
|
84
|
+
return _TEXT_TYPE_MAP.get(dtype, pl.String())
|
|
85
|
+
case _:
|
|
86
|
+
return pl.String()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ── Public API ────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def schema(dsd: DataStructure) -> dict[str, pl.DataType]:
|
|
93
|
+
"""Return a Polars schema dict for all DSD components.
|
|
94
|
+
|
|
95
|
+
Enumerated components become ``pl.Enum``; text components are mapped
|
|
96
|
+
from their ``Facet.type``; everything else defaults to
|
|
97
|
+
``pl.String``.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
df = pl.read_csv("data.csv", schema_overrides=slpl.schema(dsd))
|
|
101
|
+
"""
|
|
102
|
+
result: dict[str, pl.DataType] = {}
|
|
103
|
+
md = [dsd.measure_dimension] if dsd.measure_dimension is not None else []
|
|
104
|
+
for comp in [*dsd.dimensions, *md, *dsd.attributes, *dsd.measures]:
|
|
105
|
+
result[comp.id] = _dtype_for(comp)
|
|
106
|
+
if dsd.time_dimension is not None:
|
|
107
|
+
td = dsd.time_dimension
|
|
108
|
+
match td.representation:
|
|
109
|
+
case Facet(type=tt) if tt is not None:
|
|
110
|
+
result[td.id] = _TEXT_TYPE_MAP.get(tt, pl.String())
|
|
111
|
+
case _:
|
|
112
|
+
result[td.id] = pl.String()
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def validation_report(
|
|
117
|
+
dsd: DataStructure,
|
|
118
|
+
df: pl.LazyFrame | pl.DataFrame,
|
|
119
|
+
) -> pl.DataFrame:
|
|
120
|
+
"""Validate a DataFrame against enumerated DSD components.
|
|
121
|
+
|
|
122
|
+
Returns a DataFrame of violations::
|
|
123
|
+
|
|
124
|
+
┌──────────┬───────────────┬───────┐
|
|
125
|
+
│ column │ invalid_value │ count │
|
|
126
|
+
└──────────┴───────────────┴───────┘
|
|
127
|
+
|
|
128
|
+
Only columns present in ``df`` are checked. Non-enumerated components
|
|
129
|
+
and unresolved codelists are skipped.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
report = slpl.validation_report(dsd, df)
|
|
133
|
+
if report.is_empty():
|
|
134
|
+
print("All values valid")
|
|
135
|
+
"""
|
|
136
|
+
lf = df.lazy() if isinstance(df, pl.DataFrame) else df
|
|
137
|
+
schema_cols = set(lf.collect_schema().names())
|
|
138
|
+
|
|
139
|
+
violations: list[pl.DataFrame] = []
|
|
140
|
+
md = [dsd.measure_dimension] if dsd.measure_dimension is not None else []
|
|
141
|
+
for comp in [*dsd.dimensions, *md, *dsd.attributes, *dsd.measures]:
|
|
142
|
+
if comp.id not in schema_cols:
|
|
143
|
+
continue
|
|
144
|
+
if not (isinstance(comp.representation, Ref) and comp.representation.resolved):
|
|
145
|
+
continue
|
|
146
|
+
valid_codes = {c.id for c in comp.representation().codes} # type: ignore[union-attr]
|
|
147
|
+
bad = (
|
|
148
|
+
lf.select(pl.col(comp.id).cast(pl.String))
|
|
149
|
+
.filter(~pl.col(comp.id).is_in(valid_codes) & pl.col(comp.id).is_not_null())
|
|
150
|
+
.group_by(comp.id)
|
|
151
|
+
.agg(pl.len().alias("count"))
|
|
152
|
+
.rename({comp.id: "invalid_value"})
|
|
153
|
+
.with_columns(pl.lit(comp.id).alias("column"))
|
|
154
|
+
.select("column", "invalid_value", "count")
|
|
155
|
+
.collect()
|
|
156
|
+
)
|
|
157
|
+
if not bad.is_empty():
|
|
158
|
+
violations.append(bad)
|
|
159
|
+
|
|
160
|
+
if violations:
|
|
161
|
+
return pl.concat(violations)
|
|
162
|
+
return pl.DataFrame(
|
|
163
|
+
{
|
|
164
|
+
"column": pl.Series([], dtype=pl.String),
|
|
165
|
+
"invalid_value": pl.Series([], dtype=pl.String),
|
|
166
|
+
"count": pl.Series([], dtype=pl.UInt32),
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _label_view(
|
|
172
|
+
lf: pl.LazyFrame,
|
|
173
|
+
dsd: DataStructure,
|
|
174
|
+
lang: str = "en",
|
|
175
|
+
suffix: str = "_label",
|
|
176
|
+
) -> pl.LazyFrame:
|
|
177
|
+
"""Replace enumerated code columns with label columns, preserving column order.
|
|
178
|
+
|
|
179
|
+
For each enumerated component present in ``lf``, the code column is replaced
|
|
180
|
+
by a ``{col}{suffix}`` column containing the human-readable label as a
|
|
181
|
+
``pl.Enum`` (DSD-defined order preserved). Non-enumerated columns are
|
|
182
|
+
unchanged.
|
|
183
|
+
"""
|
|
184
|
+
enum_map: dict[str, tuple[list[str], list[str]]] = {}
|
|
185
|
+
md = [dsd.measure_dimension] if dsd.measure_dimension is not None else []
|
|
186
|
+
for comp in [*dsd.dimensions, *md, *dsd.attributes, *dsd.measures]:
|
|
187
|
+
if not (isinstance(comp.representation, Ref) and comp.representation.resolved):
|
|
188
|
+
continue
|
|
189
|
+
codes = [c.id for c in comp.representation().codes] # type: ignore[union-attr]
|
|
190
|
+
labels = [c.name.get(lang) or c.id for c in comp.representation().codes] # type: ignore[union-attr]
|
|
191
|
+
enum_map[comp.id] = (codes, labels)
|
|
192
|
+
|
|
193
|
+
select_exprs = []
|
|
194
|
+
for col_name in lf.collect_schema().names():
|
|
195
|
+
if col_name in enum_map:
|
|
196
|
+
codes, labels = enum_map[col_name]
|
|
197
|
+
unique_labels = list(dict.fromkeys(labels))
|
|
198
|
+
select_exprs.append(
|
|
199
|
+
pl.col(col_name)
|
|
200
|
+
.cast(pl.String)
|
|
201
|
+
.replace_strict(old=codes, new=labels, default=None)
|
|
202
|
+
.cast(pl.Enum(unique_labels))
|
|
203
|
+
.alias(f"{col_name}{suffix}")
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
select_exprs.append(pl.col(col_name))
|
|
207
|
+
|
|
208
|
+
return lf.select(select_exprs)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def label(
|
|
212
|
+
df: pl.LazyFrame,
|
|
213
|
+
dsd: DataStructure,
|
|
214
|
+
lang: str = "en",
|
|
215
|
+
suffix: str = "_label",
|
|
216
|
+
) -> pl.LazyFrame:
|
|
217
|
+
"""Add human-readable label columns for every enumerated component.
|
|
218
|
+
|
|
219
|
+
For each enumerated dimension/attribute/measure that exists in ``df``,
|
|
220
|
+
appends a ``{col}{suffix}`` column with the code label in ``lang``.
|
|
221
|
+
Codes with no label fall back to the code id.
|
|
222
|
+
|
|
223
|
+
Example:
|
|
224
|
+
labelled = slpl.label(pl.scan_csv("data.csv"), dsd).collect()
|
|
225
|
+
"""
|
|
226
|
+
schema_cols = set(df.collect_schema().names())
|
|
227
|
+
mds = [dsd.measure_dimension] if dsd.measure_dimension is not None else []
|
|
228
|
+
for comp in [*dsd.dimensions, *mds, *dsd.attributes, *dsd.measures]:
|
|
229
|
+
if comp.id not in schema_cols:
|
|
230
|
+
continue
|
|
231
|
+
if not (isinstance(comp.representation, Ref) and comp.representation.resolved):
|
|
232
|
+
continue
|
|
233
|
+
mapping = {c.id: c.name.get(lang) or c.id for c in comp.representation().codes} # type: ignore[union-attr]
|
|
234
|
+
old = list(mapping.keys())
|
|
235
|
+
new = list(mapping.values())
|
|
236
|
+
label_col = f"{comp.id}{suffix}"
|
|
237
|
+
df = df.with_columns(
|
|
238
|
+
pl.col(comp.id).cast(pl.String).replace_strict(old=old, new=new, default=None).alias(label_col)
|
|
239
|
+
)
|
|
240
|
+
return df
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def structure_frame(dsd: DataStructure, lang: str = "en") -> pl.DataFrame:
|
|
244
|
+
"""Export the DSD structure as a tidy DataFrame.
|
|
245
|
+
|
|
246
|
+
Returns one row per component::
|
|
247
|
+
|
|
248
|
+
┌────────────┬───────────┬──────────┬───────────┬──────────┬─────────┐
|
|
249
|
+
│ component │ role │ position │ concept │ codelist │ n_codes │
|
|
250
|
+
└────────────┴───────────┴──────────┴───────────┴──────────┴─────────┘
|
|
251
|
+
|
|
252
|
+
``role`` is one of ``"dimension"``, ``"time_dimension"``,
|
|
253
|
+
``"attribute"``, ``"measure"``.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
meta = slpl.structure_frame(dsd)
|
|
257
|
+
"""
|
|
258
|
+
rows: list[dict[str, object]] = []
|
|
259
|
+
|
|
260
|
+
for i, dim in enumerate(dsd.dimensions, start=1):
|
|
261
|
+
concept_label = (
|
|
262
|
+
dim.concept().name.get(lang) or dim.id if isinstance(dim.concept, Ref) and dim.concept.resolved else dim.id
|
|
263
|
+
)
|
|
264
|
+
_dim_rep = dim.representation if isinstance(dim.representation, Ref) and dim.representation.resolved else None
|
|
265
|
+
cl_id = _dim_rep().id if _dim_rep else None # type: ignore[union-attr]
|
|
266
|
+
n_codes = len(_dim_rep().codes) if _dim_rep else 0 # type: ignore[union-attr]
|
|
267
|
+
rows.append(
|
|
268
|
+
{
|
|
269
|
+
"component": dim.id,
|
|
270
|
+
"role": "dimension",
|
|
271
|
+
"position": i,
|
|
272
|
+
"concept": concept_label,
|
|
273
|
+
"codelist": cl_id,
|
|
274
|
+
"n_codes": n_codes,
|
|
275
|
+
}
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if dsd.time_dimension is not None:
|
|
279
|
+
td = dsd.time_dimension
|
|
280
|
+
concept_label = (
|
|
281
|
+
td.concept().name.get(lang) or td.id if isinstance(td.concept, Ref) and td.concept.resolved else td.id
|
|
282
|
+
)
|
|
283
|
+
rows.append(
|
|
284
|
+
{
|
|
285
|
+
"component": td.id,
|
|
286
|
+
"role": "time_dimension",
|
|
287
|
+
"position": None,
|
|
288
|
+
"concept": concept_label,
|
|
289
|
+
"codelist": None,
|
|
290
|
+
"n_codes": 0,
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if dsd.measure_dimension is not None:
|
|
295
|
+
md = dsd.measure_dimension
|
|
296
|
+
concept_label = (
|
|
297
|
+
md.concept().name.get(lang) or md.id if isinstance(md.concept, Ref) and md.concept.resolved else md.id
|
|
298
|
+
)
|
|
299
|
+
_md_rep = md.representation if isinstance(md.representation, Ref) and md.representation.resolved else None
|
|
300
|
+
cl_id = _md_rep().id if _md_rep else None # type: ignore[union-attr]
|
|
301
|
+
n_codes = len(_md_rep().codes) if _md_rep else 0 # type: ignore[union-attr]
|
|
302
|
+
rows.append(
|
|
303
|
+
{
|
|
304
|
+
"component": md.id,
|
|
305
|
+
"role": "measure_dimension",
|
|
306
|
+
"position": None,
|
|
307
|
+
"concept": concept_label,
|
|
308
|
+
"codelist": cl_id,
|
|
309
|
+
"n_codes": n_codes,
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
for attr in dsd.attributes:
|
|
314
|
+
if isinstance(attr.concept, Ref) and attr.concept.resolved:
|
|
315
|
+
concept_label = attr.concept().name.get(lang) or attr.id
|
|
316
|
+
else:
|
|
317
|
+
concept_label = attr.id
|
|
318
|
+
_attr_rep = (
|
|
319
|
+
attr.representation if isinstance(attr.representation, Ref) and attr.representation.resolved else None
|
|
320
|
+
)
|
|
321
|
+
cl_id = _attr_rep().id if _attr_rep else None # type: ignore[union-attr]
|
|
322
|
+
n_codes = len(_attr_rep().codes) if _attr_rep else 0 # type: ignore[union-attr]
|
|
323
|
+
rows.append(
|
|
324
|
+
{
|
|
325
|
+
"component": attr.id,
|
|
326
|
+
"role": "attribute",
|
|
327
|
+
"position": None,
|
|
328
|
+
"concept": concept_label,
|
|
329
|
+
"codelist": cl_id,
|
|
330
|
+
"n_codes": n_codes,
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
for meas in dsd.measures:
|
|
335
|
+
if isinstance(meas.concept, Ref) and meas.concept.resolved:
|
|
336
|
+
concept_label = meas.concept().name.get(lang) or meas.id
|
|
337
|
+
else:
|
|
338
|
+
concept_label = meas.id
|
|
339
|
+
rows.append(
|
|
340
|
+
{
|
|
341
|
+
"component": meas.id,
|
|
342
|
+
"role": "measure",
|
|
343
|
+
"position": None,
|
|
344
|
+
"concept": concept_label,
|
|
345
|
+
"codelist": None,
|
|
346
|
+
"n_codes": 0,
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return pl.DataFrame(
|
|
351
|
+
{
|
|
352
|
+
"component": [r["component"] for r in rows],
|
|
353
|
+
"role": [r["role"] for r in rows],
|
|
354
|
+
"position": [r["position"] for r in rows],
|
|
355
|
+
"concept": [r["concept"] for r in rows],
|
|
356
|
+
"codelist": [r["codelist"] for r in rows],
|
|
357
|
+
"n_codes": [r["n_codes"] for r in rows],
|
|
358
|
+
},
|
|
359
|
+
schema={
|
|
360
|
+
"component": pl.String,
|
|
361
|
+
"role": pl.String,
|
|
362
|
+
"position": pl.Int32,
|
|
363
|
+
"concept": pl.String,
|
|
364
|
+
"codelist": pl.String,
|
|
365
|
+
"n_codes": pl.Int32,
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def scan_csv(source: str | Path | bytes, structure: DataStructure) -> Dataset:
|
|
371
|
+
"""Parse an SDMX-CSV file or bytes into a :class:`~sdmxlib.model.dataset.Dataset`.
|
|
372
|
+
|
|
373
|
+
File paths are scanned lazily (``pl.scan_csv``); bytes are read eagerly
|
|
374
|
+
and wrapped in a ``LazyFrame``. Either way ``Dataset.data`` is always a
|
|
375
|
+
``pl.LazyFrame`` — nothing is executed until a terminal call.
|
|
376
|
+
|
|
377
|
+
Convenience entry point. For full options see
|
|
378
|
+
:func:`sdmxlib.formats.sdmx_csv.reader.scan_csv`.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
source: File path or raw bytes from an SDMX-CSV response.
|
|
382
|
+
structure: Resolved :class:`~sdmxlib.model.datastructure.DataStructure`.
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
import sdmxlib.polars as slpl
|
|
386
|
+
|
|
387
|
+
ds = slpl.scan_csv("data.csv", dsd)
|
|
388
|
+
df = ds.label().collect()
|
|
389
|
+
"""
|
|
390
|
+
from sdmxlib.formats.sdmx_csv.reader import scan_csv as _scan_csv # noqa: PLC0415
|
|
391
|
+
|
|
392
|
+
return _scan_csv(source, structure)
|
sdmxlib/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Shared DuckDB storage layer for SDMX structural metadata.
|
|
2
|
+
|
|
3
|
+
Provides normalized tables for codelists, DSDs, dataflows, concept
|
|
4
|
+
schemes, and hierarchies. Used internally by ``LocalRegistry`` and
|
|
5
|
+
importable by cataloglib for shared-database workflows.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
import duckdb
|
|
10
|
+
from sdmxlib.storage import schema, writers, readers
|
|
11
|
+
|
|
12
|
+
conn = duckdb.connect("store.duckdb")
|
|
13
|
+
schema.create_tables(conn, languages=["en", "fr"])
|
|
14
|
+
writers.put_codelist(conn, codelist)
|
|
15
|
+
cm = readers.code_map(conn, codelist_urn, "en")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from sdmxlib.storage import lazy, readers, schema, writers
|
|
19
|
+
from sdmxlib.storage.schema import create_tables, get_languages
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"create_tables",
|
|
23
|
+
"get_languages",
|
|
24
|
+
"lazy",
|
|
25
|
+
"readers",
|
|
26
|
+
"schema",
|
|
27
|
+
"writers",
|
|
28
|
+
]
|