Unofficial, fully-typed Python client for Weathercloud. Read live conditions, station metadata, history, and forecasts from any public station — no account, no API key (recommended). Authentication is optional and only required if you want to access private indoor sensors (like inside temperature, humidity, and heat index) of a station you own.
⚠️ Reverse-engineered from the public web app. Not affiliated with or endorsed by Weathercloud, and the upstream endpoints may change without notice.
- 🌡️ Typed results —
get_current_conditions()returns aCurrentConditionsdataclass, not a bag of stringly-typed JSON. - 🧱 Raw access too — every endpoint also has a
dict-returning method when you need the full payload. - 🧯 One exception to catch — every failure (network, HTTP, bad JSON) raises
WeathercloudError. - 🧪 Tested & type-checked — ships
py.typed, runs on Python 3.10–3.13.
pip install weathercloudfrom weathercloud import WeathercloudClient
# Public data requires no login:
with WeathercloudClient() as client:
cond = client.get_current_conditions("5726468552")
print(cond.temperature) # 22.8
print(cond.humidity) # 62
# Pass your credentials to fetch private indoor sensors:
with WeathercloudClient(username="my_user", password="my_password") as client:
cond = client.get_current_conditions("5726468552")
print(cond.inside_temperature) # 21.5 (None if not logged in)
print(cond.inside_humidity) # 55The client owns a requests.Session, so use it as a context manager (or call
client.close()) to release connections. You can also tune the request timeout:
client = WeathercloudClient(timeout=30) # seconds; default is 10
# or with both credentials and timeout:
client = WeathercloudClient(username="user", password="pass", timeout=15)Live sensor readings as a typed dataclass — the one you'll call most. Stations
only report the sensors they actually have, so every field is optional: a
reading the station doesn't provide comes back as None rather than raising.
| Field | Type | Unit | Field | Type | Unit |
|---|---|---|---|---|---|
temperature |
float | None |
°C | pressure |
float | None |
hPa |
dew_point |
float | None |
°C | wind_speed |
float | None |
m/s |
wind_chill |
float | None |
°C | wind_speed_avg |
float | None |
m/s |
heat_index |
float | None |
°C | wind_gust |
float | None |
m/s |
humidity |
int | None |
% | wind_direction |
int | None |
° |
rain |
float | None |
mm | rain_rate |
float | None |
mm/h |
solar_radiation |
float | None |
W/m² | uv_index |
int | None |
— |
inside_temperature |
float | None |
°C | inside_humidity |
int | None |
% |
inside_heat_index |
float | None |
°C | epoch |
int | None |
unix ts |
Station metadata. The name isn't exposed by any JSON endpoint, so it's scraped
from the page <title> (one extra request). Pass scrape_name=False to skip it
and use the device_id as the name instead.
info = client.get_station_info("5726468552")
info.name # "Ginometeo"
info.city # "Ingelmunster"
info.altitude # "18.0" (metres, as string)
info.status # "online" | "recently_online" | "offline" | "unknown"
info.seconds_since_update # int
info.account_type # 0 = free, >0 = premiumCurrent readings plus day / month / year min–max. Each value is a
[unix_timestamp, value] pair, keyed as {sensor}_{period}_{type}.
stats = client.get_device_stats("5726468552")
stats["temp_day_max"] # [1748358122, 30.9]
stats["rain_month_total"] # [1748358122, 12.4]Hourly history for a single sensor. period is "day", "week", "month", or
"year".
from weathercloud import VariableCode
evo = client.get_evolution("5726468552", VariableCode.TEMPERATURE, "week")Available codes: TEMPERATURE, HUMIDITY, DEW_POINT, PRESSURE, WIND_SPEED,
WIND_DIRECTION, WIND_GUST, RAIN, RAIN_RATE, SOLAR_RADIATION, UV_INDEX.
6-day WMO daily forecast for the station's location.
Stations within a radius of a coordinate. temp: 281 → 28.1 °C).
client.get_device_values(device_id) # same data as get_current_conditions, raw
client.get_device_info(device_id) # metadata + current values as strings
client.get_wind_rose(device_id) # wind direction distribution
client.get_update_status(device_id) # seconds since last update
client.get_owner_profile(device_id) # observer name, hardware brand/model
client.get_station_name(device_id) # scrape the station name onlyEvery method raises WeathercloudError on failure — network error, HTTP error,
non-JSON body, or an unexpected response shape. Catch the one type and you're
covered.
from weathercloud import WeathercloudClient, WeathercloudError
try:
cond = client.get_current_conditions(device_id)
except WeathercloudError as exc:
... # set unavailable, log it, retry — your callIt's the number at the end of the station URL:
app.weathercloud.net/d5726468552 → device_id = "5726468552"
METAR (airport) stations use ICAO codes (EBBR, EGLL, …) and work on most
device/* endpoints — just swap the prefix to metar/*.
- 🔓 No authentication required for public endpoints (recommended). Supply credentials only if you need to fetch private inside sensors of a station you own.
- ⏱️ Poll at most every 10 minutes — that's how often free stations update.
- 🧭 Based on the reverse-engineered OpenAPI spec in this repo.
git clone https://github.com/MauroDruwel/Weathercloud
cd Weathercloud
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check . # lint
mypy # type-check
pytest # tests
python -m build # build sdist + wheelCI runs the linter, type checker, and the test matrix (Python 3.10–3.13) on every push and pull request.
A Swagger UI is hosted at weathercloud-api.maurodruwel.be, or run it locally against a small CORS proxy:
pip install flask
python docs/proxy.py # serves the proxy + Swagger UI on :8765
# then open docs/index.html