amazon-orders 3.2.3__tar.gz → 3.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/CHANGELOG.md +20 -3
  2. {amazon_orders-3.2.3/amazon_orders.egg-info → amazon_orders-3.2.5}/PKG-INFO +2 -2
  3. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/README.md +1 -1
  4. {amazon_orders-3.2.3 → amazon_orders-3.2.5/amazon_orders.egg-info}/PKG-INFO +2 -2
  5. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/__init__.py +1 -1
  6. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/order.py +9 -2
  7. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/parsable.py +21 -3
  8. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/orders.py +2 -3
  9. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/selectors.py +16 -5
  10. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_orders.py +59 -0
  11. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/LICENSE +0 -0
  12. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/MANIFEST.in +0 -0
  13. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazon_orders.egg-info/SOURCES.txt +0 -0
  14. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazon_orders.egg-info/dependency_links.txt +0 -0
  15. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazon_orders.egg-info/entry_points.txt +0 -0
  16. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazon_orders.egg-info/requires.txt +0 -0
  17. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazon_orders.egg-info/top_level.txt +0 -0
  18. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/banner.txt +0 -0
  19. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/cli.py +0 -0
  20. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/conf.py +0 -0
  21. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/constants.py +0 -0
  22. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/__init__.py +0 -0
  23. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/item.py +0 -0
  24. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/recipient.py +0 -0
  25. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/seller.py +0 -0
  26. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/shipment.py +0 -0
  27. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/entity/transaction.py +0 -0
  28. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/exception.py +0 -0
  29. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/forms.py +0 -0
  30. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/session.py +0 -0
  31. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/transactions.py +0 -0
  32. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/amazonorders/util.py +0 -0
  33. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/pyproject.toml +0 -0
  34. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/setup.cfg +0 -0
  35. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_cli.py +0 -0
  36. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_conf.py +0 -0
  37. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_session.py +0 -0
  38. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_transactions.py +0 -0
  39. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/test_util.py +0 -0
  40. {amazon_orders-3.2.3 → amazon_orders-3.2.5}/tests/testcase.py +0 -0
@@ -4,9 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.2.2...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.2.5...HEAD)
8
8
 
9
- ## [3.2.3](https://github.com/alexdlaird/amazon-orders/compare/3.2.1...3.2.2) - 2025-02-06
9
+ ## [3.2.5](https://github.com/alexdlaird/amazon-orders/compare/3.2.4...3.2.5) - 2025-02-12
10
+
11
+ ### Fixed
12
+
13
+ - Parsing errors on gift cards totals and broken item links due to changes in Amazon.com DOM.
14
+
15
+ ## [3.2.4](https://github.com/alexdlaird/amazon-orders/compare/3.2.3...3.2.4) - 2025-02-11
16
+
17
+ ### Added
18
+
19
+ - `Order.promotion_applied` field, which is now parsed alongside other subtotals.
20
+
21
+ ### Fixed
22
+
23
+ - Broken parsing of Whole Foods Market orders, these are now skipped.
24
+ - Parsing issue on `Order.order_number` and `Order.order_placed` due to changes in Amazon.com DOM.
25
+
26
+ ## [3.2.3](https://github.com/alexdlaird/amazon-orders/compare/3.2.2...3.2.3) - 2025-02-06
10
27
 
11
28
  ### Added
12
29
 
