Skip to content

Commit

Permalink
improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Parissai committed Aug 9, 2024
1 parent 0707942 commit 96d531f
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 47 deletions.
33 changes: 24 additions & 9 deletions app/crud.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime

from app import models, schemas
import logging

# Initialize logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def get_weather_by_city_and_date(db: Session, city: str, date: datetime):
"""
Expand All @@ -18,8 +23,11 @@ def get_weather_by_city_and_date(db: Session, city: str, date: datetime):
models.Weather or None: The weather record as a SQLAlchemy model instance if found,
otherwise `None`.
"""

return db.query(models.Weather).filter(models.Weather.city == city, models.Weather.date == date).first()
try:
return db.query(models.Weather).filter(models.Weather.city == city, models.Weather.date == date).first()
except SQLAlchemyError as e:
logger.error(f"Error querying weather by city and date: {e}")
raise

def create_weather(db: Session, weather: schemas.WeatherCreate):
"""
Expand All @@ -36,11 +44,18 @@ def create_weather(db: Session, weather: schemas.WeatherCreate):
Returns:
models.Weather: The newly created weather record as a SQLAlchemy model instance.
Raises:
Exception: If there is an error creating the weather record.
"""

db_weather = models.Weather(**weather.model_dump())
db.add(db_weather)
db.commit()
db.refresh(db_weather)

return db_weather

try:
db.add(db_weather)
db.commit()
db.refresh(db_weather)
return db_weather
except SQLAlchemyError as e:
db.rollback()
logger.error(f"Error creating weather record: {e}")
raise
22 changes: 17 additions & 5 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from pydantic import BaseModel, field_validator
import re
from app import weather, schemas, database
import logging

# Initialize logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class WeatherRequest(BaseModel):
city: str
Expand All @@ -15,7 +20,7 @@ def validate_date_format(cls, value):
Validate the format of a date string.
Checks if the provided date string matches the expected format "YYYY-MM-DD".
Raises a `ValueError` if the format is invalid.
Raises a `ValueError` if the format is invalid or the date is not valid.
Args:
cls: The class method context (not used here).
Expand All @@ -25,7 +30,8 @@ def validate_date_format(cls, value):
str: The original date string if it matches the required format.
Raises:
ValueError: If the date string does not match the format "YYYY-MM-DD".
ValueError: If the date string does not match the format "YYYY-MM-DD"
or the date is invalid.
"""
if not re.match(r"^\d{4}-\d{2}-\d{2}$", value):
raise ValueError("Invalid date format. Use 'YYYY-MM-DD'.")
Expand All @@ -46,7 +52,7 @@ async def get_weather(city: str, date: str, db: Session = Depends(database.get_d
Retrieve weather data for a specific city and date.
Fetches weather data from the database for the specified city and date.
Raises a 404 error if the data is not found.
Raises a 404 error if the data is not found or a 400 error if the date format is invalid.
Args:
city (str): The name of the city.
Expand All @@ -57,14 +63,20 @@ async def get_weather(city: str, date: str, db: Session = Depends(database.get_d
schemas.WeatherResponse: The weather data for the city and date.
Raises:
HTTPException: 404 if weather data is not found.
HTTPException: 400 if the date format is invalid.
404 if weather data is not found.
"""
try:
date_obj = datetime.strptime(date, "%Y-%m-%d")
except ValueError:
raise HTTPException(status_code=400, detail="Invalid date format. Use 'YYYY-MM-DD'.")

weather_data = await weather.get_weather(db, city, date_obj)
try:
weather_data = await weather.get_weather(db, city, date_obj)
except Exception as e:
logger.error(f"Unexpected error while fetching weather data: {e}")
raise HTTPException(status_code=500, detail="Internal server error. Please try again later.")

if not weather_data:
raise HTTPException(status_code=404, detail="Weather data not found")

Expand Down
21 changes: 14 additions & 7 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
from sqlalchemy import Column, Integer, String, Float, DateTime
from app.database import Base
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func

Base = declarative_base()

class Weather(Base):
__tablename__ = "weather"

id = Column(Integer, primary_key=True, index=True)
city = Column(String, index=True)
date = Column(DateTime, index=True)
min_temp = Column(Float)
max_temp = Column(Float)
avg_temp = Column(Float)
humidity = Column(Float)
city = Column(String, index=True, nullable=False)
date = Column(DateTime, index=True, nullable=False)
min_temp = Column(Float, default=0.0)
max_temp = Column(Float, default=0.0)
avg_temp = Column(Float, default=0.0)
humidity = Column(Float, default=0.0)

