Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #315 from twm/auth
treq.auth omnibus
- Loading branch information
Showing
10 changed files
with
361 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The *auth* parameter now accepts arbitrary text and `bytes` for usernames and passwords. Text is encoded as UTF-8, per :rfc:`7617`. Previously only ASCII was allowed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The agent returned by :func:`treq.auth.add_auth()` and :func:`treq.auth.add_basic_auth()` is now marked to provide :class:`twisted.web.iweb.IAgent`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The :mod:`treq.auth` module has been documented. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
treq request APIs no longer mutates a :class:`http_headers.Headers <twisted.web.http_headers.Headers>` passed as the *headers* parameter when the *auth* parameter is also passed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Copyright (c) The treq Authors. | ||
# See LICENSE for details. | ||
from typing import Callable, List, Optional, Tuple | ||
|
||
import attr | ||
from twisted.internet.defer import Deferred | ||
from twisted.web.http_headers import Headers | ||
from twisted.web.iweb import IAgent, IBodyProducer, IResponse | ||
from zope.interface import implementer | ||
|
||
|
||
@attr.s(frozen=True, order=False, slots=True) | ||
class RequestRecord(object): | ||
""" | ||
The details of a call to :meth:`_AgentSpy.request` | ||
:ivar method: The *method* argument to :meth:`IAgent.request` | ||
:ivar uri: The *uri* argument to :meth:`IAgent.request` | ||
:ivar headers: The *headers* argument to :meth:`IAgent.request` | ||
:ivar bodyProducer: The *bodyProducer* argument to :meth:`IAgent.request` | ||
:ivar deferred: The :class:`Deferred` returned by :meth:`IAgent.request` | ||
""" | ||
|
||
method = attr.ib() # type: bytes | ||
uri = attr.ib() # type: bytes | ||
headers = attr.ib() # type: Optional[Headers] | ||
bodyProducer = attr.ib() # type: Optional[IBodyProducer] | ||
deferred = attr.ib() # type: Deferred | ||
|
||
|
||
@implementer(IAgent) | ||
@attr.s | ||
class _AgentSpy(object): | ||
""" | ||
An agent that records HTTP requests | ||
:ivar _callback: | ||
A function called with each :class:`RequestRecord` | ||
""" | ||
|
||
_callback = attr.ib() # type: Callable[Tuple[RequestRecord], None] | ||
|
||
def request(self, method, uri, headers=None, bodyProducer=None): | ||
# type: (bytes, bytes, Optional[Headers], Optional[IBodyProducer]) -> Deferred[IResponse] # noqa | ||
if not isinstance(method, bytes): | ||
raise TypeError( | ||
"method must be bytes, not {!r} of type {}".format(method, type(method)) | ||
) | ||
if not isinstance(uri, bytes): | ||
raise TypeError( | ||
"uri must be bytes, not {!r} of type {}".format(uri, type(uri)) | ||
) | ||
if headers is not None and not isinstance(headers, Headers): | ||
raise TypeError( | ||
"headers must be {}, not {!r} of type {}".format( | ||
type(Headers), headers, type(headers) | ||
) | ||
) | ||
if bodyProducer is not None and not IBodyProducer.providedBy(bodyProducer): | ||
raise TypeError( | ||
( | ||
"bodyProducer must implement IBodyProducer, but {!r} does not." | ||
" Is the implementation marked with @implementer(IBodyProducer)?" | ||
).format(bodyProducer) | ||
) | ||
d = Deferred() | ||
record = RequestRecord(method, uri, headers, bodyProducer, d) | ||
self._callback(record) | ||
return d | ||
|
||
|
||
def agent_spy(): | ||
# type: () -> Tuple[IAgent, List[RequestRecord]] | ||
""" | ||
Record HTTP requests made with an agent | ||
This is suitable for low-level testing of wrapper agents. It validates | ||
the parameters of each call to :meth:`IAgent.request` (synchronously | ||
raising :exc:`TypeError`) and captures them as a :class:`RequestRecord`, | ||
which can then be used to inspect the request or generate a response by | ||
firing the :attr:`~RequestRecord.deferred`. | ||
:returns: | ||
A two-tuple of: | ||
- An :class:`twisted.web.iweb.IAgent` | ||
- A list of calls made to the agent's | ||
:meth:`~twisted.web.iweb.IAgent.request()` method | ||
""" | ||
records = [] | ||
agent = _AgentSpy(records.append) | ||
return agent, records |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Copyright (c) The treq Authors. | ||
# See LICENSE for details. | ||
from io import BytesIO | ||
|
||
from twisted.trial.unittest import SynchronousTestCase | ||
from twisted.web.client import FileBodyProducer | ||
from twisted.web.http_headers import Headers | ||
from twisted.web.iweb import IAgent | ||
|
||
from treq._agentspy import RequestRecord, agent_spy | ||
|
||
|
||
class APISpyTests(SynchronousTestCase): | ||
""" | ||
The agent_spy API provides an agent that records each request made to it. | ||
""" | ||
|
||
def test_provides_iagent(self): | ||
""" | ||
The agent returned by agent_spy() provides the IAgent interface. | ||
""" | ||
agent, _ = agent_spy() | ||
|
||
self.assertTrue(IAgent.providedBy(agent)) | ||
|
||
def test_records(self): | ||
""" | ||
Each request made with the agent is recorded. | ||
""" | ||
agent, requests = agent_spy() | ||
|
||
body = FileBodyProducer(BytesIO(b"...")) | ||
d1 = agent.request(b"GET", b"https://foo") | ||
d2 = agent.request(b"POST", b"http://bar", Headers({})) | ||
d3 = agent.request(b"PUT", b"https://baz", None, bodyProducer=body) | ||
|
||
self.assertEqual( | ||
requests, | ||
[ | ||
RequestRecord(b"GET", b"https://foo", None, None, d1), | ||
RequestRecord(b"POST", b"http://bar", Headers({}), None, d2), | ||
RequestRecord(b"PUT", b"https://baz", None, body, d3), | ||
], | ||
) | ||
|
||
def test_record_attributes(self): | ||
""" | ||
Each parameter passed to `request` is available as an attribute of the | ||
RequestRecord. Additionally, the deferred returned by the call is | ||
available. | ||
""" | ||
agent, requests = agent_spy() | ||
headers = Headers() | ||
body = FileBodyProducer(BytesIO(b"...")) | ||
|
||
deferred = agent.request(b"method", b"uri", headers=headers, bodyProducer=body) | ||
|
||
[rr] = requests | ||
self.assertIs(rr.method, b"method") | ||
self.assertIs(rr.uri, b"uri") | ||
self.assertIs(rr.headers, headers) | ||
self.assertIs(rr.bodyProducer, body) | ||
self.assertIs(rr.deferred, deferred) | ||
|
||
def test_type_validation(self): | ||
""" | ||
The request method enforces correctness by raising TypeError when | ||
passed parameters of the wrong type. | ||
""" | ||
agent, _ = agent_spy() | ||
|
||
self.assertRaises(TypeError, agent.request, u"method not bytes", b"uri") | ||
self.assertRaises(TypeError, agent.request, b"method", u"uri not bytes") | ||
self.assertRaises( | ||
TypeError, agent.request, b"method", b"uri", {"not": "headers"} | ||
) | ||
self.assertRaises( | ||
TypeError, agent.request, b"method", b"uri", None, b"not ibodyproducer" | ||
) |
Oops, something went wrong.