Merge branch 'master' into improved_config
This commit is contained in:
commit
2d4512cd1c
|
@ -1,6 +1,7 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- '3.5'
|
- '3.5'
|
||||||
|
- '3.6'
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- pip install -r requirements-dev.txt
|
- pip install -r requirements-dev.txt
|
||||||
|
|
|
@ -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)
|
||||||
|
* [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.
|
||||||
|
|
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__)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from aiofiles import open as open_async
|
from aiofiles import open as open_async
|
||||||
from .cookies import CookieJar
|
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from ujson import dumps as json_dumps
|
from ujson import dumps as json_dumps
|
||||||
|
|
||||||
|
from .cookies import CookieJar
|
||||||
|
|
||||||
COMMON_STATUS_CODES = {
|
COMMON_STATUS_CODES = {
|
||||||
200: b'OK',
|
200: b'OK',
|
||||||
400: b'Bad Request',
|
400: b'Bad Request',
|
||||||
|
@ -79,7 +81,12 @@ class HTTPResponse:
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
|
|
||||||
if body is not None:
|
if body is not None:
|
||||||
|
try:
|
||||||
|
# Try to encode it regularly
|
||||||
self.body = body.encode('utf-8')
|
self.body = body.encode('utf-8')
|
||||||
|
except AttributeError:
|
||||||
|
# Convert it to a str if you can't
|
||||||
|
self.body = str(body).encode('utf-8')
|
||||||
else:
|
else:
|
||||||
self.body = body_bytes
|
self.body = body_bytes
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,11 @@ from multiprocessing import Process, Event
|
||||||
from signal import signal, SIGTERM, SIGINT
|
from signal import signal, SIGTERM, SIGINT
|
||||||
from time import sleep
|
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])
|
||||||
|
|
|
@ -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
|
||||||
|
@ -14,7 +15,7 @@ except ImportError:
|
||||||
|
|
||||||
from .log import log
|
from .log import log
|
||||||
from .request import Request
|
from .request import Request
|
||||||
from .exceptions import RequestTimeout, PayloadTooLarge
|
from .exceptions import RequestTimeout, PayloadTooLarge, InvalidUsage
|
||||||
|
|
||||||
|
|
||||||
class Signal:
|
class Signal:
|
||||||
|
@ -105,9 +106,9 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
# Parse request chunk or close connection
|
# Parse request chunk or close connection
|
||||||
try:
|
try:
|
||||||
self.parser.feed_data(data)
|
self.parser.feed_data(data)
|
||||||
except HttpParserError as e:
|
except HttpParserError:
|
||||||
self.bail_out(
|
exception = InvalidUsage('Bad Request')
|
||||||
"Invalid request data, connection closed ({})".format(e))
|
self.write_error(exception)
|
||||||
|
|
||||||
def on_url(self, url):
|
def on_url(self, url):
|
||||||
self.url = url
|
self.url = url
|
||||||
|
@ -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
|
20
tests/test_bad_request.py
Normal file
20
tests/test_bad_request.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import asyncio
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_request_response():
|
||||||
|
app = Sanic('test_bad_request_response')
|
||||||
|
lines = []
|
||||||
|
async def _request(sanic, loop):
|
||||||
|
connect = asyncio.open_connection('127.0.0.1', 42101)
|
||||||
|
reader, writer = await connect
|
||||||
|
writer.write(b'not http')
|
||||||
|
while True:
|
||||||
|
line = await reader.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
lines.append(line)
|
||||||
|
app.stop()
|
||||||
|
app.run(host='127.0.0.1', port=42101, debug=False, after_start=_request)
|
||||||
|
assert lines[0] == b'HTTP/1.1 400 Bad Request\r\n'
|
||||||
|
assert lines[-1] == b'Error: Bad Request'
|
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()
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
@ -32,6 +33,22 @@ def test_text():
|
||||||
assert response.text == 'Hello'
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
|
@ -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
|
||||||
|
|
18
tox.ini
18
tox.ini
|
@ -1,27 +1,30 @@
|
||||||
[tox]
|
[tox]
|
||||||
|
|
||||||
envlist = py35, report
|
envlist = py35, py36
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
aiohttp
|
aiohttp
|
||||||
pytest
|
pytest
|
||||||
# pytest-cov
|
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
coverage run -m pytest tests {posargs}
|
coverage run -m pytest -v tests {posargs}
|
||||||
mv .coverage .coverage.{envname}
|
mv .coverage .coverage.{envname}
|
||||||
|
|
||||||
basepython:
|
|
||||||
py35: python3.5
|
|
||||||
|
|
||||||
whitelist_externals =
|
whitelist_externals =
|
||||||
coverage
|
coverage
|
||||||
mv
|
mv
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
[testenv:flake8]
|
||||||
|
deps =
|
||||||
|
flake8
|
||||||
|
|
||||||
|
commands =
|
||||||
|
flake8 sanic
|
||||||
|
|
||||||
[testenv:report]
|
[testenv:report]
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
|
@ -29,6 +32,3 @@ commands =
|
||||||
coverage report
|
coverage report
|
||||||
coverage html
|
coverage html
|
||||||
echo "Open file://{toxinidir}/coverage/index.html"
|
echo "Open file://{toxinidir}/coverage/index.html"
|
||||||
|
|
||||||
basepython =
|
|
||||||
python3.5
|
|
Loading…
Reference in New Issue
Block a user