This project is a simple Python weather client that fetches current weather data from the WeatherStack API and models the response using Pydantic. In this project we also used UV.
myweather/
├── main.py # App entry point
├── .env # Contains WEATHERSTACK_TOKEN (not committed)
├── pyproject.toml # Dependency and package config
├── uv.lock # Locked dependencies
├── myweather/
│ ├── __init__.py
│ ├── client.py # WeatherClient class with logging + error handling
│ └── models.py # Pydantic models for typed weather response
└── tests/
└── test_client.py # Unit tests with pytest
- The API token is stored in
.envand loaded usingpython-dotenv .envis added to.gitignore- Loaded in
main.pyusingos.getenv("WEATHERSTACK_TOKEN")
Defined in myweather/client.py:
- Calls
http://api.weatherstack.com/current - Handles:
HTTPErrorConnectionErrorTimeoutRequestExceptionPydantic ValidationError
- Returns a
WeatherResponsePydantic model on success, orNoneon failure - Uses
logginginstead ofprint()for all diagnostics
Defined in myweather/models.py:
from pydantic import BaseModel
from typing import List
class Location(BaseModel):
name: str
country: str
class CurrentWeather(BaseModel):
temperature: int
weather_descriptions: List[str]
class WeatherResponse(BaseModel):
location: Location
current: CurrentWeatherUnit tests are located in the tests/ folder and use pytest for simple testing of the WeatherClient.
monkeypatchis used to mock therequests.getcall- The test checks both:
- Successful weather response
- Handling of HTTP errors
def fake_get_error(*args, **kwargs):
class FakeResponse:
def raise_for_status(self):
raise requests.exceptions.HTTPError("401 Client Error: Unauthorized")
return FakeResponse()
def test_get_weather_http_error_handled(monkeypatch):
import requests
monkeypatch.setattr(requests, "get", fake_get_error)
client = WeatherClient(token="invalid")
result = client.get_weather("Oslo")
assert result is None-
Expand Pydantic models
Add support for more fields from the API, such as:- Humidity
- "Feels like" temperature
- Moon phase, visibility, etc.
-
replace argparse with something fancier (like typer, which is based on Pydantic too).
We added a CLI interface (with argparse) to allow passing the city as a command-line argument.
To run the weather client, use:
uv run main.py --city Oslo- Make sure your .env file contains a valid WEATHERSTACK_TOKEN.
- If your city is a composed name, use double quotes, for example: "Los Angeles".
- If no city name is passed, the default city is "Tananger".