Skip to main content

Universal weather forecast aggregation library with a typed normalized schema.

Project description

omni-weather-forecast-apis

PyPI CI Ruff Checked with pyrefly License Python 3.13+ Ask DeepWiki

Async Python library that fans out forecast requests across multiple weather providers and normalizes the results into one typed Pydantic schema. It preserves provider-native cadence and time boundaries while converting units and condition codes into a common representation.

Requires Python 3.13 or newer.

๐Ÿ“– Documentation site

Features

  • Multi-provider fan-out with async orchestration and partial-failure tolerance
  • Typed normalized schema โ€” common Pydantic models for minutely, hourly, daily, and alert data
  • Plugin architecture โ€” 13 providers with typed per-provider config validation
  • Resilient by default โ€” retries with exponential backoff (honoring Retry-After), conditional-request HTTP caching (ETag/Last-Modified/Expires), and explicit connection pool limits
  • Rate limiting and quotas โ€” global concurrency and RPS limits with per-provider overrides, plus per-provider daily quota caps
  • Secrets from the environment โ€” reference API keys as ${ENV_VAR} placeholders instead of embedding them in config files
  • CLI โ€” loads a TOML config, queries providers, prints a table or JSON, and optionally persists normalized output to SQLite
  • Replayable raw archives โ€” SQLite runs store network responses in a unique gzipped JSONL file per invocation
  • Extensible โ€” response hooks and a documented SQLite feature view for downstream ensemble/verification projects

Supported Providers

Three of the thirteen providers need no API key at all, so you can try the library without signing up for anything.

Provider Plugin ID API key Minutely Hourly Daily Alerts Multi-model Coverage
Open-Meteo open_meteo Optional 1 h 16 d 16 d โ€” โœ… Global
MET Norway met_norway None โ€” 9 d โ€” โ€” โ€” Nordics
NWS / NOAA nws None โ€” โœ… โœ… โœ… โ€” US only
OpenWeather openweather Required 1 h 48 h 8 d โœ… โ€” Global
WeatherAPI weatherapi Required โ€” 14 d 14 d โœ… โ€” Global
Tomorrow.io tomorrow_io Required 1 h 5 d 6 d โ€” โ€” Global
Visual Crossing visual_crossing Required โ€” 15 d 15 d โœ… โ€” Global
Weatherbit weatherbit Required โ€” 10 d 16 d โ€” โ€” Global
Meteosource meteosource Required 1 h 7 d 30 d โœ… โ€” Global
Pirate Weather pirate_weather Required 1 h 48 h 8 d โœ… โ€” Global
Stormglass stormglass Required โ€” โœ… โ€” โ€” โœ… Global
Weather Unlocked weather_unlocked Required โ€” โœ… โœ… โ€” โ€” Global
Google Weather google_weather Required โ€” 10 d 10 d โ€” โ€” Global

The minutely, hourly, and daily columns give each provider's maximum forecast horizon. โœ… means the granularity is supported but the plugin declares no horizon bound, and โ€” means it is not supported at all. Multi-model providers return several independent forecasts per request โ€” Open-Meteo exposes named numerical weather models (best_match, ecmwf_ifs025, โ€ฆ) and Stormglass returns multiple upstream sources โ€” which is what makes them useful for ensembles.

Every key each plugin accepts, along with per-provider unit and semantics caveats, is in the Providers reference.

Quick Start

Open-Meteo, MET Norway, and NWS need no API keys, so this runs end to end without any signup. No install step โ€” uv fetches the package into a throwaway environment:

cat > config.toml << 'EOF'
[[providers]]
plugin_id = "open_meteo"
config = { models = ["best_match", "ecmwf_ifs025"] }

[[providers]]
plugin_id = "met_norway"
config = { user_agent = "MyApp/1.0 you@yourdomain.com" }

[[providers]]
plugin_id = "nws"
config = { user_agent = "MyApp/1.0 you@yourdomain.com" }
EOF

uvx --from "omni-weather-forecast-apis[cli]" omni-weather \
  --config ./config.toml \
  --lat 40.7128 \
  --lon -74.0060 \
  --sqlite ./forecasts.sqlite

Put a real contact address in user_agent before running this. MET Norway's terms require one to identify the caller, and their API rejects placeholder domains such as example.com with a 403 Forbidden.

Which prints:

                              Run 1 โ€” 3/3 succeeded
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“
โ”ƒ Provider   โ”ƒ Status โ”ƒ Latency โ”ƒ Hourly โ”ƒ Daily โ”ƒ Minutely โ”ƒ Alerts โ”ƒ Detail      โ”ƒ
โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ
โ”‚ open_meteo โ”‚   OK   โ”‚   924ms โ”‚    336 โ”‚    14 โ”‚        0 โ”‚      - โ”‚ 2 source(s) โ”‚
โ”‚ met_norway โ”‚   OK   โ”‚   705ms โ”‚     90 โ”‚     0 โ”‚        0 โ”‚      - โ”‚ 1 source(s) โ”‚
โ”‚ nws        โ”‚   OK   โ”‚   283ms โ”‚    156 โ”‚     8 โ”‚        0 โ”‚      - โ”‚ 1 source(s) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       Saved to forecasts.sqlite in 924ms

Open-Meteo contributes two sources because two models were requested. Every forecast is now in forecasts.sqlite, normalized and ready to query.

Prefer to be walked through it? omni-weather init runs an interactive setup wizard that collects coordinates, providers, and credentials, and can install a daily collection job.

Installation

