amazon-orders 3.2.1__tar.gz → 3.2.3__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.1 → amazon_orders-3.2.3}/CHANGELOG.md +17 -1
  2. {amazon_orders-3.2.1/amazon_orders.egg-info → amazon_orders-3.2.3}/PKG-INFO +3 -2
  3. {amazon_orders-3.2.1 → amazon_orders-3.2.3/amazon_orders.egg-info}/PKG-INFO +3 -2
  4. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/requires.txt +1 -0
  5. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/__init__.py +1 -1
  6. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/conf.py +1 -0
  7. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/order.py +2 -2
  8. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/orders.py +6 -0
  9. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/selectors.py +1 -0
  10. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/session.py +1 -1
  11. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/transactions.py +8 -14
  12. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/pyproject.toml +2 -1
  13. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_cli.py +2 -2
  14. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_conf.py +3 -1
  15. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_orders.py +24 -0
  16. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_transactions.py +6 -6
  17. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/LICENSE +0 -0
  18. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/MANIFEST.in +0 -0
  19. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/README.md +0 -0
  20. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/SOURCES.txt +0 -0
  21. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/dependency_links.txt +0 -0
  22. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/entry_points.txt +0 -0
  23. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/top_level.txt +0 -0
  24. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/banner.txt +0 -0
  25. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/cli.py +0 -0
  26. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/constants.py +0 -0
  27. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/__init__.py +0 -0
  28. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/item.py +0 -0
  29. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/parsable.py +0 -0
  30. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/recipient.py +0 -0
  31. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/seller.py +0 -0
  32. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/shipment.py +0 -0
  33. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/transaction.py +0 -0
  34. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/exception.py +0 -0
  35. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/forms.py +0 -0
  36. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/util.py +0 -0
  37. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/setup.cfg +0 -0
  38. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_session.py +0 -0
  39. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_util.py +0 -0
  40. {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/testcase.py +0 -0
@@ -4,7 +4,23 @@ 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.0...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.2.2...HEAD)
8
+
9
+ ## [3.2.3](https://github.com/alexdlaird/amazon-orders/compare/3.2.1...3.2.2) - 2025-02-06
10
+
11
+ ### Added
12
+
13
+ - `bs4_parser` to the config file, which allows for overriding the parser used by BeautifulSoup, defaulting to the built-in `html.parser`.
14
+
15
+ ## [3.2.2](https://github.com/alexdlaird/amazon-orders/compare/3.2.1...3.2.2) - 2025-01-28
16
+
17
+ ### Added
18
+
19
+ - Stability improvements.
20
+
21
+ ### Fixed
22
+
23
+ - Broken parsing of Amazon Fresh orders, these are now skipped.
8
24
 
9
25
  ## [3.2.1](https://github.com/alexdlaird/amazon-orders/compare/3.2.0...3.2.1) - 2024-11-08
10
26
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.1
3
+ Version: 3.2.3
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
@@ -62,6 +62,7 @@ Requires-Dist: responses; extra == "dev"
62
62
  Requires-Dist: flask; extra == "dev"
63
63
  Requires-Dist: twilio; extra == "dev"
64
64
  Requires-Dist: pyngrok; extra == "dev"
65
+ Requires-Dist: lxml; extra == "dev"
65
66
  Provides-Extra: docs
66
67
  Requires-Dist: Sphinx; extra == "docs"
67
68
  Requires-Dist: sphinx-notfound-page; extra == "docs"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.1
3
+ Version: 3.2.3
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
@@ -62,6 +62,7 @@ Requires-Dist: responses; extra == "dev"
62
62
  Requires-Dist: flask; extra == "dev"
63
63
  Requires-Dist: twilio; extra == "dev"
64
64
  Requires-Dist: pyngrok; extra == "dev"
65
+ Requires-Dist: lxml; extra == "dev"
65
66
  Provides-Extra: docs
66
67
  Requires-Dist: Sphinx; extra == "docs"
67
68
  Requires-Dist: sphinx-notfound-page; extra == "docs"
@@ -16,6 +16,7 @@ responses
16
16
  flask
17
17
  twilio
18
18
  pyngrok
19
+ lxml
19
20
 
20
21
  [docs]
21
22
  Sphinx
@@ -1,3 +1,3 @@
1
1
  __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
- __version__ = "3.2.1"
3
+ __version__ = "3.2.3"
@@ -36,6 +36,7 @@ class AmazonOrdersConfig:
36
36
  "order_class": "amazonorders.entity.order.Order",
37
37
  "shipment_class": "amazonorders.entity.shipment.Shipment",
38
38
  "item_class": "amazonorders.entity.item.Item",
39
+ "bs4_parser": "html.parser",
39
40
  }
40
41
 
41
42
  if os.path.exists(self.config_path):
@@ -146,7 +146,7 @@ class Order(Parsable):
146
146
  data_popover = value.get("data-a-popover", {}) # type: ignore[arg-type, var-annotated]
