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.
@@ -1,11 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: browserfetch
3
- Version: 0.10.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.10.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_ = dumps(data).encode()
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('host WebSocket was closed')
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(dumps(r).encode() + b'\0' + body)
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
- url += urlencode(params)
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: bytes | dict | str | None = None,
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 = dumps(data).encode()
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