# As a library dependency
pip install omni-weather-forecast-apis

# With the interactive CLI, discovery, diagnostics, and rich output
pip install "omni-weather-forecast-apis[cli]"

# Run the CLI without installing anything
uvx --from "omni-weather-forecast-apis[cli]" omni-weather --help

Requires Python 3.13 or newer. The console-script wrapper is installed with the base package, but CLI commands require the cli extra. Without it the wrapper exits with the exact installation command instead of failing with an import traceback.

Contributing to this repository instead? See Development for the uv sync workflow.

How It Works

  1. Fan-out โ€” A ForecastRequest is dispatched concurrently to every enabled provider using async tasks, bounded by configurable concurrency and rate limits.
  2. Normalize โ€” Each provider plugin converts its native response into the common SourceForecast schema, translating units (e.g. Fahrenheit to Celsius, mph to m/s) and mapping provider-specific condition codes to a shared WeatherCondition enum.
  3. Aggregate โ€” Results are collected into a single ForecastResponse. Providers that succeed return ProviderSuccess with their forecasts; providers that fail return ProviderError with a typed error code. The response always completes, even if some providers fail.

Library Usage

import asyncio

from omni_weather_forecast_apis import (
    ForecastRequest,
    Granularity,
    OmniWeatherConfig,
    ProviderRegistration,
    ProviderId,
    create_omni_weather,
)


async def main() -> None:
    config = OmniWeatherConfig(
        providers=[
            ProviderRegistration(
                plugin_id=ProviderId.OPEN_METEO,
                config={"models": ["best_match"]},
            ),
            ProviderRegistration(
                plugin_id=ProviderId.MET_NORWAY,
                config={"user_agent": "MyApp/1.0 you@yourdomain.com"},
            ),
        ],
    )

    async with await create_omni_weather(config) as client:
        response = await client.forecast(
            ForecastRequest(
                latitude=34.2484,
                longitude=-117.1931,
                granularity=[Granularity.HOURLY, Granularity.DAILY],
                timezone="America/Los_Angeles",
            ),
        )
        print(response.summary)
        # ForecastResponseSummary(total=2, succeeded=2, failed=0)


asyncio.run(main())

Each entry in response.results is a ProviderSuccess or a ProviderError, discriminated by status โ€” pattern-match on them to read forecasts and typed error codes. Pass the location's IANA timezone when requesting daily data or provider-local wall times. If it is omitted, plugins that need it perform an uncached Open-Meteo lookup; the CLI supplies and persistently caches this value automatically when SQLite output is enabled. CLI cache entries retain six coordinate decimals and are refreshed after 30 days. See Getting Started and the Normalized Schema.

Documentation

Full documentation lives at hbmartin.github.io/omni-weather-forecast-apis.

Guide Contents
Getting Started Install, interactive setup, first forecast, library usage
Configuration Every TOML option โ€” retries, HTTP cache, raw payload archive, daily quotas, ${ENV_VAR} placeholders
Providers Per-provider config keys, and the unit/semantics caveats worth knowing before comparing values
CLI Flags, output formats (table/json/csv/ndjson), providers and doctor subcommands, exit codes
Scheduling Recurring collection with cron, launchd, or Task Scheduler
Normalized Schema Field/unit tables for every data point, the WeatherCondition enum, and typed error codes
Observability Metrics and log hooks, MetricKind events, and the OpenTelemetry bridge
Database Design The SQLite schema, and the stacking_features view โ€” aligned per-provider forecasts for the same point and valid time, already unit-normalized, which is the input a blending or verification model wants
Extending Response hooks, custom provider plugins, and quota trackers
API Reference Generated from the source

Development

# Set up the repository (Python 3.13+)
uv sync --extra cli

# Lint, format, and type-check
uv run ruff check src --fix
uv run ruff format src tests
uv run pyrefly check src
uv run ty check src

# Dependency, package, and complexity checks
uv run deptry src
uv run pyroma --min 8 .
uv run lizard -Eduplicate -C 27 src

# Tests and the 88% coverage floor
uv run pytest tests/ --cov=src --cov-report=term-missing

The documentation site is built with Zensical (configured in zensical.toml) from the docs/ directory:

uv sync --group docs
uv run zensical serve

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

omni_weather_forecast_apis-0.3.1.tar.gz (86.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

omni_weather_forecast_apis-0.3.1-py3-none-any.whl (115.3 kB view details)

Uploaded Python 3

File details

Details for the file omni_weather_forecast_apis-0.3.1.tar.gz.

File metadata

  • Download URL: omni_weather_forecast_apis-0.3.1.tar.gz
  • Upload date:
  • Size: 86.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for omni_weather_forecast_apis-0.3.1.tar.gz
Algorithm Hash digest
SHA256 98e0ce575f02412207a0705f2a3d5440c941281dad272307d7302b1a58600532
MD5 b33f667a5bcf2e73d46f6ce112fb563b
BLAKE2b-256 0c1b8cb8aa244e72c3ef0bf153fdcf2e666fb47c98385f26cdf3fdf5755f1600

See more details on using hashes here.

File details

Details for the file omni_weather_forecast_apis-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: omni_weather_forecast_apis-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 115.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for omni_weather_forecast_apis-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a33164cf7ca9821a4e381bea5814d17738db11ca40c932f96cec227fda6ca210
MD5 30bf10a4debabbdc4c50089cd14e1278
BLAKE2b-256 ba9f0d6819e48808262deebf82385953cd1752023a3024c2ec0012ef3d25f92a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page