amazon-orders 5.2.0__tar.gz → 5.2.2__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 (54) hide show
  1. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/CHANGELOG.md +23 -1
  2. {amazon_orders-5.2.0/amazon_orders.egg-info → amazon_orders-5.2.2}/PKG-INFO +1 -1
  3. {amazon_orders-5.2.0 → amazon_orders-5.2.2/amazon_orders.egg-info}/PKG-INFO +1 -1
  4. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/__init__.py +2 -1
  5. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/constants.py +1 -3
  6. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/order.py +4 -0
  7. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/parsable.py +29 -10
  8. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/tracking.py +1 -1
  9. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/selectors.py +14 -7
  10. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/session.py +2 -1
  11. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/LICENSE +0 -0
  12. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/MANIFEST.in +0 -0
  13. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/README.md +0 -0
  14. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/SOURCES.txt +0 -0
  15. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/dependency_links.txt +0 -0
  16. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/entry_points.txt +0 -0
  17. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/requires.txt +0 -0
  18. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/top_level.txt +0 -0
  19. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/banner.txt +0 -0
  20. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/cli.py +0 -0
  21. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/conf.py +0 -0
  22. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/__init__.py +0 -0
  23. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/browser/__init__.py +0 -0
  24. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/browser/playwright.py +0 -0
  25. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/__init__.py +0 -0
  26. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/anticaptcha.py +0 -0
  27. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/base.py +0 -0
  28. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/capsolver.py +0 -0
  29. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/twocaptcha.py +0 -0
  30. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/__init__.py +0 -0
  31. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/family_member.py +0 -0
  32. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/gift_card_activity.py +0 -0
  33. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/invoice.py +0 -0
  34. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/item.py +0 -0
  35. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/order_search_result.py +0 -0
  36. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/prime_payment.py +0 -0
  37. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/recipient.py +0 -0
  38. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/seller.py +0 -0
  39. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/shipment.py +0 -0
  40. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/transaction.py +0 -0
  41. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/exception.py +0 -0
  42. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/forms.py +0 -0
  43. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/gift_cards.py +0 -0
  44. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/orders.py +0 -0
  45. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/output.py +0 -0
  46. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/paging.py +0 -0
  47. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/prime.py +0 -0
  48. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/py.typed +0 -0
  49. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/region_data.py +0 -0
  50. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/transactions.py +0 -0
  51. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/util.py +0 -0
  52. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/pyproject.toml +0 -0
  53. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/setup.cfg +0 -0
  54. {amazon_orders-5.2.0 → amazon_orders-5.2.2}/tests/testcase.py +0 -0
@@ -4,7 +4,29 @@ 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/5.2.0...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/5.2.2...HEAD)
8
+
9
+ ## [5.2.2](https://github.com/alexdlaird/amazon-orders/compare/5.2.1...5.2.2) - 2026-11-09
10
+
11
+ ### Fixed
12
+
13
+ - `parse_tracking()` parses the tracking page of a shipment that has not shipped yet, instead of raising.
14
+ - `Order.order_placed_date` is parsed on a Whole Foods Market in-store purchase's details page.
15
+ - `Tracking.carrier` is documented as `None` when the page names no carrier.
16
+
17
+ ### Security
18
+
19
+ - The `Host` header is no longer pinned, which sent Amazon's session cookies to any host requested, including through an off-site redirect.
20
+
21
+ ## [5.2.1](https://github.com/alexdlaird/amazon-orders/compare/5.2.0...5.2.1) - 2026-10-31
22
+
23
+ ### Added
24
+
25
+ - `parse_failures` on every entity, the fields that failed to parse and were left `None`, to tell them apart from fields that are `None` by design.
26
+
27
+ ### Changed
28
+
29
+ - `safe_simple_parse()`'s warning names the selector that failed, instead of `simple_parse`.
8
30
 
9
31
  ## [5.2.0](https://github.com/alexdlaird/amazon-orders/compare/5.1.0...5.2.0) - 2026-10-25
10
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-orders
3
- Version: 5.2.0
3
+ Version: 5.2.2
4
4
  Summary: A Python library (and CLI) for Amazon order history, line items, and transactions
