138 lines
5.6 KiB
Python
138 lines
5.6 KiB
Python
# services/external/app/repositories/weather_repository.py
|
|
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
from sqlalchemy import select, and_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
import structlog
|
|
import json
|
|
|
|
from app.models.weather import WeatherData
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
class WeatherRepository:
|
|
"""
|
|
Repository for weather data operations, adapted for WeatherService.
|
|
"""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
async def get_historical_weather(self,
|
|
location_id: str,
|
|
start_date: datetime,
|
|
end_date: datetime) -> List[WeatherData]:
|
|
"""
|
|
Retrieves historical weather data for a specific location and date range.
|
|
This method directly supports the data retrieval logic in WeatherService.
|
|
"""
|
|
try:
|
|
stmt = select(WeatherData).where(
|
|
and_(
|
|
WeatherData.location_id == location_id,
|
|
WeatherData.date >= start_date,
|
|
WeatherData.date <= end_date
|
|
)
|
|
).order_by(WeatherData.date)
|
|
|
|
result = await self.session.execute(stmt)
|
|
records = result.scalars().all()
|
|
logger.debug(f"Retrieved {len(records)} historical records for location {location_id}")
|
|
return list(records)
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
"Failed to get historical weather from repository",
|
|
error=str(e),
|
|
location_id=location_id
|
|
)
|
|
raise
|
|
|
|
def _serialize_json_fields(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Serialize JSON fields (raw_data, processed_data) to ensure proper JSON storage
|
|
"""
|
|
serialized = data.copy()
|
|
|
|
# Serialize raw_data if present
|
|
if 'raw_data' in serialized and serialized['raw_data'] is not None:
|
|
if not isinstance(serialized['raw_data'], str):
|
|
try:
|
|
# Convert datetime objects to strings for JSON serialization
|
|
raw_data = serialized['raw_data']
|
|
if isinstance(raw_data, dict):
|
|
# Handle datetime objects in the dict
|
|
json_safe_data = {}
|
|
for k, v in raw_data.items():
|
|
if hasattr(v, 'isoformat'): # datetime-like object
|
|
json_safe_data[k] = v.isoformat()
|
|
else:
|
|
json_safe_data[k] = v
|
|
serialized['raw_data'] = json_safe_data
|
|
except Exception as e:
|
|
logger.warning(f"Could not serialize raw_data, storing as string: {e}")
|
|
serialized['raw_data'] = str(raw_data)
|
|
|
|
# Serialize processed_data if present
|
|
if 'processed_data' in serialized and serialized['processed_data'] is not None:
|
|
if not isinstance(serialized['processed_data'], str):
|
|
try:
|
|
processed_data = serialized['processed_data']
|
|
if isinstance(processed_data, dict):
|
|
json_safe_data = {}
|
|
for k, v in processed_data.items():
|
|
if hasattr(v, 'isoformat'): # datetime-like object
|
|
json_safe_data[k] = v.isoformat()
|
|
else:
|
|
json_safe_data[k] = v
|
|
serialized['processed_data'] = json_safe_data
|
|
except Exception as e:
|
|
logger.warning(f"Could not serialize processed_data, storing as string: {e}")
|
|
serialized['processed_data'] = str(processed_data)
|
|
|
|
return serialized
|
|
|
|
async def bulk_create_weather_data(self, weather_records: List[Dict[str, Any]]) -> None:
|
|
"""
|
|
Bulk inserts new weather records into the database.
|
|
Used by WeatherService after fetching new historical data from an external API.
|
|
"""
|
|
try:
|
|
if not weather_records:
|
|
return
|
|
|
|
# Serialize JSON fields before creating model instances
|
|
serialized_records = [self._serialize_json_fields(data) for data in weather_records]
|
|
records = [WeatherData(**data) for data in serialized_records]
|
|
self.session.add_all(records)
|
|
await self.session.commit()
|
|
logger.info(f"Successfully bulk inserted {len(records)} weather records")
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error(
|
|
"Failed to bulk create weather records",
|
|
error=str(e),
|
|
count=len(weather_records)
|
|
)
|
|
raise
|
|
|
|
async def create_weather_data(self, data: Dict[str, Any]) -> WeatherData:
|
|
"""
|
|
Creates a single new weather data record.
|
|
"""
|
|
try:
|
|
# Serialize JSON fields before creating model instance
|
|
serialized_data = self._serialize_json_fields(data)
|
|
new_record = WeatherData(**serialized_data)
|
|
self.session.add(new_record)
|
|
await self.session.commit()
|
|
await self.session.refresh(new_record)
|
|
logger.info(f"Created new weather record with ID {new_record.id}")
|
|
return new_record
|
|
|
|
except Exception as e:
|
|
await self.session.rollback()
|
|
logger.error("Failed to create single weather record", error=str(e))
|
|
raise |