147
147
  inline_content = data_popover.get("inlineContent") # type: ignore[union-attr]
148
148
  if inline_content:
149
- value = BeautifulSoup(json.loads(inline_content), "html.parser")
149
+ value = BeautifulSoup(json.loads(inline_content), self.config.bs4_parser)
150
150
 
151
151
  if not value:
152
152
  # TODO: there are multiple shipToData tags, we should double check we're picking the right one
@@ -166,7 +166,7 @@ class Order(Parsable):
166
166
  )
167
167
 
168
168
  if parent_tag:
169
- value = BeautifulSoup(str(parent_tag.contents[0]).strip(), "html.parser")
169
+ value = BeautifulSoup(str(parent_tag.contents[0]).strip(), self.config.bs4_parser)
170
170
 
171
171
  if not value:
172
172
  return None
@@ -73,6 +73,12 @@ 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.
79
+ if util.select(order_tag, self.config.selectors.ORDER_HISTORY_BRAND_SELECTOR):
80
+ continue
81
+
76
82
  order: Order = self.config.order_cls(order_tag, self.config)
77
83
 
78
84
  if full_details:
@@ -55,6 +55,7 @@ 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
59
 
59
60
  #####################################
60
61
  # CSS selectors for Item fields
@@ -141,7 +141,7 @@ class AmazonSession:
141
141
 
142
142
  self.last_response = self.session.request(method, url, **kwargs)
143
143
  self.last_response_parsed = BeautifulSoup(self.last_response.text,
144
- "html.parser")
144
+ self.config.bs4_parser)
145
145
 
146
146
  cookies = dict_from_cookiejar(self.session.cookies)
147
147
  if os.path.exists(self.config.cookie_jar_path):
@@ -17,19 +17,15 @@ from amazonorders.session import AmazonSession
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
- def _get_today() -> datetime.date:
21
- return datetime.date.today()
22
-
23
-
24
- def _parse_transaction_form_tag(
25
- form_tag: Tag, config: AmazonOrdersConfig
26
- ) -> Tuple[List[Transaction], Optional[str], Optional[Dict[str, str]]]:
20
+ def _parse_transaction_form_tag(form_tag: Tag,
21
+ config: AmazonOrdersConfig) \
22
+ -> Tuple[List[Transaction], Optional[str], Optional[Dict[str, str]]]:
27
23
  transactions = []
28
24
  date_container_tags = util.select(form_tag, config.selectors.TRANSACTION_DATE_CONTAINERS_SELECTOR)
29
25
  for date_container_tag in date_container_tags:
30
26
  date_tag = util.select_one(date_container_tag, config.selectors.FIELD_TRANSACTION_COMPLETED_DATE_SELECTOR)
31
27
  if not date_tag:
32
- logger.warning("Could not find date tag in transaction form.")
28
+ logger.warning("Could not find date tag in Transaction form.")
33
29
  continue
34
30
 
35
31
  date_str = date_tag.text
@@ -38,9 +34,7 @@ def _parse_transaction_form_tag(
38
34
  transactions_container_tag = date_container_tag.find_next_sibling(
39
35
  config.selectors.TRANSACTIONS_CONTAINER_SELECTOR)
40
36
  if not isinstance(transactions_container_tag, Tag):
41
- logger.warning(
42
- "Could not find transactions container tag in transaction form."
43
- )
37
+ logger.warning("Could not find transactions container tag in Transaction form.")
44
38
  continue
45
39
 
46
40
  transaction_tags = util.select(transactions_container_tag, config.selectors.TRANSACTIONS_SELECTOR)
@@ -52,7 +46,7 @@ def _parse_transaction_form_tag(
52
46
  form_ie_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_IE_SELECTOR)
53
47
  next_page_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR)
54
48
  if not next_page_input or not form_state_input or not form_ie_input:
55
- return (transactions, None, None)
49
+ return transactions, None, None
56
50
 
57
51
  next_page_post_url = str(form_tag["action"])