5
5
  Author-email: Alex Laird <contact@alexlaird.com>
6
6
  Maintainer-email: Alex Laird <contact@alexlaird.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-orders
3
- Version: 5.2.0
3
+ Version: 5.2.2
4
4
  Summary: A Python library (and CLI) for Amazon order history, line items, and transactions
5
5
  Author-email: Alex Laird <contact@alexlaird.com>
6
6
  Maintainer-email: Alex Laird <contact@alexlaird.com>
@@ -1,3 +1,4 @@
1
1
  __copyright__ = "Copyright (c) 2024-2025 Alex Laird"
2
2
  __license__ = "MIT"
3
- __version__ = "5.2.0"
3
+ __version__ = "5.2.2"
4
+ __muppets__ = "no strings attached"
@@ -24,7 +24,7 @@ class Constants:
24
24
 
25
25
  config = AmazonOrdersConfig(data={"constants_class": "my_module.MyConstants"})
26
26
 
27
- URLs and the URL-shaped headers (``Origin``, ``Host``, ``Referer``) are derived from the active
27
+ URLs and the URL-shaped headers (``Origin``, ``Referer``) are derived from the active
28
28
  Amazon domain. ``REQUEST_COOKIES``, ``Accept-Language``, ``CURRENCY_SYMBOL``, the sign-in
29
29
  ``openid.assoc_handle``, and ``COOKIES_SET_WHEN_AUTHENTICATED`` are adjusted for known Amazon sites
30
30
  (each only when a subclass has not overridden it). The domain is resolved in this precedence order:
@@ -128,7 +128,6 @@ class Constants:
128
128
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", # noqa: E501
129
129
  "Accept-Encoding": "gzip, deflate, br, zstd",
130
130
  "Accept-Language": "en-US,en;q=0.9",
131
- "Host": urlparse(BASE_URL).netloc,
132
131
  "Origin": BASE_URL,
133
132
  "Referer": f"{SIGN_IN_URL}?{urlencode(SIGN_IN_QUERY_PARAMS)}",
134
133
  "Sec-Ch-Ua": '"Chromium";v="149", "Google Chrome";v="149", "Not.A/Brand";v="24"',
@@ -398,7 +397,6 @@ class Constants:
398
397
 
399
398
  headers = dict(vars(self).get("BASE_HEADERS", type(self).BASE_HEADERS))
400
399
  headers["Origin"] = base_url
401
- headers["Host"] = urlparse(base_url).netloc
402
400
  headers["Referer"] = f"{sign_in_url}?{urlencode(sign_in_query_params)}"
403
401
  if site and site.english_locale and self._requests_default_language():
404
402
  headers["Accept-Language"] = self._english_accept_language(site.english_locale, headers["Accept-Language"])
@@ -38,6 +38,10 @@ class Order(Parsable):
38
38
  order_number: Optional[str] = None) -> None:
39
39
  super().__init__(parsed, config)
40
40
 
41
+ if clone:
42
+ # Fields copied from the clone keep its failures; fields parsed again here clear their own
43
+ self.parse_failures = list(clone.parse_failures)
44
+
41
45
  #: If the Orders full details were populated from its details page.
42
46
  self.full_details: bool = full_details
43
47
 
@@ -3,7 +3,7 @@ __license__ = "MIT"
3
3
 
4
4
  import logging
5
5
  from datetime import date
6
- from typing import Any, Callable, Dict, Optional, Type, Union
6
+ from typing import Any, Callable, Dict, List, Optional, Type, Union
7
7
 
8
8
  from bs4 import Tag
9
9
 
@@ -14,6 +14,17 @@ from amazonorders.exception import AmazonOrdersEntityError, AmazonOrdersError
14
14
  logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
+ def _field_name(parse_function: Callable[..., Any],
18
+ kwargs: Dict[str, Any]) -> str:
19
+ """
20
+ The name a field's parse failure is logged and recorded under: the field, or for ``simple_parse`` its selector.
21
+ """
22
+ if parse_function.__name__ != "simple_parse":
23
+ return parse_function.__name__.split("_parse_")[1]
24
+ selector = kwargs.get("selector", "")
25
+ return selector if isinstance(selector, str) else ", ".join(selector)
26
+
27
+
17
28
  class Parsable:
