Merge branch 'master' of git://github.com/channelcat/sanic

This commit is contained in:
Jack Fischer
2016-12-03 15:08:07 -05:00
17 changed files with 556 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
from .sanic import Sanic
from .blueprints import Blueprint
__version__ = '0.1.7'
__version__ = '0.1.8'
__all__ = ['Sanic', 'Blueprint']

View File

@@ -91,6 +91,12 @@ class Blueprint:
return handler
return decorator
def add_route(self, handler, uri, methods=None):
"""
"""
self.record(lambda s: s.add_route(handler, uri, methods))
return handler
def listener(self, event):
"""
"""

View File

@@ -30,6 +30,10 @@ class FileNotFound(NotFound):
self.relative_url = relative_url
class RequestTimeout(SanicException):
status_code = 408
class Handler:
handlers = None

View File

@@ -67,7 +67,7 @@ class Request(dict):
try:
self.parsed_json = json_loads(self.body)
except Exception:
log.exception("failed when parsing body as json")
log.exception("Failed when parsing body as json")
return self.parsed_json
@@ -89,7 +89,7 @@ class Request(dict):
self.parsed_form, self.parsed_files = (
parse_multipart_form(self.body, boundary))
except Exception:
log.exception("failed when parsing form")
log.exception("Failed when parsing form")
return self.parsed_form

View File

@@ -30,11 +30,17 @@ class Router:
@sanic.route('/my/url/<my_parameter>', methods=['GET', 'POST', ...])
def my_route(request, my_parameter):
do stuff...
or
@sanic.route('/my/url/<my_paramter>:type', methods['GET', 'POST', ...])
def my_route_with_type(request, my_parameter):
do stuff...
Parameters will be passed as keyword arguments to the request handling
function provided Parameters can also have a type by appending :type to
the <parameter>. If no type is provided, a string is expected. A regular
expression can also be passed in as the type
function. Provided parameters can also have a type by appending :type to
the <parameter>. Given parameter must be able to be type-casted to this.
If no type is provided, a string is expected. A regular expression can
also be passed in as the type. The argument given to the function will
always be a string, independent of the type.
"""
routes_static = None
routes_dynamic = None

View File

@@ -60,6 +60,19 @@ class Sanic:
return response
def add_route(self, handler, uri, methods=None):
"""
A helper method to register class instance or
functions as a handler to the application url
routes.
:param handler: function or class instance
:param uri: path of the URL
:param methods: list or tuple of methods allowed
:return: function or class instance
"""
self.route(uri=uri, methods=methods)(handler)
return handler
# Decorator
def exception(self, *exceptions):
"""
@@ -250,6 +263,7 @@ class Sanic:
'sock': sock,
'debug': debug,
'request_handler': self.handle_request,
'error_handler': self.error_handler,
'request_timeout': self.config.REQUEST_TIMEOUT,
'request_max_size': self.config.REQUEST_MAX_SIZE,
'loop': loop

View File

