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

122 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-16 21:17 +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.parsable import Parsable 

9from amazonorders.entity.recipient import Recipient 

10from amazonorders.entity.shipment import Shipment 

11from amazonorders.entity.item import Item 

12from amazonorders.session import BASE_URL 

13 

14__author__ = "Alex Laird" 

15__copyright__ = "Copyright 2024, Alex Laird" 

16__version__ = "0.0.5" 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21class Order(Parsable): 

22 def __init__(self, 

23 parsed, 

24 full_details=False, 

25 clone=None) -> None: 

26 super().__init__(parsed) 

27 

28 self.full_details = full_details 

29 

30 if clone: 

31 self.shipments = clone.shipments 

32 self.items = clone.items 

33 self.order_details_link = clone.order_details_link 

34 self.order_number = clone.order_number 

35 self.grand_total = clone.grand_total 

36 self.order_placed_date = clone.order_placed_date 

37 self.recipient = clone.recipient 

38 else: 

39 self.shipments = self._parse_shipments() 

40 self.items = self._parse_items() 

41 self.order_details_link = self.safe_parse(self._parse_order_details_link) 

42 self.order_number = self.safe_parse(self._parse_order_number) 

43 self.grand_total = self.safe_parse(self._parse_grand_total) 

44 self.order_placed_date = self.safe_parse(self._parse_order_placed_date) 

45 self.recipient = self.safe_parse(self._parse_recipient) 

46 

47 if self.full_details: 

48 self.items = self._parse_items() 

49 self.payment_method = self._parse_payment_method() 

50 self.payment_method_last_4 = self._parse_payment_method_last_4() 

51 self.subtotal = self._parse_subtotal() 

52 self.shipping_total = self._parse_shipping_total() 

53 self.subscription_discount = self._parse_subscription_discount() 

54 self.total_before_tax = self._parse_total_before_tax() 

55 self.estimated_tax = self._parse_estimated_tax() 

56 self.refund_total = self._parse_refund_total() 

57 self.order_shipped_date = self._parse_order_shipping_date() 

58 self.refund_completed_date = self._parse_refund_completed_date() 

59 

60 def __repr__(self) -> str: 

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

62 

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

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

65 

66 def _parse_shipments(self): 

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

68 

69 def _parse_items(self): 

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

71 

72 def _parse_order_details_link(self): 

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

74 if tag: 

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

76 

77 def _parse_order_number(self): 

78 if self.order_details_link: 

79 parsed_url = urlparse(self.order_details_link) 

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

81 else: 

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

83 return tag.text.strip() 

84 

85 def _parse_grand_total(self): 

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

87 if tag: 

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

89 else: 

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

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

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

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

94 

95 def _parse_order_placed_date(self): 

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

97 if tag: 

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

99 else: 

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

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

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

103 

104 def _parse_recipient(self): 

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

106 if not tag: 

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

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

109 "id"] 

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

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

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

113 return Recipient(tag) 

114 

115 def _parse_payment_method(self): 

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

117 if tag: 

118 return tag.attrs["alt"] 

119 

120 def _parse_payment_method_last_4(self): 

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

122 if tag: 

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

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

125 

126 def _parse_subtotal(self): 

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

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

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

130 

131 def _parse_shipping_total(self): 

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

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

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

135 

136 def _parse_subscription_discount(self): 

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

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

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

140 

141 def _parse_total_before_tax(self): 

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

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

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

145 

146 def _parse_estimated_tax(self): 

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

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

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

150 

151 def _parse_refund_total(self): 

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

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

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

155 

156 def _parse_order_shipping_date(self): 

157 # TODO: find a better way to do this 

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

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

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

161 

162 def _parse_refund_completed_date(self): 

163 # TODO: find a better way to do this 

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

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

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