@@ -263,7 +280,7 @@ Build and stability improvements.
263
280
  - [AuthForm](https://amazon-orders.readthedocs.io/api.html#amazonorders.forms.AuthForm) abstract class, and
264
281
  migrated all auth flow items to subclasses of this class.
265
282
  - [Parsable.simple_parse()](https://amazon-orders.readthedocs.io/api.html#amazonorders.entities.parsable.Parsable.simple_parse),
266
- which can handle most basic fields when parised with CSS selectors.
283
+ which can handle most basic fields when parsed with CSS selectors.
267
284
  - Stability improvements.
268
285
  - Test improvements.
269
286
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.3
3
+ Version: 3.2.5
4
4
  Summary: A CLI and library for interacting with Amazon order history.
5
5
  Maintainer-email: Alex Laird <contact@alexlaird.com>
6
6
  License: MIT License
@@ -88,7 +88,7 @@ be used to interact with Amazon.com's consumer-facing website.
88
88
 
89
89
  This works by parsing website data from Amazon.com. A periodic build validates functionality to ensure its
90
90
  stability, but as Amazon provides no official API to use, this package may break at any time. This
91
- package only officially supports the English version of the website.
91
+ package only officially supports the English, `.com` version of Amazon.
92
92
 
93
93
  ## Installation
94
94
 
@@ -12,7 +12,7 @@ be used to interact with Amazon.com's consumer-facing website.
12
12
 
13
13
  This works by parsing website data from Amazon.com. A periodic build validates functionality to ensure its
14
14
  stability, but as Amazon provides no official API to use, this package may break at any time. This
15
- package only officially supports the English version of the website.
15
+ package only officially supports the English, `.com` version of Amazon.
16
16
 
17
17
  ## Installation
18
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.3
3
+ Version: 3.2.5
4
4
  Summary: A CLI and library for interacting with Amazon order history.
5
5
  Maintainer-email: Alex Laird <contact@alexlaird.com>
6
6
  License: MIT License
@@ -88,7 +88,7 @@ be used to interact with Amazon.com's consumer-facing website.
88
88
 
89
89
  This works by parsing website data from Amazon.com. A periodic build validates functionality to ensure its
90
90
  stability, but as Amazon provides no official API to use, this package may break at any time. This
91
- package only officially supports the English version of the website.
91
+ package only officially supports the English, `.com` version of Amazon.
92
92
 
93
93
  ## Installation
94
94
 
@@ -1,3 +1,3 @@
1
1
  __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
- __version__ = "3.2.3"
3
+ __version__ = "3.2.5"
@@ -43,7 +43,9 @@ class Order(Parsable):
43
43
  #: The Order number.
44
44
  self.order_number: str = clone.order_number if clone else self.safe_simple_parse(
45
45
  selector=self.config.selectors.FIELD_ORDER_NUMBER_SELECTOR,
46
- required=True)
46
+ required=True,
47
+ prefix_split="#",
48
+ prefix_split_fuzzy=True)
47
49
  #: The Order details link.
48
50
  self.order_details_link: Optional[str] = clone.order_details_link if clone else self.safe_parse(
49
51
  self._parse_order_details_link)
@@ -51,7 +53,10 @@ class Order(Parsable):
51
53
  self.grand_total: float = clone.grand_total if clone else self.safe_parse(self._parse_grand_total)
52
54
  #: The Order placed date.
53
55
  self.order_placed_date: date = clone.order_placed_date if clone else self.safe_simple_parse(
54
- selector=self.config.selectors.FIELD_ORDER_PLACED_DATE_SELECTOR, parse_date=True)
56
+ selector=self.config.selectors.FIELD_ORDER_PLACED_DATE_SELECTOR,
57
+ suffix_split="Order #",
58
+ suffix_split_fuzzy=True,
59
+ parse_date=True)
55
60
  #: The Order Recipients.
56
61
  self.recipient: Recipient = clone.recipient if clone else self.safe_parse(self._parse_recipient)
57
62
 
@@ -69,6 +74,8 @@ class Order(Parsable):
69
74
  self.subtotal: Optional[float] = self._if_full_details(self._parse_currency("subtotal"))
70
75
  #: The Order shipping total. Only populated when ``full_details`` is ``True``.
71
76
  self.shipping_total: Optional[float] = self._if_full_details(self._parse_currency("shipping"))
77
+ #: The Order promotion applied. Only populated when ``full_details`` is ``True``.
78
+ self.promotion_applied: Optional[float] = self._if_full_details(self._parse_currency("promotion"))
72
79
  #: The Order Subscribe & Save discount. Only populated when ``full_details`` is ``True``.
73
80
  self.subscription_discount: Optional[float] = self._if_full_details(self._parse_currency("subscribe"))
74
81
  #: The Order total before tax. Only populated when ``full_details`` is ``True``.
@@ -67,7 +67,10 @@ class Parsable:
67
67
  required: bool = False,
68
68
  prefix_split: Optional[str] = None,
69
69
  wrap_tag: Optional[Type] = None,
70
- parse_date: bool = False) -> Any:
70
+ parse_date: bool = False,
71
+ prefix_split_fuzzy: bool = False,
72
+ suffix_split: Optional[str] = None,
73
+ suffix_split_fuzzy: bool = False) -> Any:
71
74
  """
72
75
  Will attempt to extract the text value of the given CSS selector(s) for a field, and
73
76
  is suitable for most basic functionality on a well-formed page.
@@ -86,6 +89,9 @@ class Parsable:
86
89
  :param wrap_tag: Wrap the selected tag in this class before returning.
87
90
  :param parse_date: ``True`` if the resulting value should be fuzzy parsed in to a date (returning ``None`` if
88
91
  parsing fails).
92
+ :param prefix_split_fuzzy: ``True`` if the value should still be used even if ``prefix_split`` is not found.
93
+ :param suffix_split: Only select the field with the given suffix, returning the left side of the split if so.
94
+ :param suffix_split_fuzzy: ``True`` if the value should still be used even if ``suffix_split`` is not found.
89
95
  :return: The cleaned up return value from the parsed ``selector``.
90
96
  """
