The weather example
🌦️ Weather Addon Example¶
This addon demonstrates how to build a basic weather data API using pAPI, with persistent storage in MongoDB via the integrated Beanie ODM.
Through this example, you will learn how to:
- Integrate MongoDB using Beanie
- Add Python dependencies to your addon
- Register and list weather stations
- Fetch real-time weather data from an external API
💡 Bonus: Learn how to interact with MongoDB using the integrated shell.
🗂️ Project Structure¶
my_addons/
└── weather/
├── __init__.py
├── manifest.yaml
├── models.py
├── crud.py
└── routers.py
📄 manifest.yaml
¶
Declares the addon and its required dependencies:
name: weather
version: 1.0.0
description: Weather data API
author: Your Name
python_dependencies:
- "requests>=2.28.0"
🧬 models.py
¶
Defines the database models for weather stations and readings:
from datetime import datetime, timezone
from beanie import Document, PydanticObjectId
from pydantic import BaseModel, Field
class Location(BaseModel):
latitude: float
longitude: float
class WeatherStation(Document):
name: str
location: Location
created_at: datetime = Field(
default_factory=lambda: datetime.now(tz=timezone.utc)
)
class Settings:
name = "weather_stations"
class WeatherReading(Document):
station_id: PydanticObjectId
temperature: float
windspeed: float
humidity: float
timestamp: datetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc))
class Settings:
name = "weather_readings"
🔌 routers.py
¶
Handles the REST API endpoints:
from fastapi import HTTPException
from papi.core.response import create_response
from papi.core.router import RESTRouter
from .crud import get_weather
from .models import WeatherReading, WeatherStation
router = RESTRouter()
@router.get("/stations")
async def list_stations():
"""List all registered weather stations."""
stations = await WeatherStation.find_all().to_list()
return create_response(data=stations)
@router.post("/stations")
async def create_station(station: WeatherStation):
"""Register a new weather station."""
await station.create()
return create_response(message="Station created successfully", data=station)
@router.get("/stations/{station_id}/weather")
async def get_current_weather_for_station(station_id: str):
"""
Fetch the current weather data for a given station.
Also saves the reading to the database.
"""
station = await WeatherStation.get(station_id)
if not station:
raise HTTPException(status_code=404, detail="Station not found")
weather = get_weather(station.location.latitude, station.location.longitude)
await WeatherReading(station_id=station.id, **weather).save()
return create_response(data=weather)
🌐 crud.py
¶
Fetches weather data using the Open-Meteo public API:
import requests
def get_weather(latitude: float, longitude: float) -> dict:
"""
Fetch current weather data using the Open-Meteo API (no API key required).
"""
url = (
"https://api.open-meteo.com/v1/forecast"
f"?latitude={latitude}&longitude={longitude}"
"¤t=temperature_2m,wind_speed_10m,relative_humidity_2m"
)
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
current = data.get("current")
if not current:
raise ValueError("Missing 'current' field in API response")
return {
"temperature": current["temperature_2m"],
"windspeed": current["wind_speed_10m"],
"humidity": current["relative_humidity_2m"]
}
except Exception as e:
return {
"temperature": None,
"windspeed": None,
"humidity": None,
"error": str(e)
}
📦 __init__.py
¶
This file registers your addon so it can be discovered and loaded by pAPI.
It also registers the Beanie documents so they are initialized with the MongoDB connection during pAPI startup.
from .models import WeatherReading, WeatherStation
from .router import router
# Register the API router and Beanie documents for MongoDB
__all__ = [
"router", # Registers the API routes
"WeatherStation", # Registers the weather station document
"WeatherReading" # Registers the weather reading document
]
⚙️ Configuration (config.yaml
)¶
# base configuration check the hello world example
...
# MongoDB connection settings
database:
mongodb_uri: "mongodb://root:example@localhost:27017/weather_db?authSource=admin"
# Enable the weather addon
addons:
extra_addons_path: "my_addons"
enabled:
- weather
🧪 How to Use¶
🚀 Start the API Server¶
rye run python papi/cli.py webserver
📍 Create a Weather Station¶
curl -X 'POST' \
'http://localhost:8080/stations' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "Santa Clara, Cuba",
"location": {
"latitude": 22.4067,
"longitude": -79.9531
}
}'
✅ Example Response¶
{
"success": true,
"message": "Station created successfully",
"data": {
"_id": "684da177ebcda212e2ce8dac",
"name": "Santa Clara, Cuba",
"location": {
"latitude": 22.4067,
"longitude": -79.9531
},
"created_at": "2025-06-14T16:21:11.352782Z"
},
"error": null,
"meta": { ... }
}
📋 List All Stations¶
curl -X 'GET' \
'http://localhost:8080/stations' \
-H 'accept: application/json'
✅ Example Response¶
{
"success": true,
"message": null,
"data": [
{
"_id": "684daa34dc94122d9d84bac9",
"name": "Santa Clara, Cuba",
"location": {
"latitude": 22.4067,
"longitude": -79.9531
},
"created_at": "2025-06-14T16:58:28.540000"
}
],
"error": null,
"meta": {
"timestamp": "2025-06-14T17:17:01+00:00Z",
"requestId": "888ecf22-ea67-4f37-87e1-c13d6f32cfea"
}
}
🌡️ Get Weather for a Station¶
curl -X 'GET' \
'http://localhost:8080/stations/684da177ebcda212e2ce8dac/weather' \
-H 'accept: application/json'
✅ Example Response¶
{
"success": true,
"message": null,
"data": {
"temperature": 31.2,
"windspeed": 18.7,
"humidity": 56
},
"error": null,
"meta": { ... }
}
🛠️ MongoDB Shell Access (via pAPI Shell)¶
Access the MongoDB shell:
rye run python papi/cli.py shell
🔍 Available Documents¶
In [1]: mongo_documents
Out[1]:
{
'WeatherStation': weather.models.WeatherStation,
'WeatherReading': weather.models.WeatherReading
}
🧾 List Stations in Shell¶
In [2]: await mongo_documents["WeatherStation"].find().to_list()
✏️ Rename a Station¶
In [3]: from beanie import BeanieObjectId as ObjectId
In [4]: station = await mongo_documents["WeatherStation"].get(ObjectId('684daa34dc94122d9d84bac9'))
In [5]: station.name = "New Santa Clara"
In [6]: await station.save()
Confirm the change:
In [7]: await mongo_documents["WeatherStation"].get(ObjectId('684daa34dc94122d9d84bac9'))
Out[7]: WeatherStation(name='New Santa Clara', ...)