Skip to content

Commit e848aa9

Browse files
committed
refactore
1 parent 9369406 commit e848aa9

9 files changed

Lines changed: 146 additions & 105 deletions

File tree

Makefile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
.PHONY: help install install-dev test test-quick lint format type-check clean build docs example-docs build-all-docs serve-docs serve-examples ci-test prepare-release generate-badges
22

3+
PYTHON ?= python3
4+
35
install:
46
pip install .
57

@@ -12,9 +14,17 @@ test:
1214
test-quick:
1315
pytest --tb=short --color=yes
1416

17+
lint:
18+
$(PYTHON) -m flake8 --max-line-length=100 chizhik_api/ tests/
19+
$(PYTHON) -m black --check chizhik_api/ tests/
20+
$(PYTHON) -m isort --profile black --line-length 100 --check-only chizhik_api/ tests/
21+
22+
type-check:
23+
$(PYTHON) -m mypy chizhik_api/
24+
1525
format:
1626
black chizhik_api/ tests/
17-
isort chizhik_api/ tests/
27+
isort --profile black --line-length 100 chizhik_api/ tests/
1828

1929
clean:
2030
rm -rf build/ dist/ *.egg-info/
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
"""Реклама"""
22

3+
from __future__ import annotations
4+
35
from typing import TYPE_CHECKING
46

7+
from human_requests import ApiChild, autotest
58
from human_requests.abstraction import FetchResponse, HttpMethod
69

710
if TYPE_CHECKING:
8-
from ..manager import ChizhikAPI
11+
from ..manager import ChizhikAPI # noqa: F401
912

1013

11-
class ClassAdvertising:
14+
class ClassAdvertising(ApiChild["ChizhikAPI"]):
1215
"""Методы для работы с рекламными материалами Перекрёстка.
1316
1417
Включает получение баннеров, слайдеров, буклетов и другого рекламного контента.
1518
"""
1619

17-
def __init__(self, parent: "ChizhikAPI", CATALOG_URL: str):
18-
self._parent: "ChizhikAPI" = parent
19-
self.CATALOG_URL: str = CATALOG_URL
20-
20+
@autotest
2121
async def active_inout(self) -> FetchResponse:
2222
"""Получить активные рекламные баннеры."""
2323
return await self._parent._request(
24-
HttpMethod.GET, f"{self.CATALOG_URL}/catalog/unauthorized/active_inout/"
24+
HttpMethod.GET,
25+
f"{self._parent.CATALOG_URL}/catalog/unauthorized/active_inout/",
2526
)

chizhik_api/endpoints/catalog.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
11
"""Работа с каталогом"""
22

3+
from __future__ import annotations
4+
35
import urllib.parse
6+
from dataclasses import dataclass
47
from typing import TYPE_CHECKING, Optional
58

9+
from human_requests import ApiChild, ApiParent, api_child_field, autotest
610
from human_requests.abstraction import FetchResponse, HttpMethod
711

812
if TYPE_CHECKING:
9-
from ..manager import ChizhikAPI
13+
from ..manager import ChizhikAPI # noqa: F401
1014

1115

12-
class ClassCatalog:
16+
@dataclass(init=False)
17+
class ClassCatalog(ApiChild["ChizhikAPI"], ApiParent):
1318
"""Методы для работы с каталогом товаров.
1419
1520
Включает поиск товаров, получение информации о категориях,
1621
работу с фидами товаров и отзывами.
1722
"""
1823

19-
def __init__(self, parent: "ChizhikAPI", CATALOG_URL: str):
20-
self._parent: "ChizhikAPI" = parent
21-
self.CATALOG_URL: str = CATALOG_URL
22-
self.Product: ProductService = ProductService(
23-
parent=self._parent, CATALOG_URL=CATALOG_URL
24-
)
25-
"""Сервис для работы с товарами в каталоге."""
24+
Product: ProductService = api_child_field(
25+
lambda parent: ProductService(parent.parent)
26+
)
27+
"""Сервис для работы с товарами в каталоге."""
28+
29+
def __init__(self, parent: "ChizhikAPI"):
30+
super().__init__(parent)
31+
ApiParent.__post_init__(self)
2632

33+
@autotest
2734
async def tree(self, city_id: Optional[str] = None) -> FetchResponse:
2835
"""Получить дерево категорий."""
29-
url = f"{self.CATALOG_URL}/catalog/unauthorized/categories/"
36+
url = f"{self._parent.CATALOG_URL}/catalog/unauthorized/categories/"
3037
if city_id:
3138
url += f"?city_id={city_id}"
3239
return await self._parent._request(HttpMethod.GET, url)
3340

