Skip to content

Commit ca16cde

Browse files
committed
Product.info in progress
1 parent 2c466f7 commit ca16cde

3 files changed

Lines changed: 62 additions & 28 deletions

File tree

fixprice_api/endpoints/catalog.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from __future__ import annotations
44

5-
import asyncio
65
import json
6+
from types import MethodType
7+
from ..tools import AwaitableDict
78

89
from playwright.async_api import Response as PWResponse
910
from dataclasses import dataclass
@@ -120,12 +121,12 @@ async def info(
120121
category: str | None = None,
121122
product_id: int | None = None,
122123
slug: str | None = None,
123-
) -> dict:
124+
) -> PWResponse:
124125
"""
125126
Информация СПАРСИВАЕТСЯ (в отличии от других методов).
126127
Инфо о товаре со страницы типа
127128
https://fix-price.com/catalog/produkty-i-napitki/p-1902248-shokoladnye-konfety-inis-nickers-135-g
128-
129+
129130
Либо предоставляете url напрямую, например `products[0]["url"]`
130131
131132
Данные карточки лежат в obj["data"][0]["categoryData"]["product"]
@@ -141,33 +142,42 @@ async def info(
141142
real_url += url
142143

143144
page = await self._parent.ctx.new_page()
144-
await page.goto(real_url, wait_until="domcontentloaded")
145+
try:
146+
resp = await page.goto(real_url, wait_until="domcontentloaded")
147+
if resp is None:
148+
raise RuntimeError("page.goto() returned None")
145149

146-
raw_json = await page.evaluate("""
147-
() => {
148-
const marker = "window.__NUXT__=";
150+
raw_json = await page.evaluate("""
151+
() => {
152+
const marker = "window.__NUXT__=";
149153
150-
for (const s of document.scripts) {
151-
const txt = s.textContent || "";
152-
const idx = txt.indexOf(marker);
154+
for (const s of document.scripts) {
155+
const txt = s.textContent || "";
156+
const idx = txt.indexOf(marker);
153157
154-
if (idx !== -1) {
155-
let expr = txt.slice(idx + marker.length).trim();
158+
if (idx !== -1) {
159+
let expr = txt.slice(idx + marker.length).trim();
156160
157-
if (expr.endsWith(";")) {
158-
expr = expr.slice(0, -1);
159-
}
161+
if (expr.endsWith(";")) {
162+
expr = expr.slice(0, -1);
163+
}
160164
161-
const obj = Function('"use strict"; return (' + expr + ')')();
162-
return JSON.stringify(obj);
165+
const obj = Function('"use strict"; return (' + expr + ')')();
166+
return JSON.stringify(obj);
167+
}
163168
}
169+
170+
return null;
164171
}
172+
""")
165173

166-
return null;
167-
}
168-
""")
169-
nuxt_data = json.loads(raw_json)["data"][0]["categoryData"]["product"] if raw_json else None
174+
nuxt_data = json.loads(raw_json)["data"][0]["categoryData"]["product"] if raw_json else None
175+
176+
def _json(self):
177+
return AwaitableDict(nuxt_data)
178+
179+
resp.json = MethodType(_json, resp)
170180

171-
await page.close()
172-
173-
return nuxt_data
181+
return resp
182+
finally:
183+
await page.close()

fixprice_api/tools.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
class AwaitableDict(dict):
3+
def __init__(self, data: dict | None):
4+
self._data = data
5+
6+
def __getitem__(self, key):
7+
return self._data[key]
8+
9+
def __iter__(self):
10+
return iter(self._data)
11+
12+
def __len__(self):
13+
return len(self._data)
14+
15+
def __repr__(self):
16+
return repr(self._data)
17+
18+
def get(self, key, default=None):
19+
return self._data.get(key, default)
20+
21+
def __await__(self):
22+
async def _wrap():
23+
return self._data
24+
return _wrap().__await__()

tests/api_test.py

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

80-
for key in ["id", "url"]:
80+
for key, type in [("id", int), ("url", str)]:
8181
value = data[0].get(key)
82-
if not isinstance(value, int):
82+
if not isinstance(value, type):
8383
pytest.fail(f"Catalog.products_list did not return a valid {key}.")
8484
ctx.state[f"autotest_product_{key}"] = value
8585

@@ -95,9 +95,9 @@ def _product_balance_params(ctx: AutotestCallContext) -> dict[str, int]:
9595

9696
@autotest_depends_on(ClassCatalog.products_list)
9797
@autotest_params(target=ProductService.info)
98-
def _product_info_params(ctx: AutotestCallContext) -> dict[str, int]:
98+
def _product_info_params(ctx: AutotestCallContext) -> dict[str, str]:
9999
cached_id = ctx.state.get("autotest_product_url")
100-
if isinstance(cached_id, int):
100+
if isinstance(cached_id, str):
101101
return {"url": cached_id}
102102
pytest.fail("ProductService.info depends on Catalog.products_list.")
103103

0 commit comments

Comments
 (0)