first commit
This commit is contained in:
57
app/services/ticker_service.py
Normal file
57
app/services/ticker_service.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Ticker Registry service: add, delete, and list tracked tickers."""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.exceptions import DuplicateError, NotFoundError, ValidationError
|
||||
from app.models.ticker import Ticker
|
||||
|
||||
|
||||
async def add_ticker(db: AsyncSession, symbol: str) -> Ticker:
|
||||
"""Add a new ticker after validation.
|
||||
|
||||
Validates: non-empty, uppercase alphanumeric. Auto-uppercases input.
|
||||
Raises DuplicateError if symbol already tracked.
|
||||
"""
|
||||
stripped = symbol.strip()
|
||||
if not stripped:
|
||||
raise ValidationError("Ticker symbol must not be empty or whitespace-only")
|
||||
|
||||
normalised = stripped.upper()
|
||||
if not re.fullmatch(r"[A-Z0-9]+", normalised):
|
||||
raise ValidationError(
|
||||
f"Ticker symbol must be alphanumeric: {normalised}"
|
||||
)
|
||||
|
||||
result = await db.execute(select(Ticker).where(Ticker.symbol == normalised))
|
||||
if result.scalar_one_or_none() is not None:
|
||||
raise DuplicateError(f"Ticker already exists: {normalised}")
|
||||
|
||||
ticker = Ticker(symbol=normalised)
|
||||
db.add(ticker)
|
||||
await db.commit()
|
||||
await db.refresh(ticker)
|
||||
return ticker
|
||||
|
||||
|
||||
async def delete_ticker(db: AsyncSession, symbol: str) -> None:
|
||||
"""Delete a ticker and cascade all associated data.
|
||||
|
||||
Raises NotFoundError if the symbol is not tracked.
|
||||
"""
|
||||
normalised = symbol.strip().upper()
|
||||
result = await db.execute(select(Ticker).where(Ticker.symbol == normalised))
|
||||
ticker = result.scalar_one_or_none()
|
||||
if ticker is None:
|
||||
raise NotFoundError(f"Ticker not found: {normalised}")
|
||||
|
||||
await db.delete(ticker)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def list_tickers(db: AsyncSession) -> list[Ticker]:
|
||||
"""Return all tracked tickers sorted alphabetically by symbol."""
|
||||
result = await db.execute(select(Ticker).order_by(Ticker.symbol.asc()))
|
||||
return list(result.scalars().all())
|
||||
Reference in New Issue
Block a user