@@ -4,7 +4,8 @@ from inspect import isawaitable
from multidict import CIMultiDict
from signal import SIGINT, SIGTERM
from time import time
import httptools
from httptools import HttpRequestParser
from httptools.parser.errors import HttpParserError
try:
import uvloop as async_loop
@@ -13,6 +14,7 @@ except ImportError:
from .log import log
from .request import Request
from .exceptions import RequestTimeout
class Signal:
@@ -33,8 +35,8 @@ class HttpProtocol(asyncio.Protocol):
# connection management
'_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, signal=Signal(),
connections={}, request_timeout=60,
def __init__(self, *, loop, request_handler, error_handler,
signal=Signal(), connections={}, request_timeout=60,
request_max_size=None):
self.loop = loop
self.transport = None
@@ -45,11 +47,13 @@ class HttpProtocol(asyncio.Protocol):
self.signal = signal
self.connections = connections
self.request_handler = request_handler
self.error_handler = error_handler
self.request_timeout = request_timeout
self.request_max_size = request_max_size
self._total_request_size = 0
self._timeout_handler = None
self._last_request_time = None
self._request_handler_task = None
# -------------------------------------------- #
# Connection
@@ -75,7 +79,11 @@ class HttpProtocol(asyncio.Protocol):
self._timeout_handler = \
self.loop.call_later(time_left, self.connection_timeout)
else:
self.bail_out("Request timed out, connection closed")
if self._request_handler_task:
self._request_handler_task.cancel()
response = self.error_handler.response(
self.request, RequestTimeout('Request Timeout'))
self.write_response(response)
# -------------------------------------------- #
# Parsing
@@ -94,12 +102,12 @@ class HttpProtocol(asyncio.Protocol):
if self.parser is None:
assert self.request is None
self.headers = []
self.parser = httptools.HttpRequestParser(self)
self.parser = HttpRequestParser(self)
# Parse request chunk or close connection
try:
self.parser.feed_data(data)
except httptools.parser.errors.HttpParserError as e:
except HttpParserError as e:
self.bail_out(
"Invalid request data, connection closed ({})".format(e))
@@ -132,7 +140,7 @@ class HttpProtocol(asyncio.Protocol):
self.request.body = body
def on_message_complete(self):
self.loop.create_task(
self._request_handler_task = self.loop.create_task(
self.request_handler(self.request, self.write_response))
# -------------------------------------------- #
@@ -165,6 +173,7 @@ class HttpProtocol(asyncio.Protocol):
self.request = None
self.url = None
self.headers = None
self._request_handler_task = None
self._total_request_size = 0
def close_if_idle(self):
@@ -204,8 +213,8 @@ def trigger_events(events, loop):
loop.run_until_complete(result)
def serve(host, port, request_handler, before_start=None, after_start=None,
before_stop=None, after_stop=None,
def serve(host, port, request_handler, error_handler, before_start=None,
after_start=None, before_stop=None, after_stop=None,
debug=False, request_timeout=60, sock=None,
request_max_size=None, reuse_port=False, loop=None):
"""
@@ -240,6 +249,7 @@ def serve(host, port, request_handler, before_start=None, after_start=None,
connections=connections,
signal=signal,
request_handler=request_handler,
error_handler=error_handler,
request_timeout=request_timeout,
request_max_size=request_max_size,
), host, port, reuse_port=reuse_port, sock=sock)

View File

@@ -16,7 +16,8 @@ async def local_request(method, uri, cookies=None, *args, **kwargs):
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
loop=None, *request_args, **request_kwargs):
loop=None, debug=False, *request_args,
**request_kwargs):
results = []
exceptions = []
@@ -34,7 +35,8 @@ 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, loop=loop)
app.run(host=HOST, debug=debug, port=42101,
after_start=_collect_response, loop=loop)
if exceptions:
raise ValueError("Exception during request: {}".format(exceptions))
@@ -45,11 +47,11 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
return request, response
except:
raise ValueError(
"request and response object expected, got ({})".format(
"Request and response object expected, got ({})".format(
results))
else:
try:
return results[0]
except:
raise ValueError(
"request object expected, got ({})".format(results))
"Request object expected, got ({})".format(results))

39
sanic/views.py Normal file
View File

@@ -0,0 +1,39 @@
from .exceptions import InvalidUsage
class HTTPMethodView:
""" Simple class based implementation of view for the sanic.
You should implement methods (get, post, put, patch, delete) for the class
to every HTTP method you want to support.
For example:
class DummyView(View):
def get(self, request, *args, **kwargs):
return text('I am get method')
def put(self, request, *args, **kwargs):
return text('I am put method')
etc.
If someone tries to use a non-implemented method, there will be a
405 response.
If you need any url params just mention them in method definition:
class DummyView(View):
def get(self, request, my_param_here, *args, **kwargs):
return text('I am get method with %s' % my_param_here)
To add the view into the routing you could use
1) app.add_route(DummyView(), '/')
2) app.route('/')(DummyView())
"""
def __call__(self, request, *args, **kwargs):
handler = getattr(self, request.method.lower(), None)
if handler:
return handler(request, *args, **kwargs)
raise InvalidUsage(
'Method {} not allowed for URL {}'.format(
request.method, request.url), status_code=405)