Skip to content

Commit bd9b5d3

Browse files
authored
Document issue audit outcomes (#226)
1 parent 3a63aae commit bd9b5d3

9 files changed

Lines changed: 127 additions & 8 deletions

File tree

docs/source/deal_queries.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ Use :class:`keepa.models.backend.DealRequest` to construct the request and
3434
response = api.deals(request, typed=True)
3535
asins = [deal.asin for deal in response.dr or [] if deal is not None]
3636
37+
Deal Deltas and Previous Values
38+
-------------------------------
39+
Deal arrays such as ``current``, ``delta``, ``deltaPercent``, and
40+
``deltaLast`` are indexed by Keepa's product CSV type order. Use
41+
``keepa.csv_indices`` to map a price type name to the array position.
42+
43+
.. code-block:: python
44+
45+
deal = response.dr[0]
46+
csv_index = next(index for index, name, _ in keepa.csv_indices if name == "NEW")
47+
48+
current_new = deal.current[csv_index]
49+
change_from_last = deal.deltaLast[csv_index]
50+
previous_new = current_new - change_from_last
51+
52+
Only compute a previous value when both array entries are present and Keepa
53+
supplies a signed delta for that price type. Keepa uses sentinel values such
54+
as ``-1`` for unavailable prices, so filter those values before arithmetic.
55+
3756
Async Usage
3857
-----------
3958

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ when you want products that recently changed and match deal filters.
114114
params = keepa.ProductParams(
115115
author="jim butcher",
116116
current_SALES_lte=50_000,
117-
sort=["current_SALES", "asc"],
117+
sort=[["current_SALES", "asc"]],
118118
perPage=100,
119119
)
120120
asins = api.product_finder(params)

docs/source/offer_queries.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,25 @@ offers. Not every historical offer is active, and the field can be absent.
3737
for offer in active_offers:
3838
times, prices = keepa.convert_offer_history(offer["offerCSV"])
3939
40+
Offer Stock
41+
-----------
42+
Set ``stock=True`` together with ``offers`` when you need current offer stock
43+
data. Keepa can only collect stock for live offers, and the backend caps
44+
reported offer stock at 10 units.
45+
46+
.. code-block:: python
47+
48+
product = api.query("1454857935", offers=20, stock=True)[0]
49+
50+
for index in product.get("liveOffersOrder", []):
51+
offer = product["offers"][index]
52+
stock_history = offer.get("stockCSV")
53+
latest_stock = offer.get("stock")
54+
55+
Existing ``stockCSV`` history can be present even when ``stock=True`` is not
56+
set, but ``stock=True`` asks Keepa to refresh stock for the current live
57+
offers and consumes additional tokens.
58+
4059
.. figure:: images/Offer_History.png
4160
:alt: Active marketplace offer histories
4261
:width: 700px

docs/source/product_finder.rst

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ Basic Search
1111
1212
parameters = {
1313
"author": "jim butcher",
14-
"sort": ["current_SALES", "asc"],
14+
"sort": [["current_SALES", "asc"]],
1515
"perPage": 100,
1616
}
1717
asins = api.product_finder(parameters)
1818
19+
Sort values follow the backend schema: pass a list of ``[field, direction]``
20+
pairs, where direction is ``"asc"`` or ``"desc"``.
21+
1922
Validated Parameters
2023
--------------------
2124
:class:`keepa.ProductParams` validates backend field names before a request
@@ -25,7 +28,7 @@ consumes tokens. Unknown names raise a validation error.
2528
2629
parameters = keepa.ProductParams(
2730
author="jim butcher",
28-
sort=["current_SALES", "asc"],
31+
sort=[["current_SALES", "asc"]],
2932
perPage=100,
3033
)
3134
asins = api.product_finder(parameters)
@@ -47,6 +50,26 @@ The backend exposes more than a thousand filters. Inspect them through
4750
``ProductParams.model_json_schema()``, or
4851
``keepa.backend_models.ProductFinderRequest.model_json_schema()``.
4952

53+
Backend Filter Shapes
54+
---------------------
55+
Keep product-finder field names exactly as Keepa documents them. Range filters
56+
use suffixes such as ``_gte`` and ``_lte``; list-like filters accept either a
57+
single string or a list of strings when the backend supports multiple values.
58+
59+
.. code-block:: python
60+
61+
request = keepa.ProductParams(
62+
buyBoxSellerId=["A2L77EE7U53NWQ", "ATVPDKIKX0DER"],
63+
partNumber=["MX-1000", "MX-1001"],
64+
current_SALES_lte=50000,
65+
sort=[["current_SALES", "desc"]],
66+
)
67+
asins = api.product_finder(request)
68+
69+
This preserves backend payloads for fields such as ``buyBoxSellerId``,
70+
``partNumber``, ``categories_include``, and ``sort`` instead of rewriting them
71+
into client-specific names.
72+
5073
Result Limits and Pages
5174
-----------------------
5275
``n_products`` sets ``perPage`` only when the supplied parameters do not

docs/source/product_history.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ under ``product["data"]``. Each available value array has a matching
77
History Availability
88
--------------------
99
Products do not necessarily contain every history type. Use ``get`` when
10-
availability is not guaranteed.
10+
availability is not guaranteed. A key can be absent when Keepa has no history
11+
for that product, when the product type does not support that history, or when
12+
the request disables history parsing with ``history=False``.
1113

1214
.. code-block:: python
1315
@@ -37,6 +39,10 @@ Key Meaning
3739
``COUNT_REVIEWS`` Review count history
3840
========================== =================================================
3941

42+
If ``NEW_FBA`` or ``NEW_FBM_SHIPPING`` is absent for a product, query with
43+
``history=True`` and treat the missing key as unavailable backend data rather
44+
than a client parsing failure.
45+
4046
Plotting
4147
--------
4248
History values are discontinuous and are best represented as step plots.

docs/source/product_query.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ Keepa requests require a paid API key from `Keepa Data Access
55
by default; use ``wait=False`` only when your application manages token
66
availability itself.
77

8+
Timeouts and Token Use
9+
======================
10+
``Keepa(accesskey, timeout=...)`` controls how long the client waits for the
11+
backend to send a response; it is not a total runtime limit for a large batch.
12+
The client chunks product queries into backend-sized requests, but token cost
13+
is calculated by Keepa and can be more than one token per ASIN when options
14+
such as ``offers``, ``stock``, ``buybox``, ``rating``, ``stats``, or forced
15+
updates are enabled.
16+
17+
For large batches, keep ``wait=True``, use smaller chunks when debugging, and
18+
inspect ``api.tokens_left`` or ``api.status`` between calls. If a request is
19+
timing out, reduce expensive options first and then increase ``timeout`` only
20+
when the backend legitimately needs longer to produce the requested data.
21+
822
.. code-block:: python
923
1024
import keepa

src/keepa/keepa_sync.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,8 +1321,8 @@ def product_finder(
13211321
Notes
13221322
-----
13231323
When using the ``'sort'`` key in the ``product_parms`` parameter, use a
1324-
compatible key along with the type of sort. For example:
1325-
``["current_SALES", "asc"]``
1324+
list of ``[field, direction]`` pairs. For example:
1325+
``[["current_SALES", "asc"]]``
13261326
13271327
Examples
13281328
--------
@@ -1333,7 +1333,7 @@ def product_finder(
13331333
>>> api = keepa.Keepa("<ENTER_ACTUAL_KEY_HERE>")
13341334
>>> product_parms = {
13351335
... "author": "jim butcher",
1336-
... "sort": ["current_SALES", "asc"],
1336+
... "sort": [["current_SALES", "asc"]],
13371337
... }
13381338
>>> asins = api.product_finder(product_parms, n_products=100)
13391339
>>> asins
@@ -1349,7 +1349,7 @@ def product_finder(
13491349
13501350
>>> product_parms = keepa.ProductParams(
13511351
... author="jim butcher",
1352-
... sort=["current_SALES", "asc"],
1352+
... sort=[["current_SALES", "asc"]],
13531353
... )
13541354
>>> asins = api.product_finder(product_parms, n_products=100)
13551355

tests/test_backend_models.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,36 @@ def fake_request(request_type: str, payload: dict[str, Any], **kwargs: Any) -> d
201201
assert asins == ["B000HRMAR2"]
202202

203203

204+
def test_product_finder_preserves_backend_filter_shapes(
205+
monkeypatch: pytest.MonkeyPatch,
206+
) -> None:
207+
api = _ready_api()
208+
209+
def fake_request(request_type: str, payload: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
210+
assert request_type == "query"
211+
selection = json.loads(payload["selection"])
212+
assert selection["sort"] == [["current_SALES", "desc"]]
213+
assert selection["buyBoxSellerId"] == ["A2L77EE7U53NWQ", "ATVPDKIKX0DER"]
214+
assert selection["partNumber"] == ["MX-1000", "MX-1001"]
215+
assert selection["categories_include"] == ["2619533011"]
216+
assert selection["perPage"] == 75
217+
return {"asinList": ["B000HRMAR2"]}
218+
219+
monkeypatch.setattr(api, "_request", fake_request)
220+
221+
asins = api.product_finder(
222+
{
223+
"sort": [["current_SALES", "desc"]],
224+
"buyBoxSellerId": ["A2L77EE7U53NWQ", "ATVPDKIKX0DER"],
225+
"partNumber": ["MX-1000", "MX-1001"],
226+
"categories_include": ["2619533011"],
227+
"perPage": 75,
228+
}
229+
)
230+
231+
assert asins == ["B000HRMAR2"]
232+
233+
204234
def test_best_sellers_typed_response(monkeypatch: pytest.MonkeyPatch) -> None:
205235
api = _ready_api()
206236

tests/test_backend_schema.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,14 +445,22 @@ def test_product_params_accept_backend_fields_and_reject_unknown_fields() -> Non
445445
availabilityAmazonMinDelayInDays_gte=2,
446446
buyBoxEligibleOfferCountsNewFBA_lte=[[1, 2]],
447447
buyBoxStatsTopSellerId365="A2L77EE7U53NWQ",
448+
buyBoxSellerId=["A2L77EE7U53NWQ", "ATVPDKIKX0DER"],
449+
categories_include=["2619533011"],
448450
hasAPlus=True,
449451
historicalSellerIds=["A2L77EE7U53NWQ"],
452+
partNumber=["MX-1000", "MX-1001"],
453+
sort=[["current_SALES", "desc"]],
450454
websiteDisplayGroup="kitchen_display_on_website",
451455
srAvg211_lte=1000,
452456
)
453457

454458
dumped = params.model_dump(exclude_none=True)
455459
assert dumped["activeIngredients"] == ["ceramide"]
460+
assert dumped["buyBoxSellerId"] == ["A2L77EE7U53NWQ", "ATVPDKIKX0DER"]
461+
assert dumped["categories_include"] == ["2619533011"]
462+
assert dumped["partNumber"] == ["MX-1000", "MX-1001"]
463+
assert dumped["sort"] == [["current_SALES", "desc"]]
456464
assert dumped["srAvg211_lte"] == 1000
457465

458466
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)