Skip to content

Commit 85b8f7e

Browse files
authored
Merge pull request #431 from twisted/treq-401-followups
Fix `treq.request()` argument handling
2 parents 9fa973c + d381b11 commit 85b8f7e

13 files changed

Lines changed: 760 additions & 367 deletions

File tree

README.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@ treq: High-level Twisted HTTP Client API
99
:alt: calver: YY.MM.MICRO
1010
:target: https://calver.org/
1111

12-
.. |coverage| image:: https://coveralls.io/repos/github/twisted/treq/badge.svg
13-
:alt: Coverage
14-
:target: https://coveralls.io/github/twisted/treq
15-
1612
.. |documentation| image:: https://readthedocs.org/projects/treq/badge/
1713
:alt: Documentation
1814
:target: https://treq.readthedocs.org
1915

2016
|pypi|
2117
|calver|
22-
|coverage|
2318
|documentation|
2419

2520
``treq`` is an HTTP library inspired by

changelog.d/325.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
treq no longer depends on `requests`. Consequently, the ``cookies()`` method no longer returns a `requests.cookies.RequestsCookieJar <https://requests.readthedocs.io/en/latest/api/#requests.cookies.RequestsCookieJar>`_. Instead, it returns `treq.cookies.IndexableCookieJar`, which implements ``__getitem__`` as a compatibility shim. We have *not* attempted to maintain full dict-interface compatibility with ``RequestsCookieJar``, as many of its interface extensions are difficult to use securely because they obscure the relationship between cookies and domains. treq interfaces still accept a ``request.cookies.RequestsCookieJar`` as the *cookies* parameter, like any `http.cookiejar.CookieJar` subclass.

docs/api.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,16 @@ Cookies
9494

9595
.. module:: treq.cookies
9696

97-
.. autoclass:: IndexableCookieJar
98-
9997
.. autofunction:: scoped_cookie
10098

10199
.. autofunction:: search
102100

101+
.. autoclass:: IndexableCookieJar
102+
103+
.. automethod:: __getitem__
104+
105+
.. autoexception:: CookieCollision
106+
103107
Test Helpers
104108
------------
105109

docs/examples/using_cookies.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ async def using_cookies(reactor):
99

1010
jar = resp.cookies()
1111
[cookie] = treq.cookies.search(jar, domain="httpbin.org", name="hello")
12-
print(f"The server set our hello cookie to: {cookie.value}")
12+
print(f"The server set our hello cookie to: {cookie.value!r}")
1313

14-
await treq.get("https://httpbin.org/cookies", cookies=jar).addCallback(
15-
print_response
16-
)
14+
response = await treq.get("https://httpbin.org/cookies", cookies=jar)
15+
await print_response(response)
1716

1817

1918
react(using_cookies)

docs/index.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ Here is a list of `requests`_ features and their status in treq.
7373
+----------------------------------+----------+----------+
7474
| Digest Authentication | yes | no |
7575
+----------------------------------+----------+----------+
76-
| Elegant Key/Value Cookies | yes | yes |
77-
+----------------------------------+----------+----------+
7876
| Automatic Decompression | yes | yes |
7977
+----------------------------------+----------+----------+
8078
| Unicode Response Bodies | yes | yes |
@@ -87,8 +85,6 @@ Here is a list of `requests`_ features and their status in treq.
8785
+----------------------------------+----------+----------+
8886
| .netrc support | yes | no |
8987
+----------------------------------+----------+----------+
90-
| Python 3.x | yes | yes |
91-
+----------------------------------+----------+----------+
9288

9389
Table of Contents
9490
-----------------

src/treq/_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class _ITreqReactor(IReactorTCP, IReactorTime, IReactorPluggableNameResolver):
3333

3434
_S = Union[bytes, str]
3535

