Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Use Pydantic 2.0 #236

Merged
merged 9 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ include = ["src/ikea_api/py.typed"]
python = "^3.8"
typing-extensions = {version = "^4.0.0", python = "<3.10"}
requests = {version = "*", optional = true}
pydantic = {version = "^1.8.0", optional = true}
pydantic = {version = ">=2.0,<3.0", optional = true}
httpx = {version = "^0.23", optional = true}

[tool.poetry.dev-dependencies]
Expand Down
14 changes: 7 additions & 7 deletions src/ikea_api/wrappers/parsers/ingka_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ class ReferenceMeasurements(BaseModel):


class Measurements(BaseModel):
referenceMeasurements: Optional[List[ReferenceMeasurements]]
referenceMeasurements: Optional[List[ReferenceMeasurements]] = None


class LocalisedCommunication(BaseModel):
languageCode: str
packageMeasurements: Optional[List[PackageMeasurement]]
media: Optional[List[Media]]
packageMeasurements: Optional[List[PackageMeasurement]] = None
media: Optional[List[Media]] = None
productName: str
productType: ProductType
validDesign: Optional[ValidDesign]
measurements: Optional[Measurements]
validDesign: Optional[ValidDesign] = None
measurements: Optional[Measurements] = None


class ChildItem(BaseModel):
Expand All @@ -69,7 +69,7 @@ class ChildItem(BaseModel):
class ResponseIngkaItem(BaseModel):
itemKey: ItemKey
localisedCommunications: List[LocalisedCommunication]
childItems: Optional[List[ChildItem]]
childItems: Optional[List[ChildItem]] = None


class ResponseIngkaItems(BaseModel):
Expand Down Expand Up @@ -178,6 +178,6 @@ def parse_item(constants: Constants, item: ResponseIngkaItem) -> types.IngkaItem
def parse_ingka_items(
constants: Constants, response: dict[str, Any]
) -> Iterable[types.IngkaItem]:
parsed_resp = ResponseIngkaItems(**response)
parsed_resp = ResponseIngkaItems.model_validate(response)
for item in parsed_resp.data:
yield parse_item(constants, item)
28 changes: 13 additions & 15 deletions src/ikea_api/wrappers/parsers/item_base.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
from typing import Any, Literal

from ikea_api.utils import parse_item_codes
from pydantic import BeforeValidator
from typing_extensions import Annotated

from ikea_api.utils import parse_item_codes

class ItemCode(str):
@classmethod
def __get_validators__(cls):
yield cls.validate

@classmethod
def validate(cls, v: Any):
if isinstance(v, int):
v = str(v)
if isinstance(v, str):
parsed_item_codes = parse_item_codes(v)
if len(parsed_item_codes) != 1:
raise ValueError("invalid item code format")
return parsed_item_codes[0]
raise TypeError("string required")
def validate_item_code(value: Any) -> str:
if isinstance(value, int):
value = str(value)
if isinstance(value, str):
parsed_item_codes = parse_item_codes(value)
if len(parsed_item_codes) != 1:
raise ValueError("invalid item code format")
return parsed_item_codes[0]
raise TypeError("string required")


ItemCode = Annotated[str, BeforeValidator(validate_item_code)]
ItemType = Literal["ART", "SPR"]


Expand Down
51 changes: 23 additions & 28 deletions src/ikea_api/wrappers/parsers/order_capture.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

from datetime import datetime
from datetime import date, datetime
from typing import Any, List, Optional

from pydantic import BaseModel, validator # pyright: ignore[reportUnknownVariableType]
from pydantic import BaseModel, BeforeValidator
from typing_extensions import Annotated

from ikea_api.constants import Constants
from ikea_api.utils import translate_from_dict
Expand Down Expand Up @@ -53,17 +54,11 @@ class EarliestPossibleSlot(BaseModel):


class TimeWindows(BaseModel):
earliestPossibleSlot: Optional[EarliestPossibleSlot]
earliestPossibleSlot: Optional[EarliestPossibleSlot] = None


class SelectableInfo(BaseModel):
selectable: bool

@validator(
"selectable", pre=True
) # pyright: ignore[reportUntypedFunctionDecorator]
def validate_selectable(cls, v: Any) -> Any:
return v == "YES"
selectable: Annotated[bool, BeforeValidator(lambda value: value == "YES")]


class Metadata(BaseModel):
Expand All @@ -75,13 +70,13 @@ class UnavailableItem(BaseModel):
availableQuantity: int


def get_date(deliveries: list[HomeDelivery] | None) -> datetime | None:
def get_date(deliveries: list[HomeDelivery] | None) -> date | None:
if not deliveries:
return

