pyaterochka-api 0.1.6__tar.gz → 0.1.7__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.
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/PKG-INFO +18 -6
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/README.md +16 -5
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api/api.py +41 -13
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api/manager.py +54 -9
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/PKG-INFO +18 -6
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/requires.txt +1 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/setup.py +2 -1
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/tests/base_tests.py +11 -11
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/LICENSE +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api/__init__.py +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/SOURCES.txt +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/dependency_links.txt +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/top_level.txt +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyproject.toml +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/setup.cfg +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/tests/__init__.py +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/tests/snapshots/__init__.py +0 -0
- {pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/tests/snapshots/snap_base_tests.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyaterochka_api
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.7
|
4
4
|
Summary: A Python API client for Pyaterochka store catalog
|
5
5
|
Home-page: https://github.com/Open-Inflation/pyaterochka_api
|
6
6
|
Author: Miskler
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
23
23
|
Requires-Dist: aiohttp
|
24
24
|
Requires-Dist: camoufox[geoip]
|
25
|
+
Requires-Dist: beartype
|
25
26
|
Requires-Dist: fake-useragent
|
26
27
|
Requires-Dist: tqdm
|
27
28
|
Provides-Extra: tests
|
@@ -43,6 +44,7 @@ Dynamic: summary
|
|
43
44
|
|
44
45
|
Pyaterochka (Пятёрочка) - https://5ka.ru/
|
45
46
|
|
47
|
+
[](https://github.com/Open-Inflation/pyaterochka_api/actions?query=workflow%3A"API+Tests+Daily?query=branch%3Amain")
|
46
48
|

|
47
49
|

|
48
50
|
[](https://pypi.org/project/pyaterochka-api/)
|
@@ -72,7 +74,7 @@ import asyncio
|
|
72
74
|
|
73
75
|
|
74
76
|
async def main():
|
75
|
-
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False) as API:
|
77
|
+
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False, trust_env=False) as API:
|
76
78
|
# RUS: Вводим геоточку (самого магазина или рядом с ним) и получаем инфу о магазине
|
77
79
|
# ENG: Enter a geolocation (of the store or near it) and get info about the store
|
78
80
|
find_store = await API.find_store(longitude=37.63156, latitude=55.73768)
|
@@ -102,6 +104,12 @@ async def main():
|
|
102
104
|
# I do not recommend enabling it, if you still need to free up memory, it is better to use API.close(session=False, browser=True)
|
103
105
|
API.autoclose_browser = True
|
104
106
|
|
107
|
+
# RUS: Напрямую передается в aiohttp, так же учитывается в браузере. В первую очередь нужен для использования системного `HTTPS_PROXY`.
|
108
|
+
# Но системный прокси применяется, только если не указали иное напрямую в `API.proxy`.
|
109
|
+
# ENG: Directly passed to aiohttp, also taken into account in the browser. Primarily needed for using the system `HTTPS_PROXY`.
|
110
|
+
# But the system proxy is applied only if you did not specify otherwise directly in `API.proxy`.
|
111
|
+
API.trust_env = True
|
112
|
+
|
105
113
|
# RUS: Выводит список последних промо-акций/новостей (можно поставить ограничитель по количеству, опционально)
|
106
114
|
# ENG: Outputs a list of the latest promotions/news (you can set a limit on the number, optionally)
|
107
115
|
news = await API.get_news(limit=5)
|
@@ -121,13 +129,17 @@ async def main():
|
|
121
129
|
with open(image.name, 'wb') as f:
|
122
130
|
f.write(image.getbuffer())
|
123
131
|
|
132
|
+
# RUS: Можно указать свой таймаут (браузер может его интерпретировать как x2 т.к. там 2 итерации скачивания)
|
133
|
+
# ENG: You can specify your own timeout (the browser may interpret it as x2 since there are 2 iterations of downloading)
|
134
|
+
API.timeout = 7
|
135
|
+
|
124
136
|
# RUS: Так же как и debug, в рантайме можно переназначить прокси
|
125
137
|
# ENG: As with debug, you can reassign the proxy in runtime
|
126
138
|
API.proxy = "user:password@host:port"
|
127
|
-
# RUS:
|
128
|
-
# ENG:
|
129
|
-
await API.rebuild_connection()
|
130
|
-
await API.
|
139
|
+
# RUS: Изменения происходят сразу же, кроме product_info, т.к. за него отвечает браузер
|
140
|
+
# ENG: Changes take effect immediately, except for product_info, as it is handled by the browser
|
141
|
+
await API.rebuild_connection(session=False, browser=True)
|
142
|
+
await API.product_info(43347)
|
131
143
|
|
132
144
|
|
133
145
|
if __name__ == '__main__':
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
Pyaterochka (Пятёрочка) - https://5ka.ru/
|
4
4
|
|
5
|
+
[](https://github.com/Open-Inflation/pyaterochka_api/actions?query=workflow%3A"API+Tests+Daily?query=branch%3Amain")
|
5
6
|

|
6
7
|

|
7
8
|
[](https://pypi.org/project/pyaterochka-api/)
|
@@ -31,7 +32,7 @@ import asyncio
|
|
31
32
|
|
32
33
|
|
33
34
|
async def main():
|
34
|
-
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False) as API:
|
35
|
+
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False, trust_env=False) as API:
|
35
36
|
# RUS: Вводим геоточку (самого магазина или рядом с ним) и получаем инфу о магазине
|
36
37
|
# ENG: Enter a geolocation (of the store or near it) and get info about the store
|
37
38
|
find_store = await API.find_store(longitude=37.63156, latitude=55.73768)
|
@@ -61,6 +62,12 @@ async def main():
|
|
61
62
|
# I do not recommend enabling it, if you still need to free up memory, it is better to use API.close(session=False, browser=True)
|
62
63
|
API.autoclose_browser = True
|
63
64
|
|
65
|
+
# RUS: Напрямую передается в aiohttp, так же учитывается в браузере. В первую очередь нужен для использования системного `HTTPS_PROXY`.
|
66
|
+
# Но системный прокси применяется, только если не указали иное напрямую в `API.proxy`.
|
67
|
+
# ENG: Directly passed to aiohttp, also taken into account in the browser. Primarily needed for using the system `HTTPS_PROXY`.
|
68
|
+
# But the system proxy is applied only if you did not specify otherwise directly in `API.proxy`.
|
69
|
+
API.trust_env = True
|
70
|
+
|
64
71
|
# RUS: Выводит список последних промо-акций/новостей (можно поставить ограничитель по количеству, опционально)
|
65
72
|
# ENG: Outputs a list of the latest promotions/news (you can set a limit on the number, optionally)
|
66
73
|
news = await API.get_news(limit=5)
|
@@ -80,13 +87,17 @@ async def main():
|
|
80
87
|
with open(image.name, 'wb') as f:
|
81
88
|
f.write(image.getbuffer())
|
82
89
|
|
90
|
+
# RUS: Можно указать свой таймаут (браузер может его интерпретировать как x2 т.к. там 2 итерации скачивания)
|
91
|
+
# ENG: You can specify your own timeout (the browser may interpret it as x2 since there are 2 iterations of downloading)
|
92
|
+
API.timeout = 7
|
93
|
+
|
83
94
|
# RUS: Так же как и debug, в рантайме можно переназначить прокси
|
84
95
|
# ENG: As with debug, you can reassign the proxy in runtime
|
85
96
|
API.proxy = "user:password@host:port"
|
86
|
-
# RUS:
|
87
|
-
# ENG:
|
88
|
-
await API.rebuild_connection()
|
89
|
-
await API.
|
97
|
+
# RUS: Изменения происходят сразу же, кроме product_info, т.к. за него отвечает браузер
|
98
|
+
# ENG: Changes take effect immediately, except for product_info, as it is handled by the browser
|
99
|
+
await API.rebuild_connection(session=False, browser=True)
|
100
|
+
await API.product_info(43347)
|
90
101
|
|
91
102
|
|
92
103
|
if __name__ == '__main__':
|
@@ -4,6 +4,7 @@ from enum import Enum
|
|
4
4
|
import re
|
5
5
|
from tqdm.asyncio import tqdm
|
6
6
|
from camoufox import AsyncCamoufox
|
7
|
+
import os
|
7
8
|
|
8
9
|
|
9
10
|
class PyaterochkaAPI:
|
@@ -18,13 +19,15 @@ class PyaterochkaAPI:
|
|
18
19
|
LIST = r'(\w+)\s*:\s*\[([^\[\]]*(?:\[.*?\])*)\]' # key: [value]
|
19
20
|
FIND = r'\{.*?\}|\[.*?\]' # {} or []
|
20
21
|
|
21
|
-
def __init__(self, debug: bool = False, proxy: str = None, autoclose_browser: bool = False):
|
22
|
+
def __init__(self, debug: bool = False, proxy: str = None, autoclose_browser: bool = False, trust_env: bool = False, timeout: int = 10):
|
22
23
|
self._debug = debug
|
23
24
|
self._proxy = proxy
|
24
25
|
self._session = None
|
25
26
|
self._autoclose_browser = autoclose_browser
|
26
27
|
self._browser = None
|
27
28
|
self._bcontext = None
|
29
|
+
self._trust_env = trust_env
|
30
|
+
self._timeout = timeout
|
28
31
|
|
29
32
|
@property
|
30
33
|
def proxy(self) -> str | None:
|
@@ -38,12 +41,15 @@ class PyaterochkaAPI:
|
|
38
41
|
"""
|
39
42
|
Выполняет HTTP-запрос к указанному URL и возвращает результат.
|
40
43
|
|
41
|
-
:return: Кортеж (успех, данные или None).
|
44
|
+
:return: Кортеж (успех, данные или None, тип данных или пустота).
|
42
45
|
"""
|
43
|
-
|
44
|
-
|
46
|
+
args = {'url': url, 'timeout': aiohttp.ClientTimeout(total=self._timeout)}
|
47
|
+
if self._proxy: args["proxy"] = self._proxy
|
45
48
|
|
46
|
-
|
49
|
+
if self._debug:
|
50
|
+
print(f"Requesting \"{url}\" with proxy \"{args.get('proxy')}\", timeout {self._timeout}...", flush=True)
|
51
|
+
|
52
|
+
async with self._session.get(**args) as response:
|
47
53
|
if self._debug:
|
48
54
|
print(f"Response status: {response.status}", flush=True)
|
49
55
|
|
@@ -154,9 +160,9 @@ class PyaterochkaAPI:
|
|
154
160
|
await self._new_session(include_aiohttp=False, include_browser=True)
|
155
161
|
|
156
162
|
page = await self._bcontext.new_page()
|
157
|
-
await page.goto(url, wait_until='commit')
|
163
|
+
await page.goto(url, wait_until='commit', timeout=self._timeout * 1000)
|
158
164
|
# Wait until the selector script tag appears
|
159
|
-
await page.wait_for_selector(selector=selector, state=state)
|
165
|
+
await page.wait_for_selector(selector=selector, state=state, timeout=self._timeout * 1000)
|
160
166
|
content = await page.content()
|
161
167
|
await page.close()
|
162
168
|
|
@@ -166,7 +172,11 @@ class PyaterochkaAPI:
|
|
166
172
|
|
167
173
|
def _parse_proxy(self, proxy_str: str | None) -> dict | None:
|
168
174
|
if not proxy_str:
|
169
|
-
|
175
|
+
if self._trust_env:
|
176
|
+
proxy_str = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy")
|
177
|
+
|
178
|
+
if not proxy_str:
|
179
|
+
return None
|
170
180
|
|
171
181
|
# Example: user:pass@host:port or just host:port
|
172
182
|
match = re.match(
|
@@ -196,17 +206,35 @@ class PyaterochkaAPI:
|
|
196
206
|
await self.close(include_aiohttp=include_aiohttp, include_browser=include_browser)
|
197
207
|
|
198
208
|
if include_aiohttp:
|
199
|
-
args = {
|
200
|
-
|
209
|
+
args = {
|
210
|
+
"headers": {
|
211
|
+
"User-Agent": UserAgent().random,
|
212
|
+
"Accept": "application/json, text/plain, */*",
|
213
|
+
"Accept-Language": "en-GB,en;q=0.5",
|
214
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
215
|
+
"X-PLATFORM": "webapp",
|
216
|
+
"Origin": "https://5ka.ru",
|
217
|
+
"Connection": "keep-alive",
|
218
|
+
"Sec-Fetch-Dest": "empty",
|
219
|
+
"Sec-Fetch-Mode": "cors",
|
220
|
+
"Sec-Fetch-Site": "same-site",
|
221
|
+
"Pragma": "no-cache",
|
222
|
+
"Cache-Control": "no-cache",
|
223
|
+
"TE": "trailers",
|
224
|
+
},
|
225
|
+
"trust_env": self._trust_env,
|
226
|
+
}
|
201
227
|
self._session = aiohttp.ClientSession(**args)
|
202
228
|
|
203
|
-
if self._debug: print(f"A new connection aiohttp has been opened.
|
229
|
+
if self._debug: print(f"A new connection aiohttp has been opened. trust_env: {args.get('trust_env')}")
|
204
230
|
|
205
231
|
if include_browser:
|
206
|
-
|
232
|
+
prox = self._parse_proxy(self.proxy)
|
233
|
+
self._browser = await AsyncCamoufox(headless=not self._debug, proxy=prox, geoip=True).__aenter__()
|
207
234
|
self._bcontext = await self._browser.new_context()
|
208
235
|
|
209
|
-
|
236
|
+
toprint = "SYSTEM_PROXY" if prox and not self.proxy else prox
|
237
|
+
if self._debug: print(f"A new connection browser has been opened. Proxy used: {toprint}")
|
210
238
|
|
211
239
|
async def close(
|
212
240
|
self,
|
@@ -3,6 +3,7 @@ from enum import Enum
|
|
3
3
|
import re
|
4
4
|
import json
|
5
5
|
from io import BytesIO
|
6
|
+
from beartype import beartype
|
6
7
|
|
7
8
|
|
8
9
|
class Pyaterochka:
|
@@ -15,24 +16,30 @@ class Pyaterochka:
|
|
15
16
|
STORE = "store"
|
16
17
|
DELIVERY = "delivery"
|
17
18
|
|
18
|
-
|
19
|
+
@beartype
|
20
|
+
def __init__(self, debug: bool = False, proxy: str = None, autoclose_browser: bool = False, trust_env: bool = False, timeout: int = 10):
|
19
21
|
self._debug = debug
|
20
22
|
self._proxy = proxy
|
21
|
-
self.api = PyaterochkaAPI(debug=self._debug, proxy=self._proxy, autoclose_browser=autoclose_browser)
|
23
|
+
self.api = PyaterochkaAPI(debug=self._debug, proxy=self._proxy, autoclose_browser=autoclose_browser, trust_env=trust_env, timeout=timeout)
|
22
24
|
|
25
|
+
@beartype
|
23
26
|
def __enter__(self):
|
24
27
|
raise NotImplementedError("Use `async with Pyaterochka() as ...:`")
|
25
28
|
|
29
|
+
@beartype
|
26
30
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
27
31
|
pass
|
28
32
|
|
33
|
+
@beartype
|
29
34
|
async def __aenter__(self):
|
30
|
-
await self.rebuild_connection(session=True)
|
35
|
+
await self.rebuild_connection(session=True, browser=False)
|
31
36
|
return self
|
32
37
|
|
38
|
+
@beartype
|
33
39
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
34
40
|
await self.close()
|
35
41
|
|
42
|
+
@beartype
|
36
43
|
async def rebuild_connection(self, session: bool = True, browser: bool = False) -> None:
|
37
44
|
"""
|
38
45
|
Rebuilds the connection to the Pyaterochka API.
|
@@ -42,6 +49,7 @@ class Pyaterochka:
|
|
42
49
|
"""
|
43
50
|
await self.api._new_session(session, browser)
|
44
51
|
|
52
|
+
@beartype
|
45
53
|
async def close(self, session: bool = True, browser: bool = True) -> None:
|
46
54
|
"""
|
47
55
|
Closes the connection to the Pyaterochka API.
|
@@ -52,43 +60,74 @@ class Pyaterochka:
|
|
52
60
|
await self.api.close(include_aiohttp=session, include_browser=browser)
|
53
61
|
|
54
62
|
@property
|
63
|
+
@beartype
|
55
64
|
def debug(self) -> bool:
|
56
65
|
"""If True, it will print debug messages and disable headless in browser."""
|
57
66
|
return self._debug
|
58
67
|
|
59
68
|
@debug.setter
|
69
|
+
@beartype
|
60
70
|
def debug(self, value: bool):
|
61
71
|
self._debug = value
|
62
72
|
self.api.debug = value
|
63
73
|
|
64
74
|
@property
|
75
|
+
@beartype
|
65
76
|
def proxy(self) -> str:
|
66
77
|
"""Proxy for requests. If None, it will be used without proxy."""
|
67
78
|
return self._proxy
|
68
79
|
|
69
80
|
@proxy.setter
|
81
|
+
@beartype
|
70
82
|
def proxy(self, value: str):
|
71
83
|
self._proxy = value
|
72
84
|
self.api.proxy = value
|
73
85
|
|
74
86
|
@property
|
87
|
+
@beartype
|
75
88
|
def autoclose_browser(self) -> bool:
|
76
89
|
"""If True, the browser closes after each request, clearing all cookies and caches.
|
77
90
|
If you have more than one request and this function is enabled, the processing speed will be greatly affected! (all caches are recreated every time)"""
|
78
91
|
return self.api._autoclose_browser
|
79
92
|
|
80
|
-
@
|
93
|
+
@autoclose_browser.setter
|
94
|
+
@beartype
|
81
95
|
def autoclose_browser(self, value: bool):
|
82
96
|
self.api._autoclose_browser = value
|
97
|
+
|
98
|
+
@property
|
99
|
+
@beartype
|
100
|
+
def trust_env(self) -> bool:
|
101
|
+
"""Passed directly to aiohttp. Also, if no proxy is specified, the system proxy (variable HTTPS_PROXY) will be used for the browser."""
|
102
|
+
return self.api._trust_env
|
103
|
+
|
104
|
+
@trust_env.setter
|
105
|
+
@beartype
|
106
|
+
def trust_env(self, value: bool):
|
107
|
+
self.api._trust_env = value
|
108
|
+
|
109
|
+
@property
|
110
|
+
@beartype
|
111
|
+
def timeout(self) -> int:
|
112
|
+
"""Timeout value for the API requests."""
|
113
|
+
return self.api._timeout
|
114
|
+
|
115
|
+
@trust_env.setter
|
116
|
+
@beartype
|
117
|
+
def timeout(self, value: int):
|
118
|
+
if value <= 0:
|
119
|
+
raise ValueError("Timeout must be greater than 0")
|
83
120
|
|
121
|
+
self.api._timeout = value
|
84
122
|
|
123
|
+
@beartype
|
85
124
|
async def categories_list(
|
86
125
|
self,
|
87
126
|
subcategories: bool = False,
|
88
127
|
include_restrict: bool = True,
|
89
128
|
mode: PurchaseMode = PurchaseMode.STORE,
|
90
129
|
sap_code_store_id: str = DEFAULT_STORE_ID
|
91
|
-
) -> dict | None:
|
130
|
+
) -> list[dict] | None:
|
92
131
|
f"""
|
93
132
|
Asynchronously retrieves a list of categories from the Pyaterochka API.
|
94
133
|
|
@@ -109,9 +148,10 @@ class Pyaterochka:
|
|
109
148
|
_is_success, response, _response_type = await self.api.fetch(url=request_url)
|
110
149
|
return response
|
111
150
|
|
151
|
+
@beartype
|
112
152
|
async def products_list(
|
113
153
|
self,
|
114
|
-
category_id:
|
154
|
+
category_id: str,
|
115
155
|
mode: PurchaseMode = PurchaseMode.STORE,
|
116
156
|
sap_code_store_id: str = DEFAULT_STORE_ID,
|
117
157
|
limit: int = 30
|
@@ -120,7 +160,7 @@ class Pyaterochka:
|
|
120
160
|
Asynchronously retrieves a list of products from the Pyaterochka API for a given category.
|
121
161
|
|
122
162
|
Args:
|
123
|
-
category_id (
|
163
|
+
category_id (str): The ID of the (sub)category.
|
124
164
|
mode (PurchaseMode, optional): The purchase mode to use. Defaults to PurchaseMode.STORE.
|
125
165
|
sap_code_store_id (str, optional): The store ID (official name in API is "sap_code") to use. Defaults to "{self.DEFAULT_STORE_ID}". This lib not support search ID stores.
|
126
166
|
limit (int, optional): The maximum number of products to retrieve. Defaults to 30. Must be between 1 and 499.
|
@@ -140,6 +180,7 @@ class Pyaterochka:
|
|
140
180
|
_is_success, response, _response_type = await self.api.fetch(url=request_url)
|
141
181
|
return response
|
142
182
|
|
183
|
+
@beartype
|
143
184
|
async def product_info(self, plu_id: int) -> dict:
|
144
185
|
"""
|
145
186
|
Asynchronously retrieves product information from the Pyaterochka API for a given PLU ID. Average time processing 2 seconds (first start 6 seconds).
|
@@ -169,6 +210,7 @@ class Pyaterochka:
|
|
169
210
|
|
170
211
|
return data
|
171
212
|
|
213
|
+
@beartype
|
172
214
|
async def get_news(self, limit: int = None) -> dict | None:
|
173
215
|
"""
|
174
216
|
Asynchronously retrieves news from the Pyaterochka API.
|
@@ -187,6 +229,7 @@ class Pyaterochka:
|
|
187
229
|
|
188
230
|
return response
|
189
231
|
|
232
|
+
@beartype
|
190
233
|
async def find_store(self, longitude: float, latitude: float) -> dict | None:
|
191
234
|
"""
|
192
235
|
Asynchronously finds the store associated with the given coordinates.
|
@@ -203,6 +246,7 @@ class Pyaterochka:
|
|
203
246
|
_is_success, response, _response_type = await self.api.fetch(url=request_url)
|
204
247
|
return response
|
205
248
|
|
249
|
+
@beartype
|
206
250
|
async def download_image(self, url: str) -> BytesIO | None:
|
207
251
|
is_success, image_data, response_type = await self.api.fetch(url=url)
|
208
252
|
|
@@ -218,7 +262,8 @@ class Pyaterochka:
|
|
218
262
|
|
219
263
|
return image
|
220
264
|
|
221
|
-
|
265
|
+
@beartype
|
266
|
+
async def get_config(self) -> dict | None:
|
222
267
|
"""
|
223
268
|
Asynchronously retrieves the configuration from the hardcoded JavaScript file.
|
224
269
|
|
@@ -226,7 +271,7 @@ class Pyaterochka:
|
|
226
271
|
debug (bool, optional): Whether to print debug information. Defaults to False.
|
227
272
|
|
228
273
|
Returns:
|
229
|
-
|
274
|
+
dict | None: A dictionary representing the configuration if the request is successful, None otherwise.
|
230
275
|
"""
|
231
276
|
|
232
277
|
return await self.api.download_config(config_url=self.HARDCODE_JS_CONFIG)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pyaterochka_api
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.7
|
4
4
|
Summary: A Python API client for Pyaterochka store catalog
|
5
5
|
Home-page: https://github.com/Open-Inflation/pyaterochka_api
|
6
6
|
Author: Miskler
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
23
23
|
Requires-Dist: aiohttp
|
24
24
|
Requires-Dist: camoufox[geoip]
|
25
|
+
Requires-Dist: beartype
|
25
26
|
Requires-Dist: fake-useragent
|
26
27
|
Requires-Dist: tqdm
|
27
28
|
Provides-Extra: tests
|
@@ -43,6 +44,7 @@ Dynamic: summary
|
|
43
44
|
|
44
45
|
Pyaterochka (Пятёрочка) - https://5ka.ru/
|
45
46
|
|
47
|
+
[](https://github.com/Open-Inflation/pyaterochka_api/actions?query=workflow%3A"API+Tests+Daily?query=branch%3Amain")
|
46
48
|

|
47
49
|

|
48
50
|
[](https://pypi.org/project/pyaterochka-api/)
|
@@ -72,7 +74,7 @@ import asyncio
|
|
72
74
|
|
73
75
|
|
74
76
|
async def main():
|
75
|
-
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False) as API:
|
77
|
+
async with Pyaterochka(proxy="user:password@host:port", debug=False, autoclose_browser=False, trust_env=False) as API:
|
76
78
|
# RUS: Вводим геоточку (самого магазина или рядом с ним) и получаем инфу о магазине
|
77
79
|
# ENG: Enter a geolocation (of the store or near it) and get info about the store
|
78
80
|
find_store = await API.find_store(longitude=37.63156, latitude=55.73768)
|
@@ -102,6 +104,12 @@ async def main():
|
|
102
104
|
# I do not recommend enabling it, if you still need to free up memory, it is better to use API.close(session=False, browser=True)
|
103
105
|
API.autoclose_browser = True
|
104
106
|
|
107
|
+
# RUS: Напрямую передается в aiohttp, так же учитывается в браузере. В первую очередь нужен для использования системного `HTTPS_PROXY`.
|
108
|
+
# Но системный прокси применяется, только если не указали иное напрямую в `API.proxy`.
|
109
|
+
# ENG: Directly passed to aiohttp, also taken into account in the browser. Primarily needed for using the system `HTTPS_PROXY`.
|
110
|
+
# But the system proxy is applied only if you did not specify otherwise directly in `API.proxy`.
|
111
|
+
API.trust_env = True
|
112
|
+
|
105
113
|
# RUS: Выводит список последних промо-акций/новостей (можно поставить ограничитель по количеству, опционально)
|
106
114
|
# ENG: Outputs a list of the latest promotions/news (you can set a limit on the number, optionally)
|
107
115
|
news = await API.get_news(limit=5)
|
@@ -121,13 +129,17 @@ async def main():
|
|
121
129
|
with open(image.name, 'wb') as f:
|
122
130
|
f.write(image.getbuffer())
|
123
131
|
|
132
|
+
# RUS: Можно указать свой таймаут (браузер может его интерпретировать как x2 т.к. там 2 итерации скачивания)
|
133
|
+
# ENG: You can specify your own timeout (the browser may interpret it as x2 since there are 2 iterations of downloading)
|
134
|
+
API.timeout = 7
|
135
|
+
|
124
136
|
# RUS: Так же как и debug, в рантайме можно переназначить прокси
|
125
137
|
# ENG: As with debug, you can reassign the proxy in runtime
|
126
138
|
API.proxy = "user:password@host:port"
|
127
|
-
# RUS:
|
128
|
-
# ENG:
|
129
|
-
await API.rebuild_connection()
|
130
|
-
await API.
|
139
|
+
# RUS: Изменения происходят сразу же, кроме product_info, т.к. за него отвечает браузер
|
140
|
+
# ENG: Changes take effect immediately, except for product_info, as it is handled by the browser
|
141
|
+
await API.rebuild_connection(session=False, browser=True)
|
142
|
+
await API.product_info(43347)
|
131
143
|
|
132
144
|
|
133
145
|
if __name__ == '__main__':
|
@@ -2,11 +2,12 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='pyaterochka_api',
|
5
|
-
version='0.1.
|
5
|
+
version='0.1.7',
|
6
6
|
packages=find_packages(),
|
7
7
|
install_requires=[
|
8
8
|
'aiohttp',
|
9
9
|
'camoufox[geoip]',
|
10
|
+
'beartype',
|
10
11
|
'fake-useragent',
|
11
12
|
'tqdm'
|
12
13
|
],
|
@@ -14,7 +14,7 @@ def gen_schema(data):
|
|
14
14
|
|
15
15
|
@pytest.mark.asyncio
|
16
16
|
async def test_list(snapshot: SnapshotTest):
|
17
|
-
async with Pyaterochka() as API:
|
17
|
+
async with Pyaterochka(debug=True, trust_env=True) as API:
|
18
18
|
categories = await API.categories_list(subcategories=True)
|
19
19
|
snapshot.assert_match(gen_schema(categories), "categories_list")
|
20
20
|
|
@@ -23,25 +23,25 @@ async def test_list(snapshot: SnapshotTest):
|
|
23
23
|
|
24
24
|
@pytest.mark.asyncio
|
25
25
|
async def test_product_info(snapshot: SnapshotTest):
|
26
|
-
async with Pyaterochka() as API:
|
26
|
+
async with Pyaterochka(trust_env=True) as API:
|
27
27
|
result = await API.product_info(43347)
|
28
28
|
snapshot.assert_match(gen_schema(result), "product_info")
|
29
29
|
|
30
30
|
@pytest.mark.asyncio
|
31
31
|
async def test_get_news(snapshot: SnapshotTest):
|
32
|
-
async with Pyaterochka() as API:
|
32
|
+
async with Pyaterochka(debug=True, trust_env=True) as API:
|
33
33
|
result = await API.get_news(limit=5)
|
34
34
|
snapshot.assert_match(gen_schema(result), "get_news")
|
35
35
|
|
36
36
|
@pytest.mark.asyncio
|
37
37
|
async def test_find_store(snapshot: SnapshotTest):
|
38
|
-
async with Pyaterochka() as API:
|
38
|
+
async with Pyaterochka(debug=True, trust_env=True) as API:
|
39
39
|
categories = await API.find_store(longitude=37.63156, latitude=55.73768)
|
40
40
|
snapshot.assert_match(gen_schema(categories), "store_info")
|
41
41
|
|
42
42
|
@pytest.mark.asyncio
|
43
43
|
async def test_download_image(snapshot: SnapshotTest):
|
44
|
-
async with Pyaterochka() as API:
|
44
|
+
async with Pyaterochka(debug=True, trust_env=True) as API:
|
45
45
|
result = await API.download_image("https://photos.okolo.app/product/1392827-main/800x800.jpeg")
|
46
46
|
assert isinstance(result, BytesIO)
|
47
47
|
assert result.getvalue()
|
@@ -55,12 +55,12 @@ async def test_set_debug(snapshot: SnapshotTest):
|
|
55
55
|
|
56
56
|
@pytest.mark.asyncio
|
57
57
|
async def test_rebuild_connection(snapshot: SnapshotTest):
|
58
|
-
async with Pyaterochka() as API:
|
58
|
+
async with Pyaterochka(debug=True, trust_env=True) as API:
|
59
59
|
await API.rebuild_connection()
|
60
60
|
snapshot.assert_match("connection has been rebuilt", "rebuild_connection")
|
61
61
|
|
62
|
-
|
63
|
-
async def test_get_config(snapshot: SnapshotTest):
|
64
|
-
async with Pyaterochka() as API:
|
65
|
-
result = await API.get_config()
|
66
|
-
snapshot.assert_match(gen_schema(result), "get_config")
|
62
|
+
#@pytest.mark.asyncio
|
63
|
+
#async def test_get_config(snapshot: SnapshotTest):
|
64
|
+
# async with Pyaterochka(debug=True, trust_env=True, timeout=30) as API:
|
65
|
+
# result = await API.get_config()
|
66
|
+
# snapshot.assert_match(gen_schema(result), "get_config")
|
File without changes
|
File without changes
|
File without changes
|
{pyaterochka_api-0.1.6 → pyaterochka_api-0.1.7}/pyaterochka_api.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|