41+
@autotest
3442
async def products_list(
3543
self,
3644
page: int = 1,
@@ -39,7 +47,7 @@ async def products_list(
3947
search: Optional[str] = None,
4048
) -> FetchResponse:
4149
"""Получить список продуктов в категории."""
42-
url = f"{self.CATALOG_URL}/catalog/unauthorized/products/?page={page}"
50+
url = f"{self._parent.CATALOG_URL}/catalog/unauthorized/products/?page={page}"
4351
if category_id:
4452
url += f"&category_id={category_id}"
4553
if city_id:
@@ -49,13 +57,10 @@ async def products_list(
4957
return await self._parent._request(HttpMethod.GET, url)
5058

5159

52-
class ProductService:
60+
class ProductService(ApiChild["ChizhikAPI"]):
5361
"""Сервис для работы с товарами в каталоге."""
5462

55-
def __init__(self, parent: "ChizhikAPI", CATALOG_URL: str):
56-
self._parent: "ChizhikAPI" = parent
57-
self.CATALOG_URL: str = CATALOG_URL
58-
63+
@autotest
5964
async def info(
6065
self, product_id: int, city_id: Optional[str] = None
6166
) -> FetchResponse:
@@ -69,7 +74,7 @@ async def info(
6974
Response: Ответ от сервера с информацией о товаре.
7075
"""
7176

72-
url = f"{self.CATALOG_URL}/catalog/unauthorized/products/{product_id}/"
77+
url = f"{self._parent.CATALOG_URL}/catalog/unauthorized/products/{product_id}/"
7378
if city_id:
7479
url += f"?city_id={city_id}"
7580
return await self._parent._request(HttpMethod.GET, url)

chizhik_api/endpoints/general.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
"""Общий (не класифицируемый) функционал"""
22

3+
from __future__ import annotations
4+
35
from io import BytesIO
46
from typing import TYPE_CHECKING
57

6-
from human_requests.abstraction import Proxy
78
from aiohttp_retry import ExponentialRetry, RetryClient
9+
from human_requests import ApiChild
10+
from human_requests.abstraction import Proxy
811

912
if TYPE_CHECKING:
10-
from ..manager import ChizhikAPI
13+
from ..manager import ChizhikAPI # noqa: F401
1114

1215

13-
class ClassGeneral:
16+
class ClassGeneral(ApiChild["ChizhikAPI"]):
1417
"""Общие методы API Чижика.
1518
1619
Включает методы для работы с изображениями, формой обратной связи,
1720
получения информации о пользователе и других общих функций.
1821
"""
1922

20-
def __init__(self, parent: "ChizhikAPI", CATALOG_URL: str):
21-
self._parent: ChizhikAPI = parent
22-
self.CATALOG_URL: str = CATALOG_URL
23-
2423
async def download_image(
2524
self, url: str, retry_attempts: int = 3, timeout: float = 10
2625
) -> BytesIO:
@@ -30,7 +29,11 @@ async def download_image(
3029
)
3130

3231
async with RetryClient(retry_options=retry_options) as retry_client:
33-
async with retry_client.get(url, raise_for_status=True, proxy=Proxy(self._parent.proxy).as_str()) as resp:
32+
async with retry_client.get(
33+
url,
34+
raise_for_status=True,
35+
proxy=Proxy(self._parent.proxy).as_str(),
36+
) as resp:
3437
body = await resp.read()
3538
file = BytesIO(body)
3639
file.name = url.split("/")[-1]
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
"""Геолокация"""
22

3+
from __future__ import annotations
4+
35
from typing import TYPE_CHECKING
46

7+
from human_requests import ApiChild, autotest
58
from human_requests.abstraction import FetchResponse, HttpMethod
69

710
if TYPE_CHECKING:
8-
from ..manager import ChizhikAPI
11+
from ..manager import ChizhikAPI # noqa: F401
912

1013

11-
class ClassGeolocation:
14+
class ClassGeolocation(ApiChild["ChizhikAPI"]):
1215
"""Методы для работы с геолокацией и выбором магазинов.
1316
1417
Включает получение информации о городах, адресах, поиск магазинов
1518
и управление настройками доставки.
1619
"""
1720

18-
def __init__(self, parent: "ChizhikAPI", CATALOG_URL: str):
19-
self._parent: ChizhikAPI = parent
20-
self.CATALOG_URL: str = CATALOG_URL
21-
21+
@autotest
2222
async def cities_list(self, search_name: str, page: int = 1) -> FetchResponse:
2323
"""Получить список городов по частичному совпадению имени."""
2424
return await self._parent._request(
2525
HttpMethod.GET,
26-
f"{self.CATALOG_URL}/geo/cities/?name={search_name}&page={page}",
26+
f"{self._parent.CATALOG_URL}/geo/cities/?name={search_name}&page={page}",
2727
)

chizhik_api/manager.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
from typing import Any
66

77
from camoufox.async_api import AsyncCamoufox
8-
from human_requests import HumanBrowser, HumanContext, HumanPage
8+
from human_requests import (
9+
ApiParent,
10+
HumanBrowser,
11+
HumanContext,
12+
HumanPage,
13+
api_child_field,
14+
)
915
from human_requests.abstraction import FetchResponse, HttpMethod, Proxy
1016
from playwright.async_api import TimeoutError as PWTimeoutError
1117

@@ -21,7 +27,7 @@ def _pick_https_proxy() -> str | None:
2127

2228

2329
@dataclass
24-
class ChizhikAPI:
30+
class ChizhikAPI(ApiParent):
2531
"""
2632
Клиент Чижика.
2733
"""
@@ -50,22 +56,15 @@ class ChizhikAPI:
5056
page: HumanPage = field(init=False, repr=False)
5157
"""Внутренний страница сессии браузера"""
5258

53-
Geolocation: ClassGeolocation = field(init=False)
59+
Geolocation: ClassGeolocation = api_child_field(ClassGeolocation)
5460
"""API для работы с геолокацией."""
55-
Catalog: ClassCatalog = field(init=False)
61+
Catalog: ClassCatalog = api_child_field(ClassCatalog)
5662
"""API для работы с каталогом товаров."""
57-
Advertising: ClassAdvertising = field(init=False)
63+
Advertising: ClassAdvertising = api_child_field(ClassAdvertising)
5864
"""API для работы с рекламой."""
59-
General: ClassGeneral = field(init=False)
65+
General: ClassGeneral = api_child_field(ClassGeneral)
6066
"""API для работы с общими функциями."""
6167

62-
# ───── lifecycle ─────
63-
def __post_init__(self) -> None:
64-
self.Geolocation = ClassGeolocation(self, self.CATALOG_URL)
65-
self.Catalog = ClassCatalog(self, self.CATALOG_URL)
66-
self.Advertising = ClassAdvertising(self, self.CATALOG_URL)
67-
self.General = ClassGeneral(self, self.CATALOG_URL)
68-
6968
async def __aenter__(self):
7069
"""Вход в контекстный менеджер с автоматическим прогревом сессии."""
7170
await self._warmup()
@@ -87,7 +86,7 @@ async def _warmup(self) -> None:
8786

8887
ok = False
8988
try_count = 3
90-
while not ok or try_count <= 0:
89+
while not ok and try_count > 0:
9190
try_count -= 1
9291
try:
9392
await self.page.wait_for_selector(
@@ -97,7 +96,7 @@ async def _warmup(self) -> None:
9796
except PWTimeoutError:
9897
await self.page.reload()
9998
if not ok:
100-
raise RuntimeError(self.page.content)
99+
raise RuntimeError(await self.page.content())
101100

102101
# await self.page.wait_for_load_state("networkidle")
103102
# await asyncio.sleep(3)
@@ -127,8 +126,6 @@ async def _request(
127126
url: URL для запроса
128127
json_body: Тело запроса в формате JSON (опционально)
129128
"""
130-
# Единая точка входа в чужую библиотеку для удобства
131-
print(url)
132129
resp: FetchResponse = await self.page.fetch(
133130
url=url,
134131
method=method,

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ classifiers = [
2929
]
3030
dependencies = [
3131
"camoufox[geoip]",
32-
"human_requests",
32+
"human_requests>=0.1.6",
3333
"aiohttp",
3434
"aiohttp-retry"
3535
]
@@ -39,6 +39,7 @@ tests = [
3939
"pytest",
4040
"pytest-anyio",
4141
"pytest-jsonschema-snapshot",
42+
"pytest-subtests",
4243
"jsoncrack-for-sphinx",
4344
]
4445

@@ -59,4 +60,9 @@ filterwarnings = [
5960
"ignore:Event loop is closed:RuntimeWarning",
6061
]
6162
anyio_mode = "auto"
63+
autotest_start_class = "chizhik_api.ChizhikAPI"
64+
autotest_typecheck = "strict"
6265
addopts = "-v --tb=short --disable-warnings"
66+
67+
[tool.mypy]
68+
ignore_missing_imports = true

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
numpy<2.0
22
camoufox
3-
human_requests
3+
human_requests>=0.1.6
44
aiohttp
55
aiohttp-retry
66

77
pytest
88
pytest-anyio
99
pytest-jsonschema-snapshot
10-
pillow
10+
pytest-subtests
11+
pillow

0 commit comments

Comments
 (0)