Python API¶
Use foehn directly from notebooks or scripts. All functions are available from the top-level foehn module.
Listing datasets¶
import foehn
foehn.list_datasets()
# [{'dataset': 'smn', 'collection_id': 'ch.meteoschweiz.ogd-smn', 'category': 'A',
# 'subcategory': 'A1', 'description': 'Automatic weather stations',
# 'format': 'CSV', 'frequencies': ['t', 'h', 'd', 'm', 'y'],
# 'time_slices': ['historical', 'recent', 'now']}, ...]
Loading data¶
foehn.load() fetches data directly into a Polars DataFrame without writing anything to disk.
# Load daily data for a single station
df = foehn.load("smn", station="BER", frequency="d")
# Multiple stations and frequencies
df = foehn.load("smn", station=["BER", "ZUR"], frequency=["d", "h"])
# Include historical data alongside recent
df = foehn.load("smn", station="BER", frequency="d", time_slice=["historical", "recent"])
Filtering¶
All filters can be combined.
# Filter by year and month
df = foehn.load("smn", station="BER", frequency="d", year=2026, month=[1, 2, 3])
# Date range
df = foehn.load(
"smn", station="BER", frequency="d", time_slice="historical", date_from="2025-06-01", date_to="2025-08-31"
)
# Select specific columns (station_abbr and reference_timestamp are always kept)
df = foehn.load("smn", station="BER", frequency="d", columns=["tre200d0", "rre150d0"])
# Drop rows where a column is null
df = foehn.load("obs", frequency="y", drop_null="w3pnd2y0")
# Sort by timestamp (newest first)
df = foehn.load("smn", station="BER", frequency="d", sort="desc")
| Parameter | Type | Description |
|---|---|---|
station |
str or list[str] |
Station abbreviation(s) |
frequency |
str or list[str] |
Time frequency: t, h, d, m, y |
time_slice |
str or list[str] |
Time period: historical, recent, now |
year |
int or list[int] |
Filter by year(s) |
month |
int or list[int] |
Filter by month(s), 1--12 |
date_from |
str |
Start date inclusive, ISO format |
date_to |
str |
End date inclusive, ISO format |
columns |
list[str] |
Only return these columns |
drop_null |
str |
Drop rows where this column is null |
sort |
str |
Sort by timestamp: "asc" or "desc" |
limit |
int |
Maximum rows; zero returns an empty frame, negative values are invalid |
workers |
int |
Concurrent fetches; must be positive |
Climate scenario collections¶
The CH2025 scenario collections have their own layout but load the same way:
# Indoor climate scenarios: the whole archive is fetched once, then
# filtered in memory by station.
df = foehn.load("climate_scenarios_indoor", station="ABO")
# Local climate scenarios (C8): a wide table with one column per climate model.
# Dates are a nominal 30-year period (0001-01-01 .. 0030-12-31), so the calendar
# filters (year/month/date_from/date_to) don't apply; `sort` orders by the
# string `date` column.
df = foehn.load("climate_scenarios", station="ABE")
Metadata¶
Explore dataset metadata fetched live from the MeteoSwiss API.
# Column name mappings: shortname, description, unit, type, ...
foehn.parameters("smn")
# Station info: abbreviation, name, canton, altitude, LV95 x/y, lat, lon, ...
foehn.stations("smn")
# What each station measures and since when
foehn.inventory("smn")
# Local forecasts are per point, not per station: stations, postal code
# centres and points of interest, with the abbreviation or postal code to load
foehn.points("forecast_local")
# The same tables by name, for code that picks one at runtime
foehn.metadata("smn", "parameters")
foehn.metadata("smn", "stations")
foehn.metadata("smn", "inventory")
foehn.metadata("forecast_local", "points")
metadata() is the generic form of the functions above — useful when the
table is a variable rather than a literal. It raises ValueError for an unknown
table name, and for a table the dataset does not publish it names the ones it
does.
Local forecasts¶
forecast_local returns the newest forecast run, one row per point and time,
with every parameter as a column. station= takes station abbreviations or
postal codes (see foehn.points()), and columns= decides which parameter
files are downloaded at all — each file holds one parameter for all ~5,800
points, so it is the setting that makes a load fast:
# Hourly temperature and precipitation for Bern (postal code 3011), ~1 s
foehn.load("forecast_local", station="3011", columns=["tre200h0", "rre150h0"])
Downloading to disk¶
# Download a single dataset
foehn.download("smn", data_dir="./data/meteoswiss")
# Download with specific time slices
foehn.download("smn", time_slice=["historical", "recent"])
CSVs and metadata files are written to <data_dir>/bronze/<dataset>/.
Omit data_dir and foehn uses $FOEHN_DATA_DIR, falling back to
./data/meteoswiss — the same rule the CLI follows.
Converting to Parquet¶
Parquet files are written to <data_dir>/parquet/<collection>/.
Gridded data (NetCDF / GRIB2 / radar)¶
Gridded collections are N-dimensional fields, not tables, so they open as xarray
Datasets via foehn.open_dataset() — the grid analog of load().
All gridded formats install via pip install "foehn[grids]".
# NetCDF climate grids/normals/scenarios
ds = foehn.open_dataset("surface_derived_grid", match="rhiresd")
foehn.to_zarr("surface_derived_grid", match="rhiresd")
# GRIB2 forecasts (ICON-CH1/CH2, KENDA). match= is required and must select a
# single file (variable, member, reference + lead time):
ds = foehn.open_dataset("forecast_icon_ch1", match="YYYYMMDDHHMM-0-t_2m-ctrl")
# HDF5/ODIM radar composites (CombiPrecip, hail). match= is required and must
# select a single file (one ~5-min timestep):
ds = foehn.open_dataset("radar_precip", match="cpcYYDDDHHMM")
This path is download-then-lazy: the source file is fetched in full to the
local cache before any read. Later calls compare STAC freshness metadata and
refresh changed assets, while retaining a complete cache as the offline fallback.
See the gridded data documentation for
open_dataset, to_zarr, match, the GRIB2/radar single-file requirement, and
the Swiss-grid coordinate notes.
Logging and diagnostics¶
foehn logs through the standard logging module under the foehn logger and
attaches no handler of its own. Warnings reach stderr through Python's default
handling; progress messages stay hidden until you configure logging:
import logging
logging.basicConfig(level=logging.INFO) # download progress, conversion results
logging.getLogger("foehn").setLevel(logging.DEBUG)
When reporting a bug, include the environment report — foehn's version, Python, the platform, the data directory and every dependency's version:
foehn version prints the same from the command line. Every public function is
documented in the API reference.