18
29
  """
19
30
  A base class that contains a parsed representation of the entity, which can be extended to build an entity that
@@ -27,6 +38,9 @@ class Parsable:
27
38
  self.parsed: Tag = parsed
28
39
  #: The config to use.
29
40
  self.config: AmazonOrdersConfig = config
41
+ #: The fields :func:`safe_parse` could not parse and left ``None`` (the selector, for
42
+ #: :func:`safe_simple_parse`), so a failed field can be told apart from one that is ``None`` by design.
43
+ self.parse_failures: List[str] = []
30
44
 
31
45
  def __getstate__(self) -> Dict:
32
46
  state = self.__dict__.copy()
@@ -38,7 +52,7 @@ class Parsable:
38
52
  """
39
53
  Serialize the entity to a ``dict`` of primitives, suitable for JSON, YAML, or CSV output.
40
54
  Nested entities and lists of them are converted recursively, dates become ISO 8601 strings,
41
- and the parsed ``Tag`` and the config are omitted.
55
+ and the parsed ``Tag``, the config, and ``parse_failures`` are omitted.
42
56
 
43
57
  :param exclude_none: Leave out fields whose value is ``None``, in nested entities too. Only ``None`` is left
44
58
  out: ``False``, ``0``, and empty lists are values.
@@ -46,7 +60,7 @@ class Parsable:
46
60
  """
47
61
  serialized: Dict[str, Any] = {}
48
62
  for field, value in vars(self).items():
49
- if field in ["parsed", "config"] or (exclude_none and value is None):
63
+ if field in ["parsed", "config", "parse_failures"] or (exclude_none and value is None):
50
64
  continue
51
65
 
52
66
  if isinstance(value, Parsable):
@@ -66,7 +80,8 @@ class Parsable:
66
80
  **kwargs: Any) -> Any:
67
81
  """
68
82
  Execute the given parse function on a field, handling any common parse exceptions and passing
69
- them as warnings to the logger (suppressing them as exceptions).
83
+ them as warnings to the logger (suppressing them as exceptions). A field that fails is listed in
84
+ :attr:`parse_failures` until it parses.
70
85
 
71
86
  :param parse_function: The parse function to attempt safe execution.
72
87
  :param kwargs: The ``kwargs`` will be passed to ``parse_function``.
@@ -76,19 +91,23 @@ class Parsable:
76
91
  raise AmazonOrdersError("The name of the \"parse_function\" passed to this method must start with "
77
92
  "\"_parse_\".")
78
93
 
94
+ field = _field_name(parse_function, kwargs)
79
95
  try:
80
- return parse_function(**kwargs)
96
+ value = parse_function(**kwargs)
81
97
  except (AttributeError, IndexError, ValueError, TypeError, RecursionError):
82
- function = "simple_parse"
83
- if parse_function.__name__ != function:
84
- function = parse_function.__name__.split("_parse_")[1]
85
98
  logger.warning(
86
- "When building {name}, `{function}` "
99
+ "When building {name}, `{field}` "
87
100
  "could not be parsed.".format(name=self.__class__.__name__,
88
- function=function),
101
+ field=field),
89
102
  exc_info=True)
103
+ if field not in self.parse_failures:
104
+ self.parse_failures.append(field)
90
105
  return None
91
106
 
107
+ if field in self.parse_failures:
108
+ self.parse_failures.remove(field)
109
+ return value
110
+
92
111
  def simple_parse(self,
93
112
  selector: Union[str, list],
94
113
  attr_name: Optional[str] = None,
@@ -26,7 +26,7 @@ class Tracking(Parsable):
26
26
  config: AmazonOrdersConfig) -> None:
27
27
  super().__init__(parsed, config)
28
28
 
29
- #: The carrier name, e.g. ``UPS``, or ``Amazon`` for Amazon's own delivery network.
29
+ #: The carrier the page names, e.g. ``UPS``, or ``None`` when it names none (the "Delivery Info" layout).
30
30
  self.carrier: Optional[str] = self.safe_parse(self._parse_carrier)