for delivery in deliveries:
if delivery.timeWindows and delivery.timeWindows.earliestPossibleSlot:
return delivery.timeWindows.earliestPossibleSlot.fromDateTime
return delivery.timeWindows.earliestPossibleSlot.fromDateTime.date()


def get_type(
Expand Down Expand Up @@ -121,7 +116,7 @@ def get_unavailable_items(

class HomeDelivery(BaseModel):
type: str
timeWindows: Optional[TimeWindows]
timeWindows: Optional[TimeWindows] = None


class HomePossibleDeliveries(BaseModel):
Expand All @@ -131,24 +126,24 @@ class HomePossibleDeliveries(BaseModel):
class HomeDeliveryService(BaseModel):
metadata: Metadata
fulfillmentMethodType: str
solution: Optional[str]
solutionPrice: Optional[SolutionPrice]
possibleDeliveries: Optional[HomePossibleDeliveries]
unavailableItems: Optional[List[UnavailableItem]]
solution: Optional[str] = None
solutionPrice: Optional[SolutionPrice] = None
possibleDeliveries: Optional[HomePossibleDeliveries] = None
unavailableItems: Optional[List[UnavailableItem]] = None


class HomePossibleDeliveryServices(BaseModel):
deliveryServices: List[HomeDeliveryService]


class HomeDeliveryServicesResponse(BaseModel):
possibleDeliveryServices: Optional[HomePossibleDeliveryServices]
possibleDeliveryServices: Optional[HomePossibleDeliveryServices] = None


def parse_home_delivery_services(
constants: Constants, response: dict[str, Any]
) -> list[types.DeliveryService]:
parsed_response = HomeDeliveryServicesResponse(**response)
parsed_response = HomeDeliveryServicesResponse.model_validate(response)

res: list[types.DeliveryService] = []
if not parsed_response.possibleDeliveryServices:
Expand Down Expand Up @@ -184,8 +179,8 @@ def parse_home_delivery_services(

class PickUpPoint(BaseModel):
metadata: Metadata
timeWindows: Optional[TimeWindows]
identifier: Optional[str]
timeWindows: Optional[TimeWindows] = None
identifier: Optional[str] = None


class PossiblePickUpPoints(BaseModel):
Expand All @@ -203,18 +198,18 @@ class CollectPossibleDeliveries(BaseModel):

class CollectDeliveryService(BaseModel):
fulfillmentMethodType: str
solution: Optional[str]
solutionPrice: Optional[SolutionPrice]
possibleDeliveries: Optional[CollectPossibleDeliveries]
unavailableItems: Optional[List[UnavailableItem]]
solution: Optional[str] = None
solutionPrice: Optional[SolutionPrice] = None
possibleDeliveries: Optional[CollectPossibleDeliveries] = None
unavailableItems: Optional[List[UnavailableItem]] = None


class CollectPossibleDeliveryServices(BaseModel):
deliveryServices: List[CollectDeliveryService]


class CollectDeliveryServicesResponse(BaseModel):
possibleDeliveryServices: Optional[CollectPossibleDeliveryServices]
possibleDeliveryServices: Optional[CollectPossibleDeliveryServices] = None


def get_service_provider(constants: Constants, pickup_point: PickUpPoint) -> str | None:
Expand All @@ -230,7 +225,7 @@ def get_service_provider(constants: Constants, pickup_point: PickUpPoint) -> str
def parse_collect_delivery_services(
constants: Constants, response: dict[str, Any]
) -> list[types.DeliveryService]:
parsed_response = CollectDeliveryServicesResponse(**response)
parsed_response = CollectDeliveryServicesResponse.model_validate(response)

res: list[types.DeliveryService] = []
if not parsed_response.possibleDeliveryServices:
Expand All @@ -247,7 +242,7 @@ def parse_collect_delivery_services(
for delivery in service.possibleDeliveries.deliveries:
for point in delivery.possiblePickUpPoints.pickUpPoints:
date = (
point.timeWindows.earliestPossibleSlot.fromDateTime
point.timeWindows.earliestPossibleSlot.fromDateTime.date()
if point.timeWindows and point.timeWindows.earliestPossibleSlot
else None
)
Expand Down
6 changes: 3 additions & 3 deletions src/ikea_api/wrappers/parsers/pip_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CatalogRef(BaseModel):


class CatalogRefs(BaseModel):
products: Optional[CatalogRef]
products: Optional[CatalogRef] = None


class ResponsePipItem(BaseModel):
Expand All @@ -37,12 +37,12 @@ def get_category_name_and_url(catalog_refs: CatalogRefs):
def parse_pip_item(response: dict[str, Any]) -> types.PipItem | None:
if not response:
return
parsed_item = ResponsePipItem(**response)
parsed_item = ResponsePipItem.model_validate(response)
category_name, category_url = get_category_name_and_url(parsed_item.catalogRefs)
return types.PipItem(
item_code=parsed_item.id,
price=parsed_item.priceNumeral,
url=parsed_item.pipUrl,
url=str(parsed_item.pipUrl),
category_name=category_name,
category_url=category_url,
)
8 changes: 4 additions & 4 deletions src/ikea_api/wrappers/parsers/purchases.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class HistoryDateAndTime(BaseModel):


class HistoryTotalCost(BaseModel):
value: Optional[int]
value: Optional[int] = None


class HistoryItem(BaseModel):
Expand All @@ -85,7 +85,7 @@ class ResponseHistory(BaseModel):


def parse_status_banner_order(response: dict[str, Any]) -> types.StatusBannerOrder:
order = ResponseStatusBanner(**response)
order = ResponseStatusBanner.model_validate(response)
return types.StatusBannerOrder(
purchase_date=order.data.order.dateAndTime.date,
delivery_date=order.data.order.deliveryMethods[
Expand All @@ -95,7 +95,7 @@ def parse_status_banner_order(response: dict[str, Any]) -> types.StatusBannerOrd


def parse_costs_order(response: dict[str, Any]) -> types.CostsOrder:
order = ResponseCosts(**response)
order = ResponseCosts.model_validate(response)
costs = order.data.order.costs
return types.CostsOrder(
delivery_cost=costs.delivery.value, total_cost=costs.total.value
Expand All @@ -109,7 +109,7 @@ def get_history_datetime(item: HistoryItem) -> str:
def parse_history(
constants: Constants, response: dict[str, Any]
) -> list[types.PurchaseHistoryItem]:
history = ResponseHistory(**response)
history = ResponseHistory.model_validate(response)
return [
types.PurchaseHistoryItem(
id=i.id,
Expand Down
18 changes: 9 additions & 9 deletions src/ikea_api/wrappers/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


class ChildItem(BaseModel):
name: Optional[str]
name: Optional[str] = None
item_code: str
weight: float
qty: int
Expand All @@ -17,20 +17,20 @@ class ParsedItem(BaseModel):
is_combination: bool
item_code: str
name: str
image_url: Optional[str]
image_url: Optional[str] = None
weight: float
child_items: List[ChildItem]
price: int
url: str
category_name: Optional[str]
category_url: Optional[HttpUrl]
category_name: Optional[str] = None
category_url: Optional[HttpUrl] = None


class IngkaItem(BaseModel):
is_combination: bool
item_code: str
name: str
image_url: Optional[str]
image_url: Optional[str] = None
weight: float
child_items: List[ChildItem]

Expand All @@ -39,8 +39,8 @@ class PipItem(BaseModel):
item_code: str
price: int
url: str
category_name: Optional[str]
category_url: Optional[HttpUrl]
category_name: Optional[str] = None
category_url: Optional[HttpUrl] = None


class UnavailableItem(BaseModel):
Expand All @@ -50,10 +50,10 @@ class UnavailableItem(BaseModel):

class DeliveryService(BaseModel):
is_available: bool
date: Optional[datetime.date]
date: Optional[datetime.date] = None
type: str
price: int
service_provider: Optional[str]
service_provider: Optional[str] = None
unavailable_items: List[UnavailableItem]


Expand Down
8 changes: 4 additions & 4 deletions src/ikea_api/wrappers/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def get_purchase_info(
)
status_banner, costs = run_with_requests(endpoint)
return types.PurchaseInfo(
**parse_status_banner_order(status_banner).dict(),
**parse_costs_order(costs).dict(),
**parse_status_banner_order(status_banner).model_dump(),
**parse_costs_order(costs).model_dump(),
)


Expand All @@ -50,7 +50,7 @@ class _ExtensionsData(BaseModel):

class _Extensions(BaseModel):
code: str
data: Optional[_ExtensionsData]
data: Optional[_ExtensionsData] = None


class _CartErrorRef(BaseModel):
Expand All @@ -68,7 +68,7 @@ def add_items_to_cart(cart: Cart, items: dict[str, int]) -> types.CannotAddItems
break
except GraphQLError as exc:
for error_dict in exc.errors:
error = _CartErrorRef(**error_dict)
error = _CartErrorRef.model_validate(error_dict)
if error.extensions.code != "INVALID_ITEM_NUMBER":
continue
if not error.extensions.data:
Expand Down
Loading