Skip to content

Getting Started

Install

uv sync --extra cli

Requires Python 3.13+.

Interactive CLI setup

uv run omni-weather init

The wizard asks for required coordinates, providers, provider credentials, SQLite output, and forecast granularities. Open-Meteo is the recommended keyless default. MET Norway and NWS share one application/contact identity; the other providers are grouped as requiring API keys. The wizard validates the generated TOML and provider settings, shows an exact preview, asks before writing or overwriting, and offers a test forecast (default: yes).

Credential input is masked, but credentials are deliberately written directly to the config and visible in the preview. The generated config is owner-only on POSIX. Use environment placeholders in a manually maintained config when you do not want secrets stored there.

Running uv run omni-weather without --config uses the platform-native config, falls back to the legacy ~/.config/omni_weather_forecast_apis.toml, and starts this wizard if neither exists. Automatic setup only runs in an interactive terminal; after setup it executes the original forecast command.

Manual minimal CLI run

Open-Meteo and MET Norway require no API keys, so this works out of the box:

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

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

uv run omni-weather \
  --config ./config.toml \
  --lat 40.7128 \
  --lon -74.0060 \
  --sqlite ./forecasts.sqlite

Add --format json to emit the full normalized response as JSON on stdout, or omit --sqlite to skip persistence entirely.

Use uv run omni-weather providers to compare provider requirements and official signup links. Use uv run omni-weather doctor for static aggregated diagnostics. doctor --live opts into real provider calls, which can consume quota; add repeatable --provider ID filters to narrow those checks.

Library usage

import asyncio

from omni_weather_forecast_apis import (
    ForecastRequest,
    Granularity,
    OmniWeatherConfig,
    ProviderError,
    ProviderRegistration,
    ProviderId,
    ProviderSuccess,
    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)

        for result in response.results:
            match result:
                case ProviderSuccess(provider=pid, forecasts=forecasts):
                    for fc in forecasts:
                        for pt in fc.hourly:
                            print(f"{pid} {pt.timestamp}: {pt.temperature}°C, {pt.condition}")
                case ProviderError(provider=pid, error=err):
                    print(f"{pid} failed: {err.code}{err.message}")


asyncio.run(main())

ForecastRequest.timezone is an optional, validated IANA name. Library callers should provide it when available. A provider-supplied IANA timezone takes precedence; otherwise plugins that need civil-time conversion use the request value, then fall back to an uncached Open-Meteo coordinate lookup. A lookup failure becomes an error for only that provider, so other provider requests can still succeed.

Example output:

ProviderId.OPEN_METEO 2026-03-13 18:00:00+00:00: 12.3°C, WeatherCondition.PARTLY_CLOUDY
ProviderId.OPEN_METEO 2026-03-13 19:00:00+00:00: 11.8°C, WeatherCondition.OVERCAST
ProviderId.MET_NORWAY 2026-03-13 18:00:00+00:00: 12.1°C, WeatherCondition.RAIN
...

The response always completes even when some providers fail — see Normalized Schema for the typed error codes, and Observability for metrics hooks.

Keeping API keys out of config files

Reference environment variables from any provider config value:

[[providers]]
plugin_id = "openweather"
config = { api_key = "${OPENWEATHER_API_KEY}" }

See Configuration for details.