31
31
  #: The carrier's tracking number, e.g. ``1Z999AA10123456784`` or ``TBA000000000000``. Always a ``str``,
32
32
  #: since many (e.g. USPS) are all digits and may have leading zeros.
@@ -269,10 +269,15 @@ class Selectors:
269
269
  FIELD_ORDER_WHOLE_FOODS_TAX_SELECTOR = "#wfm-tax-total-amount"
270
270
  FIELD_ORDER_WHOLE_FOODS_PAYMENT_METHOD_SELECTOR = "#wfm-0-card-brand"
271
271
  FIELD_ORDER_WHOLE_FOODS_PAYMENT_LAST_4_SELECTOR = "#wfm-0-card-tail"
272
- FIELD_ORDER_PLACED_DATE_SELECTOR = ["[data-component='orderDate']",
273
- "span.order-date-invoice-item",
274
- "[data-component='briefOrderInfo'] div.a-column",
275
- "div:is(.a-span3, .a-span12)"]
272
+ FIELD_ORDER_WHOLE_FOODS_PURCHASED_TEXT = "Purchased on"
273
+ FIELD_ORDER_PLACED_DATE_SELECTOR = SelectorsFromText(lambda purchased_text: [
274
+ # WFM in-store details page, the store's local date (its in-store summary shows the UTC date)
275
+ f"#ufpo-order-status-secondary:-soup-contains(\"{purchased_text}\")",
276
+ "[data-component='orderDate']",
277
+ "span.order-date-invoice-item",
278
+ "[data-component='briefOrderInfo'] div.a-column",
279
+ "div:is(.a-span3, .a-span12)"
280
+ ], "FIELD_ORDER_WHOLE_FOODS_PURCHASED_TEXT")
276
281
  FIELD_ORDER_PLACED_DATE_SUFFIX = "Order #"
277
282
  # When a Gift Card was used alongside a card, prefer the card (the instrument with a number), like its last 4 are
278
283
  FIELD_ORDER_PAYMENT_METHOD_SELECTOR = ["[data-testid='payment-instrument']:has([data-testid='payment-instrument-"
@@ -362,11 +367,13 @@ class Selectors:
362
367
  # CSS selectors for Tracking fields
363
368
  #####################################
364
369
 
365
- #: The package tracking page: the carrier layout (with a map), or the status-only layout (milestones only,
366
- #: no carrier or tracking number)
370
+ #: The package tracking page: the carrier layout (with a map), the status-only layout (milestones only,
371
+ #: no carrier or tracking number), or the not-shipped layout (info cards in the page's top area, no carrier
372
+ #: or tracking number)
367
373
  TRACKING_ENTITY_SELECTOR = ["div.pt-main-container",
368
374
  "#pt-page-container-inner",
369
- "#mainContent-container:has(section.pt-card)"]
375
+ "#mainContent-container:has(section.pt-card)",
376
+ "#pageContainer-inner:has(section.pt-card)"]
370
377
  TRACKING_ERROR_SELECTOR = "div#errorPageContainer"
371
378
  FIELD_TRACKING_NUMBER_SELECTOR = [".pt-delivery-card-trackingId"]
372
379
  FIELD_TRACKING_NUMBER_PREFIX = "Tracking ID:"
@@ -205,7 +205,8 @@ class AmazonSession:
205
205
  :param kwargs: Remaining ``kwargs`` will be passed to :func:`requests.request`.
206
206
  :return: The response from the executed request.
207
207
  """
208
- kwargs["headers"] = {**self.config.constants.BASE_HEADERS, **(kwargs.get("headers") or {})}
208
+ headers = {**self.config.constants.BASE_HEADERS, **(kwargs.get("headers") or {})}
209
+ kwargs["headers"] = {name: value for name, value in headers.items() if name.lower() != "host"}
209
210
 
210
211
  if self.debug:
211
212
  logger.debug(f"{method} request: {url}")
File without changes
File without changes
File without changes
File without changes