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

1from typing import Self 

2from pydantic import BaseModel, field_validator, Field 

3from datetime import datetime 

4from math import ceil 

5 

6 

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)")) 

15 

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")) 

21 

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 

27 

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 

33 

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 

39 

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)') 

49 

50 

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€)")) 

55 

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 

61 

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) 

71 

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) 

81 

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) 

88 

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) 

95 

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)}'") 

104 

105 def __ge__(self, other: Self | int | float) -> bool: 

106 if self > other or self == other: 

107 return True 

108 return False 

109 

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)}'") 

118 

119 def __le__(self, other: Self | int | float) -> bool: 

120 if self < other or self == other: 

121 return True 

122 return False 

123 

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)}'")