58
52
  next_page_post_data = {
@@ -61,7 +55,7 @@ def _parse_transaction_form_tag(
61
55
  str(next_page_input["name"]): "",
62
56
  }
63
57
 
64
- return (transactions, next_page_post_url, next_page_post_data)
58
+ return transactions, next_page_post_url, next_page_post_data
65
59
 
66
60
 
67
61
  class AmazonTransactions:
@@ -100,7 +94,7 @@ class AmazonTransactions:
100
94
  if not self.amazon_session.is_authenticated:
101
95
  raise AmazonOrdersError("Call AmazonSession.login() to authenticate first.")
102
96
 
103
- min_date = _get_today() - datetime.timedelta(days=days)
97
+ min_date = datetime.date.today() - datetime.timedelta(days=days)
104
98
 
105
99
  self.amazon_session.get(self.config.constants.TRANSACTION_HISTORY_LANDING_URL)
106
100
  if not self.amazon_session.last_response_parsed:
@@ -41,7 +41,8 @@ dev = [
41
41
  "responses",
42
42
  "flask",
43
43
  "twilio",
44
- "pyngrok"
44
+ "pyngrok",
45
+ "lxml"
45
46
  ]
46
47
  docs = [
47
48
  "Sphinx",
@@ -86,10 +86,10 @@ class TestCli(UnitTestCase):
86
86
  self.assertIn("Order #112-2961628-4757846", response.output)
87
87
 
88
88
  @responses.activate
89
- @patch("amazonorders.transactions._get_today")
89
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
90
90
  def test_transactions_command(self, mock_get_today: Mock):
91
91
  # GIVEN
92
- mock_get_today.return_value = datetime.date(2024, 10, 11)
92
+ mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
93
93
  days = 1
94
94
  self.given_login_responses_success()
95
95
  with open(os.path.join(self.RESOURCES_DIR, "get-transactions.html"), "r", encoding="utf-8") as f:
@@ -41,6 +41,7 @@ class TestConf(TestCase):
41
41
  self.assertEqual(10, config.max_auth_attempts)
42
42
  self.assertEqual(self.test_output_dir, config.output_dir)
43
43
  self.assertEqual(self.test_cookie_jar_path, config.cookie_jar_path)
44
+ self.assertEqual("html.parser", config.bs4_parser)
44
45
 
45
46
  # GIVEN
46
47
  config.save()
@@ -48,7 +49,8 @@ class TestConf(TestCase):
48
49
  # THEN
49
50
  self.assertTrue(os.path.exists(config_path))
50
51
  with open(config.config_path, "r") as f:
51
- self.assertEqual("""constants_class: amazonorders.constants.Constants
52
+ self.assertEqual("""bs4_parser: html.parser
53
+ constants_class: amazonorders.constants.Constants
52
54
  cookie_jar_path: {}
53
55
  item_class: amazonorders.entity.item.Item
54
56
  max_auth_attempts: 10
@@ -103,6 +103,30 @@ class TestOrders(UnitTestCase):
103
103
  self.assertEqual(1, resp2.call_count)
104
104
  self.assertEqual(1, resp3.call_count)
105
105
 
106
+ @responses.activate
107
+ def test_get_order_history_skip_fresh(self):
108
+ # GIVEN
109
+ self.amazon_session.is_authenticated = True
110
+ year = 2024
111
+ start_index = 0
112
+ resp1 = self.given_order_history_landing_exists()
113
+ with open(os.path.join(self.RESOURCES_DIR, "order-history-fresh.html"), "r",
114
+ encoding="utf-8") as f:
115
+ resp2 = responses.add(
116
+ responses.GET,
117
+ self.test_config.constants.ORDER_HISTORY_URL,
118
+ body=f.read(),
119
+ status=200,
120
+ )
121
+
122
+ # WHEN
123
+ orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
124
+
125
+ # THEN
126
+ self.assertEqual(9, len(orders))
127
+ self.assertEqual(1, resp1.call_count)
128
+ self.assertEqual(1, resp2.call_count)
129
+
106
130
  @responses.activate
107
131
  def test_get_order_history_full_details(self):
108
132
  # GIVEN
@@ -31,16 +31,16 @@ class TestOrders(UnitTestCase):
31
31
  self.amazon_transactions = AmazonTransactions(self.amazon_session)
32
32
 
33
33
  @responses.activate
34
- @patch("amazonorders.transactions._get_today")
34
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
35
35
  def test_transactions_command(self, mock_get_today: Mock):
36
36
  # GIVEN
37
- mock_get_today.return_value = datetime.date(2024, 10, 11)
37
+ mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
38
38
  days = 1
39
39
  self.amazon_session.is_authenticated = True
40
40
  with open(
41
- os.path.join(self.RESOURCES_DIR, "get-transactions.html"),
42
- "r",
43
- encoding="utf-8",
41
+ os.path.join(self.RESOURCES_DIR, "get-transactions.html"),
42
+ "r",
43
+ encoding="utf-8",
44
44
  ) as f:
45
45
  responses.add(
46
46
  responses.GET,
@@ -57,7 +57,7 @@ class TestOrders(UnitTestCase):
57
57
 
58
58
  def test_parse_transaction_form_tag(self):
59
59
  # GIVEN
60
- parsed = BeautifulSoup(TEST_PARSE_TRANSACTION_FORM_TAG_HTML, "html.parser")
60
+ parsed = BeautifulSoup(TEST_PARSE_TRANSACTION_FORM_TAG_HTML, self.test_config.bs4_parser)
61
61
  form_tag = parsed.select_one("form")
62
62
 
63
63
  # WHEN
File without changes
File without changes
File without changes
File without changes