Coverage for amazonorders/entity/order.py: 75.30%
166 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-14 16:09 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-14 16:09 +0000
1import logging
2from datetime import datetime
3from urllib.parse import urlparse
4from urllib.parse import parse_qs
6from bs4 import BeautifulSoup
8from amazonorders.entity.recipient import Recipient
9from amazonorders.entity.shipment import Shipment
10from amazonorders.entity.item import Item
11from amazonorders.session import BASE_URL
13__author__ = "Alex Laird"
14__copyright__ = "Copyright 2024, Alex Laird"
15__version__ = "0.0.4"
17logger = logging.getLogger(__name__)
20class Order:
21 def __init__(self,
22 parsed,
23 full_details=False,
24 clone=None) -> None:
25 self.parsed = parsed
26 self.full_details = full_details
28 if clone:
29 self.shipments = clone.shipments
30 self.items = clone.items
31 self.order_details_link = clone.order_details_link
32 self.order_number = clone.order_number
33 self.grand_total = clone.grand_total
34 self.order_placed_date = clone.order_placed_date
35 self.recipient = clone.recipient
36 else:
37 self.shipments = self._parse_shipments()
38 self.items = self._parse_items()
39 self.order_details_link = self._parse_order_details_link()
40 self.order_number = self._parse_order_number()
41 self.grand_total = self._parse_grand_total()
42 self.order_placed_date = self._parse_order_placed_date()
43 self.recipient = self._parse_recipient()
45 if self.full_details:
46 self.items = self._parse_items()
47 self.payment_method = self._parse_payment_method()
48 self.payment_method_last_4 = self._parse_payment_method_last_4()
49 self.subtotal = self._parse_subtotal()
50 self.shipping_total = self._parse_shipping_total()
51 self.subscription_discount = self._parse_subscription_discount()
52 self.total_before_tax = self._parse_total_before_tax()
53 self.estimated_tax = self._parse_estimated_tax()
54 self.refund_total = self._parse_refund_total()
55 self.order_shipped_date = self._parse_order_shipping_date()
56 self.refund_completed_date = self._parse_refund_completed_date()
58 def __repr__(self) -> str:
59 return "<Order #{}: \"{}\">".format(self.order_number, self.items)
61 def __str__(self) -> str: # pragma: no cover
62 return "Order #{}: \"{}\"".format(self.order_number, self.items)
64 def _parse_shipments(self):
65 return [Shipment(x, self) for x in self.parsed.find_all("div", {"class": "shipment"})]
67 def _parse_items(self):
68 return [Item(x) for x in self.parsed.find_all("div", {"class": "yohtmlc-item"})]
70 def _parse_order_details_link(self):
71 try:
72 tag = self.parsed.find("a", {"class": "yohtmlc-order-details-link"})
73 if tag:
74 return "{}{}".format(BASE_URL, tag.attrs["href"])
75 except AttributeError:
76 logger.warning("When building Order, `order_details_link` could not be parsed.", exc_info=True)
78 def _parse_order_number(self):
79 try:
80 if self.order_details_link:
81 parsed_url = urlparse(self.order_details_link)
82 return parse_qs(parsed_url.query)["orderID"][0]
83 else:
84 tag = self.parsed.find("bdi", dir="ltr")
85 return tag.text.strip()
86 except (AttributeError, IndexError):
87 # TODO: refactor entities to all extend a base entity, which has this base parse function with this except
88 logger.warning("When building Order, `order_number` could not be parsed.", exc_info=True)
90 def _parse_grand_total(self):
91 try:
92 tag = self.parsed.find("div", {"class": "yohtmlc-order-total"})
93 if tag:
94 tag = tag.find("span", {"class": "value"})
95 else:
96 for tag in self.parsed.find("div", id="od-subtotals").find_all("div", {"class": "a-row"}):
97 if "grand total" in tag.text.lower():
98 tag = tag.find("div", {"class": "a-span-last"})
99 return tag.text.strip().replace("$", "")
100 except AttributeError:
101 logger.warning("When building Order, `grand_total` could not be parsed.", exc_info=True)
103 def _parse_order_placed_date(self):
104 try:
105 tag = self.parsed.find("span", {"class": "order-date-invoice-item"})
106 if tag:
107 date_str = tag.text.split("Ordered on")[1].strip()
108 else:
109 tag = self.parsed.find("div", {"class": "a-span3"}).find_all("span")
110 date_str = tag[1].text.strip()
111 return datetime.strptime(date_str, "%B %d, %Y").date()
112 except (AttributeError, IndexError):
113 logger.warning("When building Order, `order_placed_date` could not be parsed.", exc_info=True)
115 def _parse_recipient(self):
116 try:
117 tag = self.parsed.find("div", {"class": "displayAddressDiv"})
118 if not tag:
119 script_id = self.parsed.find("div",
120 id=lambda value: value and value.startswith("shipToInsertionNode")).attrs[
121 "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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)