91
97
  if isinstance(selector, str):
@@ -109,12 +115,24 @@ class Parsable:
109
115
 
110
116
  if prefix_split:
111
117
  if prefix_split not in tag.text:
112
- continue
118
+ if prefix_split_fuzzy:
119
+ value = tag.text.strip()
120
+ else:
121
+ continue
113
122
  else:
114
123
  value = tag.text.strip().split(prefix_split)[1]
115
124
  else:
116
125
  value = tag.text
117
126
 
127
+ if suffix_split:
128
+ if suffix_split not in value:
129
+ if suffix_split_fuzzy:
130
+ value = value.strip()
131
+ else:
132
+ continue
133
+ else:
134
+ value = value.strip().split(suffix_split)[0]
135
+
118
136
  if wrap_tag:
119
137
  value = wrap_tag(tag, self.config)
120
138
  else:
@@ -142,7 +160,7 @@ class Parsable:
142
160
  """
143
161
  A helper function that uses :func:`simple_parse` as the ``parse_function()`` passed to :func:`safe_parse`.
144
162
 
145
- :param selector: The selector to pass to :func:`simple_parse`.
163
+ :param selector: The CSS selector to pass to :func:`simple_parse`.
146
164
  :param kwargs: The ``kwargs`` will be passed to ``parse_function``.
147
165
  :return: The return value from :func:`simple_parse`.
148
166
  """
@@ -73,9 +73,8 @@ class AmazonOrders:
73
73
  response_parsed = self.amazon_session.last_response_parsed
74
74
 
75
75
  for order_tag in util.select(response_parsed, self.config.selectors.ORDER_HISTORY_ENTITY_SELECTOR):
76
- # If we find a brand logo (for instance, Amazon Fresh), we don't know how to parse this. If we know how
77
- # to do this in the future, we can implement it, but right now we have no reliable way, so skipping
78
- # these orders.
76
+ # First check if this Order is known to be of a type that we do not currently have a way to support
77
+ # parsing, meaning it should be skipped
79
78
  if util.select(order_tag, self.config.selectors.ORDER_HISTORY_BRAND_SELECTOR):
80
79
  continue
81
80
 
@@ -55,7 +55,14 @@ class Selectors:
55
55
  SHIPMENT_ENTITY_SELECTOR = ["[data-component='orderCard'] [data-component='shipments'] .a-box",
56
56
  "div.shipment",
57
57
  "div.delivery-box"]
58
- ORDER_HISTORY_BRAND_SELECTOR = ".brand-info-box .brand-logo img"
58
+ # Selectors defined here, if found in an Order, will cause the Order to be skipped, since it means we currently
59
+ # do not have a way to support fully parsing its details
60
+ ORDER_HISTORY_BRAND_SELECTOR = [
61
+ # Amazon Fresh is not supported
62
+ ".brand-info-box .brand-logo img",
63
+ # Whole Foods Market is not supported
64
+ "a.yohtmlc-order-details-link[href^='/wholefoodsmarket']"
65
+ ]
59
66
 
60
67
  #####################################
61
68
  # CSS selectors for Item fields
@@ -69,7 +76,8 @@ class Selectors:
69
76
  ".yohtmlc-item a", ".yohtmlc-product-title"]
70
77
  FIELD_ITEM_LINK_SELECTOR = ["[data-component='itemTitle'] a",
71
78
  ".yohtmlc-item a",
72
- "a:has(> .yohtmlc-product-title)"]
79
+ "a:has(> .yohtmlc-product-title)",
80
+ ".yohtmlc-product-title a"]
73
81
  FIELD_ITEM_TAG_ITERATOR_SELECTOR = [".yohtmlc-item div"]
74
82
  FIELD_ITEM_PRICE_SELECTOR = ["[data-component='unitPrice'] .a-text-price :not(.a-offscreen)",
75
83
  ".yohtmlc-item .a-color-price"]
@@ -81,12 +89,15 @@ class Selectors:
81
89
  #####################################
82
90
 
83
91
  FIELD_ORDER_DETAILS_LINK_SELECTOR = "a.yohtmlc-order-details-link"