36-
_URLType = Union[
36+
_SomeURL = Union[
3737
str,
3838
bytes,
3939
EncodedURL,

src/treq/api.py

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# -*- test-case-name: treq.test.test_api -*-
22
from __future__ import annotations
33

4-
from typing import TypeVar, Union
4+
from typing import TypeVar
55

6-
from hyperlink import DecodedURL, EncodedURL
76
from twisted.internet.defer import Deferred
87
from twisted.internet.interfaces import IReactorTCP
98
from twisted.web.client import Agent, HTTPConnectionPool
@@ -17,6 +16,7 @@
1716
_JSONType,
1817
_Nothing,
1918
_ParamsType,
19+
_SomeURL,
2020
)
2121
from treq.client import HTTPClient
2222
from treq.response import _Response
@@ -25,11 +25,9 @@
2525

2626
R = TypeVar("R")
2727

28-
SomeURL = Union[DecodedURL, EncodedURL, str, bytes]
29-
3028

3129
def head(
32-
url: SomeURL,
30+
url: _SomeURL,
3331
*,
3432
agent: IAgent | None = None,
3533
pool: HTTPConnectionPool | None = None,
@@ -71,7 +69,7 @@ def head(
7169

7270

7371
def get(
74-
url: SomeURL,
72+
url: _SomeURL,
7573
headers: _HeadersType | None = None,
7674
*,
7775
agent: IAgent | None = None,
@@ -113,7 +111,7 @@ def get(
113111

114112

115113
def post(
116-
url: SomeURL,
114+
url: _SomeURL,
117115
data: _DataType | None = None,
118116
*,
119117
agent: IAgent | None = None,
@@ -155,7 +153,7 @@ def post(
155153

156154

157155
def put(
158-
url: SomeURL,
156+
url: _SomeURL,
159157
data: _DataType | None = None,
160158
*,
161159
agent: IAgent | None = None,
@@ -197,7 +195,7 @@ def put(
197195

198196

199197
def patch(
200-
url: SomeURL,
198+
url: _SomeURL,
201199
data: _DataType | None = None,
202200
*,
203201
agent: IAgent | None = None,
@@ -239,7 +237,7 @@ def patch(
239237

240238

241239
def delete(
242-
url: SomeURL,
240+
url: _SomeURL,
243241
*,
244242
agent: IAgent | None = None,
245243
pool: HTTPConnectionPool | None = None,
@@ -280,7 +278,26 @@ def delete(
280278
)
281279

282280

283-
def request(method, url, **kwargs):
281+
def request(
282+
method: str,
283+
url: _SomeURL,
284+
data: _DataType | None = None,
285+
*,
286+
agent: IAgent | None = None,
287+
pool: HTTPConnectionPool | None = None,
288+
persistent: bool | None = None,
289+
params: _ParamsType | None = None,
290+
headers: _HeadersType | None = None,
291+
files: _FilesType | None = None,
292+
json: _JSONType | _Nothing = _NOTHING,
293+
auth: tuple[str | bytes, str | bytes] | None = None,
294+
cookies: _CookiesType | None = None,
295+
allow_redirects: bool = True,
296+
browser_like_redirects: bool = False,
297+
unbuffered: bool = False,
298+
reactor: _ITreqReactor | None = None,
299+
timeout: float | None = None,
300+
) -> Deferred[_Response]:
284301
"""
285302
Make an HTTP request.
286303
@@ -292,27 +309,25 @@ def request(method, url, **kwargs):
292309
:class:`hyperlink.EncodedURL`
293310
294311
:param headers: Optional HTTP Headers to send with this request.
295-
:type headers: :class:`~twisted.web.http_headers.Headers` or None
296312
297313
:param params: Optional parameters to be append to the URL query string.
298314
Any query string parameters in the *url* will be preserved.
299-
:type params: dict w/ str or list/tuple of str values, list of 2-tuples, or
300-
None.
301315
302316
:param data:
303317
Arbitrary request body data.
304318
305319
If *files* is also passed this must be a :class:`dict`,
306320
a :class:`tuple` or :class:`list` of field tuples as accepted by
307-
:class:`MultiPartProducer`. The request is assigned a Content-Type of
308-
``multipart/form-data``.
321+
:class:`~treq.multipart.MultiPartProducer`. The request is assigned
322+
a Content-Type of ``multipart/form-data``.
309323
310324
If a :class:`dict`, :class:`list`, or :class:`tuple` it is URL-encoded
311325
and the request assigned a Content-Type of
312326
``application/x-www-form-urlencoded``.
313327
314328
Otherwise, any non-``None`` value is passed to the client's
315-
*data_to_body_producer* callable (by default, :class:`IBodyProducer`),
329+
*data_to_body_producer* callable (by default,
330+
:class:`~twisted.web.iweb.IBodyProducer`),
316331
which accepts :class:`bytes` and binary files like returned by
317332
``open(..., "rb")``.
318333
:type data: `bytes`, `typing.BinaryIO`, `IBodyProducer`, or `None`
@@ -332,8 +347,8 @@ def request(method, url, **kwargs):
332347
333348
Each ``binary_file`` is a file-like object open in binary mode (like
334349
returned by ``open("filename", "rb")``). The filename is taken from
335-
the file's ``name`` attribute if not specified. The Content-Type is
336-
guessed based on the filename using :func:`mimetypes.guess_type()` if
350+
the file's ``name`` attribute, if not specified. The Content-Type is
351+
guessed based on the filename using :func:`mimetypes.guess_type()`, if
337352
not specified, falling back to ``application/octet-stream``.
338353
339354
While uploading Treq will measure the length of seekable files to
@@ -352,19 +367,20 @@ def request(method, url, **kwargs):
352367
:type auth: tuple of ``('username', 'password')``
353368
354369
:param cookies: Cookies to send with this request. The HTTP kind, not the
355-
tasty kind.
356-
:type cookies: ``dict`` or ``cookielib.CookieJar``
370+
tasty kind. If you pass a :class:`dict`, the cookies therein will be
371+
scoped to the origin of *url* (see :func:`~treq.cookies.scoped_cookie()`).
372+
:type cookies: :class:`dict` or :class:`http.cookiejar.CookieJar`
357373
358374
:param int timeout: Request timeout seconds. If a response is not
359375
received within this timeframe, a connection is aborted with
360-
``CancelledError``.
376+
:exc:`~twisted.internet.defer.CancelledError`.
361377
362378
:param bool allow_redirects: Follow HTTP redirects. Default: ``True``
363379
364380
:param bool browser_like_redirects: Follow redirects like a web browser:
365381
When a 301 or 302 redirect is received in response to a POST request
366382
convert the method to GET.
367-
See :rfc:`7231 <7231#section-6.4.3>` and
383+
See :rfc:`RFC 9110 <9110#section-15.4.3>` and
368384
:class:`~twisted.web.client.BrowserLikeRedirectAgent`). Default: ``False``
369385
370386
:param bool unbuffered: Pass ``True`` to to disable response buffering. By
@@ -387,7 +403,23 @@ def request(method, url, **kwargs):
387403
The *url* param now accepts :class:`hyperlink.DecodedURL` and
388404
:class:`hyperlink.EncodedURL` objects.
389405
"""
390-
return _client(**kwargs).request(method, url, _stacklevel=3, **kwargs)
406+
return _client(agent, pool, persistent, reactor).request(
407+
method,
408+
url,
409+
_stacklevel=3,
410+
params=params,
411+
headers=headers,
412+
data=data,
413+
files=files,
414+
json=json,
415+
auth=auth,
416+
cookies=cookies,
417+
allow_redirects=allow_redirects,
418+
browser_like_redirects=browser_like_redirects,
419+
unbuffered=unbuffered,
420+
reactor=reactor,
421+
timeout=timeout,
422+
)
391423

392424

393425
#
@@ -432,6 +464,9 @@ def default_pool(reactor, pool, persistent):
432464
if get_global_pool() is None:
433465
set_global_pool(HTTPConnectionPool(reactor, persistent=True))
434466

467+
# NOTE: This doesn't necessarily return a pool that matches
468+
# the *reactor* parameter, which can produce confusing behavior
469+
# in tests that use a fake reactor.
435470
return get_global_pool()
436471

437472

src/treq/client.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33

44
import io
55
import mimetypes
6-
import uuid
76
import sys
8-
7+
import uuid
98
from collections import abc
109
from http.cookiejar import CookieJar
1110
from json import dumps as json_dumps
@@ -19,9 +18,9 @@
1918
)
2019

2120
if sys.version_info < (3, 10):
22-
from typing_extensions import ParamSpec, Concatenate
21+
from typing_extensions import Concatenate, ParamSpec
2322
else:
24-
from typing import ParamSpec, Concatenate
23+
from typing import Concatenate, ParamSpec
2524

2625
from urllib.parse import quote_plus
2726
from urllib.parse import urlencode as _urlencode
@@ -55,7 +54,7 @@
5554
_JSONType,
5655
_Nothing,
5756
_ParamsType,
58-
_URLType,
57+
_SomeURL,
5958
)
6059
from treq.auth import add_auth
6160
from treq.cookies import scoped_cookie
@@ -146,9 +145,11 @@ def deliverBody(self, protocol):
146145
P2 = ParamSpec("P2")
147146

148147

149-
def _like(c: Callable[Concatenate[HTTPClient, str, _URLType, P], R]) -> Callable[
150-
[Callable[Concatenate[HTTPClient, _URLType, P], R]],
151-
Callable[Concatenate[HTTPClient, _URLType, P], R],
148+
def _like(
149+
c: Callable[Concatenate[HTTPClient, str, _SomeURL, P], R],
150+
) -> Callable[
151+
[Callable[Concatenate[HTTPClient, _SomeURL, P], R]],
152+
Callable[Concatenate[HTTPClient, _SomeURL, P], R],
152153
]:
153154
return lambda x: x
154155

@@ -169,7 +170,7 @@ def __init__(
169170
def request(
170171
self,
171172
method: str,
172-
url: _URLType,
173+
url: _SomeURL,
173174
*,
174175
params: Optional[_ParamsType] = None,
175176
headers: Optional[_HeadersType] = None,
@@ -259,7 +260,7 @@ def gotResult(result):
259260
return d.addCallback(_Response, self._cookiejar)
260261

261262
@_like(request)
262-
def get(self, url: _URLType, **kwargs: Any) -> "Deferred[_Response]":
263+
def get(self, url: _SomeURL, **kwargs: Any) -> "Deferred[_Response]":
263264
"""
264265
See :func:`treq.get()`.
265266
"""
@@ -268,7 +269,7 @@ def get(self, url: _URLType, **kwargs: Any) -> "Deferred[_Response]":
268269

269270
@_like(request)
270271
def put(
271-
self, url: _URLType, data: Optional[_DataType] = None, **kwargs: Any
272+
self, url: _SomeURL, data: Optional[_DataType] = None, **kwargs: Any
272273
) -> "Deferred[_Response]":
273274
"""
274275
See :func:`treq.put()`.
@@ -278,7 +279,7 @@ def put(
278279

279280
@_like(request)
280281
def patch(
281-
self, url: _URLType, data: Optional[_DataType] = None, **kwargs: Any
282+
self, url: _SomeURL, data: Optional[_DataType] = None, **kwargs: Any
282283
) -> "Deferred[_Response]":
283284
"""
284285
See :func:`treq.patch()`.
@@ -288,7 +289,7 @@ def patch(
288289

289290
@_like(request)
290291
def post(
291-
self, url: _URLType, data: Optional[_DataType] = None, **kwargs: Any
292+
self, url: _SomeURL, data: Optional[_DataType] = None, **kwargs: Any
292293
) -> "Deferred[_Response]":
293294
"""
294295
See :func:`treq.post()`.
@@ -297,15 +298,15 @@ def post(
297298
return self.request("POST", url, data=data, **kwargs)
298299

299300
@_like(request)
300-
def head(self, url: _URLType, **kwargs: Any) -> "Deferred[_Response]":
301+
def head(self, url: _SomeURL, **kwargs: Any) -> "Deferred[_Response]":
301302
"""
302303
See :func:`treq.head()`.
303304
"""
304305
kwargs.setdefault("_stacklevel", 3)
305306
return self.request("HEAD", url, **kwargs)
306307

307308
@_like(request)
308-
def delete(self, url: _URLType, **kwargs: Any) -> "Deferred[_Response]":
309+
def delete(self, url: _SomeURL, **kwargs: Any) -> "Deferred[_Response]":
309310
"""
310311
See :func:`treq.delete()`.
311312
"""

0 commit comments

Comments
 (0)