browserfetch 0.10.0__tar.gz → 0.11.0__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.
- {browserfetch-0.10.0 → browserfetch-0.11.0}/PKG-INFO +3 -2
- {browserfetch-0.10.0 → browserfetch-0.11.0}/browserfetch/__init__.py +28 -9
- browserfetch-0.11.0/tests/test_init.py +25 -0
- {browserfetch-0.10.0 → browserfetch-0.11.0}/LICENSE +0 -0
- {browserfetch-0.10.0 → browserfetch-0.11.0}/README.rst +0 -0
- {browserfetch-0.10.0 → browserfetch-0.11.0}/browserfetch/__main__.py +0 -0
- {browserfetch-0.10.0 → browserfetch-0.11.0}/browserfetch/browserfetch.js +0 -0
- {browserfetch-0.10.0 → browserfetch-0.11.0}/pyproject.toml +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: browserfetch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: fetch in Python using your browser!
|
|
5
5
|
Keywords: browser,fetch,python,cookies
|
|
6
6
|
Requires-Python: >=3.11
|
|
7
7
|
Description-Content-Type: text/x-rst
|
|
8
8
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Requires-Dist: aiohttp
|
|
10
11
|
Requires-Dist: pyperclip
|
|
11
12
|
Requires-Dist: cyclopts
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.11.0'
|
|
2
|
+
__all__ = [
|
|
3
|
+
'BrowserError',
|
|
4
|
+
'evaluate',
|
|
5
|
+
'fetch',
|
|
6
|
+
'get',
|
|
7
|
+
'post',
|
|
8
|
+
'Response',
|
|
9
|
+
'start_server',
|
|
10
|
+
]
|
|
2
11
|
import atexit
|
|
3
12
|
from asyncio import (
|
|
4
13
|
AbstractEventLoop,
|
|
@@ -10,10 +19,11 @@ from asyncio import (
|
|
|
10
19
|
)
|
|
11
20
|
from collections import defaultdict
|
|
12
21
|
from dataclasses import dataclass
|
|
22
|
+
from functools import partial as _partial
|
|
13
23
|
from html import escape
|
|
14
|
-
from json import dumps, loads
|
|
24
|
+
from json import dumps as _dumps, loads
|
|
15
25
|
from logging import getLogger
|
|
16
|
-
from urllib.parse import urlencode
|
|
26
|
+
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
|
|
17
27
|
|
|
18
28
|
from aiohttp import ClientSession, ClientWebSocketResponse
|
|
19
29
|
from aiohttp.web import (
|
|
@@ -34,6 +44,8 @@ hosts: defaultdict[
|
|
|
34
44
|
# maps response event id to its response event or response dict
|
|
35
45
|
responses: dict[int, Event | dict] = {}
|
|
36
46
|
|
|
47
|
+
_jdumps = _partial(_dumps, separators=(',', ':'), ensure_ascii=False)
|
|
48
|
+
|
|
37
49
|
|
|
38
50
|
class BrowserError(Exception):
|
|
39
51
|
pass
|
|
@@ -83,7 +95,7 @@ async def _request(
|
|
|
83
95
|
data['event_id'] = event_id
|
|
84
96
|
responses[event_id] = response_ready
|
|
85
97
|
|
|
86
|
-
bytes_ =
|
|
98
|
+
bytes_ = _jdumps(data).encode()
|
|
87
99
|
if body is not None:
|
|
88
100
|
bytes_ += b'\0' + body
|
|
89
101
|
|
|
@@ -147,7 +159,7 @@ async def _(request):
|
|
|
147
159
|
try:
|
|
148
160
|
await receive_responses(ws)
|
|
149
161
|
except TypeError:
|
|
150
|
-
logger.info('
|
|
162
|
+
logger.info('WebSocket was closed by browser')
|
|
151
163
|
hosts[host] = Event()
|
|
152
164
|
return ws
|
|
153
165
|
|
|
@@ -175,7 +187,7 @@ async def _(request: Request) -> WebSocketResponse:
|
|
|
175
187
|
|
|
176
188
|
r['event_id'] = relay_event_id
|
|
177
189
|
body = r.pop('body')
|
|
178
|
-
await ws.send_bytes(
|
|
190
|
+
await ws.send_bytes(_jdumps(r).encode() + b'\0' + body)
|
|
179
191
|
|
|
180
192
|
|
|
181
193
|
@routes.get('/')
|
|
@@ -251,7 +263,14 @@ async def fetch(
|
|
|
251
263
|
:return: a dict of response values.
|
|
252
264
|
"""
|
|
253
265
|
if params is not None:
|
|
254
|
-
|
|
266
|
+
parsed_url = urlparse(url)
|
|
267
|
+
url = urlunparse(
|
|
268
|
+
parsed_url._replace(
|
|
269
|
+
query=urlencode(
|
|
270
|
+
parse_qsl(parsed_url.query) + [*params.items()]
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
)
|
|
255
274
|
|
|
256
275
|
d = await _request(
|
|
257
276
|
host,
|
|
@@ -291,7 +310,7 @@ async def post(
|
|
|
291
310
|
url: str,
|
|
292
311
|
*,
|
|
293
312
|
params: dict | None = None,
|
|
294
|
-
data
|
|
313
|
+
data=None,
|
|
295
314
|
form: dict | None = None,
|
|
296
315
|
timeout: int | float = 95,
|
|
297
316
|
options: dict | None = None,
|
|
@@ -308,7 +327,7 @@ async def post(
|
|
|
308
327
|
elif isinstance(data, bytes):
|
|
309
328
|
body = data
|
|
310
329
|
else:
|
|
311
|
-
body =
|
|
330
|
+
body = _jdumps(data).encode()
|
|
312
331
|
headers = options.setdefault('headers', {})
|
|
313
332
|
headers['Content-Type'] = 'application/json'
|
|
314
333
|
elif form is not None:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from unittest.mock import Mock, patch
|
|
3
|
+
|
|
4
|
+
from browserfetch import fetch
|
|
5
|
+
|
|
6
|
+
request_mock = Mock()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@patch('browserfetch._request', new=request_mock)
|
|
10
|
+
async def test_params():
|
|
11
|
+
with suppress(TypeError):
|
|
12
|
+
await fetch(
|
|
13
|
+
url='http://stackoverflow.com/search?q=question',
|
|
14
|
+
params={'lang': 'en', 'tag': 'python', 'q': True},
|
|
15
|
+
)
|
|
16
|
+
request_mock.assert_called_once_with(
|
|
17
|
+
None, # host
|
|
18
|
+
{
|
|
19
|
+
'action': 'fetch',
|
|
20
|
+
'url': 'http://stackoverflow.com/search?q=question&lang=en&tag=python&q=True',
|
|
21
|
+
'options': None,
|
|
22
|
+
'timeout': 95,
|
|
23
|
+
}, # data
|
|
24
|
+
None, # body
|
|
25
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|