Skip to content

Commit 9bf714d

Browse files
committed
add Product.info
1 parent 15b6840 commit 9bf714d

2 files changed

Lines changed: 85 additions & 6 deletions

File tree

fixprice_api/endpoints/catalog.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
from __future__ import annotations
44

5+
import asyncio
6+
import json
7+
8+
from playwright.async_api import Response as PWResponse
59
from dataclasses import dataclass
6-
from typing import Optional, TYPE_CHECKING
10+
from typing import Optional, TYPE_CHECKING, overload
711
from .. import abstraction
812
from human_requests import autotest
913
from human_requests.abstraction import FetchResponse, HttpMethod
@@ -101,3 +105,69 @@ async def balance(
101105
url += "&inStock=true"
102106

103107
return await self._parent._request(HttpMethod.GET, url)
108+
109+
@overload
110+
async def info(self, *, url: str): ...
111+
112+
@overload
113+
async def info(self, *, category: str, product_id: int, slug: str): ...
114+
115+
@autotest
116+
async def info(
117+
self,
118+
*,
119+
url: str | None = None,
120+
category: str | None = None,
121+
product_id: int | None = None,
122+
slug: str | None = None,
123+
) -> dict:
124+
"""
125+
Информация СПАРСИВАЕТСЯ (в отличии от других методов).
126+
Инфо о товаре со страницы типа
127+
https://fix-price.com/catalog/produkty-i-napitki/p-1902248-shokoladnye-konfety-inis-nickers-135-g
128+
129+
Либо предоставляете url напрямую, например `products[0]["url"]`
130+
131+
Данные карточки лежат в obj["data"][0]["categoryData"]["product"]
132+
"""
133+
134+
real_url = "https://fix-price.com/catalog/"
135+
if url is None:
136+
if category is None or product_id is None or slug is None:
137+
raise TypeError("Either url or (category, product_id, slug) must be provided")
138+
139+
real_url += f"{category}/p-{product_id}-{slug}"
140+
else:
141+
real_url += url
142+
143+
page = await self._parent.ctx.new_page()
144+
await page.goto(real_url, wait_until="domcontentloaded")
145+
146+
raw_json = await page.evaluate("""
147+
() => {
148+
const marker = "window.__NUXT__=";
149+
150+
for (const s of document.scripts) {
151+
const txt = s.textContent || "";
152+
const idx = txt.indexOf(marker);
153+
154+
if (idx !== -1) {
155+
let expr = txt.slice(idx + marker.length).trim();
156+
157+
if (expr.endsWith(";")) {
158+
expr = expr.slice(0, -1);
159+
}
160+
161+
const obj = Function('"use strict"; return (' + expr + ')')();
162+
return JSON.stringify(obj);
163+
}
164+
}
165+
166+
return null;
167+
}
168+
""")
169+
nuxt_data = json.loads(raw_json)["data"][0]["categoryData"]["product"] if raw_json else None
170+
171+
await page.close()
172+
173+
return nuxt_data

tests/api_test.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ def _capture_product_id(
7777
if not isinstance(data, list) or not data:
7878
pytest.fail("Catalog.products_list returned empty data.")
7979

80-
product_id = data[0].get("id")
81-
if not isinstance(product_id, int):
82-
pytest.fail("Catalog.products_list did not return a valid product id.")
83-
84-
ctx.state["autotest_product_id"] = product_id
80+
for key in ["id", "url"]:
81+
value = data[0].get(key)
82+
if not isinstance(value[key], int):
83+
pytest.fail(f"Catalog.products_list did not return a valid {key}.")
84+
ctx.state[f"autotest_product_{key}"] = value[key]
8585

8686

8787
@autotest_depends_on(ClassCatalog.products_list)
@@ -93,6 +93,15 @@ def _product_balance_params(ctx: AutotestCallContext) -> dict[str, int]:
9393
pytest.fail("ProductService.balance depends on Catalog.products_list.")
9494

9595

96+
@autotest_depends_on(ClassCatalog.products_list)
97+
@autotest_params(target=ProductService.info)
98+
def _product_info_params(ctx: AutotestCallContext) -> dict[str, int]:
99+
cached_id = ctx.state.get("autotest_product_url")
100+
if isinstance(cached_id, int):
101+
return {"url": cached_id}
102+
pytest.fail("ProductService.info depends on Catalog.products_list.")
103+
104+
96105
@autotest_data(name="unstandard_headers")
97106
def _unstandard_headers_data(ctx: AutotestDataContext) -> dict[str, Any]:
98107
return ctx.api.unstandard_headers

0 commit comments

Comments
 (0)