From f3fc958a0c8bea34df8b278804411dd4aa761ef0 Mon Sep 17 00:00:00 2001 From: Clenimar Filemon Date: Thu, 27 Oct 2016 11:09:36 -0300 Subject: [PATCH 01/28] Fix comments over-indentation --- sanic/server.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index e4bff6fc..ca5b2974 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -45,8 +45,7 @@ class HttpProtocol(asyncio.Protocol): self._total_request_size = 0 self._timeout_handler = None - # -------------------------------------------- # - + # -------------------------------------------- # # Connection # -------------------------------------------- # @@ -64,8 +63,7 @@ class HttpProtocol(asyncio.Protocol): def connection_timeout(self): self.bail_out("Request timed out, connection closed") - # -------------------------------------------- # - + # -------------------------------------------- # # Parsing # -------------------------------------------- # From bd28da0abc16bb37384f72d6b45ac8c4c34ce35c Mon Sep 17 00:00:00 2001 From: Channel Cat Date: Fri, 28 Oct 2016 02:56:32 -0700 Subject: [PATCH 02/28] Keep-alive requests stay open if communicating --- sanic/server.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index e4bff6fc..94f59b37 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -1,4 +1,5 @@ import asyncio +from functools import partial from inspect import isawaitable from signal import SIGINT, SIGTERM @@ -16,7 +17,6 @@ from .request import Request class Signal: stopped = False - class HttpProtocol(asyncio.Protocol): __slots__ = ( # event loop, connection @@ -26,7 +26,7 @@ class HttpProtocol(asyncio.Protocol): # request config 'request_handler', 'request_timeout', 'request_max_size', # connection management - '_total_request_size', '_timeout_handler') + '_total_request_size', '_timeout_handler', '_last_communication_time') def __init__(self, *, loop, request_handler, signal=Signal(), connections={}, request_timeout=60, @@ -44,6 +44,7 @@ class HttpProtocol(asyncio.Protocol): self.request_max_size = request_max_size self._total_request_size = 0 self._timeout_handler = None + self._last_request_time = None # -------------------------------------------- # @@ -55,6 +56,7 @@ class HttpProtocol(asyncio.Protocol): self._timeout_handler = self.loop.call_later( self.request_timeout, self.connection_timeout) self.transport = transport + self._last_request_time = current_time def connection_lost(self, exc): del self.connections[self] @@ -62,7 +64,14 @@ class HttpProtocol(asyncio.Protocol): self.cleanup() def connection_timeout(self): - self.bail_out("Request timed out, connection closed") + # Check if + time_elapsed = current_time - self._last_request_time + if time_elapsed < self.request_timeout: + time_left = self.request_timeout - time_elapsed + self._timeout_handler = \ + self.loop.call_later(time_left, self.connection_timeout) + else: + self.bail_out("Request timed out, connection closed") # -------------------------------------------- # @@ -133,13 +142,15 @@ class HttpProtocol(asyncio.Protocol): if not keep_alive: self.transport.close() else: + # Record that we received data + self._last_request_time = current_time self.cleanup() except Exception as e: self.bail_out( "Writing request failed, connection closed {}".format(e)) def bail_out(self, message): - log.error(message) + log.debug(message) self.transport.close() def cleanup(self): @@ -159,6 +170,19 @@ class HttpProtocol(asyncio.Protocol): return True return False +# Keep check on the current time +current_time = None +def update_current_time(loop): + """ + Caches the current time, since it is needed + at the end of every keep-alive request to update the request timeout time + :param loop: + :return: + """ + global current_time + current_time = loop.time() + loop.call_later(0.5, partial(update_current_time, loop)) + def trigger_events(events, loop): """ @@ -173,7 +197,6 @@ def trigger_events(events, loop): if isawaitable(result): loop.run_until_complete(result) - def serve(host, port, request_handler, before_start=None, after_start=None, before_stop=None, after_stop=None, debug=False, request_timeout=60, sock=None, @@ -214,6 +237,10 @@ def serve(host, port, request_handler, before_start=None, after_start=None, request_max_size=request_max_size, ), host, port, reuse_port=reuse_port, sock=sock) + # Instead of pulling time at the end of every request, + # pull it once per minute + loop.call_soon(partial(update_current_time, loop)) + try: http_server = loop.run_until_complete(server_coroutine) except Exception: From c44b5551bcc0b7726fe695dbcd03ece5123a4905 Mon Sep 17 00:00:00 2001 From: Channel Cat Date: Fri, 28 Oct 2016 03:13:03 -0700 Subject: [PATCH 03/28] time.time faster than loop.time? --- sanic/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 94f59b37..5440b706 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -2,6 +2,7 @@ import asyncio from functools import partial from inspect import isawaitable from signal import SIGINT, SIGTERM +from time import time import httptools @@ -180,8 +181,8 @@ def update_current_time(loop): :return: """ global current_time - current_time = loop.time() - loop.call_later(0.5, partial(update_current_time, loop)) + current_time = time() + loop.call_later(1, partial(update_current_time, loop)) def trigger_events(events, loop): From 707c55fbe71a5e1fc8aad7b1ff41cd0733a8f909 Mon Sep 17 00:00:00 2001 From: Channel Cat Date: Fri, 28 Oct 2016 03:35:30 -0700 Subject: [PATCH 04/28] Fix flake8 --- sanic/server.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 5440b706..9c2ea749 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -18,6 +18,10 @@ from .request import Request class Signal: stopped = False + +current_time = None + + class HttpProtocol(asyncio.Protocol): __slots__ = ( # event loop, connection @@ -171,8 +175,7 @@ class HttpProtocol(asyncio.Protocol): return True return False -# Keep check on the current time -current_time = None + def update_current_time(loop): """ Caches the current time, since it is needed @@ -198,6 +201,7 @@ def trigger_events(events, loop): if isawaitable(result): loop.run_until_complete(result) + def serve(host, port, request_handler, before_start=None, after_start=None, before_stop=None, after_stop=None, debug=False, request_timeout=60, sock=None, From 96fcd8443f7994f73d6e82525d14009995a18f6a Mon Sep 17 00:00:00 2001 From: Ryan Kung Date: Tue, 1 Nov 2016 14:35:06 +0800 Subject: [PATCH 05/28] Update README.md via flake8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a02dc703..65c3eda7 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ app = Sanic(__name__) @app.route("/") async def test(request): - return json({ "hello": "world" }) + return json({"hello": "world"}) app.run(host="0.0.0.0", port=8000) ``` From 80fcacaf8b8458a7243c989a39442bc25ae7b940 Mon Sep 17 00:00:00 2001 From: Marcin Baran Date: Wed, 2 Nov 2016 12:27:58 +0100 Subject: [PATCH 06/28] Add loop kwargs to sanic_endpoint_test --- sanic/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/utils.py b/sanic/utils.py index 04a7803a..0749464b 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -16,7 +16,7 @@ async def local_request(method, uri, cookies=None, *args, **kwargs): def sanic_endpoint_test(app, method='get', uri='/', gather_request=True, - *request_args, **request_kwargs): + loop=None, *request_args, **request_kwargs): results = [] exceptions = [] @@ -34,7 +34,7 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True, exceptions.append(e) app.stop() - app.run(host=HOST, port=42101, after_start=_collect_response) + app.run(host=HOST, port=42101, after_start=_collect_response, loop=loop) if exceptions: raise ValueError("Exception during request: {}".format(exceptions)) From 3cd3b2d9b7c463b8490d9409674fc4fb63b347a9 Mon Sep 17 00:00:00 2001 From: imbolc Date: Thu, 3 Nov 2016 12:34:55 +0700 Subject: [PATCH 07/28] Fix upload without content-type --- sanic/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/request.py b/sanic/request.py index 2687d86b..c2ab7260 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -71,7 +71,7 @@ class Request: self.parsed_form = {} self.parsed_files = {} content_type, parameters = parse_header( - self.headers.get('Content-Type')) + self.headers.get('Content-Type', '')) try: is_url_encoded = ( content_type == 'application/x-www-form-urlencoded') From df2f91b82f9a2b666e0da0c9aa179c4de01b4a72 Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Thu, 3 Nov 2016 09:35:14 -0500 Subject: [PATCH 08/28] Add aiofiles to requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2e0feec8..cef8660e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ httptools ujson -uvloop \ No newline at end of file +uvloop +aiofiles From 3a2eeb97095330a95be790c048d982a25d6ec70a Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Sat, 5 Nov 2016 13:12:55 -0500 Subject: [PATCH 09/28] Fix value error for query string test --- tests/test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_requests.py b/tests/test_requests.py index 290c9b99..756113b2 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -56,7 +56,7 @@ def test_query_string(): async def handler(request): return text('OK') - request, response = sanic_endpoint_test(app, params=[("test1", 1), ("test2", "false"), ("test2", "true")]) + request, response = sanic_endpoint_test(app, params=[("test1", "1"), ("test2", "false"), ("test2", "true")]) assert request.args.get('test1') == '1' assert request.args.get('test2') == 'false' From ce8742c60562eb1f718704b317101542505471ca Mon Sep 17 00:00:00 2001 From: Manuel Miranda Date: Sun, 6 Nov 2016 16:26:15 +0100 Subject: [PATCH 10/28] Caching example using aiocache (#140) * Keep-alive requests stay open if communicating * time.time faster than loop.time? * Fix flake8 * Add aiofiles to requirements.txt * Caching example using aiocache * Caching example using aiocache * Added aiocache to requirements --- examples/cache_example.py | 38 ++++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 3 ++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 examples/cache_example.py diff --git a/examples/cache_example.py b/examples/cache_example.py new file mode 100644 index 00000000..44cc7140 --- /dev/null +++ b/examples/cache_example.py @@ -0,0 +1,38 @@ +""" +Example of caching using aiocache package. To run it you will need a Redis +instance running in localhost:6379. + +Running this example you will see that the first call lasts 3 seconds and +the rest are instant because the value is retrieved from the Redis. + +If you want more info about the package check +https://github.com/argaen/aiocache +""" + +import asyncio +import aiocache + +from sanic import Sanic +from sanic.response import json +from sanic.log import log +from aiocache import RedisCache, cached +from aiocache.serializers import JsonSerializer + +app = Sanic(__name__) +aiocache.set_defaults(cache=RedisCache) + + +@cached(key="my_custom_key", serializer=JsonSerializer()) +async def expensive_call(): + log.info("Expensive has been called") + await asyncio.sleep(3) + return {"test": True} + + +@app.route("/") +async def test(request): + log.info("Received GET /") + return json(await expensive_call()) + + +app.run(host="0.0.0.0", port=8000, loop=asyncio.get_event_loop()) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9593b0cf..00feb17d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ httptools ujson uvloop aiohttp +aiocache pytest coverage tox @@ -9,4 +10,4 @@ gunicorn bottle kyoukai falcon -tornado \ No newline at end of file +tornado From 1b65b2e0c658dc0e87a5a93f3e3665782b01edef Mon Sep 17 00:00:00 2001 From: Pahaz Blinov Date: Sun, 6 Nov 2016 21:08:55 +0500 Subject: [PATCH 11/28] fix(blueprints): @middleware IndexError (#139) --- sanic/blueprints.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sanic/blueprints.py b/sanic/blueprints.py index c9c54b62..bfef8557 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -109,8 +109,9 @@ class Blueprint: # Detect which way this was called, @middleware or @middleware('AT') if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): + middleware = args[0] args = [] - return register_middleware(args[0]) + return register_middleware(middleware) else: return register_middleware From 5efe51b66129d5bc902f0a70f0290b94fbd17c84 Mon Sep 17 00:00:00 2001 From: Pahaz Blinov Date: Tue, 8 Nov 2016 02:27:50 +0500 Subject: [PATCH 12/28] fix(request.py): problem in case of request without content-type header (#142) * fix(request.py): exception if access request.form on GET request * fix(request): just make a unification (parsed_form and parsed_files) + RFC fixes parsed_form and parsed_files must be a RequestParameters type in all cases! --- sanic/request.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sanic/request.py b/sanic/request.py index c2ab7260..109b1483 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -8,6 +8,12 @@ from ujson import loads as json_loads from .log import log +DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream" +# HTTP/1.1: https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1 +# > If the media type remains unknown, the recipient SHOULD treat it +# > as type "application/octet-stream" + + class RequestParameters(dict): """ Hosts a dict with lists as values where get returns the first @@ -68,14 +74,13 @@ class Request: @property def form(self): if self.parsed_form is None: - self.parsed_form = {} - self.parsed_files = {} - content_type, parameters = parse_header( - self.headers.get('Content-Type', '')) + self.parsed_form = RequestParameters() + self.parsed_files = RequestParameters() + content_type = self.headers.get( + 'Content-Type', DEFAULT_HTTP_CONTENT_TYPE) + content_type, parameters = parse_header(content_type) try: - is_url_encoded = ( - content_type == 'application/x-www-form-urlencoded') - if content_type is None or is_url_encoded: + if content_type == 'application/x-www-form-urlencoded': self.parsed_form = RequestParameters( parse_qs(self.body.decode('utf-8'))) elif content_type == 'multipart/form-data': @@ -86,7 +91,6 @@ class Request: except Exception as e: log.exception(e) pass - return self.parsed_form @property From 0e9819fba168ee2aa6b32827db751bb4c0a70dab Mon Sep 17 00:00:00 2001 From: Pahaz Blinov Date: Wed, 9 Nov 2016 00:36:37 +0500 Subject: [PATCH 13/28] fix(request): parse_multipart_form should return RequestParameters I have this code: ``` form = FileForm(request.files) ``` and it raise error because the `request.files` is `dict` but `RequestParameters` is expected =/ --- sanic/request.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sanic/request.py b/sanic/request.py index 109b1483..a843b73b 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -134,8 +134,8 @@ def parse_multipart_form(body, boundary): :param boundary: Bytes multipart boundary :return: fields (dict), files (dict) """ - files = {} - fields = {} + files = RequestParameters() + fields = RequestParameters() form_parts = body.split(boundary) for form_part in form_parts[1:-1]: @@ -166,9 +166,16 @@ def parse_multipart_form(body, boundary): post_data = form_part[line_index:-4] if file_name or file_type: - files[field_name] = File( - type=file_type, name=file_name, body=post_data) + file = File(type=file_type, name=file_name, body=post_data) + if field_name in files: + files[field_name].append(file) + else: + files[field_name] = [file] else: - fields[field_name] = post_data.decode('utf-8') + value = post_data.decode('utf-8') + if field_name in fields: + fields[field_name].append(value) + else: + fields[field_name] = [value] return fields, files From 0d9fb2f9279e3303f51a0b18204a88d6cfbb1a0e Mon Sep 17 00:00:00 2001 From: Pahaz Blinov Date: Wed, 9 Nov 2016 18:04:15 +0500 Subject: [PATCH 14/28] docs(request): return value docstring --- sanic/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/request.py b/sanic/request.py index a843b73b..7373a104 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -132,7 +132,7 @@ def parse_multipart_form(body, boundary): Parses a request body and returns fields and files :param body: Bytes request body :param boundary: Bytes multipart boundary - :return: fields (dict), files (dict) + :return: fields (RequestParameters), files (RequestParameters) """ files = RequestParameters() fields = RequestParameters() From be5588d5d80958778166e3cb321c875f260b0a17 Mon Sep 17 00:00:00 2001 From: Paul Jongsma Date: Thu, 10 Nov 2016 12:53:00 +0100 Subject: [PATCH 15/28] Add the client address to the request header --- sanic/server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sanic/server.py b/sanic/server.py index 70dca448..729e1044 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -114,6 +114,11 @@ class HttpProtocol(asyncio.Protocol): self.headers.append((name.decode(), value.decode('utf-8'))) def on_headers_complete(self): + + ra = self.transport.get_extra_info('peername') + if ra: + self.headers.append(('Remote-Addr','%s:%s' % ra)) + self.request = Request( url_bytes=self.url, headers=dict(self.headers), From b92e46df4018aa9288e1918eca2f65e2820b61c7 Mon Sep 17 00:00:00 2001 From: Paul Jongsma Date: Thu, 10 Nov 2016 13:06:27 +0100 Subject: [PATCH 16/28] fix whitespace --- sanic/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 729e1044..ec4e5780 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -114,11 +114,10 @@ class HttpProtocol(asyncio.Protocol): self.headers.append((name.decode(), value.decode('utf-8'))) def on_headers_complete(self): - ra = self.transport.get_extra_info('peername') if ra: self.headers.append(('Remote-Addr','%s:%s' % ra)) - + self.request = Request( url_bytes=self.url, headers=dict(self.headers), From 8ebc92c236652f357394e618640505a11ba49283 Mon Sep 17 00:00:00 2001 From: Paul Jongsma Date: Thu, 10 Nov 2016 13:09:37 +0100 Subject: [PATCH 17/28] pass flake8 tests --- sanic/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/server.py b/sanic/server.py index ec4e5780..e802b5df 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -116,7 +116,7 @@ class HttpProtocol(asyncio.Protocol): def on_headers_complete(self): ra = self.transport.get_extra_info('peername') if ra: - self.headers.append(('Remote-Addr','%s:%s' % ra)) + self.headers.append(('Remote-Addr', '%s:%s' % ra)) self.request = Request( url_bytes=self.url, From 28ce2447ef9c4bd75ad6e21bc9f3a660714a3b02 Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Thu, 10 Nov 2016 15:28:16 -0600 Subject: [PATCH 18/28] Update variable name Give `ra` a more explicit name --- sanic/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index e802b5df..0fd85440 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -114,9 +114,9 @@ class HttpProtocol(asyncio.Protocol): self.headers.append((name.decode(), value.decode('utf-8'))) def on_headers_complete(self): - ra = self.transport.get_extra_info('peername') - if ra: - self.headers.append(('Remote-Addr', '%s:%s' % ra)) + remote_addr = self.transport.get_extra_info('peername') + if remote_addr: + self.headers.append(('Remote-Addr', '%s:%s' % remote_addr)) self.request = Request( url_bytes=self.url, From 695f8733bbb9c35f82ec61f4c977e4828128cc19 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Fri, 11 Nov 2016 04:11:07 +0000 Subject: [PATCH 19/28] Add Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 65c3eda7..cc309c8c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Sanic +[![Join the chat at https://gitter.im/sanic-python/Lobby](https://badges.gitter.im/sanic-python/Lobby.svg)](https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + [![Build Status](https://travis-ci.org/channelcat/sanic.svg?branch=master)](https://travis-ci.org/channelcat/sanic) [![PyPI](https://img.shields.io/pypi/v/sanic.svg)](https://pypi.python.org/pypi/sanic/) [![PyPI](https://img.shields.io/pypi/pyversions/sanic.svg)](https://pypi.python.org/pypi/sanic/) From 0822674f70b05d42b87dfd82769cbc8222756468 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Fri, 11 Nov 2016 22:36:49 +0200 Subject: [PATCH 20/28] aiohttp is slightly faster actually Disabling access log increases RPS a lot --- tests/performance/aiohttp/simple_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/performance/aiohttp/simple_server.py b/tests/performance/aiohttp/simple_server.py index 8cb97b33..7c61f723 100644 --- a/tests/performance/aiohttp/simple_server.py +++ b/tests/performance/aiohttp/simple_server.py @@ -15,4 +15,4 @@ async def handle(request): app = web.Application(loop=loop) app.router.add_route('GET', '/', handle) -web.run_app(app, port=sys.argv[1]) +web.run_app(app, port=sys.argv[1], access_log=None) From edb25f799d2e85f3d715582fd2005cc80c8a6dd5 Mon Sep 17 00:00:00 2001 From: Manuel Miranda Date: Mon, 14 Nov 2016 00:11:31 +0100 Subject: [PATCH 21/28] Caching example (#150) * Caching example using aiocache * Caching example using aiocache * Added aiocache to requirements * Fixed example with newest aiocache --- examples/cache_example.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/cache_example.py b/examples/cache_example.py index 44cc7140..60823366 100644 --- a/examples/cache_example.py +++ b/examples/cache_example.py @@ -15,11 +15,14 @@ import aiocache from sanic import Sanic from sanic.response import json from sanic.log import log -from aiocache import RedisCache, cached +from aiocache import cached from aiocache.serializers import JsonSerializer app = Sanic(__name__) -aiocache.set_defaults(cache=RedisCache) + +aiocache.settings.set_defaults( + cache="aiocache.RedisCache" +) @cached(key="my_custom_key", serializer=JsonSerializer()) From 9e0747db15282f6f8f8474f32e6b9ff0f1bf9174 Mon Sep 17 00:00:00 2001 From: Jack Fischer Date: Tue, 15 Nov 2016 19:37:40 -0500 Subject: [PATCH 22/28] Example for using error_handler --- examples/exception_monitoring.py | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 examples/exception_monitoring.py diff --git a/examples/exception_monitoring.py b/examples/exception_monitoring.py new file mode 100644 index 00000000..51f8bfba --- /dev/null +++ b/examples/exception_monitoring.py @@ -0,0 +1,59 @@ +""" +Example intercepting uncaught exceptions using Sanic's error handler framework. + +This may be useful for developers wishing to use Sentry, Airbrake, etc. +or a custom system to log and monitor unexpected errors in production. + +First we create our own class inheriting from Handler in sanic.exceptions, +and pass in an instance of it when we create our Sanic instance. Inside this +class' default handler, we can do anything including sending exceptions to +an external service. +""" + + + +""" +Imports and code relevant for our CustomHandler class +(Ordinarily this would be in a separate file) +""" +from sanic.response import text +from sanic.exceptions import Handler, SanicException + +class CustomHandler(Handler): + def default(self, request, exception): + # Here, we have access to the exception object + # and can do anything with it (log, send to external service, etc) + + # Some exceptions are trivial and built into Sanic (404s, etc) + if not issubclass(type(exception), SanicException): + print(exception) + + # Then, we must finish handling the exception by + # returning our response to the client + return text("An error occured", status=500) + + + + +""" +This is an ordinary Sanic server, with the exception that we set the +server's error_handler to an instance of our CustomHandler +""" + +from sanic import Sanic +from sanic.response import json + +app = Sanic(__name__) + +handler = CustomHandler(sanic=app) +app.error_handler = handler + +@app.route("/") +async def test(request): + # Here, something occurs which causes an unexpected exception + # This exception will flow to our custom handler. + x = 1 / 0 + return json({"test": True}) + + +app.run(host="0.0.0.0", port=8000) From d9f6846c7641e6ae9c4ea4c688a4d24f56e83a8b Mon Sep 17 00:00:00 2001 From: Jack Fischer Date: Wed, 16 Nov 2016 07:55:54 -0500 Subject: [PATCH 23/28] improved default handling --- examples/exception_monitoring.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/exception_monitoring.py b/examples/exception_monitoring.py index 51f8bfba..34b46a14 100644 --- a/examples/exception_monitoring.py +++ b/examples/exception_monitoring.py @@ -28,9 +28,10 @@ class CustomHandler(Handler): if not issubclass(type(exception), SanicException): print(exception) - # Then, we must finish handling the exception by - # returning our response to the client - return text("An error occured", status=500) + # Then, we must finish handling the exception by returning + # our response to the client + # For this we can just call the super class' default handler + return super.default(self, request, exception) @@ -56,4 +57,4 @@ async def test(request): return json({"test": True}) -app.run(host="0.0.0.0", port=8000) +app.run(host="0.0.0.0", port=8000, debug=True) From edb12da154f30b208571f43aac018bd56e587b19 Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Wed, 16 Nov 2016 12:55:13 -0600 Subject: [PATCH 24/28] Fix the flake8 error caused by new flake8 version --- sanic/cookies.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sanic/cookies.py b/sanic/cookies.py index 622a5a08..b7669e76 100644 --- a/sanic/cookies.py +++ b/sanic/cookies.py @@ -30,6 +30,7 @@ def _quote(str): else: return '"' + str.translate(_Translator) + '"' + _is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch # ------------------------------------------------------------ # From f16ea20de5fa844c53639f91cf521d2993806fb8 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Fri, 18 Nov 2016 17:06:16 -0800 Subject: [PATCH 25/28] provide default app name --- sanic/sanic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sanic/sanic.py b/sanic/sanic.py index edb3a973..cbc12278 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -1,7 +1,7 @@ from asyncio import get_event_loop from collections import deque from functools import partial -from inspect import isawaitable +from inspect import isawaitable, stack, getmodulename from multiprocessing import Process, Event from signal import signal, SIGTERM, SIGINT from time import sleep @@ -18,7 +18,10 @@ from .exceptions import ServerError class Sanic: - def __init__(self, name, router=None, error_handler=None): + def __init__(self, name=None, router=None, error_handler=None): + if name is None: + frame_records = stack()[1] + name = getmodulename(frame_records[1]) self.name = name self.router = router or Router() self.error_handler = error_handler or Handler(self) From 8be4dc8fb52f27a4a51cc5d31a1aa6a767450450 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Fri, 18 Nov 2016 17:22:16 -0800 Subject: [PATCH 26/28] update readme example to use default --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc309c8c..4b6d87de 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ All tests were run on an AWS medium instance running ubuntu, using 1 process. E from sanic import Sanic from sanic.response import json -app = Sanic(__name__) +app = Sanic() @app.route("/") async def test(request): From 635921adc71c8d27bc315f3b18ad927b6e14a5a5 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Sat, 19 Nov 2016 16:03:09 -0800 Subject: [PATCH 27/28] Update headers to use CIMultiDict instead of dict --- sanic/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 0fd85440..0afeca23 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -3,7 +3,7 @@ from functools import partial from inspect import isawaitable from signal import SIGINT, SIGTERM from time import time - +from aiohttp import CIMultiDict import httptools try: @@ -120,7 +120,7 @@ class HttpProtocol(asyncio.Protocol): self.request = Request( url_bytes=self.url, - headers=dict(self.headers), + headers=CIMultiDict(self.headers), version=self.parser.get_http_version(), method=self.parser.get_method().decode() ) From d02fffb6b84d4d9e7e728c38c6115ee3b417d662 Mon Sep 17 00:00:00 2001 From: Channel Cat Date: Sat, 19 Nov 2016 18:41:40 -0800 Subject: [PATCH 28/28] Fixing import of CIMultiDict --- requirements.txt | 1 + sanic/server.py | 2 +- setup.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cef8660e..3acfbb1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ httptools ujson uvloop aiofiles +multidict diff --git a/sanic/server.py b/sanic/server.py index 0afeca23..9081b729 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -1,9 +1,9 @@ import asyncio from functools import partial from inspect import isawaitable +from multidict import CIMultiDict from signal import SIGINT, SIGTERM from time import time -from aiohttp import CIMultiDict import httptools try: diff --git a/setup.py b/setup.py index 60606ad4..e6e9b4cc 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ setup( 'httptools>=0.0.9', 'ujson>=1.35', 'aiofiles>=0.3.0', + 'multidict>=2.0', ], classifiers=[ 'Development Status :: 2 - Pre-Alpha',