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.
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/CHANGELOG.md +23 -1
- {amazon_orders-5.2.0/amazon_orders.egg-info → amazon_orders-5.2.2}/PKG-INFO +1 -1
- {amazon_orders-5.2.0 → amazon_orders-5.2.2/amazon_orders.egg-info}/PKG-INFO +1 -1
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/__init__.py +2 -1
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/constants.py +1 -3
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/order.py +4 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/parsable.py +29 -10
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/tracking.py +1 -1
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/selectors.py +14 -7
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/session.py +2 -1
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/LICENSE +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/MANIFEST.in +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/README.md +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/SOURCES.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/dependency_links.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/entry_points.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/requires.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazon_orders.egg-info/top_level.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/banner.txt +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/cli.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/conf.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/__init__.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/browser/__init__.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/browser/playwright.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/__init__.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/anticaptcha.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/base.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/capsolver.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/contrib/waf/twocaptcha.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/__init__.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/family_member.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/gift_card_activity.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/invoice.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/item.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/order_search_result.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/prime_payment.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/recipient.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/seller.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/shipment.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/entity/transaction.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/exception.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/forms.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/gift_cards.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/orders.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/output.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/paging.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/prime.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/py.typed +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/region_data.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/transactions.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/amazonorders/util.py +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/pyproject.toml +0 -0
- {amazon_orders-5.2.0 → amazon_orders-5.2.2}/setup.cfg +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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>
|
|
@@ -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``, ``
|
|
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
|
|
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
|
-
|
|
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}, `{
|
|
99
|
+
"When building {name}, `{field}` "
|
|
87
100
|
"could not be parsed.".format(name=self.__class__.__name__,
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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),
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|