Python API reference¶
Everything below is importable from the top-level package:
Tabular datasets come back as Polars DataFrames; gridded
datasets as xarray Datasets. Every function takes the
dataset's short name — run foehn.list_datasets() or foehn list to see them.
For worked examples, see the Python API guide and the
gridded data guide.
Discovering datasets¶
list_datasets ¶
Return metadata about all available datasets.
Each dict has keys: dataset, collection_id, category, subcategory,
description, format, frequencies, time_slices.
Loading tabular data¶
load ¶
load(dataset: str, *, station: str | list[str] | None = None, frequency: str | list[str] | None = None, time_slice: str | list[str] | None = None, year: int | list[int] | None = None, month: int | list[int] | None = None, date_from: str | None = None, date_to: str | None = None, columns: list[str] | None = None, drop_null: str | None = None, sort: str | None = None, limit: int | None = None, workers: int = 8) -> DataFrame
Load a dataset and return it as an in-memory Polars DataFrame.
No files are written to disk. Data is fetched from the MeteoSwiss STAC API, parsed directly in memory, and returned as a single concatenated DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
station
|
str | list[str] | None
|
Station abbreviation(s) to include (e.g. "BER" or ["BER", "ZUR"]). Filters at the STAC item level so unmatched stations are never downloaded. Case-insensitive. If None, all stations are included. |
None
|
frequency
|
str | list[str] | None
|
Granularity filter(s). Options: "t" (10-minute, near real-time), "h" (hourly), "d" (daily), "m" (monthly), "y" (yearly). Can be a single string or list. If None, all are included. |
None
|
time_slice
|
str | list[str] | None
|
Time slice(s) to include. Options: "now" (last ~24 hours, updated every 10 minutes — t and h only), "recent" (this calendar year through yesterday, updated daily), "historical" (start of measurements through Dec 31 of last year). Defaults to ["recent"]. Can be a single string or list. |
None
|
year
|
int | list[int] | None
|
Filter to specific year(s) (e.g. 2025 or [2020, 2021, 2022]). |
None
|
month
|
int | list[int] | None
|
Filter to specific month(s) (1-12, e.g. 7 or [6, 7, 8] for summer). |
None
|
date_from
|
str | None
|
Start date (inclusive), ISO format "YYYY-MM-DD". |
None
|
date_to
|
str | None
|
End date (inclusive), ISO format "YYYY-MM-DD". |
None
|
columns
|
list[str] | None
|
Only return these columns. |
None
|
drop_null
|
str | None
|
Drop rows where this column is null. |
None
|
sort
|
str | None
|
Sort by timestamp. Options: "asc" (oldest first) or "desc" (newest first). |
None
|
limit
|
int | None
|
Cap the returned DataFrame to N rows. Applied after sort/columns.
NOTE: this only bounds the returned shape — it does not reduce
network bytes, since CSVs are per-station and not pre-bucketed by
row count. To reduce wire volume, narrow with |
None
|
workers
|
int
|
Concurrent CSV downloads (default 8). The CSV fetches are parallelised via a ThreadPoolExecutor; metadata fetch stays serial. |
8
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A Polars DataFrame containing all matching CSV data. |
Example::
import foehn
# Recent daily data for Bern
df = foehn.load("smn", station="BER", frequency="d")
# Hourly data for multiple stations
df = foehn.load("smn", station=["BER", "ZUR"], frequency="h")
# Daily data for Bern, only January 2026
df = foehn.load("smn", station="BER", frequency="d", year=2026, month=1)
# Latest 10 readings (sort+limit are applied after the CSV is parsed)
df = foehn.load("smn", station="BER", frequency="t",
time_slice="now", sort="desc", limit=10)
# Summer 2025 temperatures, sorted newest first
df = foehn.load("smn", station="BER", frequency="d",
time_slice="historical", date_from="2025-06-01",
date_to="2025-08-31", columns=["tre200d0"],
sort="desc")
Metadata¶
parameters ¶
Fetch parameter metadata for a dataset.
Returns a DataFrame with columns: shortname, description, unit, type, granularity, decimals, group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
stations ¶
Fetch station metadata for a dataset.
Returns a DataFrame with columns: abbr, name, canton, altitude,
lv95_east, lv95_north, lat, lon, data_since. data_since keeps the
MeteoSwiss station metadata format (DD.MM.YYYY).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
points ¶
Fetch forecast point metadata for a dataset (forecast_local).
Local forecasts are published per point rather than per station: every
station, plus one point per postal code centre and some points of interest.
Returns a DataFrame with columns: point_id, point_type_id, type, abbr,
postal_code, name, altitude, lv95_east, lv95_north, lat, lon. abbr is
null unless the point is a station, postal_code unless it is a postal
code centre.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "forecast_local"). |
required |
inventory ¶
Fetch the data inventory for a dataset.
Returns a DataFrame with columns: station, parameter, data_since, data_till, owner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
metadata ¶
Fetch one of a dataset's metadata tables from the STAC API.
The single implementation behind :func:parameters, :func:stations,
:func:points and :func:inventory; take it directly when the table is chosen at runtime, as
the CLI does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
table
|
str
|
One of |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A Polars DataFrame with foehn's published column names. |
Downloading and converting¶
download ¶
download(dataset: str, *, data_dir: Path | str | None = None, time_slice: str | list[str] | None = None, since: str | None = None, workers: int = 8, force: bool = False) -> DownloadResult
Download a single dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). Use list_datasets() to see options. |
required |
data_dir
|
Path | str | None
|
Root data directory. Defaults to ./data/meteoswiss. |
None
|
time_slice
|
str | list[str] | None
|
Time slice(s) to download. Options: "now" (last ~24 hours, updated every 10 minutes — t and h only), "recent" (this calendar year through yesterday, updated daily), "historical" (start of measurements through Dec 31 of last year). Defaults to ["recent"]. Can be a single string or list. Ignored for binary/grid datasets: NetCDF and radar fetch what the collection holds, and GRIB2 forecasts fetch the control member of the newest complete run (~8 GB for ICON-CH1). |
None
|
since
|
str | None
|
ISO timestamp for incremental updates — only assets updated after
it are fetched. This function does not track state itself: pass the
previous run's timestamp, or use the |
None
|
workers
|
int
|
Concurrent HTTP downloads (default 8). |
8
|
force
|
bool
|
Re-download every selected file even when the local copy looks
up to date: no freshness check, and no stored ETag sent, so a
damaged file in the cache is replaced. |
False
|
Returns:
| Type | Description |
|---|---|
DownloadResult
|
DownloadResult summarising the download. Use |
DownloadResult
|
to gate expensive downstream processing and |
DownloadResult
|
partial failures. |
DownloadResult ¶
DownloadResult(total_assets: int = 0, downloaded: int = 0, skipped: int = 0, failed: int = 0, filenames: list[str] = <factory>)
Summary of a download call.
Callers use this to decide whether to run expensive downstream work (e.g. Spark MERGE INTO) without scanning the output directory.
downloaded + skipped + failed == total_assets always holds:
total_assets is every asset handed to :func:fetch_all, skipped
counts both the ones skip rejected locally and the ones the server
answered 304 for, and destination collisions count as skipped too.
to_parquet ¶
Convert downloaded CSVs to Parquet for a single dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "smn"). |
required |
data_dir
|
Path | str | None
|
Root data directory. Defaults to ./data/meteoswiss. |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If one or more groups fail to convert. Each failure is
also logged as a warning on the |
Gridded data¶
open_dataset ¶
open_dataset(dataset: str, *, variables: str | list[str] | None = None, match: str | None = None, data_dir: Path | str | None = None, engine: str | None = None) -> Dataset
Open a gridded dataset as an xarray Dataset.
The grid analog of foehn.load(), for NetCDF collections (climate grids,
normals, scenarios), GRIB2 forecasts (ICON-CH1/CH2, KENDA), and HDF5/ODIM
radar composites (CombiPrecip, hail). This is download-then-lazy: the source
file(s) are fetched in full to data_dir/bronze/<dataset>/ on first use,
then opened and read from that local copy. It is not cloud-lazy — there is no
byte-range/partial read of the remote file, so the first call pays the full
file size up front. Subsequent calls reuse the cache. A match spanning
several NetCDF files stays lazy only with dask installed (it is not part of
the 'grids' extra): without it, combining the files reads their values into
memory on open, and foehn warns when that is over 1 GB.
GRIB2 and radar (HDF5) collections require match, and it must resolve
to a single file:
- GRIB2 forecast collections hold thousands of files (one per variable ×
ensemble member × lead time × reference time), and ICON's native
unstructured (icosahedral) grid — a 1-D
valuesdimension with no dimension coordinate — can't be stacked bycombine_by_coords. Include the reference + lead time, e.g.match="YYYYMMDDHHMM-0-t_2m-ctrl". The one field comes back on thevaluesgrid with celllat/loncoordinates joined from the collection's horizontal-constants file. - Radar collections hold one Cartesian composite per timestep (every ~5 min).
Match a single file, e.g.
match="cpcYYDDDHHMM". The composite is read with ODIM gain/offset scaling,nodatamasked to NaN, on Swiss LV95x/ycoordinates (matching the NetCDF grids).
open_dataset reads one field; to assemble many matched files into a cube
use to_zarr(..., stack=True) instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name (e.g. "surface_derived_grid", "forecast_icon_ch1", "radar_precip"). Use list_datasets() to see options. Must be a NetCDF, GRIB2, or HDF5/radar collection. |
required |
variables
|
str | list[str] | None
|
Restrict to these data variable(s). If None, all are kept. |
None
|
match
|
str | None
|
Keep only source files whose name contains this substring. Narrows a heterogeneous multi-file collection to one coherent set — analogous to the station/frequency filters on load(). Required for GRIB2 and radar collections, where it must select a single file. |
None
|
data_dir
|
Path | str | None
|
Root data directory. Defaults to ./data/meteoswiss. |
None
|
engine
|
str | None
|
xarray backend to open the file(s) with (e.g. "h5netcdf"). Leave unset to let xarray choose, which is right for every published collection; name one only to work around a backend-specific fault. |
None
|
Returns:
| Type | Description |
|---|---|
Dataset
|
An xarray Dataset backed by the local file(s), downloaded in full first |
Dataset
|
(see the download-then-lazy note above) — e.g. the first |
Dataset
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the dataset is unknown, tabular (CSV), a GRIB2/radar
collection opened without a single-file |
OSError
|
If one of the matched files cannot be read — a corrupt entry in the local cache. The message names the file; delete it and retry. |
ImportError
|
If the optional 'grids' dependencies are not installed. Raised before anything is downloaded. |
Example::
import foehn
# NetCDF: a coherent single-parameter slice of a multi-file collection
ds = foehn.open_dataset("surface_derived_grid", match="rhiresd")
ds = foehn.open_dataset("climate_scenarios_grid", match="_pr_", variables="pr")
# GRIB2: a single forecast field — variable + member + reference + lead time
ds = foehn.open_dataset("forecast_icon_ch1", match="YYYYMMDDHHMM-0-t_2m-ctrl")
# Radar: a single CombiPrecip composite (one 5-min timestep)
ds = foehn.open_dataset("radar_precip", match="cpcYYDDDHHMM")
to_zarr ¶
to_zarr(dataset: str, *, variables: str | list[str] | None = None, match: str | None = None, data_dir: Path | str | None = None, store: Path | str | None = None, rechunk: dict[str, int] | None = None, mode: str = 'w', stack: bool | str | None = False) -> Path
Materialise a gridded dataset to a Zarr store on disk.
The grid analog of foehn.to_parquet(): reads the source (NetCDF, GRIB2,
or HDF5/radar) via open_dataset() and writes a single Zarr store under
data_dir/zarr/. (GRIB2 and radar collections require match — see
open_dataset.)
The default store name encodes match so that different filtered slices of
the same collection don't silently overwrite each other:
<dataset>.zarr when unfiltered, <dataset>__<match>.zarr otherwise
(e.g. surface_derived_grid__rhiresd.zarr). Pass store for an explicit
path that overrides this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
str
|
Dataset name. Must be a NetCDF, GRIB2, or HDF5/radar collection. |
required |
variables
|
str | list[str] | None
|
Restrict to these data variable(s) before writing. |
None
|
match
|
str | None
|
Narrow a multi-file collection to a coherent set (see open_dataset);
required for GRIB2 and radar collections, and for |
None
|
data_dir
|
Path | str | None
|
Root data directory. Defaults to ./data/meteoswiss. |
None
|
store
|
Path | str | None
|
Explicit output path for the |
None
|
rechunk
|
dict[str, int] | None
|
Optional dim→chunk-size mapping applied before writing, e.g.
|
None
|
mode
|
str
|
Zarr write mode (default "w" — overwrite the store at this path).
Note distinct |
'w'
|
stack
|
bool | str | None
|
Assemble the matched files into one cube, using whichever method
the dataset's kind uses — radar stacks CombiPrecip timesteps into a
|
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path to the written |
Diagnostics¶
show_versions ¶
Print foehn's version, the platform and every dependency's version.
Paste the output into a bug report::
import foehn
foehn.show_versions()
The command line equivalent is foehn version.