# accuweather-client

[![PyPI version](https://img.shields.io/pypi/v/accuweather-client)](https://pypi.org/project/accuweather-client/)
[![Python versions](https://img.shields.io/pypi/pyversions/accuweather-client)](https://pypi.org/project/accuweather-client/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/Thomas1942/AccuWeather/actions/workflows/ci.yml/badge.svg)](https://github.com/Thomas1942/AccuWeather/actions/workflows/ci.yml)
[![Downloads](https://img.shields.io/pypi/dm/accuweather-client)](https://pypistats.org/packages/accuweather-client)
[![Typed](https://img.shields.io/badge/types-typed-brightgreen)](https://peps.python.org/pep-0561/)
[![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

A **human-friendly, fully typed Python client for the [AccuWeather API](https://developer.accuweather.com)** — address locations the way people do (city name, point of interest, or coordinates) and get documented [pydantic](https://docs.pydantic.dev) models back. Sync and async clients, a pretty CLI, pandas export, automatic retries, quota-aware caching, and typed exceptions.

```python
from accuweather_client import WeatherClient

client = WeatherClient(token="your-api-key", city="sydney")

client.get_current_conditions().current_conditions
# 'At the moment: sunny, with a temperature of 17.2C. The wind is coming from the WSW at 15.4km/h.'

client.get_5day_forecast().forecast_tomorrow
# 'Partly sunny, max temp is 18.9C and 25% chance of rain.'
```

## Why this package?

* **Locations the human way.** No manual location-key lookups: pass `city="sydney"` — or disambiguate with `country="canada"`, use a `poi="Eiffel tower"`, or a `lat`/`lon` pair. The location key is resolved and cached for you.
* **Typed everywhere.** Every response is a documented pydantic model, the package ships a `py.typed` marker, and every public method has a complete docstring — your IDE (and your AI assistant) autocompletes the whole API.
* **Sync *and* async.** `WeatherClient` and `AsyncWeatherClient` mirror each other method-for-method.
* **Quota-friendly.** The free tier allows 50 calls/day; the client caches location lookups, optionally caches weather responses (`cache_ttl=600`), retries transient errors with backoff, and raises a dedicated `RateLimitExceededError` carrying the `RateLimit-*` headers.
* **Batteries included.** A `rich`-powered CLI, emoji weather icons, and one-line pandas DataFrame export.

## Installation

```bash
pip install accuweather-client            # core (requests + pydantic only)
pip install 'accuweather-client[all]'     # + pandas, async (httpx), cli (typer/rich)
```

Extras: `[pandas]` for DataFrame export, `[async]` for `AsyncWeatherClient`, `[cli]` for the `accuweather` command.

## Getting an API key

Create a free account at [developer.accuweather.com](https://developer.accuweather.com), register an app, and copy its API key. The free tier includes 50 requests/day and covers location search, current conditions, 1/5-day daily and 1/12-hour hourly forecasts; other endpoints (alerts, indices, historical conditions, longer forecasts) require a paid plan and answer with HTTP 403 otherwise.

## Quick start

```python
from accuweather_client import WeatherClient

client = WeatherClient(token=API_KEY, city="sydney")
client.location          # Sydney, Australia (key=22889) — resolved lazily, cached

# Same name, different country? Point of interest? Coordinates? All work:
WeatherClient(token=API_KEY, city="sydney", country="canada")
WeatherClient(token=API_KEY, poi="Eiffel tower")
WeatherClient(token=API_KEY, lat=51.988, lon=-4.88)

# Current conditions
conditions = client.get_current_conditions()
conditions.current_conditions   # friendly sentence
conditions.temperature          # 17.2

# Daily forecasts (1 & 5 days on the free tier, 10/15 on paid plans)
forecast = client.get_5day_forecast()
forecast.forecast_tomorrow      # one-line summary
print(forecast)                 # multi-line overview with emoji
forecast.to_pandas_df()         # flat DataFrame, one row per day

# Hourly forecasts (1 & 12 h free, 24/72/120 h paid)
client.get_hourly_forecast(hours=12)

# More endpoints
client.get_alerts()                       # severe-weather alerts
client.get_indices().get("running")       # lifestyle indices (pollen, UV, sports…)
client.get_historical_conditions(24)      # past 24 h observations
```

Units and language are configurable: `WeatherClient(..., metric=False, language="nl-nl")`.

### Async

```python
import asyncio
from accuweather_client import AsyncWeatherClient

async def main():
    async with AsyncWeatherClient(token=API_KEY, city="sydney") as client:
        conditions, forecast = await asyncio.gather(
            client.get_current_conditions(),
            client.get_5day_forecast(),
        )
        print(conditions.current_conditions)

asyncio.run(main())
```

### CLI

```bash
export ACCUWEATHER_API_KEY="your-api-key"   # or use --token / a .env file

accuweather now --city sydney
accuweather forecast --city sydney --days 5
accuweather hourly --lat 51.9 --lon -4.8 --hours 12
accuweather alerts --poi "Eiffel tower"
```

`forecast` and `hourly` print rich tables with emoji weather icons.

### Location search on its own

```python
from accuweather_client import LocationClient

locations = LocationClient(token=API_KEY)
locations.search_city("sydney").first        # best-ranked match
locations.autocomplete("syd")                # typeahead suggestions
locations.search_postal_code("1012", country="NL")
locations.search_ip()                        # geolocate the caller's IP
```

## Supported endpoints

| Method | AccuWeather endpoint | Free tier |
|---|---|---|
| `get_current_conditions()` | `currentconditions/v1/{key}` | ✅ |
| `get_historical_conditions(hours=6\|24)` | `currentconditions/v1/{key}/historical[/24]` | paid |
| `get_daily_forecast(days=1\|5\|10\|15)` (+ `get_5day_forecast()` …) | `forecasts/v1/daily/{n}day/{key}` | 1, 5 |
| `get_hourly_forecast(hours=1\|12\|24\|72\|120)` | `forecasts/v1/hourly/{n}hour/{key}` | 1, 12 |
| `get_alerts()` | `alerts/v1/{key}` | paid |
| `get_indices(index_id=None)` | `indices/v1/daily/1day/{key}` | paid |
| `LocationClient.search_city/poi/geoposition/postal_code/ip/autocomplete` | `locations/v1/…` | ✅ |

## Error handling

All errors derive from `AccuWeatherError`:

```python
from accuweather_client.exceptions import (
    AccuWeatherError, InvalidTokenError, LocationNotFoundError,
    RateLimitExceededError, ApiRequestError, ApiServerError,
)

try:
    client.get_current_conditions()
except RateLimitExceededError as e:
    print("Daily quota used up.", e.rate_limit_headers)
except LocationNotFoundError:
    print("Try adding a country name.")
```

## Using with AI coding assistants

This package is intentionally easy for LLM-based tools (Claude Code, Copilot, Cursor, …) to work with:

* [`llms.txt`](llms.txt) — a compact, machine-readable API reference you can paste into any assistant's context.
* Fully typed public API (`py.typed`) with Google-style docstrings and examples on every method, so assistants generate correct calls from signatures alone.
* Predictable naming (`get_<thing>()`), typed exceptions, and models mirroring the official API field names.

Point your assistant at `llms.txt` or just at this README — both are written to be sufficient context for generating working integrations.

## Development

```bash
git clone https://github.com/Thomas1942/AccuWeather && cd AccuWeather
uv venv && uv pip install -e '.[all]' --group dev   # or pip install -e '.[all]'
pytest          # 90%+ coverage, no network access needed
ruff check .    # lint + format
mypy            # type check
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, [CHANGELOG.md](CHANGELOG.md) for release history, [ROADMAP.md](ROADMAP.md) for what's planned, and our [Code of Conduct](CODE_OF_CONDUCT.md). Contributions are welcome — issues labeled [`good first issue`](https://github.com/Thomas1942/AccuWeather/labels/good%20first%20issue) are a great place to start.

## License

[MIT](LICENSE) — this is an unofficial client; AccuWeather is a trademark of AccuWeather, Inc. Use of the API is subject to [AccuWeather's terms](https://developer.accuweather.com/terms-conditions).
