Coverage for amazonorders/entity/order.py: 19.76%

167 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-12 15:20 +0000

1import logging 

2from datetime import datetime 

3from urllib.parse import urlparse 

4from urllib.parse import parse_qs 

5 

6from bs4 import BeautifulSoup 

7 

8from amazonorders.entity.recipient import Recipient 

9from amazonorders.entity.shipment import Shipment 

10from amazonorders.entity.item import Item 

11from amazonorders.exception import AmazonOrdersError 

12from amazonorders.session import BASE_URL 

13 

14__author__ = "Alex Laird" 

15__copyright__ = "Copyright 2024, Alex Laird" 

16__version__ = "0.0.3" 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21class Order: 

22 def __init__(self, 

23 parsed, 

24 full_details=False, 

25 clone=None) -> None: 

26 self.parsed = parsed 

27 self.full_details = full_details 

28 

29 if clone: 

30 self.shipments = clone.shipments 

31 self.items = clone.items 

32 self.order_details_link = clone.order_details_link 

33 self.order_number = clone.order_number 

34 self.grand_total = clone.grand_total 

35 self.order_placed_date = clone.order_placed_date 

36 self.recipient = clone.recipient 

37 else: 

38 self.shipments = self._parse_shipments() 

39 self.items = self._parse_items() 

40 self.order_details_link = self._parse_order_details_link() 

41 self.order_number = self._parse_order_number() 

42 self.grand_total = self._parse_grand_total() 

43 self.order_placed_date = self._parse_order_placed_date() 

44 self.recipient = self._parse_recipient() 

45 

46 if self.full_details: 

47 self.items = self._parse_items() 

48 self.payment_method = self._parse_payment_method() 

49 self.payment_method_last_4 = self._parse_payment_method_last_4() 

50 self.subtotal = self._parse_subtotal() 

51 self.shipping_total = self._parse_shipping_total() 

52 self.subscription_discount = self._parse_subscription_discount() 

53 self.total_before_tax = self._parse_total_before_tax() 

54 self.estimated_tax = self._parse_estimated_tax() 

55 self.refund_total = self._parse_refund_total() 

56 self.order_shipped_date = self._parse_order_shipping_date() 

57 self.refund_completed_date = self._parse_refund_completed_date() 

58 

59 def __repr__(self) -> str: 

60 return "<Order #{}: \"{}\">".format(self.order_number, self.items) 

61 

62 def __str__(self) -> str: # pragma: no cover 

63 return "Order #{}: \"{}\"".format(self.order_number, self.items) 

64 

65 def _parse_shipments(self): 

66 return [Shipment(x, self) for x in self.parsed.find_all("div", {"class": "shipment"})] 

67 

68 def _parse_items(self): 

69 return [Item(x) for x in self.parsed.find_all("div", {"class": "yohtmlc-item"})] 

70 

71 def _parse_order_details_link(self): 

72 try: 

73 tag = self.parsed.find("a", {"class": "yohtmlc-order-details-link"}) 

74 if tag: 

75 return "{}{}".format(BASE_URL, tag.attrs["href"]) 

76 except AttributeError: 

77 logger.warning("When building Order, `order_details_link` could not be parsed.", exc_info=True) 

78 

79 def _parse_order_number(self): 

80 try: 

81 if self.order_details_link: 

82 parsed_url = urlparse(self.order_details_link) 

83 return parse_qs(parsed_url.query)["orderID"][0] 

84 else: 

85 tag = self.parsed.find("bdi", dir="ltr") 

86 return tag.text.strip() 

87 except (AttributeError, IndexError): 

88 # TODO: refactor entities to all extend a base entity, which has this base parse function with this except 

89 logger.warning("When building Order, `order_number` could not be parsed.", exc_info=True) 

90 

91 def _parse_grand_total(self): 

92 try: 

93 tag = self.parsed.find("div", {"class": "yohtmlc-order-total"}) 

94 if tag: 

95 tag = tag.find("span", {"class": "value"}) 

96 else: 

97 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

98 if "grand total" in tag.text.lower(): 

99 tag = tag.find("div", {"class": "a-span-last"}) 

100 return tag.text.strip().replace("$", "") 

101 except AttributeError: 

102 logger.warning("When building Order, `grand_total` could not be parsed.", exc_info=True) 

103 

104 def _parse_order_placed_date(self): 

105 try: 

106 tag = self.parsed.find("span", {"class": "order-date-invoice-item"}) 

107 if tag: 

108 date_str = tag.text.split("Ordered on")[1].strip() 

109 else: 

