from enum import Enum from typing import Annotated, Any, Dict, List, Optional from uuid import uuid4 from pydantic import BaseModel, Field, validator from pydantic.deprecated.class_validators import root_validator def to_camel_case(snake_str: str) -> str: components = snake_str.split("_") return components[0] + "".join(x.title() for x in components[1:]) class ColumnItemType(str, Enum): PRODUCT = "PRODUCT" COMPONENT = "COMPONENT" QuantityInt = Annotated[int, Field(ge=0, le=2147483647)] StrictSmallInt = Annotated[int, Field(ge=0, le=32767)] class CorrelationId(BaseModel): correlation_id: str = Field(default_factory=lambda: uuid4().hex) class ColumnsInput(BaseModel): column_number: StrictSmallInt = Field( alias="columnNumber", description="View index in Televend" ) external_product_id: Optional[str] = Field( default=None, alias="externalProductId", description="Product or Component external ID used for product/component identification in external partner's ERP system", min_length=1, max_length=32, ) old_qty: Optional[QuantityInt] = Field( default_factory=lambda: None, alias="oldQty", ) new_qty: Optional[QuantityInt] = Field( default_factory=lambda: None, alias="newQty", ) old_price: Optional[float] = Field( default_factory=lambda: None, alias="oldPrc", ge=0, le=99999999.99, ) new_price: Optional[float] = Field( default_factory=lambda: None, alias="newPrc", ge=0, le=99999999.99, ) select_map: Optional[List[StrictSmallInt]] = Field( default_factory=lambda: None, alias="selectMap" ) item_type: Optional[ColumnItemType] = Field( default_factory=lambda: ColumnItemType.PRODUCT, alias="itemType", description="MUST be set if item is COMPONENT", ) class Config: populate_by_name = True alias_generator = to_camel_case str_strip_whitespace = True # @root_validator # def check_required_fields(cls, values): # if values.get("external_product_id"): # if values.get("old_qty") is None: # raise ValueError( # f"provide oldQty for product {values['external_product_id']}" # ) # if values.get("new_qty") is None: # raise ValueError( # f"provide newQty for product {values['external_product_id']}" # ) # if values.get("old_price") is None: # raise ValueError( # f"provide oldPrc for product {values['external_product_id']}" # ) # if values.get("new_price") is None: # raise ValueError( # f"provide newPrc for product {values['external_product_id']}" # ) # return values @validator("item_type") def set_item_type(cls, value): return value or ColumnItemType.PRODUCT class PlanogramInput(CorrelationId, BaseModel): machine_external_id: str = Field( alias="machineExternalId", description="Machine external ID", min_length=1, max_length=32, ) columns: List[ColumnsInput] = Field( title="Columns", description="A list of columns this planogram specifies" ) class Config: title = "Planogram" alias_generator = to_camel_case populate_by_name = True str_strip_whitespace = True class PlanogramsBulkInputPayload(BaseModel): planograms: List[PlanogramInput] = Field( title="Planograms", description="A list of Planograms this bulk carries", ) class Config: populate_by_name = True alias_generator = to_camel_case # @root_validator # def check_machine_unique(cls, values): # planograms = values.get("planograms", []) # external_ids = set() # for planogram in planograms: # if planogram.machine_external_id in external_ids: # raise ValueError( # f"Machine externalId must be unique! Duplicate {planogram.machine_external_id}" # ) # external_ids.add(planogram.machine_external_id) # return values