Files
calminer/routes/currencies.py
zwitschi 97b1c0360b
Some checks failed
Run Tests / e2e tests (push) Failing after 1m27s
Run Tests / lint tests (push) Failing after 6s
Run Tests / unit tests (push) Failing after 7s
Refactor test cases for improved readability and consistency
- Updated test functions in various test files to enhance code clarity by formatting long lines and improving indentation.
- Adjusted assertions to use multi-line formatting for better readability.
- Added new test cases for theme settings API to ensure proper functionality.
- Ensured consistent use of line breaks and spacing across test files for uniformity.
2025-10-27 10:32:55 +01:00

194 lines
5.3 KiB
Python

from typing import Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, ConfigDict, Field, field_validator
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from models.currency import Currency
from routes.dependencies import get_db
router = APIRouter(prefix="/api/currencies", tags=["Currencies"])
DEFAULT_CURRENCY_CODE = "USD"
DEFAULT_CURRENCY_NAME = "US Dollar"
DEFAULT_CURRENCY_SYMBOL = "$"
class CurrencyBase(BaseModel):
name: str = Field(..., min_length=1, max_length=128)
symbol: Optional[str] = Field(default=None, max_length=8)
@staticmethod
def _normalize_symbol(value: Optional[str]) -> Optional[str]:
if value is None:
return None
value = value.strip()
return value or None
@field_validator("name")
@classmethod
def _strip_name(cls, value: str) -> str:
return value.strip()
@field_validator("symbol")
@classmethod
def _strip_symbol(cls, value: Optional[str]) -> Optional[str]:
return cls._normalize_symbol(value)
class CurrencyCreate(CurrencyBase):
code: str = Field(..., min_length=3, max_length=3)
is_active: bool = True
@field_validator("code")
@classmethod
def _normalize_code(cls, value: str) -> str:
return value.strip().upper()
class CurrencyUpdate(CurrencyBase):
is_active: Optional[bool] = None
class CurrencyActivation(BaseModel):
is_active: bool
class CurrencyRead(CurrencyBase):
id: int
code: str
is_active: bool
model_config = ConfigDict(from_attributes=True)
def _ensure_default_currency(db: Session) -> Currency:
existing = (
db.query(Currency)
.filter(Currency.code == DEFAULT_CURRENCY_CODE)
.one_or_none()
)
if existing:
return existing
default_currency = Currency(
code=DEFAULT_CURRENCY_CODE,
name=DEFAULT_CURRENCY_NAME,
symbol=DEFAULT_CURRENCY_SYMBOL,
is_active=True,
)
db.add(default_currency)
try:
db.commit()
except IntegrityError:
db.rollback()
existing = (
db.query(Currency)
.filter(Currency.code == DEFAULT_CURRENCY_CODE)
.one()
)
return existing
db.refresh(default_currency)
return default_currency
def _get_currency_or_404(db: Session, code: str) -> Currency:
normalized = code.strip().upper()
currency = (
db.query(Currency).filter(Currency.code == normalized).one_or_none()
)
if currency is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Currency not found"
)
return currency
@router.get("/", response_model=List[CurrencyRead])
def list_currencies(
include_inactive: bool = Query(
False, description="Include inactive currencies"
),
db: Session = Depends(get_db),
):
_ensure_default_currency(db)
query = db.query(Currency)
if not include_inactive:
query = query.filter(Currency.is_active.is_(True))
currencies = query.order_by(Currency.code).all()
return currencies
@router.post(
"/", response_model=CurrencyRead, status_code=status.HTTP_201_CREATED
)
def create_currency(payload: CurrencyCreate, db: Session = Depends(get_db)):
code = payload.code
existing = db.query(Currency).filter(Currency.code == code).one_or_none()
if existing is not None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Currency '{code}' already exists",
)
currency = Currency(
code=code,
name=payload.name,
symbol=CurrencyBase._normalize_symbol(payload.symbol),
is_active=payload.is_active,
)
db.add(currency)
db.commit()
db.refresh(currency)
return currency
@router.put("/{code}", response_model=CurrencyRead)
def update_currency(
code: str, payload: CurrencyUpdate, db: Session = Depends(get_db)
):
currency = _get_currency_or_404(db, code)
if payload.name is not None:
setattr(currency, "name", payload.name)
if payload.symbol is not None or payload.symbol == "":
setattr(
currency,
"symbol",
CurrencyBase._normalize_symbol(payload.symbol),
)
if payload.is_active is not None:
code_value = getattr(currency, "code")
if code_value == DEFAULT_CURRENCY_CODE and payload.is_active is False:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="The default currency cannot be deactivated.",
)
setattr(currency, "is_active", payload.is_active)
db.add(currency)
db.commit()
db.refresh(currency)
return currency
@router.patch("/{code}/activation", response_model=CurrencyRead)
def toggle_currency_activation(
code: str, body: CurrencyActivation, db: Session = Depends(get_db)
):
currency = _get_currency_or_404(db, code)
code_value = getattr(currency, "code")
if code_value == DEFAULT_CURRENCY_CODE and body.is_active is False:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="The default currency cannot be deactivated.",
)
setattr(currency, "is_active", body.is_active)
db.add(currency)
db.commit()
db.refresh(currency)
return currency