Merge branch 'master' into improved_config
This commit is contained in:
commit
0b9094d348
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,11 +1,13 @@
|
||||||
*~
|
*~
|
||||||
*.egg-info
|
*.egg-info
|
||||||
*.egg
|
*.egg
|
||||||
|
*.eggs
|
||||||
|
*.pyc
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
coverage
|
coverage
|
||||||
.tox
|
.tox
|
||||||
settings.py
|
settings.py
|
||||||
*.pyc
|
|
||||||
.idea/*
|
.idea/*
|
||||||
.cache/*
|
.cache/*
|
||||||
|
.python-version
|
||||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -1,15 +1,10 @@
|
||||||
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- '3.5'
|
- '3.5'
|
||||||
- '3.6'
|
- '3.6'
|
||||||
install:
|
install: pip install tox-travis
|
||||||
- pip install -r requirements.txt
|
script: tox
|
||||||
- pip install -r requirements-dev.txt
|
|
||||||
- python setup.py install
|
|
||||||
- pip install flake8
|
|
||||||
- pip install pytest
|
|
||||||
before_script: flake8 sanic
|
|
||||||
script: py.test -v tests
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: pypi
|
provider: pypi
|
||||||
user: channelcat
|
user: channelcat
|
||||||
|
|
|
@ -60,6 +60,7 @@ if __name__ == "__main__":
|
||||||
* [Cookies](docs/cookies.md)
|
* [Cookies](docs/cookies.md)
|
||||||
* [Static Files](docs/static_files.md)
|
* [Static Files](docs/static_files.md)
|
||||||
* [Configuration](docs/config.md)
|
* [Configuration](docs/config.md)
|
||||||
|
* [Custom Protocol](docs/custom_protocol.md)
|
||||||
* [Testing](docs/testing.md)
|
* [Testing](docs/testing.md)
|
||||||
* [Deploying](docs/deploying.md)
|
* [Deploying](docs/deploying.md)
|
||||||
* [Contributing](docs/contributing.md)
|
* [Contributing](docs/contributing.md)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class SimpleView(HTTPMethodView):
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
return text('I am delete method')
|
return text('I am delete method')
|
||||||
|
|
||||||
app.add_route(SimpleView(), '/')
|
app.add_route(SimpleView.as_view(), '/')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -40,6 +40,19 @@ class NameView(HTTPMethodView):
|
||||||
def get(self, request, name):
|
def get(self, request, name):
|
||||||
return text('Hello {}'.format(name))
|
return text('Hello {}'.format(name))
|
||||||
|
|
||||||
app.add_route(NameView(), '/<name>')
|
app.add_route(NameView.as_view(), '/<name>')
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to add decorator for class, you could set decorators variable
|
||||||
|
|
||||||
|
```
|
||||||
|
class ViewWithDecorator(HTTPMethodView):
|
||||||
|
decorators = [some_decorator_here]
|
||||||
|
|
||||||
|
def get(self, request, name):
|
||||||
|
return text('Hello I have a decorator')
|
||||||
|
|
||||||
|
app.add_route(ViewWithDecorator.as_view(), '/url')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
70
docs/custom_protocol.md
Normal file
70
docs/custom_protocol.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# Custom Protocol
|
||||||
|
|
||||||
|
You can change the behavior of protocol by using custom protocol.
|
||||||
|
If you want to use custom protocol, you should put subclass of [protocol class](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes) in the protocol keyword argument of `sanic.run()`. The constructor of custom protocol class gets following keyword arguments from Sanic.
|
||||||
|
|
||||||
|
* loop
|
||||||
|
`loop` is an asyncio compatible event loop.
|
||||||
|
|
||||||
|
* connections
|
||||||
|
`connections` is a `set object` to store protocol objects.
|
||||||
|
When Sanic receives `SIGINT` or `SIGTERM`, Sanic executes `protocol.close_if_idle()` for a `protocol objects` stored in connections.
|
||||||
|
|
||||||
|
* signal
|
||||||
|
`signal` is a `sanic.server.Signal object` with `stopped attribute`.
|
||||||
|
When Sanic receives `SIGINT` or `SIGTERM`, `signal.stopped` becomes `True`.
|
||||||
|
|
||||||
|
* request_handler
|
||||||
|
`request_handler` is a coroutine that takes a `sanic.request.Request` object and a `response callback` as arguments.
|
||||||
|
|
||||||
|
* error_handler
|
||||||
|
`error_handler` is a `sanic.exceptions.Handler` object.
|
||||||
|
|
||||||
|
* request_timeout
|
||||||
|
`request_timeout` is seconds for timeout.
|
||||||
|
|
||||||
|
* request_max_size
|
||||||
|
`request_max_size` is bytes of max request size.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
By default protocol, an error occurs, if the handler does not return an `HTTPResponse object`.
|
||||||
|
In this example, By rewriting `write_response()`, if the handler returns `str`, it will be converted to an `HTTPResponse object`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.server import HttpProtocol
|
||||||
|
from sanic.response import text
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomHttpProtocol(HttpProtocol):
|
||||||
|
|
||||||
|
def __init__(self, *, loop, request_handler, error_handler,
|
||||||
|
signal, connections, request_timeout, request_max_size):
|
||||||
|
super().__init__(
|
||||||
|
loop=loop, request_handler=request_handler,
|
||||||
|
error_handler=error_handler, signal=signal,
|
||||||
|
connections=connections, request_timeout=request_timeout,
|
||||||
|
request_max_size=request_max_size)
|
||||||
|
|
||||||
|
def write_response(self, response):
|
||||||
|
if isinstance(response, str):
|
||||||
|
response = text(response)
|
||||||
|
self.transport.write(
|
||||||
|
response.output(self.request.version)
|
||||||
|
)
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def string(request):
|
||||||
|
return 'string'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/1')
|
||||||
|
async def response(request):
|
||||||
|
return text('response')
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
|
||||||
|
```
|
|
@ -33,12 +33,12 @@ async def handler1(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
app.add_route(handler1, '/test')
|
app.add_route(handler1, '/test')
|
||||||
|
|
||||||
async def handler(request, name):
|
async def handler2(request, name):
|
||||||
return text('Folder - {}'.format(name))
|
return text('Folder - {}'.format(name))
|
||||||
app.add_route(handler, '/folder/<name>')
|
app.add_route(handler2, '/folder/<name>')
|
||||||
|
|
||||||
async def person_handler(request, name):
|
async def person_handler2(request, name):
|
||||||
return text('Person - {}'.format(name))
|
return text('Person - {}'.format(name))
|
||||||
app.add_route(handler, '/person/<name:[A-z]>')
|
app.add_route(person_handler2, '/person/<name:[A-z]>')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
65
examples/sanic_asyncpg_example.py
Normal file
65
examples/sanic_asyncpg_example.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
""" To run this example you need additional asyncpg package
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import uvloop
|
||||||
|
from asyncpg import create_pool
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
|
||||||
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': '<host>',
|
||||||
|
'user': '<username>',
|
||||||
|
'password': '<password>',
|
||||||
|
'port': '<port>',
|
||||||
|
'database': '<database>'
|
||||||
|
}
|
||||||
|
|
||||||
|
def jsonify(records):
|
||||||
|
""" Parse asyncpg record response into JSON format
|
||||||
|
|
||||||
|
"""
|
||||||
|
return [{key: value for key, value in
|
||||||
|
zip(r.keys(), r.values())} for r in records]
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
async def make_pool():
|
||||||
|
return await create_pool(**DB_CONFIG)
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
pool = loop.run_until_complete(make_pool())
|
||||||
|
|
||||||
|
async def create_db():
|
||||||
|
""" Create some table and add some data
|
||||||
|
|
||||||
|
"""
|
||||||
|
async with pool.acquire() as connection:
|
||||||
|
async with connection.transaction():
|
||||||
|
await connection.execute('DROP TABLE IF EXISTS sanic_post')
|
||||||
|
await connection.execute("""CREATE TABLE sanic_post (
|
||||||
|
id serial primary key,
|
||||||
|
content varchar(50),
|
||||||
|
post_date timestamp
|
||||||
|
);""")
|
||||||
|
for i in range(0, 100):
|
||||||
|
await connection.execute(f"""INSERT INTO sanic_post
|
||||||
|
(id, content, post_date) VALUES ({i}, {i}, now())""")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def handler(request):
|
||||||
|
async with pool.acquire() as connection:
|
||||||
|
async with connection.transaction():
|
||||||
|
results = await connection.fetch('SELECT * FROM sanic_post')
|
||||||
|
return json({'posts': jsonify(results)})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop.run_until_complete(create_db())
|
||||||
|
app.run(host='0.0.0.0', port=8000, loop=loop)
|
|
@ -64,11 +64,11 @@ def query_string(request):
|
||||||
# Run Server
|
# Run Server
|
||||||
# ----------------------------------------------- #
|
# ----------------------------------------------- #
|
||||||
|
|
||||||
def after_start(loop):
|
def after_start(app, loop):
|
||||||
log.info("OH OH OH OH OHHHHHHHH")
|
log.info("OH OH OH OH OHHHHHHHH")
|
||||||
|
|
||||||
|
|
||||||
def before_stop(loop):
|
def before_stop(app, loop):
|
||||||
log.info("TRIED EVERYTHING")
|
log.info("TRIED EVERYTHING")
|
||||||
|
|
||||||
|
|
||||||
|
|
32
examples/vhosts.py
Normal file
32
examples/vhosts.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from sanic.response import text
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
# curl -H "Host: example.com" localhost:8000
|
||||||
|
# curl -H "Host: sub.example.com" localhost:8000
|
||||||
|
# curl -H "Host: bp.example.com" localhost:8000/question
|
||||||
|
# curl -H "Host: bp.example.com" localhost:8000/answer
|
||||||
|
|
||||||
|
app = Sanic()
|
||||||
|
bp = Blueprint("bp", host="bp.example.com")
|
||||||
|
|
||||||
|
@app.route('/', host="example.com")
|
||||||
|
async def hello(request):
|
||||||
|
return text("Answer")
|
||||||
|
@app.route('/', host="sub.example.com")
|
||||||
|
async def hello(request):
|
||||||
|
return text("42")
|
||||||
|
|
||||||
|
@bp.route("/question")
|
||||||
|
async def hello(request):
|
||||||
|
return text("What is the meaning of life?")
|
||||||
|
|
||||||
|
@bp.route("/answer")
|
||||||
|
async def hello(request):
|
||||||
|
return text("42")
|
||||||
|
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host="0.0.0.0", port=8000)
|
|
@ -20,7 +20,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
module = import_module(module_name)
|
module = import_module(module_name)
|
||||||
app = getattr(module, app_name, None)
|
app = getattr(module, app_name, None)
|
||||||
if type(app) is not Sanic:
|
if not isinstance(app, Sanic):
|
||||||
raise ValueError("Module is not a Sanic app, it is a {}. "
|
raise ValueError("Module is not a Sanic app, it is a {}. "
|
||||||
"Perhaps you meant {}.app?"
|
"Perhaps you meant {}.app?"
|
||||||
.format(type(app).__name__, args.module))
|
.format(type(app).__name__, args.module))
|
||||||
|
|
|
@ -18,14 +18,17 @@ class BlueprintSetup:
|
||||||
#: blueprint.
|
#: blueprint.
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods):
|
def add_route(self, handler, uri, methods, host=None):
|
||||||
"""
|
"""
|
||||||
A helper method to register a handler to the application url routes.
|
A helper method to register a handler to the application url routes.
|
||||||
"""
|
"""
|
||||||
if self.url_prefix:
|
if self.url_prefix:
|
||||||
uri = self.url_prefix + uri
|
uri = self.url_prefix + uri
|
||||||
|
|
||||||
self.app.route(uri=uri, methods=methods)(handler)
|
if host is None:
|
||||||
|
host = self.blueprint.host
|
||||||
|
|
||||||
|
self.app.route(uri=uri, methods=methods, host=host)(handler)
|
||||||
|
|
||||||
def add_exception(self, handler, *args, **kwargs):
|
def add_exception(self, handler, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +56,7 @@ class BlueprintSetup:
|
||||||
|
|
||||||
|
|
||||||
class Blueprint:
|
class Blueprint:
|
||||||
def __init__(self, name, url_prefix=None):
|
def __init__(self, name, url_prefix=None, host=None):
|
||||||
"""
|
"""
|
||||||
Creates a new blueprint
|
Creates a new blueprint
|
||||||
:param name: Unique name of the blueprint
|
:param name: Unique name of the blueprint
|
||||||
|
@ -63,6 +66,7 @@ class Blueprint:
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
self.deferred_functions = []
|
self.deferred_functions = []
|
||||||
self.listeners = defaultdict(list)
|
self.listeners = defaultdict(list)
|
||||||
|
self.host = host
|
||||||
|
|
||||||
def record(self, func):
|
def record(self, func):
|
||||||
"""
|
"""
|
||||||
|
@ -83,18 +87,18 @@ class Blueprint:
|
||||||
for deferred in self.deferred_functions:
|
for deferred in self.deferred_functions:
|
||||||
deferred(state)
|
deferred(state)
|
||||||
|
|
||||||
def route(self, uri, methods=None):
|
def route(self, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
self.record(lambda s: s.add_route(handler, uri, methods))
|
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods=None):
|
def add_route(self, handler, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
self.record(lambda s: s.add_route(handler, uri, methods))
|
self.record(lambda s: s.add_route(handler, uri, methods, host))
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def listener(self, event):
|
def listener(self, event):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .response import text
|
from .response import text
|
||||||
|
from .log import log
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,18 +57,31 @@ class Handler:
|
||||||
:return: Response object
|
:return: Response object
|
||||||
"""
|
"""
|
||||||
handler = self.handlers.get(type(exception), self.default)
|
handler = self.handlers.get(type(exception), self.default)
|
||||||
|
try:
|
||||||
response = handler(request=request, exception=exception)
|
response = handler(request=request, exception=exception)
|
||||||
|
except:
|
||||||
|
if self.sanic.debug:
|
||||||
|
response_message = (
|
||||||
|
'Exception raised in exception handler "{}" '
|
||||||
|
'for uri: "{}"\n{}').format(
|
||||||
|
handler.__name__, request.url, format_exc())
|
||||||
|
log.error(response_message)
|
||||||
|
return text(response_message, 500)
|
||||||
|
else:
|
||||||
|
return text('An error occurred while handling an error', 500)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def default(self, request, exception):
|
def default(self, request, exception):
|
||||||
if issubclass(type(exception), SanicException):
|
if issubclass(type(exception), SanicException):
|
||||||
return text(
|
return text(
|
||||||
"Error: {}".format(exception),
|
'Error: {}'.format(exception),
|
||||||
status=getattr(exception, 'status_code', 500))
|
status=getattr(exception, 'status_code', 500))
|
||||||
elif self.sanic.debug:
|
elif self.sanic.debug:
|
||||||
return text(
|
response_message = (
|
||||||
"Error: {}\nException: {}".format(
|
'Exception occurred while handling uri: "{}"\n{}'.format(
|
||||||
exception, format_exc()), status=500)
|
request.url, format_exc()))
|
||||||
|
log.error(response_message)
|
||||||
|
return text(response_message, status=500)
|
||||||
else:
|
else:
|
||||||
return text(
|
return text(
|
||||||
"An error occurred while generating the request", status=500)
|
'An error occurred while generating the response', status=500)
|
||||||
|
|
|
@ -25,6 +25,9 @@ class RequestParameters(dict):
|
||||||
self.super = super()
|
self.super = super()
|
||||||
self.super.__init__(*args, **kwargs)
|
self.super.__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
return self.get(name)
|
||||||
|
|
||||||
def get(self, name, default=None):
|
def get(self, name, default=None):
|
||||||
values = self.super.get(name)
|
values = self.super.get(name)
|
||||||
return values[0] if values else default
|
return values[0] if values else default
|
||||||
|
@ -64,7 +67,7 @@ class Request(dict):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def json(self):
|
def json(self):
|
||||||
if not self.parsed_json:
|
if self.parsed_json is None:
|
||||||
try:
|
try:
|
||||||
self.parsed_json = json_loads(self.body)
|
self.parsed_json = json_loads(self.body)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -72,6 +75,17 @@ class Request(dict):
|
||||||
|
|
||||||
return self.parsed_json
|
return self.parsed_json
|
||||||
|
|
||||||
|
@property
|
||||||
|
def token(self):
|
||||||
|
"""
|
||||||
|
Attempts to return the auth header token.
|
||||||
|
:return: token related to request
|
||||||
|
"""
|
||||||
|
auth_header = self.headers.get('Authorization')
|
||||||
|
if auth_header is not None:
|
||||||
|
return auth_header.split()[1]
|
||||||
|
return auth_header
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self):
|
def form(self):
|
||||||
if self.parsed_form is None:
|
if self.parsed_form is None:
|
||||||
|
|
|
@ -103,10 +103,14 @@ class HTTPResponse:
|
||||||
|
|
||||||
headers = b''
|
headers = b''
|
||||||
if self.headers:
|
if self.headers:
|
||||||
headers = b''.join(
|
for name, value in self.headers.items():
|
||||||
b'%b: %b\r\n' % (name.encode(), value.encode('utf-8'))
|
try:
|
||||||
for name, value in self.headers.items()
|
headers += (
|
||||||
)
|
b'%b: %b\r\n' % (name.encode(), value.encode('utf-8')))
|
||||||
|
except AttributeError:
|
||||||
|
headers += (
|
||||||
|
b'%b: %b\r\n' % (
|
||||||
|
str(name).encode(), str(value).encode('utf-8')))
|
||||||
|
|
||||||
# Try to pull from the common codes first
|
# Try to pull from the common codes first
|
||||||
# Speeds up response rate 6% over pulling from all
|
# Speeds up response rate 6% over pulling from all
|
||||||
|
|
|
@ -24,16 +24,20 @@ class RouteExists(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RouteDoesNotExist(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
class Router:
|
||||||
"""
|
"""
|
||||||
Router supports basic routing with parameters and method checks
|
Router supports basic routing with parameters and method checks
|
||||||
Usage:
|
Usage:
|
||||||
@sanic.route('/my/url/<my_parameter>', methods=['GET', 'POST', ...])
|
@app.route('/my_url/<my_param>', methods=['GET', 'POST', ...])
|
||||||
def my_route(request, my_parameter):
|
def my_route(request, my_param):
|
||||||
do stuff...
|
do stuff...
|
||||||
or
|
or
|
||||||
@sanic.route('/my/url/<my_paramter>:type', methods['GET', 'POST', ...])
|
@app.route('/my_url/<my_param:my_type>', methods=['GET', 'POST', ...])
|
||||||
def my_route_with_type(request, my_parameter):
|
def my_route_with_type(request, my_param: my_type):
|
||||||
do stuff...
|
do stuff...
|
||||||
|
|
||||||
Parameters will be passed as keyword arguments to the request handling
|
Parameters will be passed as keyword arguments to the request handling
|
||||||
|
@ -52,8 +56,9 @@ class Router:
|
||||||
self.routes_static = {}
|
self.routes_static = {}
|
||||||
self.routes_dynamic = defaultdict(list)
|
self.routes_dynamic = defaultdict(list)
|
||||||
self.routes_always_check = []
|
self.routes_always_check = []
|
||||||
|
self.hosts = None
|
||||||
|
|
||||||
def add(self, uri, methods, handler):
|
def add(self, uri, methods, handler, host=None):
|
||||||
"""
|
"""
|
||||||
Adds a handler to the route list
|
Adds a handler to the route list
|
||||||
:param uri: Path to match
|
:param uri: Path to match
|
||||||
|
@ -63,6 +68,17 @@ class Router:
|
||||||
When executed, it should provide a response object.
|
When executed, it should provide a response object.
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if host is not None:
|
||||||
|
# we want to track if there are any
|
||||||
|
# vhosts on the Router instance so that we can
|
||||||
|
# default to the behavior without vhosts
|
||||||
|
if self.hosts is None:
|
||||||
|
self.hosts = set(host)
|
||||||
|
else:
|
||||||
|
self.hosts.add(host)
|
||||||
|
uri = host + uri
|
||||||
|
|
||||||
if uri in self.routes_all:
|
if uri in self.routes_all:
|
||||||
raise RouteExists("Route already registered: {}".format(uri))
|
raise RouteExists("Route already registered: {}".format(uri))
|
||||||
|
|
||||||
|
@ -110,6 +126,25 @@ class Router:
|
||||||
else:
|
else:
|
||||||
self.routes_static[uri] = route
|
self.routes_static[uri] = route
|
||||||
|
|
||||||
|
def remove(self, uri, clean_cache=True, host=None):
|
||||||
|
if host is not None:
|
||||||
|
uri = host + uri
|
||||||
|
try:
|
||||||
|
route = self.routes_all.pop(uri)
|
||||||
|
except KeyError:
|
||||||
|
raise RouteDoesNotExist("Route was not registered: {}".format(uri))
|
||||||
|
|
||||||
|
if route in self.routes_always_check:
|
||||||
|
self.routes_always_check.remove(route)
|
||||||
|
elif url_hash(uri) in self.routes_dynamic \
|
||||||
|
and route in self.routes_dynamic[url_hash(uri)]:
|
||||||
|
self.routes_dynamic[url_hash(uri)].remove(route)
|
||||||
|
else:
|
||||||
|
self.routes_static.pop(uri)
|
||||||
|
|
||||||
|
if clean_cache:
|
||||||
|
self._get.cache_clear()
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
"""
|
||||||
Gets a request handler based on the URL of the request, or raises an
|
Gets a request handler based on the URL of the request, or raises an
|
||||||
|
@ -117,10 +152,14 @@ class Router:
|
||||||
:param request: Request object
|
:param request: Request object
|
||||||
:return: handler, arguments, keyword arguments
|
:return: handler, arguments, keyword arguments
|
||||||
"""
|
"""
|
||||||
return self._get(request.url, request.method)
|
if self.hosts is None:
|
||||||
|
return self._get(request.url, request.method, '')
|
||||||
|
else:
|
||||||
|
return self._get(request.url, request.method,
|
||||||
|
request.headers.get("Host", ''))
|
||||||
|
|
||||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
def _get(self, url, method):
|
def _get(self, url, method, host):
|
||||||
"""
|
"""
|
||||||
Gets a request handler based on the URL of the request, or raises an
|
Gets a request handler based on the URL of the request, or raises an
|
||||||
error. Internal method for caching.
|
error. Internal method for caching.
|
||||||
|
@ -128,6 +167,7 @@ class Router:
|
||||||
:param method: Request method
|
:param method: Request method
|
||||||
:return: handler, arguments, keyword arguments
|
:return: handler, arguments, keyword arguments
|
||||||
"""
|
"""
|
||||||
|
url = host + url
|
||||||
# Check against known static routes
|
# Check against known static routes
|
||||||
route = self.routes_static.get(url)
|
route = self.routes_static.get(url)
|
||||||
if route:
|
if route:
|
||||||
|
|
|
@ -4,7 +4,6 @@ from functools import partial
|
||||||
from inspect import isawaitable, stack, getmodulename
|
from inspect import isawaitable, stack, getmodulename
|
||||||
from multiprocessing import Process, Event
|
from multiprocessing import Process, Event
|
||||||
from signal import signal, SIGTERM, SIGINT
|
from signal import signal, SIGTERM, SIGINT
|
||||||
from time import sleep
|
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -13,9 +12,11 @@ from .exceptions import Handler
|
||||||
from .log import log
|
from .log import log
|
||||||
from .response import HTTPResponse
|
from .response import HTTPResponse
|
||||||
from .router import Router
|
from .router import Router
|
||||||
from .server import serve
|
from .server import serve, HttpProtocol
|
||||||
from .static import register as static_register
|
from .static import register as static_register
|
||||||
from .exceptions import ServerError
|
from .exceptions import ServerError
|
||||||
|
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
||||||
|
from os import set_inheritable
|
||||||
|
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
|
@ -39,6 +40,8 @@ class Sanic:
|
||||||
self._blueprint_order = []
|
self._blueprint_order = []
|
||||||
self.loop = None
|
self.loop = None
|
||||||
self.debug = None
|
self.debug = None
|
||||||
|
self.sock = None
|
||||||
|
self.processes = None
|
||||||
|
|
||||||
# Register alternative method names
|
# Register alternative method names
|
||||||
self.go_fast = self.run
|
self.go_fast = self.run
|
||||||
|
@ -48,7 +51,7 @@ class Sanic:
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def route(self, uri, methods=None):
|
def route(self, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
Decorates a function to be registered as a route
|
Decorates a function to be registered as a route
|
||||||
:param uri: path of the URL
|
:param uri: path of the URL
|
||||||
|
@ -62,12 +65,13 @@ class Sanic:
|
||||||
uri = '/' + uri
|
uri = '/' + uri
|
||||||
|
|
||||||
def response(handler):
|
def response(handler):
|
||||||
self.router.add(uri=uri, methods=methods, handler=handler)
|
self.router.add(uri=uri, methods=methods, handler=handler,
|
||||||
|
host=host)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods=None):
|
def add_route(self, handler, uri, methods=None, host=None):
|
||||||
"""
|
"""
|
||||||
A helper method to register class instance or
|
A helper method to register class instance or
|
||||||
functions as a handler to the application url
|
functions as a handler to the application url
|
||||||
|
@ -77,9 +81,12 @@ class Sanic:
|
||||||
:param methods: list or tuple of methods allowed
|
:param methods: list or tuple of methods allowed
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
self.route(uri=uri, methods=methods)(handler)
|
self.route(uri=uri, methods=methods, host=host)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
def remove_route(self, uri, clean_cache=True, host=None):
|
||||||
|
self.router.remove(uri, clean_cache, host)
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def exception(self, *exceptions):
|
def exception(self, *exceptions):
|
||||||
"""
|
"""
|
||||||
|
@ -239,25 +246,27 @@ 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, sock=None,
|
||||||
workers=1, loop=None):
|
workers=1, loop=None, protocol=HttpProtocol, 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.
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
:param port: Port to host on
|
:param port: Port to host on
|
||||||
:param debug: Enables debug output (slows server)
|
:param debug: Enables debug output (slows server)
|
||||||
:param before_start: Function to be executed before the server starts
|
:param before_start: Functions to be executed before the server starts
|
||||||
accepting connections
|
accepting connections
|
||||||
:param after_start: Function to be executed after the server starts
|
:param after_start: Functions to be executed after the server starts
|
||||||
accepting connections
|
accepting connections
|
||||||
:param before_stop: Function to be executed when a stop signal is
|
:param before_stop: Functions to be executed when a stop signal is
|
||||||
received before it is respected
|
received before it is respected
|
||||||
:param after_stop: Function to be executed when all requests are
|
:param after_stop: Functions to be executed when all requests are
|
||||||
complete
|
complete
|
||||||
: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
|
||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
self.error_handler.debug = True
|
self.error_handler.debug = True
|
||||||
|
@ -265,6 +274,7 @@ class Sanic:
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
|
|
||||||
server_settings = {
|
server_settings = {
|
||||||
|
'protocol': protocol,
|
||||||
'host': host,
|
'host': host,
|
||||||
'port': port,
|
'port': port,
|
||||||
'sock': sock,
|
'sock': sock,
|
||||||
|
@ -273,7 +283,8 @@ class Sanic:
|
||||||
'error_handler': self.error_handler,
|
'error_handler': self.error_handler,
|
||||||
'request_timeout': self.config.REQUEST_TIMEOUT,
|
'request_timeout': self.config.REQUEST_TIMEOUT,
|
||||||
'request_max_size': self.config.REQUEST_MAX_SIZE,
|
'request_max_size': self.config.REQUEST_MAX_SIZE,
|
||||||
'loop': loop
|
'loop': loop,
|
||||||
|
'backlog': backlog
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
@ -290,7 +301,7 @@ class Sanic:
|
||||||
for blueprint in self.blueprints.values():
|
for blueprint in self.blueprints.values():
|
||||||
listeners += blueprint.listeners[event_name]
|
listeners += blueprint.listeners[event_name]
|
||||||
if args:
|
if args:
|
||||||
if type(args) is not list:
|
if callable(args):
|
||||||
args = [args]
|
args = [args]
|
||||||
listeners += args
|
listeners += args
|
||||||
if reverse:
|
if reverse:
|
||||||
|
@ -312,7 +323,7 @@ class Sanic:
|
||||||
else:
|
else:
|
||||||
log.info('Spinning up {} workers...'.format(workers))
|
log.info('Spinning up {} workers...'.format(workers))
|
||||||
|
|
||||||
self.serve_multiple(server_settings, workers)
|
self.serve_multiple(server_settings, workers, stop_event)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(
|
log.exception(
|
||||||
|
@ -324,10 +335,13 @@ class Sanic:
|
||||||
"""
|
"""
|
||||||
This kills the Sanic
|
This kills the Sanic
|
||||||
"""
|
"""
|
||||||
|
if self.processes is not None:
|
||||||
|
for process in self.processes:
|
||||||
|
process.terminate()
|
||||||
|
self.sock.close()
|
||||||
get_event_loop().stop()
|
get_event_loop().stop()
|
||||||
|
|
||||||
@staticmethod
|
def serve_multiple(self, server_settings, workers, stop_event=None):
|
||||||
def serve_multiple(server_settings, workers, stop_event=None):
|
|
||||||
"""
|
"""
|
||||||
Starts multiple server processes simultaneously. Stops on interrupt
|
Starts multiple server processes simultaneously. Stops on interrupt
|
||||||
and terminate signals, and drains connections when complete.
|
and terminate signals, and drains connections when complete.
|
||||||
|
@ -339,26 +353,28 @@ class Sanic:
|
||||||
server_settings['reuse_port'] = True
|
server_settings['reuse_port'] = True
|
||||||
|
|
||||||
# Create a stop event to be triggered by a signal
|
# Create a stop event to be triggered by a signal
|
||||||
if not stop_event:
|
if stop_event is None:
|
||||||
stop_event = Event()
|
stop_event = Event()
|
||||||
signal(SIGINT, lambda s, f: stop_event.set())
|
signal(SIGINT, lambda s, f: stop_event.set())
|
||||||
signal(SIGTERM, lambda s, f: stop_event.set())
|
signal(SIGTERM, lambda s, f: stop_event.set())
|
||||||
|
|
||||||
processes = []
|
self.sock = socket()
|
||||||
|
self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||||
|
self.sock.bind((server_settings['host'], server_settings['port']))
|
||||||
|
set_inheritable(self.sock.fileno(), True)
|
||||||
|
server_settings['sock'] = self.sock
|
||||||
|
server_settings['host'] = None
|
||||||
|
server_settings['port'] = None
|
||||||
|
|
||||||
|
self.processes = []
|
||||||
for _ in range(workers):
|
for _ in range(workers):
|
||||||
process = Process(target=serve, kwargs=server_settings)
|
process = Process(target=serve, kwargs=server_settings)
|
||||||
|
process.daemon = True
|
||||||
process.start()
|
process.start()
|
||||||
processes.append(process)
|
self.processes.append(process)
|
||||||
|
|
||||||
# Infinitely wait for the stop event
|
for process in self.processes:
|
||||||
try:
|
|
||||||
while not stop_event.is_set():
|
|
||||||
sleep(0.3)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
log.info('Spinning down workers...')
|
|
||||||
for process in processes:
|
|
||||||
process.terminate()
|
|
||||||
for process in processes:
|
|
||||||
process.join()
|
process.join()
|
||||||
|
|
||||||
|
# the above processes will block this until they're stopped
|
||||||
|
self.stop()
|
||||||
|
|
|
@ -224,24 +224,30 @@ 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,
|
after_start=None, before_stop=None, after_stop=None, debug=False,
|
||||||
debug=False, request_timeout=60, sock=None,
|
request_timeout=60, sock=None, request_max_size=None,
|
||||||
request_max_size=None, reuse_port=False, loop=None):
|
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.
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
:param port: Port to host on
|
:param port: Port to host on
|
||||||
:param request_handler: Sanic request handler with middleware
|
:param request_handler: Sanic request handler with middleware
|
||||||
|
:param error_handler: Sanic error handler with middleware
|
||||||
|
:param before_start: Function to be executed before the server starts
|
||||||
|
listening. Takes single argument `loop`
|
||||||
:param after_start: Function to be executed after the server starts
|
:param after_start: Function to be executed after the server starts
|
||||||
listening. Takes single argument `loop`
|
listening. Takes single argument `loop`
|
||||||
:param before_stop: Function to be executed when a stop signal is
|
:param before_stop: Function to be executed when a stop signal is
|
||||||
received before it is respected. Takes single argumenet `loop`
|
received before it is respected. Takes single argumenet `loop`
|
||||||
|
:param after_stop: Function to be executed when a stop signal is
|
||||||
|
received after it is respected. Takes single argumenet `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 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
|
||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
loop = loop or async_loop.new_event_loop()
|
loop = loop or async_loop.new_event_loop()
|
||||||
|
@ -255,7 +261,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
connections = set()
|
connections = set()
|
||||||
signal = Signal()
|
signal = Signal()
|
||||||
server = partial(
|
server = partial(
|
||||||
HttpProtocol,
|
protocol,
|
||||||
loop=loop,
|
loop=loop,
|
||||||
connections=connections,
|
connections=connections,
|
||||||
signal=signal,
|
signal=signal,
|
||||||
|
@ -270,7 +276,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
reuse_port=reuse_port,
|
reuse_port=reuse_port,
|
||||||
sock=sock
|
sock=sock,
|
||||||
|
backlog=backlog
|
||||||
)
|
)
|
||||||
|
|
||||||
# Instead of pulling time at the end of every request,
|
# Instead of pulling time at the end of every request,
|
||||||
|
|
|
@ -16,15 +16,15 @@ async def local_request(method, uri, cookies=None, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
loop=None, debug=False, *request_args,
|
loop=None, debug=False, server_kwargs={},
|
||||||
**request_kwargs):
|
*request_args, **request_kwargs):
|
||||||
results = []
|
results = []
|
||||||
exceptions = []
|
exceptions = []
|
||||||
|
|
||||||
if gather_request:
|
if gather_request:
|
||||||
@app.middleware
|
|
||||||
def _collect_request(request):
|
def _collect_request(request):
|
||||||
results.append(request)
|
results.append(request)
|
||||||
|
app.request_middleware.appendleft(_collect_request)
|
||||||
|
|
||||||
async def _collect_response(sanic, loop):
|
async def _collect_response(sanic, loop):
|
||||||
try:
|
try:
|
||||||
|
@ -35,8 +35,8 @@ def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
||||||
exceptions.append(e)
|
exceptions.append(e)
|
||||||
app.stop()
|
app.stop()
|
||||||
|
|
||||||
app.run(host=HOST, debug=debug, port=42101,
|
app.run(host=HOST, debug=debug, port=PORT,
|
||||||
after_start=_collect_response, loop=loop)
|
after_start=_collect_response, loop=loop, **server_kwargs)
|
||||||
|
|
||||||
if exceptions:
|
if exceptions:
|
||||||
raise ValueError("Exception during request: {}".format(exceptions))
|
raise ValueError("Exception during request: {}".format(exceptions))
|
||||||
|
|
|
@ -7,7 +7,7 @@ class HTTPMethodView:
|
||||||
to every HTTP method you want to support.
|
to every HTTP method you want to support.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
class DummyView(View):
|
class DummyView(HTTPMethodView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
@ -20,20 +20,44 @@ class HTTPMethodView:
|
||||||
405 response.
|
405 response.
|
||||||
|
|
||||||
If you need any url params just mention them in method definition:
|
If you need any url params just mention them in method definition:
|
||||||
class DummyView(View):
|
class DummyView(HTTPMethodView):
|
||||||
|
|
||||||
def get(self, request, my_param_here, *args, **kwargs):
|
def get(self, request, my_param_here, *args, **kwargs):
|
||||||
return text('I am get method with %s' % my_param_here)
|
return text('I am get method with %s' % my_param_here)
|
||||||
|
|
||||||
To add the view into the routing you could use
|
To add the view into the routing you could use
|
||||||
1) app.add_route(DummyView(), '/')
|
1) app.add_route(DummyView.as_view(), '/')
|
||||||
2) app.route('/')(DummyView())
|
2) app.route('/')(DummyView.as_view())
|
||||||
|
|
||||||
|
To add any decorator you could set it into decorators variable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, request, *args, **kwargs):
|
decorators = []
|
||||||
|
|
||||||
|
def dispatch_request(self, request, *args, **kwargs):
|
||||||
handler = getattr(self, request.method.lower(), None)
|
handler = getattr(self, request.method.lower(), None)
|
||||||
if handler:
|
if handler:
|
||||||
return handler(request, *args, **kwargs)
|
return handler(request, *args, **kwargs)
|
||||||
raise InvalidUsage(
|
raise InvalidUsage(
|
||||||
'Method {} not allowed for URL {}'.format(
|
'Method {} not allowed for URL {}'.format(
|
||||||
request.method, request.url), status_code=405)
|
request.method, request.url), status_code=405)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def as_view(cls, *class_args, **class_kwargs):
|
||||||
|
""" Converts the class into an actual view function that can be used
|
||||||
|
with the routing system.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def view(*args, **kwargs):
|
||||||
|
self = view.view_class(*class_args, **class_kwargs)
|
||||||
|
return self.dispatch_request(*args, **kwargs)
|
||||||
|
|
||||||
|
if cls.decorators:
|
||||||
|
view.__module__ = cls.__module__
|
||||||
|
for decorator in cls.decorators:
|
||||||
|
view = decorator(view)
|
||||||
|
|
||||||
|
view.view_class = cls
|
||||||
|
view.__doc__ = cls.__doc__
|
||||||
|
view.__module__ = cls.__module__
|
||||||
|
return view
|
||||||
|
|
|
@ -59,6 +59,71 @@ def test_several_bp_with_url_prefix():
|
||||||
request, response = sanic_endpoint_test(app, uri='/test2/')
|
request, response = sanic_endpoint_test(app, uri='/test2/')
|
||||||
assert response.text == 'Hello2'
|
assert response.text == 'Hello2'
|
||||||
|
|
||||||
|
def test_bp_with_host():
|
||||||
|
app = Sanic('test_bp_host')
|
||||||
|
bp = Blueprint('test_bp_host', url_prefix='/test1', host="example.com")
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
def handler(request):
|
||||||
|
return text('Hello')
|
||||||
|
|
||||||
|
@bp.route('/', host="sub.example.com")
|
||||||
|
def handler(request):
|
||||||
|
return text('Hello subdomain!')
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
headers = {"Host": "example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test1/',
|
||||||
|
headers=headers)
|
||||||
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
headers = {"Host": "sub.example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test1/',
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
assert response.text == 'Hello subdomain!'
|
||||||
|
|
||||||
|
|
||||||
|
def test_several_bp_with_host():
|
||||||
|
app = Sanic('test_text')
|
||||||
|
bp = Blueprint('test_text',
|
||||||
|
url_prefix='/test',
|
||||||
|
host="example.com")
|
||||||
|
bp2 = Blueprint('test_text2',
|
||||||
|
url_prefix='/test',
|
||||||
|
host="sub.example.com")
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
def handler(request):
|
||||||
|
return text('Hello')
|
||||||
|
|
||||||
|
@bp2.route('/')
|
||||||
|
def handler2(request):
|
||||||
|
return text('Hello2')
|
||||||
|
|
||||||
|
@bp2.route('/other/')
|
||||||
|
def handler2(request):
|
||||||
|
return text('Hello3')
|
||||||
|
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
app.blueprint(bp2)
|
||||||
|
|
||||||
|
assert bp.host == "example.com"
|
||||||
|
headers = {"Host": "example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test/',
|
||||||
|
headers=headers)
|
||||||
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
assert bp2.host == "sub.example.com"
|
||||||
|
headers = {"Host": "sub.example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test/',
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
assert response.text == 'Hello2'
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test/other/',
|
||||||
|
headers=headers)
|
||||||
|
assert response.text == 'Hello3'
|
||||||
|
|
||||||
def test_bp_middleware():
|
def test_bp_middleware():
|
||||||
app = Sanic('test_middleware')
|
app = Sanic('test_middleware')
|
||||||
|
|
32
tests/test_custom_protocol.py
Normal file
32
tests/test_custom_protocol.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.server import HttpProtocol
|
||||||
|
from sanic.response import text
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
app = Sanic('test_custom_porotocol')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomHttpProtocol(HttpProtocol):
|
||||||
|
|
||||||
|
def write_response(self, response):
|
||||||
|
if isinstance(response, str):
|
||||||
|
response = text(response)
|
||||||
|
self.transport.write(
|
||||||
|
response.output(self.request.version)
|
||||||
|
)
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/1')
|
||||||
|
async def handler_1(request):
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
def test_use_custom_protocol():
|
||||||
|
server_kwargs = {
|
||||||
|
'protocol': CustomHttpProtocol
|
||||||
|
}
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/1',
|
||||||
|
server_kwargs=server_kwargs)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == 'OK'
|
|
@ -1,51 +1,86 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
from sanic.exceptions import InvalidUsage, ServerError, NotFound
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
|
||||||
# GET
|
|
||||||
# ------------------------------------------------------------ #
|
|
||||||
|
|
||||||
exception_app = Sanic('test_exceptions')
|
class SanicExceptionTestException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@exception_app.route('/')
|
@pytest.fixture(scope='module')
|
||||||
|
def exception_app():
|
||||||
|
app = Sanic('test_exceptions')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
@app.route('/error')
|
||||||
@exception_app.route('/error')
|
|
||||||
def handler_error(request):
|
def handler_error(request):
|
||||||
raise ServerError("OK")
|
raise ServerError("OK")
|
||||||
|
|
||||||
|
@app.route('/404')
|
||||||
@exception_app.route('/404')
|
|
||||||
def handler_404(request):
|
def handler_404(request):
|
||||||
raise NotFound("OK")
|
raise NotFound("OK")
|
||||||
|
|
||||||
|
@app.route('/invalid')
|
||||||
@exception_app.route('/invalid')
|
|
||||||
def handler_invalid(request):
|
def handler_invalid(request):
|
||||||
raise InvalidUsage("OK")
|
raise InvalidUsage("OK")
|
||||||
|
|
||||||
|
@app.route('/divide_by_zero')
|
||||||
|
def handle_unhandled_exception(request):
|
||||||
|
1 / 0
|
||||||
|
|
||||||
def test_no_exception():
|
@app.route('/error_in_error_handler_handler')
|
||||||
|
def custom_error_handler(request):
|
||||||
|
raise SanicExceptionTestException('Dummy message!')
|
||||||
|
|
||||||
|
@app.exception(SanicExceptionTestException)
|
||||||
|
def error_in_error_handler_handler(request, exception):
|
||||||
|
1 / 0
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_exception(exception_app):
|
||||||
|
"""Test that a route works without an exception"""
|
||||||
request, response = sanic_endpoint_test(exception_app)
|
request, response = sanic_endpoint_test(exception_app)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
|
||||||
|
|
||||||
def test_server_error_exception():
|
def test_server_error_exception(exception_app):
|
||||||
|
"""Test the built-in ServerError exception works"""
|
||||||
request, response = sanic_endpoint_test(exception_app, uri='/error')
|
request, response = sanic_endpoint_test(exception_app, uri='/error')
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_usage_exception():
|
def test_invalid_usage_exception(exception_app):
|
||||||
|
"""Test the built-in InvalidUsage exception works"""
|
||||||
request, response = sanic_endpoint_test(exception_app, uri='/invalid')
|
request, response = sanic_endpoint_test(exception_app, uri='/invalid')
|
||||||
assert response.status == 400
|
assert response.status == 400
|
||||||
|
|
||||||
|
|
||||||
def test_not_found_exception():
|
def test_not_found_exception(exception_app):
|
||||||
|
"""Test the built-in NotFound exception works"""
|
||||||
request, response = sanic_endpoint_test(exception_app, uri='/404')
|
request, response = sanic_endpoint_test(exception_app, uri='/404')
|
||||||
assert response.status == 404
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_handled_unhandled_exception(exception_app):
|
||||||
|
"""Test that an exception not built into sanic is handled"""
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
exception_app, uri='/divide_by_zero')
|
||||||
|
assert response.status == 500
|
||||||
|
assert response.body == b'An error occurred while generating the response'
|
||||||
|
|
||||||
|
|
||||||
|
def test_exception_in_exception_handler(exception_app):
|
||||||
|
"""Test that an exception thrown in an error handler is handled"""
|
||||||
|
request, response = sanic_endpoint_test(
|
||||||
|
exception_app, uri='/error_in_error_handler_handler')
|
||||||
|
assert response.status == 500
|
||||||
|
assert response.body == b'An error occurred while handling an error'
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from multiprocessing import Array, Event, Process
|
from multiprocessing import Array, Event, Process
|
||||||
from time import sleep
|
from time import sleep, time
|
||||||
from ujson import loads as json_loads
|
from ujson import loads as json_loads
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json
|
from sanic.response import json
|
||||||
from sanic.utils import local_request, HOST, PORT
|
from sanic.utils import local_request, HOST, PORT
|
||||||
|
@ -13,8 +15,9 @@ from sanic.utils import local_request, HOST, PORT
|
||||||
|
|
||||||
# TODO: Figure out why this freezes on pytest but not when
|
# TODO: Figure out why this freezes on pytest but not when
|
||||||
# executed via interpreter
|
# executed via interpreter
|
||||||
|
@pytest.mark.skip(
|
||||||
def skip_test_multiprocessing():
|
reason="Freezes with pytest not on interpreter")
|
||||||
|
def test_multiprocessing():
|
||||||
app = Sanic('test_json')
|
app = Sanic('test_json')
|
||||||
|
|
||||||
response = Array('c', 50)
|
response = Array('c', 50)
|
||||||
|
@ -51,3 +54,28 @@ def skip_test_multiprocessing():
|
||||||
raise ValueError("Expected JSON response but got '{}'".format(response))
|
raise ValueError("Expected JSON response but got '{}'".format(response))
|
||||||
|
|
||||||
assert results.get('test') == True
|
assert results.get('test') == True
|
||||||
|
|
||||||
|
@pytest.mark.skip(
|
||||||
|
reason="Freezes with pytest not on interpreter")
|
||||||
|
def test_drain_connections():
|
||||||
|
app = Sanic('test_json')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
return json({"test": True})
|
||||||
|
|
||||||
|
stop_event = Event()
|
||||||
|
async def after_start(*args, **kwargs):
|
||||||
|
http_response = await local_request('get', '/')
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
start = time()
|
||||||
|
app.serve_multiple({
|
||||||
|
'host': HOST,
|
||||||
|
'port': PORT,
|
||||||
|
'after_start': after_start,
|
||||||
|
'request_handler': app.handle_request,
|
||||||
|
}, workers=2, stop_event=stop_event)
|
||||||
|
end = time()
|
||||||
|
|
||||||
|
assert end - start < 0.05
|
||||||
|
|
|
@ -33,6 +33,31 @@ def test_text():
|
||||||
assert response.text == 'Hello'
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
|
||||||
|
def test_headers():
|
||||||
|
app = Sanic('test_text')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
headers = {"spam": "great"}
|
||||||
|
return text('Hello', headers=headers)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.headers.get('spam') == 'great'
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_str_headers():
|
||||||
|
app = Sanic('test_text')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
headers = {"answer": 42}
|
||||||
|
return text('Hello', headers=headers)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
assert response.headers.get('answer') == '42'
|
||||||
|
|
||||||
def test_invalid_response():
|
def test_invalid_response():
|
||||||
app = Sanic('test_invalid_response')
|
app = Sanic('test_invalid_response')
|
||||||
|
|
||||||
|
@ -92,6 +117,24 @@ def test_query_string():
|
||||||
assert request.args.get('test2') == 'false'
|
assert request.args.get('test2') == 'false'
|
||||||
|
|
||||||
|
|
||||||
|
def test_token():
|
||||||
|
app = Sanic('test_post_token')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
# uuid4 generated token.
|
||||||
|
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Authorization': 'Token {}'.format(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
|
||||||
|
assert request.token == token
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# POST
|
# POST
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.router import RouteExists
|
from sanic.router import RouteExists, RouteDoesNotExist
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,3 +356,110 @@ def test_add_route_method_not_allowed():
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
request, response = sanic_endpoint_test(app, method='post', uri='/test')
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_static_route():
|
||||||
|
app = Sanic('test_remove_static_route')
|
||||||
|
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK1')
|
||||||
|
|
||||||
|
async def handler2(request):
|
||||||
|
return text('OK2')
|
||||||
|
|
||||||
|
app.add_route(handler1, '/test')
|
||||||
|
app.add_route(handler2, '/test2')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test2')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
app.remove_route('/test')
|
||||||
|
app.remove_route('/test2')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test2')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_dynamic_route():
|
||||||
|
app = Sanic('test_remove_dynamic_route')
|
||||||
|
|
||||||
|
async def handler(request, name):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<name>')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test123')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
app.remove_route('/folder/<name>')
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test123')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_inexistent_route():
|
||||||
|
app = Sanic('test_remove_inexistent_route')
|
||||||
|
|
||||||
|
with pytest.raises(RouteDoesNotExist):
|
||||||
|
app.remove_route('/test')
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_unhashable_route():
|
||||||
|
app = Sanic('test_remove_unhashable_route')
|
||||||
|
|
||||||
|
async def handler(request, unhashable):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/folder/<unhashable:[A-Za-z0-9/]+>/end/')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/asdf/end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test///////end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/end/')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
app.remove_route('/folder/<unhashable:[A-Za-z0-9/]+>/end/')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/asdf/end/')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test///////end/')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/folder/test/end/')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_route_without_clean_cache():
|
||||||
|
app = Sanic('test_remove_static_route')
|
||||||
|
|
||||||
|
async def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
app.add_route(handler, '/test')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
app.remove_route('/test', clean_cache=True)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
app.add_route(handler, '/test')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
app.remove_route('/test', clean_cache=False)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/test')
|
||||||
|
assert response.status == 200
|
||||||
|
|
59
tests/test_server_events.py
Normal file
59
tests/test_server_events.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from io import StringIO
|
||||||
|
from random import choice
|
||||||
|
from string import ascii_letters
|
||||||
|
import signal
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
AVAILABLE_LISTENERS = [
|
||||||
|
'before_start',
|
||||||
|
'after_start',
|
||||||
|
'before_stop',
|
||||||
|
'after_stop'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_listener(listener_name, in_list):
|
||||||
|
async def _listener(app, loop):
|
||||||
|
print('DEBUG MESSAGE FOR PYTEST for {}'.format(listener_name))
|
||||||
|
in_list.insert(0, app.name + listener_name)
|
||||||
|
return _listener
|
||||||
|
|
||||||
|
|
||||||
|
def start_stop_app(random_name_app, **run_kwargs):
|
||||||
|
|
||||||
|
def stop_on_alarm(signum, frame):
|
||||||
|
raise KeyboardInterrupt('SIGINT for sanic to stop gracefully')
|
||||||
|
|
||||||
|
signal.signal(signal.SIGALRM, stop_on_alarm)
|
||||||
|
signal.alarm(1)
|
||||||
|
try:
|
||||||
|
random_name_app.run(**run_kwargs)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('listener_name', AVAILABLE_LISTENERS)
|
||||||
|
def test_single_listener(listener_name):
|
||||||
|
"""Test that listeners on their own work"""
|
||||||
|
random_name_app = Sanic(''.join(
|
||||||
|
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
|
||||||
|
output = list()
|
||||||
|
start_stop_app(
|
||||||
|
random_name_app,
|
||||||
|
**{listener_name: create_listener(listener_name, output)})
|
||||||
|
assert random_name_app.name + listener_name == output.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_listeners():
|
||||||
|
random_name_app = Sanic(''.join(
|
||||||
|
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
|
||||||
|
output = list()
|
||||||
|
start_stop_app(
|
||||||
|
random_name_app,
|
||||||
|
**{listener_name: create_listener(listener_name, output)
|
||||||
|
for listener_name in AVAILABLE_LISTENERS})
|
||||||
|
for listener_name in AVAILABLE_LISTENERS:
|
||||||
|
assert random_name_app.name + listener_name == output.pop()
|
23
tests/test_vhosts.py
Normal file
23
tests/test_vhosts.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json, text
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
|
def test_vhosts():
|
||||||
|
app = Sanic('test_text')
|
||||||
|
|
||||||
|
@app.route('/', host="example.com")
|
||||||
|
async def handler(request):
|
||||||
|
return text("You're at example.com!")
|
||||||
|
|
||||||
|
@app.route('/', host="subdomain.example.com")
|
||||||
|
async def handler(request):
|
||||||
|
return text("You're at subdomain.example.com!")
|
||||||
|
|
||||||
|
headers = {"Host": "example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "You're at example.com!"
|
||||||
|
|
||||||
|
headers = {"Host": "subdomain.example.com"}
|
||||||
|
request, response = sanic_endpoint_test(app, headers=headers)
|
||||||
|
assert response.text == "You're at subdomain.example.com!"
|
|
@ -26,7 +26,7 @@ def test_methods():
|
||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
return text('I am delete method')
|
return text('I am delete method')
|
||||||
|
|
||||||
app.add_route(DummyView(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, method="get")
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
assert response.text == 'I am get method'
|
assert response.text == 'I am get method'
|
||||||
|
@ -48,7 +48,7 @@ def test_unexisting_methods():
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
app.add_route(DummyView(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
request, response = sanic_endpoint_test(app, method="get")
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
assert response.text == 'I am get method'
|
assert response.text == 'I am get method'
|
||||||
request, response = sanic_endpoint_test(app, method="post")
|
request, response = sanic_endpoint_test(app, method="post")
|
||||||
|
@ -63,7 +63,7 @@ def test_argument_methods():
|
||||||
def get(self, request, my_param_here):
|
def get(self, request, my_param_here):
|
||||||
return text('I am get method with %s' % my_param_here)
|
return text('I am get method with %s' % my_param_here)
|
||||||
|
|
||||||
app.add_route(DummyView(), '/<my_param_here>')
|
app.add_route(DummyView.as_view(), '/<my_param_here>')
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/test123')
|
request, response = sanic_endpoint_test(app, uri='/test123')
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ def test_with_bp():
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
bp.add_route(DummyView(), '/')
|
bp.add_route(DummyView.as_view(), '/')
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
request, response = sanic_endpoint_test(app)
|
request, response = sanic_endpoint_test(app)
|
||||||
|
@ -96,7 +96,7 @@ def test_with_bp_with_url_prefix():
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
bp.add_route(DummyView(), '/')
|
bp.add_route(DummyView.as_view(), '/')
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
request, response = sanic_endpoint_test(app, uri='/test1/')
|
request, response = sanic_endpoint_test(app, uri='/test1/')
|
||||||
|
@ -112,7 +112,7 @@ def test_with_middleware():
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
app.add_route(DummyView(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ def test_with_middleware_response():
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return text('I am get method')
|
return text('I am get method')
|
||||||
|
|
||||||
app.add_route(DummyView(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app)
|
request, response = sanic_endpoint_test(app)
|
||||||
|
|
||||||
|
@ -153,3 +153,44 @@ def test_with_middleware_response():
|
||||||
assert type(results[0]) is Request
|
assert type(results[0]) is Request
|
||||||
assert type(results[1]) is Request
|
assert type(results[1]) is Request
|
||||||
assert issubclass(type(results[2]), HTTPResponse)
|
assert issubclass(type(results[2]), HTTPResponse)
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_custom_class_methods():
|
||||||
|
app = Sanic('test_with_custom_class_methods')
|
||||||
|
|
||||||
|
class DummyView(HTTPMethodView):
|
||||||
|
global_var = 0
|
||||||
|
|
||||||
|
def _iternal_method(self):
|
||||||
|
self.global_var += 10
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
self._iternal_method()
|
||||||
|
return text('I am get method and global var is {}'.format(self.global_var))
|
||||||
|
|
||||||
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
|
assert response.text == 'I am get method and global var is 10'
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_decorator():
|
||||||
|
app = Sanic('test_with_decorator')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
def stupid_decorator(view):
|
||||||
|
def decorator(*args, **kwargs):
|
||||||
|
results.append(1)
|
||||||
|
return view(*args, **kwargs)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
class DummyView(HTTPMethodView):
|
||||||
|
decorators = [stupid_decorator]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return text('I am get method')
|
||||||
|
|
||||||
|
app.add_route(DummyView.as_view(), '/')
|
||||||
|
request, response = sanic_endpoint_test(app, method="get")
|
||||||
|
assert response.text == 'I am get method'
|
||||||
|
assert results[0] == 1
|
||||||
|
|
25
tox.ini
25
tox.ini
|
@ -1,22 +1,21 @@
|
||||||
[tox]
|
[tox]
|
||||||
|
|
||||||
envlist = py35, py36
|
envlist = py35, py36, flake8
|
||||||
|
|
||||||
|
[travis]
|
||||||
|
|
||||||
|
python =
|
||||||
|
3.5: py35, flake8
|
||||||
|
3.6: py36, flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
aiohttp
|
aiohttp
|
||||||
pytest
|
pytest
|
||||||
coverage
|
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
coverage run -m pytest -v tests {posargs}
|
pytest tests {posargs}
|
||||||
mv .coverage .coverage.{envname}
|
|
||||||
|
|
||||||
whitelist_externals =
|
|
||||||
coverage
|
|
||||||
mv
|
|
||||||
echo
|
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
deps =
|
deps =
|
||||||
|
@ -24,11 +23,3 @@ deps =
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
flake8 sanic
|
flake8 sanic
|
||||||
|
|
||||||
[testenv:report]
|
|
||||||
|
|
||||||
commands =
|
|
||||||
coverage combine
|
|
||||||
coverage report
|
|
||||||
coverage html
|
|
||||||
echo "Open file://{toxinidir}/coverage/index.html"
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user