def __repr__(self):
return f"<Weather(id={self.id}, city='{self.city}', date={self.date}, min_temp={self.min_temp}, max_temp={self.max_temp}, avg_temp={self.avg_temp}, humidity={self.humidity})>"
19 changes: 13 additions & 6 deletions app/schemas.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, Field, field_validator, ConfigDict
from datetime import datetime

class WeatherBase(BaseModel):
city: str
city: str = Field(..., example="New York", min_length=1)
date: datetime
min_temp: float
max_temp: float
avg_temp: float
humidity: float
min_temp: float = Field(..., gt=-100.0, lt=100.0, example=10.5)
max_temp: float = Field(..., gt=-100.0, lt=100.0, example=25.5)
avg_temp: float = Field(..., gt=-100.0, lt=100.0, example=18.0)
humidity: float = Field(..., ge=0.0, le=100.0, example=65.0)

@field_validator('date', mode='before')
def check_date(cls, value):
if value > datetime.now():
raise ValueError('Date cannot be in the future.')
return value


class WeatherCreate(WeatherBase):
pass
Expand Down
52 changes: 32 additions & 20 deletions app/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ async def fetch_weather_data(city: str, date: str) -> dict:
Raises:
httpx.HTTPStatusError: If the HTTP request to the weather API fails.
httpx.RequestError: For network-related errors.
ValueError: If the response does not contain expected data.
"""
async with httpx.AsyncClient() as client:
try:
response = await client.get(
Config.WEATHER_API_URL,
params={"key": Config.API_KEY, "q": city, "dt": date}
params={"key": Config.API_KEY, "q": city, "dt": date},
timeout=10.0
)
response.raise_for_status()
data = response.json()
Expand All @@ -54,12 +56,18 @@ async def fetch_weather_data(city: str, date: str) -> dict:
raise ValueError("Unexpected response structure from weather API.")

return data
except httpx.RequestError as e:
logger.error("Network-related error occurred while requesting weather data: %s", e)
raise
except httpx.HTTPStatusError as e:
logger.error("HTTP request failed: %s", e)
logger.error("HTTP request failed with status %d: %s", e.response.status_code, e)
raise
except ValueError as e:
logger.error("Data validation error: %s", e)
raise
except Exception as e:
logger.error("Unexpected error: %s", e)
raise

async def get_weather(db: Session, city: str, date: str) -> schemas.WeatherResponse:
"""
Expand All @@ -74,30 +82,34 @@ async def get_weather(db: Session, city: str, date: str) -> schemas.WeatherRespo
schemas.WeatherResponse: The weather data object.
Raises:
ValueError: If there's an issue with data retrieval or storage.
Exception: If there is an error fetching or storing weather data.
"""
# Fetch weather from the database
weather = crud.get_weather_by_city_and_date(db, city, date)
if weather:
return weather
weather_data = crud.get_weather_by_city_and_date(db, city, date)
if weather_data:
return weather_data

# Fetch weather from the external API
data = await fetch_weather_data(city, date)
forecast = data["forecast"]["forecastday"][0]["day"]

# Create a weather data object
weather_data = schemas.WeatherCreate(
city=city,
date=date,
min_temp=forecast.get("mintemp_c"),
max_temp=forecast.get("maxtemp_c"),
avg_temp=forecast.get("avgtemp_c"),
humidity=forecast.get("avghumidity")
)

# Store weather data in the database
try:
data = await fetch_weather_data(city, date)
forecast = data["forecast"]["forecastday"][0]["day"]

# Create a weather data object
weather_data = schemas.WeatherCreate(
city=city,
date=date,
min_temp=forecast.get("mintemp_c"),
max_temp=forecast.get("maxtemp_c"),
avg_temp=forecast.get("avgtemp_c"),
humidity=forecast.get("avghumidity")
)

# Store weather data in the database
return crud.create_weather(db, weather_data)
except ValueError as e:
logger.error("Error processing weather data: %s", e)
raise
except Exception as e:
logger.error("Error saving weather data to database: %s", e)
logger.error("Error fetching or saving weather data: %s", e)
raise

0 comments on commit 96d531f

Please sign in to comment.