Merge branch 'master' into 178
This commit is contained in:
commit
f1c2854358
12
.travis.yml
12
.travis.yml
|
@ -1,14 +1,10 @@
|
||||||
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- '3.5'
|
- '3.5'
|
||||||
install:
|
- '3.6'
|
||||||
- pip install -r requirements.txt
|
install: pip install tox-travis
|
||||||
- pip install -r requirements-dev.txt
|
script: tox
|
||||||
- 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
|
||||||
|
|
|
@ -59,6 +59,7 @@ if __name__ == "__main__":
|
||||||
* [Class Based Views](docs/class_based_views.md)
|
* [Class Based Views](docs/class_based_views.md)
|
||||||
* [Cookies](docs/cookies.md)
|
* [Cookies](docs/cookies.md)
|
||||||
* [Static Files](docs/static_files.md)
|
* [Static Files](docs/static_files.md)
|
||||||
|
* [Testing](docs/testing.md)
|
||||||
* [Deploying](docs/deploying.md)
|
* [Deploying](docs/deploying.md)
|
||||||
* [Contributing](docs/contributing.md)
|
* [Contributing](docs/contributing.md)
|
||||||
* [License](LICENSE)
|
* [License](LICENSE)
|
||||||
|
|
|
@ -6,6 +6,7 @@ Sanic has simple class based implementation. You should implement methods(get, p
|
||||||
```python
|
```python
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.views import HTTPMethodView
|
from sanic.views import HTTPMethodView
|
||||||
|
from sanic.response import text
|
||||||
|
|
||||||
app = Sanic('some_name')
|
app = Sanic('some_name')
|
||||||
|
|
||||||
|
|
|
@ -27,3 +27,23 @@ async def handler(request):
|
||||||
|
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8000)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Middleware chain
|
||||||
|
|
||||||
|
If you want to apply the middleware as a chain, applying more than one, is so easy. You only have to be aware that you do **not return** any response in your middleware:
|
||||||
|
|
||||||
|
```python
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@app.middleware('response')
|
||||||
|
async def custom_banner(request, response):
|
||||||
|
response.headers["Server"] = "Fake-Server"
|
||||||
|
|
||||||
|
@app.middleware('response')
|
||||||
|
async def prevent_xss(request, response):
|
||||||
|
response.headers["x-xss-protection"] = "1; mode=block"
|
||||||
|
|
||||||
|
app.run(host="0.0.0.0", port=8000)
|
||||||
|
```
|
||||||
|
|
||||||
|
The above code will apply the two middlewares in order. First the middleware **custom_banner** will change the HTTP Response headers *Server* by *Fake-Server*, and the second middleware **prevent_xss** will add the HTTP Headers for prevent Cross-Site-Scripting (XSS) attacks.
|
||||||
|
|
|
@ -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]>')
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
51
docs/testing.md
Normal file
51
docs/testing.md
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
Sanic endpoints can be tested locally using the `sanic.utils` module, which
|
||||||
|
depends on the additional [aiohttp](https://aiohttp.readthedocs.io/en/stable/)
|
||||||
|
library. The `sanic_endpoint_test` function runs a local server, issues a
|
||||||
|
configurable request to an endpoint, and returns the result. It takes the
|
||||||
|
following arguments:
|
||||||
|
|
||||||
|
- `app` An instance of a Sanic app.
|
||||||
|
- `method` *(default `'get'`)* A string representing the HTTP method to use.
|
||||||
|
- `uri` *(default `'/'`)* A string representing the endpoint to test.
|
||||||
|
- `gather_request` *(default `True`)* A boolean which determines whether the
|
||||||
|
original request will be returned by the function. If set to `True`, the
|
||||||
|
return value is a tuple of `(request, response)`, if `False` only the
|
||||||
|
response is returned.
|
||||||
|
- `loop` *(default `None`)* The event loop to use.
|
||||||
|
- `debug` *(default `False`)* A boolean which determines whether to run the
|
||||||
|
server in debug mode.
|
||||||
|
|
||||||
|
The function further takes the `*request_args` and `**request_kwargs`, which
|
||||||
|
are passed directly to the aiohttp ClientSession request. For example, to
|
||||||
|
supply data with a GET request, `method` would be `get` and the keyword
|
||||||
|
argument `params={'value', 'key'}` would be supplied. More information about
|
||||||
|
the available arguments to aiohttp can be found
|
||||||
|
[in the documentation for ClientSession](https://aiohttp.readthedocs.io/en/stable/client_reference.html#client-session).
|
||||||
|
|
||||||
|
Below is a complete example of an endpoint test,
|
||||||
|
using [pytest](http://doc.pytest.org/en/latest/). The test checks that the
|
||||||
|
`/challenge` endpoint responds to a GET request with a supplied challenge
|
||||||
|
string.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
import aiohttp
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
# Import the Sanic app, usually created with Sanic(__name__)
|
||||||
|
from external_server import app
|
||||||
|
|
||||||
|
def test_endpoint_challenge():
|
||||||
|
# Create the challenge data
|
||||||
|
request_data = {'challenge': 'dummy_challenge'}
|
||||||
|
|
||||||
|
# Send the request to the endpoint, using the default `get` method
|
||||||
|
request, response = sanic_endpoint_test(app,
|
||||||
|
uri='/challenge',
|
||||||
|
params=request_data)
|
||||||
|
|
||||||
|
# Assert that the server responds with the challenge string
|
||||||
|
assert response.text == request_data['challenge']
|
||||||
|
```
|
23
examples/override_logging.py
Normal file
23
examples/override_logging.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import text
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
|
||||||
|
logging_format += "%(module)s::%(funcName)s():l%(lineno)d: "
|
||||||
|
logging_format += "%(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format=logging_format,
|
||||||
|
level=logging.DEBUG
|
||||||
|
)
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
# Set logger to override default basicConfig
|
||||||
|
sanic = Sanic(logger=True)
|
||||||
|
@sanic.route("/")
|
||||||
|
def test(request):
|
||||||
|
log.info("received request; responding with 'hey'")
|
||||||
|
return text("hey")
|
||||||
|
|
||||||
|
sanic.run(host="0.0.0.0", port=8000)
|
|
@ -1,6 +1,6 @@
|
||||||
from .sanic import Sanic
|
from .sanic import Sanic
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
|
|
||||||
__version__ = '0.1.8'
|
__version__ = '0.1.9'
|
||||||
|
|
||||||
__all__ = ['Sanic', 'Blueprint']
|
__all__ = ['Sanic', 'Blueprint']
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO, format="%(asctime)s: %(levelname)s: %(message)s")
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
|
@ -23,6 +23,10 @@ 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
|
||||||
|
@ -109,6 +113,23 @@ class Router:
|
||||||
else:
|
else:
|
||||||
self.routes_static[uri] = route
|
self.routes_static[uri] = route
|
||||||
|
|
||||||
|
def remove(self, uri, clean_cache=True):
|
||||||
|
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
|
||||||
|
|
|
@ -3,13 +3,14 @@ from collections import deque
|
||||||
from functools import partial
|
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 select import select
|
||||||
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
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .exceptions import Handler
|
from .exceptions import Handler
|
||||||
from .log import log, logging
|
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
|
||||||
|
@ -18,7 +19,13 @@ from .exceptions import ServerError
|
||||||
|
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
def __init__(self, name=None, router=None, error_handler=None):
|
def __init__(self, name=None, router=None,
|
||||||
|
error_handler=None, logger=None):
|
||||||
|
if logger is None:
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s: %(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
if name is None:
|
if name is None:
|
||||||
frame_records = stack()[1]
|
frame_records = stack()[1]
|
||||||
name = getmodulename(frame_records[1])
|
name = getmodulename(frame_records[1])
|
||||||
|
@ -73,6 +80,9 @@ class Sanic:
|
||||||
self.route(uri=uri, methods=methods)(handler)
|
self.route(uri=uri, methods=methods)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
def remove_route(self, uri, clean_cache=True):
|
||||||
|
self.router.remove(uri, clean_cache)
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def exception(self, *exceptions):
|
def exception(self, *exceptions):
|
||||||
"""
|
"""
|
||||||
|
@ -345,8 +355,7 @@ class Sanic:
|
||||||
|
|
||||||
# Infinitely wait for the stop event
|
# Infinitely wait for the stop event
|
||||||
try:
|
try:
|
||||||
while not stop_event.is_set():
|
select(stop_event)
|
||||||
sleep(0.3)
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from signal import SIGINT, SIGTERM
|
||||||
from time import time
|
from time import time
|
||||||
from httptools import HttpRequestParser
|
from httptools import HttpRequestParser
|
||||||
from httptools.parser.errors import HttpParserError
|
from httptools.parser.errors import HttpParserError
|
||||||
|
from .exceptions import ServerError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvloop as async_loop
|
import uvloop as async_loop
|
||||||
|
@ -173,8 +174,9 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
"Writing error failed, connection closed {}".format(e))
|
"Writing error failed, connection closed {}".format(e))
|
||||||
|
|
||||||
def bail_out(self, message):
|
def bail_out(self, message):
|
||||||
log.debug(message)
|
exception = ServerError(message)
|
||||||
self.transport.close()
|
self.write_error(exception)
|
||||||
|
log.error(message)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.parser = None
|
self.parser = None
|
||||||
|
|
|
@ -2,6 +2,7 @@ from aiofiles.os import stat
|
||||||
from os import path
|
from os import path
|
||||||
from re import sub
|
from re import sub
|
||||||
from time import strftime, gmtime
|
from time import strftime, gmtime
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from .exceptions import FileNotFound, InvalidUsage
|
from .exceptions import FileNotFound, InvalidUsage
|
||||||
from .response import file, HTTPResponse
|
from .response import file, HTTPResponse
|
||||||
|
@ -32,12 +33,17 @@ def register(app, uri, file_or_directory, pattern, use_modified_since):
|
||||||
# served. os.path.realpath seems to be very slow
|
# served. os.path.realpath seems to be very slow
|
||||||
if file_uri and '../' in file_uri:
|
if file_uri and '../' in file_uri:
|
||||||
raise InvalidUsage("Invalid URL")
|
raise InvalidUsage("Invalid URL")
|
||||||
|
|
||||||
# Merge served directory and requested file if provided
|
# Merge served directory and requested file if provided
|
||||||
# Strip all / that in the beginning of the URL to help prevent python
|
# Strip all / that in the beginning of the URL to help prevent python
|
||||||
# from herping a derp and treating the uri as an absolute path
|
# from herping a derp and treating the uri as an absolute path
|
||||||
file_path = path.join(file_or_directory, sub('^[/]*', '', file_uri)) \
|
file_path = file_or_directory
|
||||||
if file_uri else file_or_directory
|
if file_uri:
|
||||||
|
file_path = path.join(
|
||||||
|
file_or_directory, sub('^[/]*', '', file_uri))
|
||||||
|
|
||||||
|
# URL decode the path sent by the browser otherwise we won't be able to
|
||||||
|
# match filenames which got encoded (filenames with spaces etc)
|
||||||
|
file_path = unquote(file_path)
|
||||||
try:
|
try:
|
||||||
headers = {}
|
headers = {}
|
||||||
# Check if the client has been sent this file before
|
# Check if the client has been sent this file before
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"os"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TestJSONResponse struct {
|
||||||
|
Test bool
|
||||||
|
}
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
|
response := TestJSONResponse{true}
|
||||||
|
|
||||||
|
js, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(js)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", handler)
|
http.HandleFunc("/", handler)
|
||||||
http.ListenAndServe(":" + os.Args[1], nil)
|
http.ListenAndServe(":"+os.Args[1], nil)
|
||||||
}
|
}
|
||||||
|
|
1
tests/static/decode me.txt
Normal file
1
tests/static/decode me.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
I need to be decoded as a uri
|
1
tests/static/test.file
Normal file
1
tests/static/test.file
Normal file
|
@ -0,0 +1 @@
|
||||||
|
I am just a regular static file
|
33
tests/test_logging.py
Normal file
33
tests/test_logging.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import asyncio
|
||||||
|
from sanic.response import text
|
||||||
|
from sanic import Sanic
|
||||||
|
from io import StringIO
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging_format = '''module: %(module)s; \
|
||||||
|
function: %(funcName)s(); \
|
||||||
|
message: %(message)s'''
|
||||||
|
|
||||||
|
def test_log():
|
||||||
|
log_stream = StringIO()
|
||||||
|
for handler in logging.root.handlers[:]:
|
||||||
|
logging.root.removeHandler(handler)
|
||||||
|
logging.basicConfig(
|
||||||
|
format=logging_format,
|
||||||
|
level=logging.DEBUG,
|
||||||
|
stream=log_stream
|
||||||
|
)
|
||||||
|
log = logging.getLogger()
|
||||||
|
app = Sanic('test_logging', logger=True)
|
||||||
|
@app.route('/')
|
||||||
|
def handler(request):
|
||||||
|
log.info('hello world')
|
||||||
|
return text('hello')
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
log_text = log_stream.getvalue().strip().split('\n')[-3]
|
||||||
|
assert log_text == "module: test_logging; function: handler(); message: hello world"
|
||||||
|
|
||||||
|
if __name__ =="__main__":
|
||||||
|
test_log()
|
|
@ -1,5 +1,5 @@
|
||||||
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
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
@ -51,3 +51,27 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -2,6 +2,7 @@ from json import loads as json_loads, dumps as json_dumps
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
from sanic.exceptions import ServerError
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
@ -45,7 +46,7 @@ def test_headers():
|
||||||
assert response.headers.get('spam') == 'great'
|
assert response.headers.get('spam') == 'great'
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_headers():
|
def test_non_str_headers():
|
||||||
app = Sanic('test_text')
|
app = Sanic('test_text')
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -57,6 +58,21 @@ def test_invalid_headers():
|
||||||
|
|
||||||
assert response.headers.get('answer') == '42'
|
assert response.headers.get('answer') == '42'
|
||||||
|
|
||||||
|
def test_invalid_response():
|
||||||
|
app = Sanic('test_invalid_response')
|
||||||
|
|
||||||
|
@app.exception(ServerError)
|
||||||
|
def handler_exception(request, exception):
|
||||||
|
return text('Internal Server Error.', 500)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def handler(request):
|
||||||
|
return 'This should fail'
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app)
|
||||||
|
assert response.status == 500
|
||||||
|
assert response.text == "Internal Server Error."
|
||||||
|
|
||||||
|
|
||||||
def test_json():
|
def test_json():
|
||||||
app = Sanic('test_json')
|
app = Sanic('test_json')
|
||||||
|
|
18
tests/test_response.py
Normal file
18
tests/test_response.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import HTTPResponse
|
||||||
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_body_not_a_string():
|
||||||
|
"""Test when a response body sent from the application is not a string"""
|
||||||
|
app = Sanic('response_body_not_a_string')
|
||||||
|
random_num = choice(range(1000))
|
||||||
|
|
||||||
|
@app.route('/hello')
|
||||||
|
async def hello_route(request):
|
||||||
|
return HTTPResponse(body=random_num)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/hello')
|
||||||
|
assert response.text == str(random_num)
|
|
@ -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
|
||||||
|
|
|
@ -1,30 +1,62 @@
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.utils import sanic_endpoint_test
|
from sanic.utils import sanic_endpoint_test
|
||||||
|
|
||||||
def test_static_file():
|
|
||||||
current_file = inspect.getfile(inspect.currentframe())
|
|
||||||
with open(current_file, 'rb') as file:
|
|
||||||
current_file_contents = file.read()
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def static_file_directory():
|
||||||
|
"""The static directory to serve"""
|
||||||
|
current_file = inspect.getfile(inspect.currentframe())
|
||||||
|
current_directory = os.path.dirname(os.path.abspath(current_file))
|
||||||
|
static_directory = os.path.join(current_directory, 'static')
|
||||||
|
return static_directory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def static_file_path(static_file_directory):
|
||||||
|
"""The path to the static file that we want to serve"""
|
||||||
|
return os.path.join(static_file_directory, 'test.file')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def static_file_content(static_file_path):
|
||||||
|
"""The content of the static file to check"""
|
||||||
|
with open(static_file_path, 'rb') as file:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_file(static_file_path, static_file_content):
|
||||||
app = Sanic('test_static')
|
app = Sanic('test_static')
|
||||||
app.static('/testing.file', current_file)
|
app.static('/testing.file', static_file_path)
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/testing.file')
|
request, response = sanic_endpoint_test(app, uri='/testing.file')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == current_file_contents
|
assert response.body == static_file_content
|
||||||
|
|
||||||
def test_static_directory():
|
|
||||||
current_file = inspect.getfile(inspect.currentframe())
|
def test_static_directory(
|
||||||
current_directory = os.path.dirname(os.path.abspath(current_file))
|
static_file_directory, static_file_path, static_file_content):
|
||||||
with open(current_file, 'rb') as file:
|
|
||||||
current_file_contents = file.read()
|
|
||||||
|
|
||||||
app = Sanic('test_static')
|
app = Sanic('test_static')
|
||||||
app.static('/dir', current_directory)
|
app.static('/dir', static_file_directory)
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/dir/test_static.py')
|
request, response = sanic_endpoint_test(app, uri='/dir/test.file')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == current_file_contents
|
assert response.body == static_file_content
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_url_decode_file(static_file_directory):
|
||||||
|
decode_me_path = os.path.join(static_file_directory, 'decode me.txt')
|
||||||
|
with open(decode_me_path, 'rb') as file:
|
||||||
|
decode_me_contents = file.read()
|
||||||
|
|
||||||
|
app = Sanic('test_static')
|
||||||
|
app.static('/dir', static_file_directory)
|
||||||
|
|
||||||
|
request, response = sanic_endpoint_test(app, uri='/dir/decode me.txt')
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.body == decode_me_contents
|
||||||
|
|
33
tox.ini
33
tox.ini
|
@ -1,34 +1,25 @@
|
||||||
[tox]
|
[tox]
|
||||||
|
|
||||||
envlist = py35, report
|
envlist = py35, py36, flake8
|
||||||
|
|
||||||
|
[travis]
|
||||||
|
|
||||||
|
python =
|
||||||
|
3.5: py35, flake8
|
||||||
|
3.6: py36, flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
aiohttp
|
aiohttp
|
||||||
pytest
|
pytest
|
||||||
# pytest-cov
|
|
||||||
coverage
|
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
coverage run -m pytest tests {posargs}
|
pytest tests {posargs}
|
||||||
mv .coverage .coverage.{envname}
|
|
||||||
|
|
||||||
basepython:
|
[testenv:flake8]
|
||||||
py35: python3.5
|
deps =
|
||||||
|
flake8
|
||||||
whitelist_externals =
|
|
||||||
coverage
|
|
||||||
mv
|
|
||||||
echo
|
|
||||||
|
|
||||||
[testenv:report]
|
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
coverage combine
|
flake8 sanic
|
||||||
coverage report
|
|
||||||
coverage html
|
|
||||||
echo "Open file://{toxinidir}/coverage/index.html"
|
|
||||||
|
|
||||||
basepython =
|
|
||||||
python3.5
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user