Skip to content

Commit

Permalink
feat: simply Price.currency validation (#67)
Browse files Browse the repository at this point in the history
* Replace sqlalchemy_utils CurrencyType by custom CurrencyEnum

* Rename LocationOSMType to LocationOSMEnum
  • Loading branch information
raphodn committed Nov 27, 2023
1 parent e3e9098 commit d7dfb0c
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 28 deletions.
4 changes: 2 additions & 2 deletions app/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from sqlalchemy.sql import func

from app import config
from app.enums import LocationOSMType
from app.enums import LocationOSMEnum
from app.models import Location, Price, Product, Proof, User
from app.schemas import (
LocationBase,
Expand Down Expand Up @@ -214,7 +214,7 @@ def get_location_by_id(db: Session, id: int):


def get_location_by_osm_id_and_type(
db: Session, osm_id: int, osm_type: LocationOSMType
db: Session, osm_id: int, osm_type: LocationOSMEnum
):
return (
db.query(Location)
Expand Down
8 changes: 7 additions & 1 deletion app/enums.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from enum import Enum

from babel.numbers import list_currencies

class LocationOSMType(Enum):
CURRENCIES = [(currency, currency) for currency in list_currencies()]

CurrencyEnum = Enum("CurrencyEnum", CURRENCIES)


class LocationOSMEnum(Enum):
NODE = "NODE"
WAY = "WAY"
RELATION = "RELATION"
9 changes: 4 additions & 5 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
from sqlalchemy.sql import func
from sqlalchemy_utils import force_auto_coercion
from sqlalchemy_utils.types.choice import ChoiceType
from sqlalchemy_utils.types.currency import CurrencyType

from app.db import Base
from app.enums import LocationOSMType
from app.enums import CurrencyEnum, LocationOSMEnum

force_auto_coercion()

Expand Down Expand Up @@ -55,7 +54,7 @@ class Location(Base):
id = Column(Integer, primary_key=True, index=True)

osm_id = Column(BigInteger)
osm_type = Column(ChoiceType(LocationOSMType))
osm_type = Column(ChoiceType(LocationOSMEnum))
osm_name = Column(String)
osm_display_name = Column(String)
osm_address_postcode = Column(String)
Expand Down Expand Up @@ -97,10 +96,10 @@ class Price(Base):
product: Mapped[Product] = relationship(back_populates="prices")

price = Column(Numeric(precision=10, scale=2))
currency = Column(CurrencyType)
currency = Column(ChoiceType(CurrencyEnum))

location_osm_id = Column(BigInteger, index=True)
location_osm_type = Column(ChoiceType(LocationOSMType))
location_osm_type = Column(ChoiceType(LocationOSMEnum))
location_id: Mapped[int] = mapped_column(ForeignKey("locations.id"), nullable=True)
location: Mapped[Location] = relationship(back_populates="prices")

Expand Down
25 changes: 5 additions & 20 deletions app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
BaseModel,
ConfigDict,
Field,
field_serializer,
field_validator,
model_validator,
)
from sqlalchemy_utils import Currency

from app.enums import LocationOSMType
from app.enums import CurrencyEnum, LocationOSMEnum
from app.models import Price


Expand Down Expand Up @@ -65,7 +63,7 @@ class LocationCreate(BaseModel):
model_config = ConfigDict(from_attributes=True, arbitrary_types_allowed=True)

osm_id: int = Field(gt=0)
osm_type: LocationOSMType
osm_type: LocationOSMEnum


class LocationBase(LocationCreate):
Expand Down Expand Up @@ -126,7 +124,7 @@ class PriceCreate(BaseModel):
"kilogram or per liter.",
examples=["1.99"],
)
currency: str | Currency = Field(
currency: CurrencyEnum = Field(
description="currency of the price, as a string. "
"The currency must be a valid currency code. "
"See https://en.wikipedia.org/wiki/ISO_4217 for a list of valid currency codes.",
Expand All @@ -137,7 +135,7 @@ class PriceCreate(BaseModel):
description="ID of the location in OpenStreetMap: the store where the product was bought.",
examples=[1234567890],
)
location_osm_type: LocationOSMType = Field(
location_osm_type: LocationOSMEnum = Field(
description="type of the OpenStreetMap location object. Stores can be represented as nodes, "
"ways or relations in OpenStreetMap. It is necessary to be able to fetch the correct "
"information about the store using the ID.",
Expand All @@ -152,25 +150,12 @@ class PriceCreate(BaseModel):
examples=[15],
)

@field_validator("currency")
def currency_is_valid(cls, v):
try:
return Currency(v).code
except ValueError:
raise ValueError("not a valid currency code")

@field_validator("labels_tags")
def labels_tags_is_valid(cls, v):
if v is not None:
if len(v) == 0:
raise ValueError("`labels_tags` cannot be empty")

@field_serializer("currency")
def serialize_currency(self, currency: Currency, _info):
if type(currency) is Currency:
return currency.code
return currency

@model_validator(mode="after")
def product_code_and_category_tag_are_exclusive(self):
"""Validator that checks that `product_code` and `category_tag` are
Expand Down Expand Up @@ -207,7 +192,7 @@ class ProofBase(ProofCreate):
class PriceFilter(Filter):
product_code: Optional[str] | None = None
location_osm_id: Optional[int] | None = None
location_osm_type: Optional[LocationOSMType] | None = None
location_osm_type: Optional[LocationOSMEnum] | None = None
price: Optional[int] | None = None
currency: Optional[str] | None = None
price__gt: Optional[int] | None = None
Expand Down

0 comments on commit d7dfb0c

Please sign in to comment.