- Add create() method to PriceRepository - Update PriceService to use repository.create() instead of direct db operations
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""Price service for fetching and managing price history."""
|
|
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from exceptions import ConflictError
|
|
from models import PriceHistory
|
|
from price_fetcher import PAIR_BTC_EUR, SOURCE_BITFINEX, fetch_btc_eur_price
|
|
from repositories.price import PriceRepository
|
|
|
|
PRICE_HISTORY_LIMIT = 20
|
|
|
|
|
|
class PriceService:
|
|
"""Service for price-related business logic."""
|
|
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
self.price_repo = PriceRepository(db)
|
|
|
|
async def get_recent_prices(
|
|
self, limit: int = PRICE_HISTORY_LIMIT
|
|
) -> list[PriceHistory]:
|
|
"""
|
|
Get recent price history records.
|
|
|
|
Args:
|
|
limit: Maximum number of records to return (default: 20)
|
|
|
|
Returns:
|
|
List of PriceHistory records, most recent first
|
|
"""
|
|
return await self.price_repo.get_recent(limit)
|
|
|
|
async def fetch_and_store_price(self) -> PriceHistory:
|
|
"""
|
|
Fetch price from Bitfinex and store it in the database.
|
|
|
|
Handles duplicate timestamp conflicts by returning the existing record.
|
|
|
|
Returns:
|
|
PriceHistory record (newly created or existing if duplicate)
|
|
|
|
Raises:
|
|
ConflictError: If unable to fetch or store price after retries
|
|
"""
|
|
price_value, timestamp = await fetch_btc_eur_price()
|
|
|
|
record = PriceHistory(
|
|
source=SOURCE_BITFINEX,
|
|
pair=PAIR_BTC_EUR,
|
|
price=price_value,
|
|
timestamp=timestamp,
|
|
)
|
|
|
|
try:
|
|
return await self.price_repo.create(record)
|
|
except IntegrityError:
|
|
# Duplicate timestamp - return the existing record
|
|
await self.db.rollback()
|
|
existing_record = await self.price_repo.get_by_timestamp(
|
|
timestamp, SOURCE_BITFINEX, PAIR_BTC_EUR
|
|
)
|
|
if existing_record:
|
|
return existing_record
|
|
# This should not happen, but handle gracefully
|
|
raise ConflictError("Failed to fetch or store price") from None
|