Writing a data route¶
The library serves no /data route. A data handler decides how responses are
cached, which partitions a query prunes and which vendor parameters exist, and
the services that run this library each decide those differently. The library
supplies the parts of a handler that every service writes the same way, in
sdmxlib_rest.data, and the consumer writes the handler around them.
The helpers¶
Every helper takes the request's parameters and the flow's
sdmxlib.DataflowBinding, which maps SDMX components to physical columns.
Parsing the request (data.params):
| Helper | Does |
|---|---|
parse_c_params |
Parses c[<id>]=<expr> filters, with the operators eq, ne, lt, le, gt, ge, co, nc, sw and ew |
build_non_eq_where |
Turns the non-equality filters into SQL fragments and parameters |
parse_sort |
Parses sort= into physical columns and directions |
validate_dimension_at_observation |
Checks dimensionAtObservation against the binding |
reject_unsupported_standard_params |
Answers 501 for standard parameters a single-edition service cannot honour |
Choosing columns (data.projection):
| Helper | Does |
|---|---|
key_dimension_ids |
The flow's key dimensions in data structure order, which is the order a dotted key is read in |
project_binding |
Trims the binding to the attributes= and measures= the request names |
project_arrow_batches |
Applies the same projection to Arrow batches, for Parquet output |
label_maps_for_dims |
Code-to-label maps for a CSV with label columns |
Shaping the query (data.sql):
| Helper | Does |
|---|---|
decimals_format_replace_clause |
Formats numeric columns to each row's DECIMALS attribute |
qualify_is_active |
Whether firstNObservations or lastNObservations can drop any row |
Paging (data.paging): parse_items_range reads a row-count Range
header, as the .Stat Data Explorer sends; with_range and range_status
build the Content-Range headers and the 206.
Encoding (data.encoding): each encoder takes an iterable of Arrow batches
or CSV chunks and yields bytes, so memory stays bounded by the batch size
whatever the result size.
| Helper | Writes |
|---|---|
emit_csv_with_labels |
SDMX-CSV with a <dim>_label column after each coded dimension |
prepend_utf8_bom |
A UTF-8 byte-order mark first, for spreadsheet clients |
stream_csv_zip |
A single-entry zip, streamed |
encode_parquet |
A Parquet file, streamed |
A handler's outline¶
@router.get("/data/{context}/{agencyID}/{resourceID}/{version}/{key}")
def data(..., registry=Depends(get_registry)):
binding = registry.binding_for(flow_urn)
dims = key_dimension_ids(registry, flow_urn, binding)
# parse the key against `dims`, then c[], sort, projection
# shape the filter with runtime.filter_shaper
# build and run the query; stream batches through an encoder
...
binding_for returns the binding with its database_role naming the catalog
its table is attached as in this registry (registry.lake_alias for the lake,
an alias prefixed with the product id and edition id), so SQL built as
<database_role>.<table_location> reads the right lake. A binding read some other way, with get_all, goes through
registry.locate(binding) first.
get_registry, get_meta_conn, get_token and the other request
dependencies in sdmxlib_rest.runtime give a handler the same runtime the
library routes use. Include the router in create_app's routers list.