110 tag = self.parsed.find("div", {"class": "a-span3"}).find_all("span") 

111 date_str = tag[1].text.strip() 

112 return datetime.strptime(date_str, "%B %d, %Y").date() 

113 except (AttributeError, IndexError): 

114 logger.warning("When building Order, `order_placed_date` could not be parsed.", exc_info=True) 

115 

116 def _parse_recipient(self): 

117 try: 

118 tag = self.parsed.find("div", {"class": "displayAddressDiv"}) 

119 if not tag: 

120 script_id = self.parsed.find("div", 

121 id=lambda value: value and value.startswith("shipToInsertionNode")).attrs["id"] 

122 tag = self.parsed.find("script", 

123 id="shipToData-shippingAddress-{}".format(script_id.split("-")[2])) 

124 tag = BeautifulSoup(str(tag.contents[0]).strip(), "html.parser") 

125 return Recipient(tag) 

126 except (AttributeError, IndexError): 

127 logger.warning("When building Order, `recipient` could not be parsed.", exc_info=True) 

128 

129 def _parse_payment_method(self): 

130 try: 

131 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"}) 

132 if tag: 

133 return tag.attrs["alt"] 

134 except (AttributeError, IndexError): 

135 logger.warning("When building Order, `payment_method` could not be parsed.", exc_info=True) 

136 

137 def _parse_payment_method_last_4(self): 

138 try: 

139 tag = self.parsed.find("img", {"class": "pmts-payment-credit-card-instrument-logo"}) 

140 if tag: 

141 ending_sibling = tag.find_next_siblings()[-1] 

142 return ending_sibling.text.split("ending in")[1].strip() 

143 except (AttributeError, IndexError): 

144 logger.warning("When building Order, `payment_method_last_4` could not be parsed.", exc_info=True) 

145 

146 def _parse_subtotal(self): 

147 try: 

148 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

149 if "subtotal" in tag.text.lower(): 

150 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

151 except (AttributeError, IndexError): 

152 logger.warning("When building Order, `subtotal` could not be parsed.", exc_info=True) 

153 

154 def _parse_shipping_total(self): 

155 try: 

156 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

157 if "shipping" in tag.text.lower(): 

158 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

159 except (AttributeError, IndexError): 

160 logger.warning("When building Order, `shipping_total` could not be parsed.", exc_info=True) 

161 

162 def _parse_subscription_discount(self): 

163 try: 

164 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

165 if "subscribe" in tag.text.lower(): 

166 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

167 except (AttributeError, IndexError): 

168 logger.warning("When building Order, `subscription_discount` could not be parsed.", exc_info=True) 

169 

170 def _parse_total_before_tax(self): 

171 try: 

172 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

173 if "before tax" in tag.text.lower(): 

174 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

175 except (AttributeError, IndexError): 

176 logger.warning("When building Order, `total_before_tax` could not be parsed.", exc_info=True) 

177 

178 def _parse_estimated_tax(self): 

179 try: 

180 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

181 if "estimated tax" in tag.text.lower(): 

182 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

183 except (AttributeError, IndexError): 

184 logger.warning("When building Order, `estimated_tax` could not be parsed.", exc_info=True) 

185 

186 def _parse_refund_total(self): 

187 try: 

188 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}): 

189 if "refund total" in tag.text.lower() and "tax refund" not in tag.text.lower(): 

190 return tag.find("div", {"class": "a-span-last"}).text.strip().replace("$", "") 

191 except (AttributeError, IndexError): 

192 logger.warning("When building Order, `refund_total` could not be parsed.", exc_info=True) 

193 

194 def _parse_order_shipping_date(self): 

195 try: 

196 # TODO: find a better way to do this 

197 if "Items shipped:" in self.parsed.text: 

198 date_str = self.parsed.text.split("Items shipped:")[1].strip().split("-")[0].strip() 

199 return datetime.strptime(date_str, "%B %d, %Y").date() 

200 except (AttributeError, IndexError): 

201 logger.warning("When building Order, `order_shipping_date` could not be parsed.", exc_info=True) 

202 

203 def _parse_refund_completed_date(self): 

204 try: 

205 # TODO: find a better way to do this 

206 if "Refund: Completed" in self.parsed.text: 

207 date_str = self.parsed.text.split("Refund: Completed")[1].strip().split("-")[0].strip() 

208 return datetime.strptime(date_str, "%B %d, %Y").date() 

209 except (AttributeError, IndexError): 

210 logger.warning("When building Order, `refund_completed_date` could not be parsed.", exc_info=True)