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.
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/CHANGELOG.md +17 -1
- {amazon_orders-3.2.1/amazon_orders.egg-info → amazon_orders-3.2.3}/PKG-INFO +3 -2
- {amazon_orders-3.2.1 → amazon_orders-3.2.3/amazon_orders.egg-info}/PKG-INFO +3 -2
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/requires.txt +1 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/__init__.py +1 -1
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/conf.py +1 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/order.py +2 -2
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/orders.py +6 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/selectors.py +1 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/session.py +1 -1
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/transactions.py +8 -14
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/pyproject.toml +2 -1
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_cli.py +2 -2
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_conf.py +3 -1
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_orders.py +24 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_transactions.py +6 -6
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/LICENSE +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/MANIFEST.in +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/README.md +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/SOURCES.txt +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/dependency_links.txt +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/entry_points.txt +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazon_orders.egg-info/top_level.txt +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/banner.txt +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/cli.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/constants.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/__init__.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/item.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/parsable.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/recipient.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/seller.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/shipment.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/entity/transaction.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/exception.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/forms.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/amazonorders/util.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/setup.cfg +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_session.py +0 -0
- {amazon_orders-3.2.1 → amazon_orders-3.2.3}/tests/test_util.py +0 -0
- {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.
|
|
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
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: amazon-orders
|
|
3
|
-
Version: 3.2.
|
|
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
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: amazon-orders
|
|
3
|
-
Version: 3.2.
|
|
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"
|
|
@@ -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),
|
|
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(),
|
|
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
|
-
|
|
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
|
|
21
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
@@ -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.
|
|
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("""
|
|
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.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
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
|
|
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
|