Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add response hook #2605

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e47676b
code and unit tests
Dec 5, 2017
312e72b
per flake8 recomendations
Dec 6, 2017
083172d
- Update docs
Dec 12, 2017
a6ab91d
Merge branch 'upstream/master' into add_response_hook
Dec 12, 2017
a29dc9c
- add myself to contributors.txt
Dec 12, 2017
b988674
increase coverage
Dec 13, 2017
ab5dc4d
Merge branch 'upstream/master' into add_response_hook
Dec 13, 2017
7e3f555
Drop encoding param (#2606)
asvetlov Dec 13, 2017
7054639
Fix docstrings for req.host and req.remote (#2607)
asvetlov Dec 14, 2017
affbbd9
Merge branch '2.3'
asvetlov Dec 14, 2017
245d3bf
Update spelling whitelist
asvetlov Dec 14, 2017
f0fe923
Merge branch '2.3'
asvetlov Dec 14, 2017
76f9274
Support .netrc by trust_env (#2584)
Dec 14, 2017
dd234f3
Document .netrc support
asvetlov Dec 14, 2017
c256c29
Fix small typos (#2609)
socketpair Dec 14, 2017
b57caab
Avoid to create unnecessary resources (#2586) (#2603)
sheb Dec 14, 2017
29e5eac
Fix documentation around JSON and content-type (#2598)
socketpair Dec 11, 2017
1cb4fa2
Small documentation fix
asvetlov Dec 14, 2017
d02ca2a
Fix docs typo
asvetlov Dec 15, 2017
05a5a6c
Add Nikolay Kim to contributors
asvetlov Dec 16, 2017
c178a99
Add support to Flask-style decorators with class-based Views (#2611)
sheb Dec 16, 2017
486eaf2
Drop access to TCP tuning options outside of the stream scope (#2612)
pfreixes Dec 18, 2017
4412fc9
Fix #2604: Drop tcp_cork/tcp_nodelay from docs
asvetlov Dec 18, 2017
9cc03cd
allow custom port to TestServer (#2613)
samuelcolvin Dec 18, 2017
bd1c617
web_site -> web_runner
asvetlov Dec 19, 2017
51961a7
Add a link to aiohttp-remotes
asvetlov Dec 19, 2017
6f955fb
Merge branch '2.3'
asvetlov Dec 19, 2017
c4efde8
Fix spelling
asvetlov Dec 19, 2017
97b9373
Merge branch '2.3'
asvetlov Dec 19, 2017
19c138c
web.run_app support access_log_class param (#2616)
codeif Dec 21, 2017
b513a1e
Add source=self for ResourceWarning
asvetlov Dec 21, 2017
dc7d0c5
Fix tests
asvetlov Dec 21, 2017
6650b35
Restore compatibility with Python 3.5
asvetlov Dec 21, 2017
d45a1ee
fix test case
Dec 21, 2017
3406c86
code and unit tests
Dec 5, 2017
9a2955f
per flake8 recomendations
Dec 6, 2017
7b9e56f
- Update docs
Dec 12, 2017
7f7ede0
- add myself to contributors.txt
Dec 12, 2017
9fde198
increase coverage
Dec 13, 2017
f1fec44
fix test case
Dec 21, 2017
d7df00a
Merge branch 'add_response_hook' of https://github.com/dadocsis/aioht…
Dec 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* The format is <Name> <Surname>.
* Please keep alphabetical order, the file is sorted by names.
- [ ] Add a new news fragment into the `CHANGES` folder
* name it `<issue_id>.<type>` for example (588.bug)
* name it `<issue_id>.<type>` for example (588.bugfix)
* if you don't have an `issue_id` change it to the pr id after creating the pr
* ensure type is one of the following:
* `.feature`: Signifying a new feature.
Expand Down
1 change: 1 addition & 0 deletions CHANGES/2472.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support to Flask-style decorators with class-based Views.
1 change: 1 addition & 0 deletions CHANGES/2581.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support `.netrc` by `trust_env`
2 changes: 2 additions & 0 deletions CHANGES/2586.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid to create a new resource when adding a route with the same
name and path of the last added resource
1 change: 1 addition & 0 deletions CHANGES/2591.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix docstring for request.host
1 change: 1 addition & 0 deletions CHANGES/2592.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix docstring for request.remote
1 change: 1 addition & 0 deletions CHANGES/2604.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Drop access to TCP tuning options from PayloadWriter and Response classes
1 change: 1 addition & 0 deletions CHANGES/2606.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Drop deprecated `encoding` parameter from client API
1 change: 1 addition & 0 deletions CHANGES/2613.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow a custom port to be used by `TestServer` (and associated pytest fixtures)
1 change: 1 addition & 0 deletions CHANGES/2615.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add param access_log_class to web.run_app function
1 change: 1 addition & 0 deletions CHANGES/434.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow Custom Authentication handlers to be passed into ClientSession
4 changes: 4 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Morgan Delahaye-Prat
Moss Collum
Mun Gwan-gyeong
Nicolas Braem
Nikolay Kim
Nikolay Novik
Olaf Conradi
Pahaz Blinov
Expand Down Expand Up @@ -200,6 +201,8 @@ W. Trevor King
Will McGugan
Willem de Groot
Wilson Ong
Wei Lin
Weiwei Wang
Yannick Koechlin
Yannick Péroux
Yegor Roganov
Expand All @@ -209,3 +212,4 @@ Yury Selivanov
Yusuke Tsutsumi
Марк Коренберг
Семён Марьясин
Alberto Zuniga
29 changes: 13 additions & 16 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from .connector import * # noqa
from .connector import TCPConnector
from .cookiejar import CookieJar
from .helpers import (CeilTimeout, TimeoutHandle, proxies_from_env, sentinel,
strip_auth_from_url)
from .helpers import (PY_36, CeilTimeout, TimeoutHandle, proxies_from_env,
sentinel, strip_auth_from_url)
from .http import WS_KEY, WebSocketReader, WebSocketWriter
from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse
from .streams import FlowControlDataQueue
Expand Down Expand Up @@ -83,7 +83,7 @@ def __init__(self, *, connector=None, loop=None, cookies=None,

if implicit_loop and not loop.is_running():
warnings.warn("Creating a client session outside of coroutine is "
"a very dangerous idea", ResourceWarning,
"a very dangerous idea",
stacklevel=2)
context = {'client_session': self,
'message': 'Creating a client session outside '
Expand Down Expand Up @@ -133,8 +133,13 @@ def __init__(self, *, connector=None, loop=None, cookies=None,

def __del__(self, _warnings=warnings):
if not self.closed:
if PY_36:
kwargs = {'source': self}
else:
kwargs = {}
_warnings.warn("Unclosed client session {!r}".format(self),
ResourceWarning)
ResourceWarning,
**kwargs)
context = {'client_session': self,
'message': 'Unclosed client session'}
if self._source_traceback is not None:
Expand All @@ -154,7 +159,6 @@ async def _request(self, method, url, *,
auth=None,
allow_redirects=True,
max_redirects=10,
encoding=None,
compress=None,
chunked=None,
expect100=False,
Expand All @@ -172,12 +176,6 @@ async def _request(self, method, url, *,
# set the default to None because we need to detect if the user wants
# to use the existing timeouts by setting timeout to None.

if encoding is not None:
warnings.warn(
"encoding parameter is not supported, "
"please use FormData(charset='utf-8') instead",
DeprecationWarning)

if self.closed:
raise RuntimeError('Session is closed')

Expand Down Expand Up @@ -213,7 +211,7 @@ async def _request(self, method, url, *,
try:
proxy = URL(proxy)
except ValueError:
raise InvalidURL(url)
raise InvalidURL(proxy)

# timeout is cumulative for all request operations
# (request, redirects, responses, data consuming)
Expand All @@ -222,8 +220,6 @@ async def _request(self, method, url, *,
timeout if timeout is not sentinel else self._read_timeout)
handle = tm.start()

url = URL(url)

traces = [
Trace(
self,
Expand Down Expand Up @@ -314,6 +310,9 @@ async def _request(self, method, url, *,

self._cookie_jar.update_cookies(resp.cookies, resp.url)

# emit event
resp = await req.dispatch_hooks("response")

# redirects
if resp.status in (
301, 302, 303, 307, 308) and allow_redirects:
Expand Down Expand Up @@ -793,7 +792,6 @@ def request(method, url, *,
auth=None,
allow_redirects=True,
max_redirects=10,
encoding=None,
version=http.HttpVersion11,
compress=None,
chunked=None,
Expand Down Expand Up @@ -854,7 +852,6 @@ def request(method, url, *,
auth=auth,
allow_redirects=allow_redirects,
max_redirects=max_redirects,
encoding=encoding,
compress=compress,
chunked=chunked,
expect100=expect100,
Expand Down
77 changes: 60 additions & 17 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import codecs
import collections
import inspect
import io
import json
import ssl
Expand All @@ -20,7 +21,7 @@
ClientResponseError, ContentTypeError,
InvalidURL)
from .formdata import FormData
from .helpers import HeadersMixin, TimerNoop, noop, reify, set_result
from .helpers import PY_36, HeadersMixin, TimerNoop, noop, reify, set_result
from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, PayloadWriter
from .log import client_logger
from .streams import StreamReader
Expand Down Expand Up @@ -56,7 +57,36 @@
ConnectionKey = namedtuple('ConnectionKey', ['host', 'port', 'ssl'])


class ClientRequest:
class HooksMixin:
def register_hook(self, event, hook):
"""Register an event hook."""
if event not in self._hooks:
raise ValueError(
"Unsupported event specified, "
"with event name {0}".format(event))

assert inspect.iscoroutinefunction(hook), \
"hook {0} must be a coroutine".format(
getattr(hook, '__name__', ''))

self._hooks[event].append(hook)

def deregister_hook(self, event, hook):
"""Deregister a previously registered event hook.
Returns True if the hook existed, False if not.
"""
try:
self._hooks[event].remove(hook)
return True
except ValueError:
return False

async def dispatch_hooks(self, event, **extras):
"""implement this method to call hooks"""
raise NotImplementedError()


class ClientRequest(HooksMixin):
GET_METHODS = {
hdrs.METH_GET,
hdrs.METH_HEAD,
Expand All @@ -70,6 +100,7 @@ class ClientRequest:
hdrs.ACCEPT: '*/*',
hdrs.ACCEPT_ENCODING: 'gzip, deflate',
}
EVENTS = ['response']

body = b''
auth = None
Expand Down Expand Up @@ -123,6 +154,7 @@ def __init__(self, method, url, *,
self._auto_decompress = auto_decompress
self._verify_ssl = verify_ssl
self._ssl_context = ssl_context
self._hooks = dict((event, []) for event in self.EVENTS)

if loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1))
Expand Down Expand Up @@ -279,16 +311,15 @@ def update_transfer_encoding(self):
self.headers[hdrs.CONTENT_LENGTH] = str(len(self.body))

def update_auth(self, auth):
"""Set basic auth."""
"""Set basic auth. or custom if callable"""
if auth is None:
auth = self.auth
if auth is None:
return

if not isinstance(auth, helpers.BasicAuth):
raise TypeError('BasicAuth() tuple is required instead')

self.headers[hdrs.AUTHORIZATION] = auth.encode()
if not callable(auth):
raise TypeError('Auth must be a callable')
auth(self)

def update_body_from_data(self, body):
if not body:
Expand Down Expand Up @@ -488,6 +519,15 @@ def terminate(self):
self._writer.cancel()
self._writer = None

async def dispatch_hooks(self, event, **extras):
"""call and hooks associated to an event"""
"""return results which should be a response or None"""
rsp = None
for hook in self._hooks[event]:
rsp = await hook(self, **extras)

return rsp or self.response


class ClientResponse(HeadersMixin):

Expand Down Expand Up @@ -578,16 +618,19 @@ def __del__(self, _warnings=warnings):
self._connection.release()
self._cleanup_writer()

# warn
if __debug__:
if self._loop.get_debug():
_warnings.warn("Unclosed response {!r}".format(self),
ResourceWarning)
context = {'client_response': self,
'message': 'Unclosed response'}
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)
if self._loop.get_debug():
if PY_36:
kwargs = {'source': self}
else:
kwargs = {}
_warnings.warn("Unclosed response {!r}".format(self),
ResourceWarning,
**kwargs)
context = {'client_response': self,
'message': 'Unclosed response'}
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)

def __repr__(self):
out = io.StringIO()
Expand Down
16 changes: 13 additions & 3 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
ssl_errors)
from .client_proto import ResponseHandler
from .client_reqrep import ClientRequest
from .helpers import is_ip_address, noop, sentinel
from .helpers import PY_36, is_ip_address, noop, sentinel
from .locks import EventResultOrError
from .resolver import DefaultResolver

Expand Down Expand Up @@ -61,8 +61,13 @@ def __repr__(self):

def __del__(self, _warnings=warnings):
if self._protocol is not None:
if PY_36:
kwargs = {'source': self}
else:
kwargs = {}
_warnings.warn('Unclosed connection {!r}'.format(self),
ResourceWarning)
ResourceWarning,
**kwargs)
if self._loop.is_closed():
return

Expand Down Expand Up @@ -210,8 +215,13 @@ def __del__(self, _warnings=warnings):

self.close()

if PY_36:
kwargs = {'source': self}
else:
kwargs = {}
_warnings.warn("Unclosed connector {!r}".format(self),
ResourceWarning)
ResourceWarning,
**kwargs)
context = {'connector': self,
'connections': conns,
'message': 'Unclosed connector'}
Expand Down
Loading