84
- FIELD_ORDER_NUMBER_SELECTOR = [".order-date-invoice-item bdi[dir='ltr']",
92
+ FIELD_ORDER_NUMBER_SELECTOR = ["[data-component='briefOrderInfo'] div.a-column",
93
+ ".order-date-invoice-item bdi[dir='ltr']",
85
94
  "bdi[dir='ltr']",
86
95
  "span[dir='ltr']"]
87
96
  FIELD_ORDER_GRAND_TOTAL_SELECTOR = ["div.yohtmlc-order-total span.value",
88
- "div.order-header div.a-column.a-span2"]
89
- FIELD_ORDER_PLACED_DATE_SELECTOR = ["span.order-date-invoice-item",
97
+ "div.order-header div.a-column.a-span2",
98
+ "div.order-header div.a-col-left .a-span9"]
99
+ FIELD_ORDER_PLACED_DATE_SELECTOR = ["[data-component='briefOrderInfo'] div.a-column",
100
+ "span.order-date-invoice-item",
90
101
  "div.a-span3"]
91
102
  FIELD_ORDER_PAYMENT_METHOD_SELECTOR = "img.pmts-payment-credit-card-instrument-logo"
92
103
  FIELD_ORDER_PAYMENT_METHOD_LAST_4_SELECTOR = "span:has(img.pmts-payment-credit-card-instrument-logo):last-child"
@@ -3,6 +3,7 @@ __license__ = "MIT"
3
3
 
4
4
  import os
5
5
  import unittest
6
+ from datetime import date
6
7
 
7
8
  import responses
8
9
 
@@ -77,6 +78,40 @@ class TestOrders(UnitTestCase):
77
78
  self.assertEqual(1, resp1.call_count)
78
79
  self.assertEqual(1, resp2.call_count)
79
80
 
81
+ @responses.activate
82
+ def test_get_order_history_2025_gift_card(self):
83
+ # GIVEN
84
+ self.amazon_session.is_authenticated = True
85
+ year = 2024
86
+ start_index = 0
87
+ resp1 = self.given_order_history_landing_exists()
88
+ with open(os.path.join(self.RESOURCES_DIR, "order-history-egift.html"), "r",
89
+ encoding="utf-8") as f:
90
+ resp2 = responses.add(
91
+ responses.GET,
92
+ self.test_config.constants.ORDER_HISTORY_URL,
93
+ body=f.read(),
94
+ status=200,
95
+ )
96
+
97
+ # WHEN
98
+ orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
99
+
100
+ # THEN
101
+ self.assertEqual(9, len(orders))
102
+ self.assertEqual(1, resp1.call_count)
103
+ self.assertEqual(1, resp2.call_count)
104
+ order = orders[5]
105
+ self.assertEqual("112-8022032-9113020", order.order_number)
106
+ self.assertEqual(150.00, order.grand_total)
107
+ self.assertIsNotNone(order.order_details_link)
108
+ self.assertEqual(date(2024, 10, 28), order.order_placed_date)
109
+ self.assertEqual(1, len(order.items))
110
+ self.assertEqual("Amazon eGift Card - Birthday Candles (Animated)",
111
+ order.items[0].title)
112
+ self.assertIsNotNone(order.items[0].link)
113
+ self.assertIsNotNone(order.items[0].image_link)
114
+
80
115
  @responses.activate
81
116
  def test_get_order_history_paginated(self):
82
117
  # GIVEN
@@ -127,6 +162,30 @@ class TestOrders(UnitTestCase):
127
162
  self.assertEqual(1, resp1.call_count)
128
163
  self.assertEqual(1, resp2.call_count)
129
164
 
165
+ @responses.activate
166
+ def test_get_order_history_skip_wholefoods(self):
167
+ # GIVEN
168
+ self.amazon_session.is_authenticated = True
169
+ year = 2024
170
+ start_index = 0
171
+ resp1 = self.given_order_history_landing_exists()
172
+ with open(os.path.join(self.RESOURCES_DIR, "order-history-wholefoods.html"), "r",
173
+ encoding="utf-8") as f:
174
+ resp2 = responses.add(
175
+ responses.GET,
176
+ self.test_config.constants.ORDER_HISTORY_URL,
177
+ body=f.read(),
178
+ status=200,
179
+ )
180
+
181
+ # WHEN
182
+ orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
183
+
184
+ # THEN
185
+ self.assertEqual(9, len(orders))
186
+ self.assertEqual(1, resp1.call_count)
187
+ self.assertEqual(1, resp2.call_count)
188
+
130
189
  @responses.activate
131
190
  def test_get_order_history_full_details(self):
132
191
  # GIVEN
File without changes
File without changes
File without changes