Metadata-Version: 2.4
Name: truewire-weather-gov
Version: 0.2.0
Summary: Typed, validated Python client for the US National Weather Service API, generated by Truewire.
License-Expression: MIT
Project-URL: Homepage, https://github.com/truewire-dev/weather-gov
Project-URL: Truewire, https://truewire.dev
Keywords: weather,nws,noaa,forecast,weather.gov,api-client,truewire
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: truewire-core<0.4,>=0.3.0
Provides-Extra: dev
Requires-Dist: truewire==0.11.0; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: pyright>=1.1.390; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"

# truewire-weather-gov

A typed, validated, async Python client for the
[US National Weather Service API](https://www.weather.gov/documentation/services-web-api):
forecasts, hourly forecasts, the raw gridded data behind both, station observations, and
every watch, warning and advisory in effect.

Generated by [Truewire](https://truewire.dev) from a spec of what the API puts on the wire,
and checked against recordings of real responses.

Into a fresh virtual environment:

```sh
python -m venv .venv
. .venv/bin/activate  # on Windows: .venv\Scripts\activate
pip install truewire-weather-gov
```

The fresh environment matters: PyPI has an unrelated `weather-gov` package that also
imports as `weather_gov`, and the two cannot share an environment.

No account, no key, no quota. The service asks one thing: a `User-Agent` that identifies
the application, with a way to reach you if you have one. That is the `contact` argument,
and it is required — a default would be a lie about who is calling. Nothing checks the value: for a first try, the placeholder `you@example.com` in the examples
below works as it is. Before you ship, replace it with something that names you, such as an
email address you read or your project's URL.

## Use

Almost everything here is addressed by *grid cell* rather than by coordinates, so a caller
starts at `points.get_point`:

```python
import asyncio

from weather_gov import Weather


async def main() -> None:
  async with Weather.new(contact='you@example.com') as client:
    point = await client.points.get_point(latitude=47.6062, longitude=-122.3321)

    forecast = await client.forecast.get_forecast(
      office=point['gridId'], grid_x=point['gridX'], grid_y=point['gridY']
    )
    for period in forecast['periods'][:3]:
      print(
        f'{period["name"]:<16} {period["temperature"]:.0f}°{period["temperatureUnit"]}  {period["shortForecast"]}'
      )


asyncio.run(main())
```

```text
Tonight          56°F  Mostly Clear
Wednesday        74°F  Sunny
Wednesday Night  57°F  Partly Cloudy
```

## Every measurement carries its unit

This API never sends a bare number. A temperature is
`{'unitCode': 'wmoUnit:degC', 'value': 13, 'qualityControl': 'V'}`, and `value` is `None`
wherever the measurement is missing rather than zero — an airport station's `windGust`
has a `value` only when there were gusts. The types say so, so the null is hard to forget.

## Walking a span of observations

`stations.get_observations` is paged by time, and the walk knows something hand-written
code usually does not: the service caps a response at 500 observations, keeps the newest,
and says nothing about the ones it withheld. A full page is evidence the span held more, so
`stations.get_observations_paged` moves `end` back to the oldest observation it held and
asks again, until the span is done. Await it for every observation in the span, newest
first, or iterate it for one page at a time:

```python
from datetime import datetime, timedelta, timezone

from weather_gov import Weather


async def last_week(client: Weather) -> None:
  end = datetime.now(timezone.utc)
  # A week is more than 500 observations, so this is several requests -- by design.
  observations = await client.stations.get_observations_paged(
    'KSEA', start=end - timedelta(days=7), end=end
  )
  print(len(observations), 'observations')
```

## Everything else

Fifty-eight endpoints across eleven groups, three response vocabularies (GeoJSON, schema.org,
JSON-LD) described as they actually arrive, and TypeScript and Rust clients from the same
spec.

Full documentation, the spec, and the recordings:
**https://github.com/truewire-dev/weather-gov**

MIT. NWS data is a work of the US government and is in the public domain.
