feat: Add Weather Forecast Service with new endpoint, model, schema, migration, and requirements (auto-linted)

This commit is contained in:
Backend IM Bot 2025-04-29 07:25:31 +00:00
parent 3141584e92
commit 3fd22cdcd8
6 changed files with 245 additions and 0 deletions

View File

@ -0,0 +1,35 @@
"""create table for weather_forecasts
Revision ID: 1a2b3c4d5e6f
Revises: 0001
Create Date: 2023-05-24 12:00:00
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import func
import uuid
# revision identifiers, used by Alembic.
revision = '1a2b3c4d5e6f'
down_revision = '0001'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'weather_forecasts',
sa.Column('id', sa.String(36), primary_key=True, default=lambda: str(uuid.uuid4())),
sa.Column('location', sa.String(), nullable=False),
sa.Column('date', sa.DateTime(), nullable=False),
sa.Column('temperature', sa.Float(), nullable=False),
sa.Column('humidity', sa.Float(), nullable=False),
sa.Column('wind_speed', sa.Float(), nullable=False),
sa.Column('precipitation_probability', sa.Float(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=func.now()),
sa.Column('updated_at', sa.DateTime(), server_default=func.now(), onupdate=func.now()),
sa.Index('ix_weather_forecasts_location', 'location'),
sa.Index('ix_weather_forecasts_date', 'date')
)
def downgrade():
op.drop_table('weather_forecasts')

View File

@ -0,0 +1,24 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime
from core.database import get_db
from schemas.weather_forecast import WeatherForecastSchema
from helpers.weather_forecast_helpers import get_weather_forecasts
router = APIRouter()
@router.get("/weather", status_code=200, response_model=List[WeatherForecastSchema])
async def get_weather_forecast(
location: str = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
db: Session = Depends(get_db)
):
forecasts = get_weather_forecasts(
db=db,
location=location,
start_date=start_date,
end_date=end_date
)
return forecasts

View File

@ -0,0 +1,131 @@
from typing import List, Optional, Union
import uuid
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime
from models.weather_forecast import WeatherForecast
from schemas.weather_forecast import WeatherForecastCreate, WeatherForecastUpdate, WeatherForecastSchema
def get_weather_forecasts(
db: Session, location: Optional[str] = None, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None
) -> List[WeatherForecast]:
"""
Retrieves weather forecasts from the database based on the provided filters.
Args:
db (Session): The database session.
location (Optional[str]): Filter by location.
start_date (Optional[datetime]): Filter by start date (inclusive).
end_date (Optional[datetime]): Filter by end date (inclusive).
Returns:
List[WeatherForecast]: A list of weather forecast objects matching the filters.
"""
query = db.query(WeatherForecast)
if location:
query = query.filter(WeatherForecast.location == location)
if start_date:
query = query.filter(WeatherForecast.date >= start_date)
if end_date:
query = query.filter(WeatherForecast.date <= end_date)
return query.all()
def get_weather_forecast_by_id(db: Session, forecast_id: Union[str, uuid.UUID]) -> Optional[WeatherForecast]:
"""
Retrieves a weather forecast by its ID.
Args:
db (Session): The database session.
forecast_id (Union[str, uuid.UUID]): The ID of the weather forecast.
Returns:
Optional[WeatherForecast]: The weather forecast object if found, otherwise None.
"""
return db.query(WeatherForecast).filter(WeatherForecast.id == forecast_id).first()
def create_weather_forecast(db: Session, forecast_data: WeatherForecastCreate) -> WeatherForecastSchema:
"""
Creates a new weather forecast in the database.
Args:
db (Session): The database session.
forecast_data (WeatherForecastCreate): The data for the weather forecast to create.
Returns:
WeatherForecastSchema: The newly created weather forecast object.
"""
db_forecast = WeatherForecast(**forecast_data.dict())
db.add(db_forecast)
db.commit()
db.refresh(db_forecast)
return WeatherForecastSchema.from_orm(db_forecast)
def update_weather_forecast(
db: Session, forecast_id: Union[str, uuid.UUID], forecast_data: WeatherForecastUpdate
) -> WeatherForecastSchema:
"""
Updates an existing weather forecast in the database.
Args:
db (Session): The database session.
forecast_id (Union[str, uuid.UUID]): The ID of the weather forecast to update.
forecast_data (WeatherForecastUpdate): The updated data for the weather forecast.
Returns:
WeatherForecastSchema: The updated weather forecast object.
"""
db_forecast = db.query(WeatherForecast).filter(WeatherForecast.id == forecast_id).first()
if not db_forecast:
raise ValueError(f"Weather forecast with ID {forecast_id} not found")
update_data = forecast_data.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(db_forecast, field, value)
db.commit()
db.refresh(db_forecast)
return WeatherForecastSchema.from_orm(db_forecast)
def delete_weather_forecast(db: Session, forecast_id: Union[str, uuid.UUID]) -> bool:
"""
Deletes a weather forecast from the database.
Args:
db (Session): The database session.
forecast_id (Union[str, uuid.UUID]): The ID of the weather forecast to delete.
Returns:
bool: True if the weather forecast was successfully deleted, False otherwise.
"""
db_forecast = db.query(WeatherForecast).filter(WeatherForecast.id == forecast_id).first()
if not db_forecast:
return False
db.delete(db_forecast)
db.commit()
return True
def get_average_temperature_by_location(db: Session, location: str, start_date: datetime, end_date: datetime) -> Optional[float]:
"""
Retrieves the average temperature for a given location and date range.
Args:
db (Session): The database session.
location (str): The location to filter by.
start_date (datetime): The start date of the date range (inclusive).
end_date (datetime): The end date of the date range (inclusive).
Returns:
Optional[float]: The average temperature if forecasts are found, otherwise None.
"""
avg_temp = db.query(func.avg(WeatherForecast.temperature)).filter(
WeatherForecast.location == location,
WeatherForecast.date >= start_date,
WeatherForecast.date <= end_date,
).scalar()
return avg_temp if avg_temp is not None else None

View File

@ -0,0 +1,19 @@
from sqlalchemy import Column, String, Float, DateTime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from core.database import Base
import uuid
class WeatherForecast(Base):
__tablename__ = "weather_forecasts"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
location = Column(String, nullable=False, index=True)
date = Column(DateTime, nullable=False, index=True)
temperature = Column(Float, nullable=False)
humidity = Column(Float, nullable=False)
wind_speed = Column(Float, nullable=False)
precipitation_probability = Column(Float, nullable=False)
description = Column(String, nullable=False)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())

View File

@ -7,3 +7,6 @@ sqlalchemy>=1.4.0
python-dotenv>=0.19.0
bcrypt>=3.2.0
alembic>=1.13.1
jose
passlib
pydantic

View File

@ -0,0 +1,33 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
import uuid
class WeatherForecastBase(BaseModel):
location: str
date: datetime
temperature: float
humidity: float
wind_speed: float
precipitation_probability: float
description: str
class WeatherForecastCreate(WeatherForecastBase):
pass
class WeatherForecastUpdate(WeatherForecastBase):
location: Optional[str] = None
date: Optional[datetime] = None
temperature: Optional[float] = None
humidity: Optional[float] = None
wind_speed: Optional[float] = None
precipitation_probability: Optional[float] = None
description: Optional[str] = None
class WeatherForecastSchema(WeatherForecastBase):
id: uuid.UUID
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True