Coverage for app/delivery_fee/models.py: 93%
89 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
1from typing import Self
2from pydantic import BaseModel, field_validator, Field
3from datetime import datetime
4from math import ceil
7class OrderInfo(BaseModel):
8 # in cents (e.g. €1.00 = 100 = 1e2)
9 cart_value: int = Field(description=("Value of the shopping cart in cents. "
10 "Example: 790 (790 cents = 7.90€)"))
11 # in meters (e.g. 1km = 1000 = 1e3)
12 delivery_distance: int = Field(description=("The distance between the store and customer’s "
13 "location in meters. Example: 2235 (2235 meters "
14 "= 2.235 km)"))
16 number_of_items: int = Field(description=("The number of items in the customer's shopping cart. "
17 "Example: 4 (customer has 4 items in the cart)"))
18 # timestamp in UTC ISO format (e.g. 2024-01-15T13:00:00Z)
19 time: datetime = Field(description=("Order time in UTC in ISO format. "
20 "Example: 2024-01-15T13:00:00Z"))
22 @field_validator('cart_value')
23 def cart_value__must_be_non_negative(cls, value):
24 if value < 0:
25 raise ValueError('cart_value must be non-negative')
26 return value
28 @field_validator('delivery_distance')
29 def delivery_distance__must_be_non_negative(cls, value):
30 if value < 0:
31 raise ValueError('delivery_distance must be non-negative')
32 return value
34 @field_validator('number_of_items', mode='after')
35 def number_of_items__must_be_non_negative(cls, value):
36 if value < 0:
37 raise ValueError('number_of_items must be non-negative')
38 return value
40 @field_validator('time', mode='before')
41 def delivery_time__parser(cls, value):
42 try:
43 if isinstance(value, str):
44 return datetime.fromisoformat(value)
45 raise ValueError()
46 except ValueError:
47 raise ValueError(
48 'time must be in UTC ISO format (e.g. 2024-01-15T13:00:00Z)')
51class DeliveryFee(BaseModel):
52 # in cents (e.g. €1.00 = 100 = 1e2)
53 delivery_fee: int = Field(description=("Calculated delivery fee in cents. "
54 "Example: 710 (710 cents = 7.10€)"))
56 @field_validator('delivery_fee')
57 def delivery_fee__must_be_non_negative(cls, value):
58 if value < 0:
59 raise ValueError('delivery_fee must be non-negative')
60 return value
62 def __sub__(self, other: Self | int | float) -> Self:
63 if isinstance(other, (int, float)):
64 total_fee = max(self.delivery_fee - other, 0)
65 elif isinstance(other, DeliveryFee):
66 total_fee = max(self.delivery_fee - other.delivery_fee, 0)
67 else:
68 raise TypeError(
69 f"Unsupported operand type(s) for -: '{type(self)}' and '{type(other)}'")
70 return DeliveryFee(delivery_fee=total_fee)
72 def __add__(self, other: Self | int | float) -> Self:
73 if isinstance(other, (int, float)):
74 total_fee = max(self.delivery_fee + other, 0)
75 elif isinstance(other, DeliveryFee):
76 total_fee = max(self.delivery_fee + other.delivery_fee, 0)
77 else:
78 raise TypeError(
79 f"Unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'")
80 return DeliveryFee(delivery_fee=total_fee)
82 def __mul__(self, other: int | float) -> Self:
83 if not isinstance(other, (int, float)):
84 raise TypeError(
85 f"Unsupported operand type(s) for *: '{type(self)}' and '{type(other)}'")
86 total_fee = max(ceil(self.delivery_fee * other), 0)
87 return DeliveryFee(delivery_fee=total_fee)
89 def __truediv__(self, other: int | float) -> Self:
90 if not isinstance(other, (int, float)):
91 raise TypeError(
92 f"Unsupported operand type(s) for /: '{type(self)}' and '{type(other)}'")
93 total_fee = max(ceil(self.delivery_fee / other), 0)
94 return DeliveryFee(delivery_fee=total_fee)
96 def __gt__(self, other: Self | int | float) -> bool:
97 if isinstance(other, (int, float)):
98 return self.delivery_fee > other
99 elif isinstance(other, DeliveryFee):
100 return self.delivery_fee > other.delivery_fee
101 else:
102 raise TypeError(
103 f"Unsupported operand type(s) for >: '{type(self)}' and '{type(other)}'")
105 def __ge__(self, other: Self | int | float) -> bool:
106 if self > other or self == other:
107 return True
108 return False
110 def __lt__(self, other: Self | int | float) -> bool:
111 if isinstance(other, (int, float)):
112 return self.delivery_fee < other
113 elif isinstance(other, DeliveryFee):
114 return self.delivery_fee < other.delivery_fee
115 else:
116 raise TypeError(
117 f"Unsupported operand type(s) for <: '{type(self)}' and '{type(other)}'")
119 def __le__(self, other: Self | int | float) -> bool:
120 if self < other or self == other:
121 return True
122 return False
124 def __eq__(self, other: Self | int | float) -> bool:
125 if isinstance(other, (int, float)):
126 return self.delivery_fee == other
127 elif isinstance(other, DeliveryFee):
128 return self.delivery_fee == other.delivery_fee
129 else:
130 raise TypeError(
131 f"Unsupported operand type(s) for ==: '{type(self)}' and '{type(other)}'")