Merge branch 'master' into sphinx-docs
This commit is contained in:
commit
7c4ffa8866
15
README.rst
15
README.rst
|
@ -56,6 +56,21 @@ Hello World Example
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8000)
|
||||||
|
|
||||||
|
SSL Example
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Optionally pass in an SSLContext:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import ssl
|
||||||
|
certificate = "/path/to/certificate"
|
||||||
|
keyfile = "/path/to/keyfile"
|
||||||
|
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
|
||||||
|
context.load_cert_chain(certificate, keyfile=keyfile)
|
||||||
|
|
||||||
|
app.run(host="0.0.0.0", port=8443, ssl=context)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,16 @@ from sanic.blueprints import Blueprint
|
||||||
app = Sanic()
|
app = Sanic()
|
||||||
bp = Blueprint("bp", host="bp.example.com")
|
bp = Blueprint("bp", host="bp.example.com")
|
||||||
|
|
||||||
|
@app.route('/', host=["example.com",
|
||||||
|
"somethingelse.com",
|
||||||
|
"therestofyourdomains.com"])
|
||||||
|
async def hello(request):
|
||||||
|
return text("Some defaults")
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("Answer")
|
return text("Answer")
|
||||||
|
|
||||||
@app.route('/', host="sub.example.com")
|
@app.route('/', host="sub.example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("42")
|
return text("42")
|
||||||
|
|
|
@ -3,6 +3,7 @@ from collections import defaultdict
|
||||||
|
|
||||||
class BlueprintSetup:
|
class BlueprintSetup:
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint state like object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, blueprint, app, options):
|
def __init__(self, blueprint, app, options):
|
||||||
|
@ -32,13 +33,13 @@ class BlueprintSetup:
|
||||||
|
|
||||||
def add_exception(self, handler, *args, **kwargs):
|
def add_exception(self, handler, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Registers exceptions to sanic
|
Registers exceptions to sanic.
|
||||||
"""
|
"""
|
||||||
self.app.exception(*args, **kwargs)(handler)
|
self.app.exception(*args, **kwargs)(handler)
|
||||||
|
|
||||||
def add_static(self, uri, file_or_directory, *args, **kwargs):
|
def add_static(self, uri, file_or_directory, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Registers static files to sanic
|
Registers static files to sanic.
|
||||||
"""
|
"""
|
||||||
if self.url_prefix:
|
if self.url_prefix:
|
||||||
uri = self.url_prefix + uri
|
uri = self.url_prefix + uri
|
||||||
|
@ -47,7 +48,7 @@ class BlueprintSetup:
|
||||||
|
|
||||||
def add_middleware(self, middleware, *args, **kwargs):
|
def add_middleware(self, middleware, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Registers middleware to sanic
|
Registers middleware to sanic.
|
||||||
"""
|
"""
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
self.app.middleware(*args, **kwargs)(middleware)
|
self.app.middleware(*args, **kwargs)(middleware)
|
||||||
|
@ -77,11 +78,13 @@ class Blueprint:
|
||||||
|
|
||||||
def make_setup_state(self, app, options):
|
def make_setup_state(self, app, options):
|
||||||
"""
|
"""
|
||||||
|
Returns a new BlueprintSetup object
|
||||||
"""
|
"""
|
||||||
return BlueprintSetup(self, app, options)
|
return BlueprintSetup(self, app, options)
|
||||||
|
|
||||||
def register(self, app, options):
|
def register(self, app, options):
|
||||||
"""
|
"""
|
||||||
|
Registers the blueprint to the sanic app.
|
||||||
"""
|
"""
|
||||||
state = self.make_setup_state(app, options)
|
state = self.make_setup_state(app, options)
|
||||||
for deferred in self.deferred_functions:
|
for deferred in self.deferred_functions:
|
||||||
|
@ -89,6 +92,9 @@ class Blueprint:
|
||||||
|
|
||||||
def route(self, uri, methods=None, host=None):
|
def route(self, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint route from a decorated function.
|
||||||
|
:param uri: Endpoint at which the route will be accessible.
|
||||||
|
:param methods: List of acceptable HTTP methods.
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
||||||
|
@ -97,12 +103,18 @@ class Blueprint:
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods=None, host=None):
|
def add_route(self, handler, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint route from a function.
|
||||||
|
:param handler: Function to handle uri request.
|
||||||
|
:param uri: Endpoint at which the route will be accessible.
|
||||||
|
:param methods: List of acceptable HTTP methods.
|
||||||
"""
|
"""
|
||||||
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def listener(self, event):
|
def listener(self, event):
|
||||||
"""
|
"""
|
||||||
|
Create a listener from a decorated function.
|
||||||
|
:param event: Event to listen to.
|
||||||
"""
|
"""
|
||||||
def decorator(listener):
|
def decorator(listener):
|
||||||
self.listeners[event].append(listener)
|
self.listeners[event].append(listener)
|
||||||
|
@ -111,6 +123,7 @@ class Blueprint:
|
||||||
|
|
||||||
def middleware(self, *args, **kwargs):
|
def middleware(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint middleware from a decorated function.
|
||||||
"""
|
"""
|
||||||
def register_middleware(middleware):
|
def register_middleware(middleware):
|
||||||
self.record(
|
self.record(
|
||||||
|
@ -127,6 +140,7 @@ class Blueprint:
|
||||||
|
|
||||||
def exception(self, *args, **kwargs):
|
def exception(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint exception from a decorated function.
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
self.record(lambda s: s.add_exception(handler, *args, **kwargs))
|
self.record(lambda s: s.add_exception(handler, *args, **kwargs))
|
||||||
|
@ -135,6 +149,9 @@ class Blueprint:
|
||||||
|
|
||||||
def static(self, uri, file_or_directory, *args, **kwargs):
|
def static(self, uri, file_or_directory, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
Creates a blueprint static route from a decorated function.
|
||||||
|
:param uri: Endpoint at which the route will be accessible.
|
||||||
|
:param file_or_directory: Static asset.
|
||||||
"""
|
"""
|
||||||
self.record(
|
self.record(
|
||||||
lambda s: s.add_static(uri, file_or_directory, *args, **kwargs))
|
lambda s: s.add_static(uri, file_or_directory, *args, **kwargs))
|
||||||
|
|
|
@ -174,6 +174,7 @@ class Handler:
|
||||||
try:
|
try:
|
||||||
response = handler(request=request, exception=exception)
|
response = handler(request=request, exception=exception)
|
||||||
except:
|
except:
|
||||||
|
log.error(format_exc())
|
||||||
if self.sanic.debug:
|
if self.sanic.debug:
|
||||||
response_message = (
|
response_message = (
|
||||||
'Exception raised in exception handler "{}" '
|
'Exception raised in exception handler "{}" '
|
||||||
|
@ -186,6 +187,7 @@ class Handler:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def default(self, request, exception):
|
def default(self, request, exception):
|
||||||
|
log.error(format_exc())
|
||||||
if issubclass(type(exception), SanicException):
|
if issubclass(type(exception), SanicException):
|
||||||
return text(
|
return text(
|
||||||
'Error: {}'.format(exception),
|
'Error: {}'.format(exception),
|
||||||
|
|
|
@ -143,21 +143,45 @@ class HTTPResponse:
|
||||||
|
|
||||||
|
|
||||||
def json(body, status=200, headers=None):
|
def json(body, status=200, headers=None):
|
||||||
|
"""
|
||||||
|
Returns response object with body in json format.
|
||||||
|
:param body: Response data to be serialized.
|
||||||
|
:param status: Response code.
|
||||||
|
:param headers: Custom Headers.
|
||||||
|
"""
|
||||||
return HTTPResponse(json_dumps(body), headers=headers, status=status,
|
return HTTPResponse(json_dumps(body), headers=headers, status=status,
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
def text(body, status=200, headers=None):
|
def text(body, status=200, headers=None):
|
||||||
|
"""
|
||||||
|
Returns response object with body in text format.
|
||||||
|
:param body: Response data to be encoded.
|
||||||
|
:param status: Response code.
|
||||||
|
:param headers: Custom Headers.
|
||||||
|
"""
|
||||||
return HTTPResponse(body, status=status, headers=headers,
|
return HTTPResponse(body, status=status, headers=headers,
|
||||||
content_type="text/plain; charset=utf-8")
|
content_type="text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
def html(body, status=200, headers=None):
|
def html(body, status=200, headers=None):
|
||||||
|
"""
|
||||||
|
Returns response object with body in html format.
|
||||||
|
:param body: Response data to be encoded.
|
||||||
|
:param status: Response code.
|
||||||
|
:param headers: Custom Headers.
|
||||||
|
"""
|
||||||
return HTTPResponse(body, status=status, headers=headers,
|
return HTTPResponse(body, status=status, headers=headers,
|
||||||
content_type="text/html; charset=utf-8")
|
content_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
async def file(location, mime_type=None, headers=None):
|
async def file(location, mime_type=None, headers=None):
|
||||||
|
"""
|
||||||
|
Returns response object with file data.
|
||||||
|
:param location: Location of file on system.
|
||||||
|
:param mime_type: Specific mime_type.
|
||||||
|
:param headers: Custom Headers.
|
||||||
|
"""
|
||||||
filename = path.split(location)[-1]
|
filename = path.split(location)[-1]
|
||||||
|
|
||||||
async with open_async(location, mode='rb') as _file:
|
async with open_async(location, mode='rb') as _file:
|
||||||
|
|
|
@ -3,6 +3,7 @@ from collections import defaultdict, namedtuple
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .exceptions import NotFound, InvalidUsage
|
from .exceptions import NotFound, InvalidUsage
|
||||||
|
from .views import CompositionView
|
||||||
|
|
||||||
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
|
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
|
||||||
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
||||||
|
@ -84,11 +85,15 @@ class Router:
|
||||||
if self.hosts is None:
|
if self.hosts is None:
|
||||||
self.hosts = set(host)
|
self.hosts = set(host)
|
||||||
else:
|
else:
|
||||||
|
if isinstance(host, list):
|
||||||
|
host = set(host)
|
||||||
self.hosts.add(host)
|
self.hosts.add(host)
|
||||||
uri = host + uri
|
if isinstance(host, str):
|
||||||
|
uri = host + uri
|
||||||
if uri in self.routes_all:
|
else:
|
||||||
raise RouteExists("Route already registered: {}".format(uri))
|
for h in host:
|
||||||
|
self.add(uri, methods, handler, h)
|
||||||
|
return
|
||||||
|
|
||||||
# Dict for faster lookups of if method allowed
|
# Dict for faster lookups of if method allowed
|
||||||
if methods:
|
if methods:
|
||||||
|
@ -122,9 +127,35 @@ class Router:
|
||||||
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
|
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
|
||||||
pattern = re.compile(r'^{}$'.format(pattern_string))
|
pattern = re.compile(r'^{}$'.format(pattern_string))
|
||||||
|
|
||||||
route = Route(
|
def merge_route(route, methods, handler):
|
||||||
handler=handler, methods=methods, pattern=pattern,
|
# merge to the existing route when possible.
|
||||||
parameters=parameters)
|
if not route.methods or not methods:
|
||||||
|
# method-unspecified routes are not mergeable.
|
||||||
|
raise RouteExists(
|
||||||
|
"Route already registered: {}".format(uri))
|
||||||
|
elif route.methods.intersection(methods):
|
||||||
|
# already existing method is not overloadable.
|
||||||
|
duplicated = methods.intersection(route.methods)
|
||||||
|
raise RouteExists(
|
||||||
|
"Route already registered: {} [{}]".format(
|
||||||
|
uri, ','.join(list(duplicated))))
|
||||||
|
if isinstance(route.handler, CompositionView):
|
||||||
|
view = route.handler
|
||||||
|
else:
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(route.methods, route.handler)
|
||||||
|
view.add(methods, handler)
|
||||||
|
route = route._replace(
|
||||||
|
handler=view, methods=methods.union(route.methods))
|
||||||
|
return route
|
||||||
|
|
||||||
|
route = self.routes_all.get(uri)
|
||||||
|
if route:
|
||||||
|
route = merge_route(route, methods, handler)
|
||||||
|
else:
|
||||||
|
route = Route(
|
||||||
|
handler=handler, methods=methods, pattern=pattern,
|
||||||
|
parameters=parameters)
|
||||||
|
|
||||||
self.routes_all[uri] = route
|
self.routes_all[uri] = route
|
||||||
if properties['unhashable']:
|
if properties['unhashable']:
|
||||||
|
|
|
@ -237,7 +237,7 @@ class Sanic:
|
||||||
e, format_exc()))
|
e, format_exc()))
|
||||||
else:
|
else:
|
||||||
response = HTTPResponse(
|
response = HTTPResponse(
|
||||||
"An error occured while handling an error")
|
"An error occurred while handling an error")
|
||||||
|
|
||||||
response_callback(response)
|
response_callback(response)
|
||||||
|
|
||||||
|
@ -246,9 +246,10 @@ class Sanic:
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
||||||
after_start=None, before_stop=None, after_stop=None, sock=None,
|
after_start=None, before_stop=None, after_stop=None, ssl=None,
|
||||||
workers=1, loop=None, protocol=HttpProtocol, backlog=100,
|
sock=None, workers=1, loop=None, protocol=HttpProtocol,
|
||||||
stop_event=None, logger=None):
|
backlog=100, stop_event=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Runs the HTTP Server and listens until keyboard interrupt or term
|
Runs the HTTP Server and listens until keyboard interrupt or term
|
||||||
signal. On termination, drains connections before closing.
|
signal. On termination, drains connections before closing.
|
||||||
|
@ -264,7 +265,7 @@ class Sanic:
|
||||||
received before it is respected
|
received before it is respected
|
||||||
:param after_stop: Functions to be executed when all requests are
|
:param after_stop: Functions to be executed when all requests are
|
||||||
complete
|
complete
|
||||||
|
:param ssl: SSLContext for SSL encryption of worker(s)
|
||||||
:param sock: Socket for the server to accept connections from
|
:param sock: Socket for the server to accept connections from
|
||||||
:param workers: Number of processes
|
:param workers: Number of processes
|
||||||
received before it is respected
|
received before it is respected
|
||||||
|
@ -285,6 +286,7 @@ class Sanic:
|
||||||
'host': host,
|
'host': host,
|
||||||
'port': port,
|
'port': port,
|
||||||
'sock': sock,
|
'sock': sock,
|
||||||
|
'ssl': ssl,
|
||||||
'debug': debug,
|
'debug': debug,
|
||||||
'request_handler': self.handle_request,
|
'request_handler': self.handle_request,
|
||||||
'error_handler': self.error_handler,
|
'error_handler': self.error_handler,
|
||||||
|
@ -322,7 +324,11 @@ class Sanic:
|
||||||
log.debug(self.config.LOGO)
|
log.debug(self.config.LOGO)
|
||||||
|
|
||||||
# Serve
|
# Serve
|
||||||
log.info('Goin\' Fast @ http://{}:{}'.format(host, port))
|
if ssl is None:
|
||||||
|
proto = "http"
|
||||||
|
else:
|
||||||
|
proto = "https"
|
||||||
|
log.info('Goin\' Fast @ {}://{}:{}'.format(proto, host, port))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if workers == 1:
|
if workers == 1:
|
||||||
|
|
|
@ -248,7 +248,7 @@ def trigger_events(events, loop):
|
||||||
|
|
||||||
def serve(host, port, request_handler, error_handler, before_start=None,
|
def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
after_start=None, before_stop=None, after_stop=None, debug=False,
|
after_start=None, before_stop=None, after_stop=None, debug=False,
|
||||||
request_timeout=60, sock=None, request_max_size=None,
|
request_timeout=60, ssl=None, sock=None, request_max_size=None,
|
||||||
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100):
|
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100):
|
||||||
"""
|
"""
|
||||||
Starts asynchronous HTTP Server on an individual process.
|
Starts asynchronous HTTP Server on an individual process.
|
||||||
|
@ -269,6 +269,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
argument `loop`
|
argument `loop`
|
||||||
:param debug: Enables debug output (slows server)
|
:param debug: Enables debug output (slows server)
|
||||||
:param request_timeout: time in seconds
|
:param request_timeout: time in seconds
|
||||||
|
:param ssl: SSLContext
|
||||||
:param sock: Socket for the server to accept connections from
|
:param sock: Socket for the server to accept connections from
|
||||||
:param request_max_size: size in bytes, `None` for no limit
|
:param request_max_size: size in bytes, `None` for no limit
|
||||||
:param reuse_port: `True` for multiple workers
|
:param reuse_port: `True` for multiple workers
|
||||||
|
@ -301,6 +302,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
server,
|
server,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
ssl=ssl,
|
||||||
reuse_port=reuse_port,
|
reuse_port=reuse_port,
|
||||||
sock=sock,
|
sock=sock,
|
||||||
backlog=backlog
|
backlog=backlog
|
||||||
|
|
|
@ -65,3 +65,38 @@ class HTTPMethodView:
|
||||||
view.__doc__ = cls.__doc__
|
view.__doc__ = cls.__doc__
|
||||||
view.__module__ = cls.__module__
|
view.__module__ = cls.__module__
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
class CompositionView:
|
||||||
|
""" Simple method-function mapped view for the sanic.
|
||||||
|
You can add handler functions to methods (get, post, put, patch, delete)
|
||||||
|
for every HTTP method you want to support.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
view = CompositionView()
|
||||||
|
view.add(['GET'], lambda request: text('I am get method'))
|
||||||
|
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
|
||||||
|
|
||||||
|
etc.
|
||||||
|
|
||||||
|
If someone tries to use a non-implemented method, there will be a
|
||||||
|
405 response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.handlers = {}
|
||||||
|
|
||||||
|
def add(self, methods, handler):
|
||||||
|
for method in methods:
|
||||||
|
if method in self.handlers:
|
||||||
|
raise KeyError(
|
||||||
|
'Method {} already is registered.'.format(method))
|
||||||
|
self.handlers[method] = handler
|
||||||
|
|
||||||
|
def __call__(self, request, *args, **kwargs):
|
||||||
|
handler = self.handlers.get(request.method.upper(), None)
|
||||||
|
if handler is None:
|
||||||
|
raise InvalidUsage(
|
||||||
|
'Method {} not allowed for URL {}'.format(
|
||||||
|
request.method, request.url), status_code=405)
|
||||||
|
return handler(request, *args, **kwargs)
|
||||||
|
|
|
@ -463,3 +463,67 @@ def test_remove_route_without_clean_cache():
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/test')
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_overload_routes():
|
||||||
|
app = Sanic('test_dynamic_route')
|
||||||
|
|
||||||
|
@app.route('/overload', methods=['GET'])
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
@app.route('/overload', methods=['POST', 'PUT'])
|
||||||
|
async def handler2(request):
|
||||||
|
return text('OK2')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload')
|
||||||
|
assert response.text == 'OK2'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'put', uri='/overload')
|
||||||
|
assert response.text == 'OK2'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'delete', uri='/overload')
|
||||||
|
assert response.status == 405
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload', methods=['PUT', 'DELETE'])
|
||||||
|
async def handler3(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
|
||||||
|
def test_unmergeable_overload_routes():
|
||||||
|
app = Sanic('test_dynamic_route')
|
||||||
|
|
||||||
|
@app.route('/overload_whole')
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload_whole', methods=['POST', 'PUT'])
|
||||||
|
async def handler2(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload_whole')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload_whole')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/overload_part', methods=['GET'])
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
with pytest.raises(RouteExists):
|
||||||
|
@app.route('/overload_part')
|
||||||
|
async def handler2(request):
|
||||||
|
return text('Duplicated')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'get', uri='/overload_part')
|
||||||
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, 'post', uri='/overload_part')
|
||||||
|
assert response.status == 405
|
||||||
|
|
|
@ -4,7 +4,7 @@ from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
def test_vhosts():
|
def test_vhosts():
|
||||||
app = Sanic('test_text')
|
app = Sanic('test_vhosts')
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
|
@ -21,3 +21,19 @@ def test_vhosts():
|
||||||
headers = {"Host": "subdomain.example.com"}
|
headers = {"Host": "subdomain.example.com"}
|
||||||
request, response = sanic_endpoint_test(app, headers=headers)
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
assert response.text == "You're at subdomain.example.com!"
|
assert response.text == "You're at subdomain.example.com!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_vhosts_with_list():
|
||||||
|
app = Sanic('test_vhosts')
|
||||||
|
|
||||||
|
@app.route('/', host=["hello.com", "world.com"])
|
||||||
|
async def handler(request):
|
||||||
|
return text("Hello, world!")
|
||||||
|
|
||||||
|
headers = {"Host": "hello.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "Hello, world!"
|
||||||
|
|
||||||
|
headers = {"Host": "world.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "Hello, world!"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user