Merge pull request #32 from huge-success/master
merge upstream master branch
This commit is contained in:
commit
92cd10c6a8
0
docs/_static/.gitkeep
vendored
Normal file
0
docs/_static/.gitkeep
vendored
Normal file
|
@ -21,8 +21,11 @@ Guides
|
||||||
sanic/streaming
|
sanic/streaming
|
||||||
sanic/class_based_views
|
sanic/class_based_views
|
||||||
sanic/custom_protocol
|
sanic/custom_protocol
|
||||||
|
sanic/sockets
|
||||||
sanic/ssl
|
sanic/ssl
|
||||||
sanic/logging
|
sanic/logging
|
||||||
|
sanic/versioning
|
||||||
|
sanic/debug_mode
|
||||||
sanic/testing
|
sanic/testing
|
||||||
sanic/deploying
|
sanic/deploying
|
||||||
sanic/extensions
|
sanic/extensions
|
||||||
|
|
|
@ -48,7 +48,7 @@ by that blueprint. In this example, the registered routes in the `app.router`
|
||||||
will look like:
|
will look like:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
[Route(handler=<function bp_root at 0x7f908382f9d8>, methods=None, pattern=re.compile('^/$'), parameters=[])]
|
[Route(handler=<function bp_root at 0x7f908382f9d8>, methods=frozenset({'GET'}), pattern=re.compile('^/$'), parameters=[], name='my_blueprint.bp_root', uri='/')]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Blueprint groups and nesting
|
## Blueprint groups and nesting
|
||||||
|
@ -87,7 +87,7 @@ from sanic import Blueprint
|
||||||
from .static import static
|
from .static import static
|
||||||
from .authors import authors
|
from .authors import authors
|
||||||
|
|
||||||
content = Blueprint.group(assets, authors, url_prefix='/content')
|
content = Blueprint.group(static, authors, url_prefix='/content')
|
||||||
```
|
```
|
||||||
```python
|
```python
|
||||||
# api/info.py
|
# api/info.py
|
||||||
|
@ -254,5 +254,3 @@ async def root(request):
|
||||||
async def post_handler(request, post_id):
|
async def post_handler(request, post_id):
|
||||||
return text('Post {} in Blueprint V1'.format(post_id))
|
return text('Post {} in Blueprint V1'.format(post_id))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ See it's that simple!
|
||||||
## Pull requests!
|
## Pull requests!
|
||||||
|
|
||||||
So the pull request approval rules are pretty simple:
|
So the pull request approval rules are pretty simple:
|
||||||
1. All pull requests must pass unit tests
|
* All pull requests must pass unit tests
|
||||||
* All pull requests must be reviewed and approved by at least
|
* All pull requests must be reviewed and approved by at least
|
||||||
one current collaborator on the project
|
one current collaborator on the project
|
||||||
* All pull requests must pass flake8 checks
|
* All pull requests must pass flake8 checks
|
||||||
|
|
|
@ -4,8 +4,13 @@ Make sure you have both [pip](https://pip.pypa.io/en/stable/installing/) and at
|
||||||
least version 3.5 of Python before starting. Sanic uses the new `async`/`await`
|
least version 3.5 of Python before starting. Sanic uses the new `async`/`await`
|
||||||
syntax, so earlier versions of python won't work.
|
syntax, so earlier versions of python won't work.
|
||||||
|
|
||||||
1. Install Sanic: `python3 -m pip install sanic`
|
## 1. Install Sanic
|
||||||
2. Create a file called `main.py` with the following code:
|
|
||||||
|
```
|
||||||
|
python3 -m pip install sanic
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Create a file called `main.py`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
@ -21,8 +26,15 @@ syntax, so earlier versions of python won't work.
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8000)
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run the server: `python3 main.py`
|
## 3. Run the server
|
||||||
4. Open the address `http://0.0.0.0:8000` in your web browser. You should see
|
|
||||||
the message *Hello world!*.
|
```
|
||||||
|
python3 main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Check your browser
|
||||||
|
|
||||||
|
Open the address `http://0.0.0.0:8000` in your web browser. You should see
|
||||||
|
the message *Hello world!*.
|
||||||
|
|
||||||
You now have a working Sanic server!
|
You now have a working Sanic server!
|
||||||
|
|
|
@ -17,7 +17,7 @@ string representing its type: `'request'` or `'response'`.
|
||||||
|
|
||||||
The simplest middleware doesn't modify the request or response at all:
|
The simplest middleware doesn't modify the request or response at all:
|
||||||
|
|
||||||
```python
|
```
|
||||||
@app.middleware('request')
|
@app.middleware('request')
|
||||||
async def print_on_request(request):
|
async def print_on_request(request):
|
||||||
print("I print when a request is received by the server")
|
print("I print when a request is received by the server")
|
||||||
|
@ -33,7 +33,7 @@ Middleware can modify the request or response parameter it is given, *as long
|
||||||
as it does not return it*. The following example shows a practical use-case for
|
as it does not return it*. The following example shows a practical use-case for
|
||||||
this.
|
this.
|
||||||
|
|
||||||
```python
|
```
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
@app.middleware('response')
|
@app.middleware('response')
|
||||||
|
@ -60,7 +60,7 @@ and the response will be returned. If this occurs to a request before the
|
||||||
relevant user route handler is reached, the handler will never be called.
|
relevant user route handler is reached, the handler will never be called.
|
||||||
Returning a response will also prevent any further middleware from running.
|
Returning a response will also prevent any further middleware from running.
|
||||||
|
|
||||||
```python
|
```
|
||||||
@app.middleware('request')
|
@app.middleware('request')
|
||||||
async def halt_request(request):
|
async def halt_request(request):
|
||||||
return text('I halted the request')
|
return text('I halted the request')
|
||||||
|
@ -83,7 +83,7 @@ These listeners are implemented as decorators on functions which accept the app
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```python
|
```
|
||||||
@app.listener('before_server_start')
|
@app.listener('before_server_start')
|
||||||
async def setup_db(app, loop):
|
async def setup_db(app, loop):
|
||||||
app.db = await db_setup()
|
app.db = await db_setup()
|
||||||
|
@ -105,7 +105,7 @@ It's also possible to register a listener using the `register_listener` method.
|
||||||
This may be useful if you define your listeners in another module besides
|
This may be useful if you define your listeners in another module besides
|
||||||
the one you instantiate your app in.
|
the one you instantiate your app in.
|
||||||
|
|
||||||
```python
|
```
|
||||||
app = Sanic()
|
app = Sanic()
|
||||||
|
|
||||||
async def setup_db(app, loop):
|
async def setup_db(app, loop):
|
||||||
|
@ -118,7 +118,7 @@ app.register_listener(setup_db, 'before_server_start')
|
||||||
If you want to schedule a background task to run after the loop has started,
|
If you want to schedule a background task to run after the loop has started,
|
||||||
Sanic provides the `add_task` method to easily do so.
|
Sanic provides the `add_task` method to easily do so.
|
||||||
|
|
||||||
```python
|
```
|
||||||
async def notify_server_started_after_five_seconds():
|
async def notify_server_started_after_five_seconds():
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
print('Server successfully started!')
|
print('Server successfully started!')
|
||||||
|
@ -128,7 +128,7 @@ app.add_task(notify_server_started_after_five_seconds())
|
||||||
|
|
||||||
Sanic will attempt to automatically inject the app, passing it as an argument to the task:
|
Sanic will attempt to automatically inject the app, passing it as an argument to the task:
|
||||||
|
|
||||||
```python
|
```
|
||||||
async def notify_server_started_after_five_seconds(app):
|
async def notify_server_started_after_five_seconds(app):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
print(app.name)
|
print(app.name)
|
||||||
|
@ -138,7 +138,7 @@ app.add_task(notify_server_started_after_five_seconds)
|
||||||
|
|
||||||
Or you can pass the app explicitly for the same effect:
|
Or you can pass the app explicitly for the same effect:
|
||||||
|
|
||||||
```python
|
```
|
||||||
async def notify_server_started_after_five_seconds(app):
|
async def notify_server_started_after_five_seconds(app):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
print(app.name)
|
print(app.name)
|
||||||
|
|
66
docs/sanic/sockets.rst
Normal file
66
docs/sanic/sockets.rst
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
Sockets
|
||||||
|
=======
|
||||||
|
|
||||||
|
Sanic can use the python
|
||||||
|
`socket module <https://docs.python.org/3/library/socket.html>`_ to accommodate
|
||||||
|
non IPv4 sockets.
|
||||||
|
|
||||||
|
IPv6 example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
import socket
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||||
|
sock.bind(('::', 7777))
|
||||||
|
|
||||||
|
app = Sanic()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def test(request):
|
||||||
|
return json({"hello": "world"})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(sock=sock)
|
||||||
|
|
||||||
|
to test IPv6 ``curl -g -6 "http://[::1]:7777/"``
|
||||||
|
|
||||||
|
|
||||||
|
UNIX socket example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
|
||||||
|
|
||||||
|
server_socket = '/tmp/sanic.sock'
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
sock.bind(server_socket)
|
||||||
|
|
||||||
|
app = Sanic()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def test(request):
|
||||||
|
return json({"hello": "world"})
|
||||||
|
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
print('Exiting')
|
||||||
|
os.unlink(server_socket)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(sock=sock)
|
||||||
|
|
||||||
|
to test UNIX: ``curl -v --unix-socket /tmp/sanic.sock http://localhost/hello``
|
|
@ -43,6 +43,7 @@ and ``recv`` methods to send and receive data respectively.
|
||||||
You could setup your own WebSocket configuration through ``app.config``, like
|
You could setup your own WebSocket configuration through ``app.config``, like
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
app.config.WEBSOCKET_MAX_SIZE = 2 ** 20
|
app.config.WEBSOCKET_MAX_SIZE = 2 ** 20
|
||||||
app.config.WEBSOCKET_MAX_QUEUE = 32
|
app.config.WEBSOCKET_MAX_QUEUE = 32
|
||||||
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
app.config.WEBSOCKET_READ_LIMIT = 2 ** 16
|
||||||
|
|
|
@ -17,5 +17,5 @@ dependencies:
|
||||||
- aiofiles>=0.3.0
|
- aiofiles>=0.3.0
|
||||||
- websockets>=6.0
|
- websockets>=6.0
|
||||||
- sphinxcontrib-asyncio>=0.2.0
|
- sphinxcontrib-asyncio>=0.2.0
|
||||||
- multidict>=4.0<5.0
|
- multidict>=4.0,<5.0
|
||||||
- https://github.com/channelcat/docutils-fork/zipball/master
|
- https://github.com/channelcat/docutils-fork/zipball/master
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.8.3"
|
__version__ = "0.8.3"
|
||||||
|
|
||||||
__all__ = ["Sanic", "Blueprint"]
|
__all__ = ["Sanic", "Blueprint"]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from sanic.log import logger
|
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
|
from sanic.log import logger
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = ArgumentParser(prog="sanic")
|
parser = ArgumentParser(prog="sanic")
|
||||||
|
@ -51,5 +52,5 @@ if __name__ == "__main__":
|
||||||
" Example File: project/sanic_server.py -> app\n"
|
" Example File: project/sanic_server.py -> app\n"
|
||||||
" Example Module: project.sanic_server.app".format(e.name)
|
" Example Module: project.sanic_server.app".format(e.name)
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
logger.exception("Failed to run app")
|
logger.exception("Failed to run app")
|
||||||
|
|
34
sanic/app.py
34
sanic/app.py
|
@ -1,29 +1,30 @@
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from asyncio import get_event_loop, ensure_future, CancelledError
|
|
||||||
from collections import deque, defaultdict
|
from asyncio import CancelledError, ensure_future, get_event_loop
|
||||||
|
from collections import defaultdict, deque
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import getmodulename, isawaitable, signature, stack
|
from inspect import getmodulename, isawaitable, signature, stack
|
||||||
|
from ssl import Purpose, create_default_context
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from urllib.parse import urlencode, urlunparse
|
from urllib.parse import urlencode, urlunparse
|
||||||
from ssl import create_default_context, Purpose
|
|
||||||
|
|
||||||
|
from sanic import reloader_helpers
|
||||||
from sanic.config import Config
|
from sanic.config import Config
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import ServerError, URLBuildError, SanicException
|
from sanic.exceptions import SanicException, ServerError, URLBuildError
|
||||||
from sanic.handlers import ErrorHandler
|
from sanic.handlers import ErrorHandler
|
||||||
from sanic.log import logger, error_logger, LOGGING_CONFIG_DEFAULTS
|
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||||
from sanic.response import HTTPResponse, StreamingHTTPResponse
|
from sanic.response import HTTPResponse, StreamingHTTPResponse
|
||||||
from sanic.router import Router
|
from sanic.router import Router
|
||||||
from sanic.server import serve, serve_multiple, HttpProtocol, Signal
|
from sanic.server import HttpProtocol, Signal, serve, serve_multiple
|
||||||
from sanic.static import register as static_register
|
from sanic.static import register as static_register
|
||||||
from sanic.testing import SanicTestClient
|
from sanic.testing import SanicTestClient
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
from sanic.websocket import WebSocketProtocol, ConnectionClosed
|
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||||
import sanic.reloader_helpers as reloader_helpers
|
|
||||||
|
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
|
@ -370,8 +371,7 @@ class Sanic:
|
||||||
):
|
):
|
||||||
"""Decorate a function to be registered as a websocket route
|
"""Decorate a function to be registered as a websocket route
|
||||||
:param uri: path of the URL
|
:param uri: path of the URL
|
||||||
:param subprotocols: optional list of strings with the supported
|
:param subprotocols: optional list of str with supported subprotocols
|
||||||
subprotocols
|
|
||||||
:param host:
|
:param host:
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
|
@ -567,7 +567,7 @@ class Sanic:
|
||||||
return self.blueprint(*args, **kwargs)
|
return self.blueprint(*args, **kwargs)
|
||||||
|
|
||||||
def url_for(self, view_name: str, **kwargs):
|
def url_for(self, view_name: str, **kwargs):
|
||||||
"""Build a URL based on a view name and the values provided.
|
r"""Build a URL based on a view name and the values provided.
|
||||||
|
|
||||||
In order to build a URL, all request parameters must be supplied as
|
In order to build a URL, all request parameters must be supplied as
|
||||||
keyword arguments, and each parameter must pass the test for the
|
keyword arguments, and each parameter must pass the test for the
|
||||||
|
@ -578,7 +578,7 @@ class Sanic:
|
||||||
the output URL's query string.
|
the output URL's query string.
|
||||||
|
|
||||||
:param view_name: string referencing the view name
|
:param view_name: string referencing the view name
|
||||||
:param \*\*kwargs: keys and values that are used to build request
|
:param \**kwargs: keys and values that are used to build request
|
||||||
parameters and query string arguments.
|
parameters and query string arguments.
|
||||||
|
|
||||||
:return: the built URL
|
:return: the built URL
|
||||||
|
@ -835,6 +835,14 @@ class Sanic:
|
||||||
access_log=True,
|
access_log=True,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
if "loop" in kwargs:
|
||||||
|
raise TypeError(
|
||||||
|
"loop is not a valid argument. To use an existing loop, "
|
||||||
|
"change to create_server().\nSee more: "
|
||||||
|
"https://sanic.readthedocs.io/en/latest/sanic/deploying.html"
|
||||||
|
"#asynchronous-support"
|
||||||
|
)
|
||||||
|
|
||||||
"""Run the HTTP Server and listen until keyboard interrupt or term
|
"""Run the HTTP Server and listen until keyboard interrupt or term
|
||||||
signal. On termination, drain connections before closing.
|
signal. On termination, drain connections before closing.
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ from collections import defaultdict, namedtuple
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
|
||||||
|
|
||||||
FutureRoute = namedtuple(
|
FutureRoute = namedtuple(
|
||||||
"Route",
|
"FutureRoute",
|
||||||
[
|
[
|
||||||
"handler",
|
"handler",
|
||||||
"uri",
|
"uri",
|
||||||
|
@ -16,11 +17,15 @@ FutureRoute = namedtuple(
|
||||||
"name",
|
"name",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
FutureListener = namedtuple("Listener", ["handler", "uri", "methods", "host"])
|
FutureListener = namedtuple(
|
||||||
FutureMiddleware = namedtuple("Route", ["middleware", "args", "kwargs"])
|
"FutureListener", ["handler", "uri", "methods", "host"]
|
||||||
FutureException = namedtuple("Route", ["handler", "args", "kwargs"])
|
)
|
||||||
|
FutureMiddleware = namedtuple(
|
||||||
|
"FutureMiddleware", ["middleware", "args", "kwargs"]
|
||||||
|
)
|
||||||
|
FutureException = namedtuple("FutureException", ["handler", "args", "kwargs"])
|
||||||
FutureStatic = namedtuple(
|
FutureStatic = namedtuple(
|
||||||
"Route", ["uri", "file_or_directory", "args", "kwargs"]
|
"FutureStatic", ["uri", "file_or_directory", "args", "kwargs"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# SimpleCookie
|
# SimpleCookie
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
@ -17,7 +18,7 @@ _Translator.update({ord('"'): '\\"', ord("\\"): "\\\\"})
|
||||||
|
|
||||||
|
|
||||||
def _quote(str):
|
def _quote(str):
|
||||||
"""Quote a string for use in a cookie header.
|
r"""Quote a string for use in a cookie header.
|
||||||
If the string does not need to be double-quoted, then just return the
|
If the string does not need to be double-quoted, then just return the
|
||||||
string. Otherwise, surround the string in doublequotes and quote
|
string. Otherwise, surround the string in doublequotes and quote
|
||||||
(with a \) special characters.
|
(with a \) special characters.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from sanic.helpers import STATUS_CODES
|
from sanic.helpers import STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
TRACEBACK_STYLE = """
|
TRACEBACK_STYLE = """
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from traceback import extract_tb, format_exc
|
from traceback import extract_tb, format_exc
|
||||||
|
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
ContentRangeError,
|
|
||||||
HeaderNotFound,
|
|
||||||
INTERNAL_SERVER_ERROR_HTML,
|
INTERNAL_SERVER_ERROR_HTML,
|
||||||
InvalidRangeType,
|
TRACEBACK_BORDER,
|
||||||
SanicException,
|
|
||||||
TRACEBACK_LINE_HTML,
|
TRACEBACK_LINE_HTML,
|
||||||
TRACEBACK_STYLE,
|
TRACEBACK_STYLE,
|
||||||
TRACEBACK_WRAPPER_HTML,
|
TRACEBACK_WRAPPER_HTML,
|
||||||
TRACEBACK_WRAPPER_INNER_HTML,
|
TRACEBACK_WRAPPER_INNER_HTML,
|
||||||
TRACEBACK_BORDER,
|
ContentRangeError,
|
||||||
|
HeaderNotFound,
|
||||||
|
InvalidRangeType,
|
||||||
|
SanicException,
|
||||||
)
|
)
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
from sanic.response import text, html
|
from sanic.response import html, text
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler:
|
class ErrorHandler:
|
||||||
|
@ -166,17 +167,17 @@ class ContentRangeHandler:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# this case represents `Content-Range: bytes 5-`
|
# this case represents `Content-Range: bytes 5-`
|
||||||
self.end = self.total
|
self.end = self.total - 1
|
||||||
else:
|
else:
|
||||||
if self.start is None:
|
if self.start is None:
|
||||||
# this case represents `Content-Range: bytes -5`
|
# this case represents `Content-Range: bytes -5`
|
||||||
self.start = self.total - self.end
|
self.start = self.total - self.end
|
||||||
self.end = self.total
|
self.end = self.total - 1
|
||||||
if self.start >= self.end:
|
if self.start >= self.end:
|
||||||
raise ContentRangeError(
|
raise ContentRangeError(
|
||||||
"Invalid for Content Range parameters", self
|
"Invalid for Content Range parameters", self
|
||||||
)
|
)
|
||||||
self.size = self.end - self.start
|
self.size = self.end - self.start + 1
|
||||||
self.headers = {
|
self.headers = {
|
||||||
"Content-Range": "bytes %s-%s/%s"
|
"Content-Range": "bytes %s-%s/%s"
|
||||||
% (self.start, self.end, self.total)
|
% (self.start, self.end, self.total)
|
||||||
|
|
|
@ -6,7 +6,7 @@ LOGGING_CONFIG_DEFAULTS = dict(
|
||||||
version=1,
|
version=1,
|
||||||
disable_existing_loggers=False,
|
disable_existing_loggers=False,
|
||||||
loggers={
|
loggers={
|
||||||
"root": {"level": "INFO", "handlers": ["console"]},
|
"sanic.root": {"level": "INFO", "handlers": ["console"]},
|
||||||
"sanic.error": {
|
"sanic.error": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"handlers": ["error_console"],
|
"handlers": ["error_console"],
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
from time import sleep
|
import sys
|
||||||
|
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
def _iter_module_files():
|
def _iter_module_files():
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
from cgi import parse_header
|
from cgi import parse_header
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
from httptools import parse_url
|
|
||||||
from urllib.parse import parse_qs, urlunparse
|
from urllib.parse import parse_qs, urlunparse
|
||||||
|
|
||||||
|
from httptools import parse_url
|
||||||
|
|
||||||
|
from sanic.exceptions import InvalidUsage
|
||||||
|
from sanic.log import error_logger, logger
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ujson import loads as json_loads
|
from ujson import loads as json_loads
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -18,8 +24,6 @@ except ImportError:
|
||||||
else:
|
else:
|
||||||
json_loads = json.loads
|
json_loads = json.loads
|
||||||
|
|
||||||
from sanic.exceptions import InvalidUsage
|
|
||||||
from sanic.log import error_logger, logger
|
|
||||||
|
|
||||||
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
|
from functools import partial
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import path
|
from os import path
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
try:
|
|
||||||
from ujson import dumps as json_dumps
|
|
||||||
except BaseException:
|
|
||||||
from json import dumps as json_dumps
|
|
||||||
|
|
||||||
from aiofiles import open as open_async
|
from aiofiles import open as open_async
|
||||||
from multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
|
||||||
from sanic.helpers import STATUS_CODES, has_message_body, remove_entity_headers
|
|
||||||
from sanic.cookies import CookieJar
|
from sanic.cookies import CookieJar
|
||||||
|
from sanic.helpers import STATUS_CODES, has_message_body, remove_entity_headers
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ujson import dumps as json_dumps
|
||||||
|
except BaseException:
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
# This is done in order to ensure that the JSON response is
|
||||||
|
# kept consistent across both ujson and inbuilt json usage.
|
||||||
|
json_dumps = partial(dumps, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
class BaseHTTPResponse:
|
class BaseHTTPResponse:
|
||||||
|
@ -301,6 +307,7 @@ async def file(
|
||||||
_range.end,
|
_range.end,
|
||||||
_range.total,
|
_range.total,
|
||||||
)
|
)
|
||||||
|
status = 206
|
||||||
else:
|
else:
|
||||||
out_stream = await _file.read()
|
out_stream = await _file.read()
|
||||||
|
|
||||||
|
@ -370,6 +377,7 @@ async def file_stream(
|
||||||
_range.end,
|
_range.end,
|
||||||
_range.total,
|
_range.total,
|
||||||
)
|
)
|
||||||
|
status = 206
|
||||||
return StreamingHTTPResponse(
|
return StreamingHTTPResponse(
|
||||||
streaming_fn=_streaming_fn,
|
streaming_fn=_streaming_fn,
|
||||||
status=status,
|
status=status,
|
||||||
|
@ -421,7 +429,7 @@ def redirect(
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
|
|
||||||
# URL Quote the URL before redirecting
|
# URL Quote the URL before redirecting
|
||||||
safe_to = quote_plus(to, safe=":/#?&=@[]!$&'()*+,;")
|
safe_to = quote_plus(to, safe=":/%#?&=@[]!$&'()*+,;")
|
||||||
|
|
||||||
# According to RFC 7231, a relative URI is now permitted.
|
# According to RFC 7231, a relative URI is now permitted.
|
||||||
headers["Location"] = safe_to
|
headers["Location"] = safe_to
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from sanic.exceptions import NotFound, MethodNotSupported
|
from sanic.exceptions import MethodNotSupported, NotFound
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
|
||||||
|
|
||||||
Route = namedtuple(
|
Route = namedtuple(
|
||||||
"Route", ["handler", "methods", "pattern", "parameters", "name", "uri"]
|
"Route", ["handler", "methods", "pattern", "parameters", "name", "uri"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from signal import SIGTERM, SIGINT, SIG_IGN, signal as signal_func, Signals
|
from signal import SIG_IGN, SIGINT, SIGTERM, Signals
|
||||||
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
from signal import signal as signal_func
|
||||||
|
from socket import SO_REUSEADDR, SOL_SOCKET, socket
|
||||||
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 multidict import CIMultiDict
|
from multidict import CIMultiDict
|
||||||
|
|
||||||
|
from sanic.exceptions import (
|
||||||
|
InvalidUsage,
|
||||||
|
PayloadTooLarge,
|
||||||
|
RequestTimeout,
|
||||||
|
ServerError,
|
||||||
|
ServiceUnavailable,
|
||||||
|
)
|
||||||
|
from sanic.log import access_logger, logger
|
||||||
|
from sanic.request import Request
|
||||||
|
from sanic.response import HTTPResponse
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import uvloop
|
import uvloop
|
||||||
|
|
||||||
|
@ -19,16 +33,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from sanic.log import logger, access_logger
|
|
||||||
from sanic.response import HTTPResponse
|
|
||||||
from sanic.request import Request
|
|
||||||
from sanic.exceptions import (
|
|
||||||
RequestTimeout,
|
|
||||||
PayloadTooLarge,
|
|
||||||
InvalidUsage,
|
|
||||||
ServerError,
|
|
||||||
ServiceUnavailable,
|
|
||||||
)
|
|
||||||
|
|
||||||
current_time = None
|
current_time = None
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import path
|
from os import path
|
||||||
from re import sub
|
from re import sub
|
||||||
from time import strftime, gmtime
|
from time import gmtime, strftime
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from aiofiles.os import stat
|
from aiofiles.os import stat
|
||||||
|
@ -13,7 +13,7 @@ from sanic.exceptions import (
|
||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
)
|
)
|
||||||
from sanic.handlers import ContentRangeHandler
|
from sanic.handlers import ContentRangeHandler
|
||||||
from sanic.response import file, file_stream, HTTPResponse
|
from sanic.response import HTTPResponse, file, file_stream
|
||||||
|
|
||||||
|
|
||||||
def register(
|
def register(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from sanic.log import logger
|
|
||||||
from sanic.exceptions import MethodNotSupported
|
from sanic.exceptions import MethodNotSupported
|
||||||
|
from sanic.log import logger
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ class SanicTestClient:
|
||||||
) as response:
|
) as response:
|
||||||
try:
|
try:
|
||||||
response.text = await response.text()
|
response.text = await response.text()
|
||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError:
|
||||||
response.text = None
|
response.text = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from sanic.exceptions import InvalidUsage
|
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.exceptions import InvalidUsage
|
||||||
|
|
||||||
|
|
||||||
class HTTPMethodView:
|
class HTTPMethodView:
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
from httptools import HttpParserUpgrade
|
||||||
|
from websockets import ConnectionClosed # noqa
|
||||||
|
from websockets import InvalidHandshake, WebSocketCommonProtocol, handshake
|
||||||
|
|
||||||
from sanic.exceptions import InvalidUsage
|
from sanic.exceptions import InvalidUsage
|
||||||
from sanic.server import HttpProtocol
|
from sanic.server import HttpProtocol
|
||||||
from httptools import HttpParserUpgrade
|
|
||||||
from websockets import handshake, WebSocketCommonProtocol, InvalidHandshake
|
|
||||||
from websockets import ConnectionClosed # noqa
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketProtocol(HttpProtocol):
|
class WebSocketProtocol(HttpProtocol):
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import signal
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import gunicorn.workers.base as base
|
||||||
|
|
||||||
|
from sanic.server import HttpProtocol, Signal, serve, trigger_events
|
||||||
|
from sanic.websocket import WebSocketProtocol
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -16,10 +22,6 @@ try:
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
import gunicorn.workers.base as base
|
|
||||||
|
|
||||||
from sanic.server import trigger_events, serve, HttpProtocol, Signal
|
|
||||||
from sanic.websocket import WebSocketProtocol
|
|
||||||
|
|
||||||
|
|
||||||
class GunicornWorker(base.Worker):
|
class GunicornWorker(base.Worker):
|
||||||
|
|
13
setup.cfg
13
setup.cfg
|
@ -2,3 +2,16 @@
|
||||||
# https://github.com/ambv/black#slices
|
# https://github.com/ambv/black#slices
|
||||||
# https://github.com/ambv/black#line-breaks--binary-operators
|
# https://github.com/ambv/black#line-breaks--binary-operators
|
||||||
ignore = E203, W503
|
ignore = E203, W503
|
||||||
|
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
atomic=true
|
||||||
|
default_section = THIRDPARTY
|
||||||
|
include_trailing_comma = true
|
||||||
|
known_first_party = sanic
|
||||||
|
known_third_party = pytest
|
||||||
|
line_length = 79
|
||||||
|
lines_after_imports = 2
|
||||||
|
lines_between_types = 1
|
||||||
|
multi_line_output = 3
|
||||||
|
not_skip = __init__.py
|
||||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
|
|
||||||
def test_bad_request_response(app):
|
def test_bad_request_response(app):
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
@app.listener('after_server_start')
|
@app.listener('after_server_start')
|
||||||
async def _request(sanic, loop):
|
async def _request(sanic, loop):
|
||||||
connect = asyncio.open_connection('127.0.0.1', 42101)
|
connect = asyncio.open_connection('127.0.0.1', 42101)
|
||||||
|
|
|
@ -48,6 +48,20 @@ def test_load_env_prefix():
|
||||||
del environ["MYAPP_TEST_ANSWER"]
|
del environ["MYAPP_TEST_ANSWER"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_env_prefix_float_values():
|
||||||
|
environ["MYAPP_TEST_ROI"] = "2.3"
|
||||||
|
app = Sanic(load_env="MYAPP_")
|
||||||
|
assert app.config.TEST_ROI == 2.3
|
||||||
|
del environ["MYAPP_TEST_ROI"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_env_prefix_string_value():
|
||||||
|
environ["MYAPP_TEST_TOKEN"] = "somerandomtesttoken"
|
||||||
|
app = Sanic(load_env="MYAPP_")
|
||||||
|
assert app.config.TEST_TOKEN == "somerandomtesttoken"
|
||||||
|
del environ["MYAPP_TEST_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
def test_load_from_file(app):
|
def test_load_from_file(app):
|
||||||
config = dedent("""
|
config = dedent("""
|
||||||
VALUE = 'some value'
|
VALUE = 'some value'
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
from sanic import Sanic
|
from sanic.response import text
|
||||||
from sanic.response import json, text
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from sanic.cookies import Cookie
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# GET
|
# GET
|
||||||
|
@ -62,6 +61,7 @@ def test_false_cookies(app, httponly, expected):
|
||||||
|
|
||||||
assert ('HttpOnly' in response_cookies['right_back'].output()) == expected
|
assert ('HttpOnly' in response_cookies['right_back'].output()) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_http2_cookies(app):
|
def test_http2_cookies(app):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -74,6 +74,7 @@ def test_http2_cookies(app):
|
||||||
|
|
||||||
assert response.text == 'Cookies are: working!'
|
assert response.text == 'Cookies are: working!'
|
||||||
|
|
||||||
|
|
||||||
def test_cookie_options(app):
|
def test_cookie_options(app):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -81,7 +82,8 @@ def test_cookie_options(app):
|
||||||
response = text("OK")
|
response = text("OK")
|
||||||
response.cookies['test'] = 'at you'
|
response.cookies['test'] = 'at you'
|
||||||
response.cookies['test']['httponly'] = True
|
response.cookies['test']['httponly'] = True
|
||||||
response.cookies['test']['expires'] = datetime.now() + timedelta(seconds=10)
|
response.cookies['test']['expires'] = (datetime.now() +
|
||||||
|
timedelta(seconds=10))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
@ -89,7 +91,8 @@ def test_cookie_options(app):
|
||||||
response_cookies.load(response.headers.get('Set-Cookie', {}))
|
response_cookies.load(response.headers.get('Set-Cookie', {}))
|
||||||
|
|
||||||
assert response_cookies['test'].value == 'at you'
|
assert response_cookies['test'].value == 'at you'
|
||||||
assert response_cookies['test']['httponly'] == True
|
assert response_cookies['test']['httponly'] is True
|
||||||
|
|
||||||
|
|
||||||
def test_cookie_deletion(app):
|
def test_cookie_deletion(app):
|
||||||
|
|
||||||
|
@ -107,4 +110,23 @@ def test_cookie_deletion(app):
|
||||||
|
|
||||||
assert int(response_cookies['i_want_to_die']['max-age']) == 0
|
assert int(response_cookies['i_want_to_die']['max-age']) == 0
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
hold_my_beer = response.cookies['i_never_existed']
|
response.cookies['i_never_existed']
|
||||||
|
|
||||||
|
|
||||||
|
def test_cookie_reserved_cookie():
|
||||||
|
with pytest.raises(expected_exception=KeyError) as e:
|
||||||
|
Cookie("domain", "testdomain.com")
|
||||||
|
assert e.message == "Cookie name is a reserved word"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cookie_illegal_key_format():
|
||||||
|
with pytest.raises(expected_exception=KeyError) as e:
|
||||||
|
Cookie("testå", "test")
|
||||||
|
assert e.message == "Cookie key contains illegal characters"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cookie_set_unknown_property():
|
||||||
|
c = Cookie("test_cookie", "value")
|
||||||
|
with pytest.raises(expected_exception=KeyError) as e:
|
||||||
|
c["invalid"] = "value"
|
||||||
|
assert e.message == "Unknown cookie property"
|
||||||
|
|
|
@ -28,6 +28,7 @@ def test_create_task(app):
|
||||||
request, response = app.test_client.get('/late')
|
request, response = app.test_client.get('/late')
|
||||||
assert response.body == b'True'
|
assert response.body == b'True'
|
||||||
|
|
||||||
|
|
||||||
def test_create_task_with_app_arg(app):
|
def test_create_task_with_app_arg(app):
|
||||||
q = Queue()
|
q = Queue()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from sanic import Sanic
|
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.router import RouteExists
|
from sanic.router import RouteExists
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -131,7 +131,7 @@ def test_exception_handler_lookup():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ModuleNotFoundError
|
ModuleNotFoundError
|
||||||
except:
|
except Exception:
|
||||||
class ModuleNotFoundError(ImportError):
|
class ModuleNotFoundError(ImportError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
14
tests/test_helpers.py
Normal file
14
tests/test_helpers.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from sanic.helpers import has_message_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_message_body():
|
||||||
|
tests = (
|
||||||
|
(100, False),
|
||||||
|
(102, False),
|
||||||
|
(204, False),
|
||||||
|
(200, True),
|
||||||
|
(304, False),
|
||||||
|
(400, True),
|
||||||
|
)
|
||||||
|
for status_code, expected in tests:
|
||||||
|
assert has_message_body(status_code) is expected
|
|
@ -140,10 +140,11 @@ class ReuseableSanicTestClient(SanicTestClient):
|
||||||
if self._tcp_connector:
|
if self._tcp_connector:
|
||||||
conn = self._tcp_connector
|
conn = self._tcp_connector
|
||||||
else:
|
else:
|
||||||
conn = ReuseableTCPConnector(verify_ssl=False,
|
conn = ReuseableTCPConnector(
|
||||||
|
verify_ssl=False,
|
||||||
loop=self._loop,
|
loop=self._loop,
|
||||||
keepalive_timeout=
|
keepalive_timeout=request_keepalive
|
||||||
request_keepalive)
|
)
|
||||||
self._tcp_connector = conn
|
self._tcp_connector = conn
|
||||||
session = aiohttp.ClientSession(cookies=cookies,
|
session = aiohttp.ClientSession(cookies=cookies,
|
||||||
connector=conn,
|
connector=conn,
|
||||||
|
|
|
@ -49,7 +49,7 @@ def test_logging_defaults():
|
||||||
reset_logging()
|
reset_logging()
|
||||||
app = Sanic("test_logging")
|
app = Sanic("test_logging")
|
||||||
|
|
||||||
for fmt in [h.formatter for h in logging.getLogger('root').handlers]:
|
for fmt in [h.formatter for h in logging.getLogger('sanic.root').handlers]:
|
||||||
assert fmt._fmt == LOGGING_CONFIG_DEFAULTS['formatters']['generic']['format']
|
assert fmt._fmt == LOGGING_CONFIG_DEFAULTS['formatters']['generic']['format']
|
||||||
|
|
||||||
for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]:
|
for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]:
|
||||||
|
@ -68,7 +68,7 @@ def test_logging_pass_customer_logconfig():
|
||||||
|
|
||||||
app = Sanic("test_logging", log_config=modified_config)
|
app = Sanic("test_logging", log_config=modified_config)
|
||||||
|
|
||||||
for fmt in [h.formatter for h in logging.getLogger('root').handlers]:
|
for fmt in [h.formatter for h in logging.getLogger('sanic.root').handlers]:
|
||||||
assert fmt._fmt == modified_config['formatters']['generic']['format']
|
assert fmt._fmt == modified_config['formatters']['generic']['format']
|
||||||
|
|
||||||
for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]:
|
for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]:
|
||||||
|
@ -82,7 +82,7 @@ def test_logging_pass_customer_logconfig():
|
||||||
def test_log_connection_lost(app, debug, monkeypatch):
|
def test_log_connection_lost(app, debug, monkeypatch):
|
||||||
""" Should not log Connection lost exception on non debug """
|
""" Should not log Connection lost exception on non debug """
|
||||||
stream = StringIO()
|
stream = StringIO()
|
||||||
root = logging.getLogger('root')
|
root = logging.getLogger('sanic.root')
|
||||||
root.addHandler(logging.StreamHandler(stream))
|
root.addHandler(logging.StreamHandler(stream))
|
||||||
monkeypatch.setattr(sanic.server, 'logger', root)
|
monkeypatch.setattr(sanic.server, 'logger', root)
|
||||||
|
|
||||||
|
@ -102,3 +102,15 @@ def test_log_connection_lost(app, debug, monkeypatch):
|
||||||
assert 'Connection lost before response written @' in log
|
assert 'Connection lost before response written @' in log
|
||||||
else:
|
else:
|
||||||
assert 'Connection lost before response written @' not in log
|
assert 'Connection lost before response written @' not in log
|
||||||
|
|
||||||
|
|
||||||
|
def test_logging_modified_root_logger_config():
|
||||||
|
reset_logging()
|
||||||
|
|
||||||
|
modified_config = LOGGING_CONFIG_DEFAULTS
|
||||||
|
modified_config['loggers']['sanic.root']['level'] = 'DEBUG'
|
||||||
|
|
||||||
|
app = Sanic("test_logging", log_config=modified_config)
|
||||||
|
|
||||||
|
assert logging.getLogger('sanic.root').getEffectiveLevel() == logging.DEBUG
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ def test_middleware_request(app):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@app.middleware
|
@app.middleware
|
||||||
async def handler(request):
|
async def handler1(request):
|
||||||
results.append(request)
|
results.append(request)
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def handler(request):
|
async def handler2(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
@ -28,7 +28,7 @@ def test_middleware_response(app):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@app.middleware('request')
|
@app.middleware('request')
|
||||||
async def process_response(request):
|
async def process_request(request):
|
||||||
results.append(request)
|
results.append(request)
|
||||||
|
|
||||||
@app.middleware('response')
|
@app.middleware('response')
|
||||||
|
@ -68,6 +68,7 @@ def test_middleware_response_exception(app):
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
assert result['status_code'] == 404
|
assert result['status_code'] == 404
|
||||||
|
|
||||||
|
|
||||||
def test_middleware_override_request(app):
|
def test_middleware_override_request(app):
|
||||||
|
|
||||||
@app.middleware
|
@app.middleware
|
||||||
|
@ -134,4 +135,4 @@ def test_middleware_order(app):
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert order == [1,2,3,4,5,6]
|
assert order == [1, 2, 3, 4, 5, 6]
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import random
|
import random
|
||||||
import signal
|
import signal
|
||||||
|
import pickle
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic.testing import HOST, PORT
|
from sanic.testing import HOST, PORT
|
||||||
|
from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
@ -27,3 +29,54 @@ def test_multiprocessing(app):
|
||||||
app.run(HOST, PORT, workers=num_workers)
|
app.run(HOST, PORT, workers=num_workers)
|
||||||
|
|
||||||
assert len(process_list) == num_workers
|
assert len(process_list) == num_workers
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiprocessing_with_blueprint(app):
|
||||||
|
from sanic import Blueprint
|
||||||
|
# Selects a number at random so we can spot check
|
||||||
|
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
|
||||||
|
process_list = set()
|
||||||
|
|
||||||
|
def stop_on_alarm(*args):
|
||||||
|
for process in multiprocessing.active_children():
|
||||||
|
process_list.add(process.pid)
|
||||||
|
process.terminate()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGALRM, stop_on_alarm)
|
||||||
|
signal.alarm(3)
|
||||||
|
|
||||||
|
bp = Blueprint('test_text')
|
||||||
|
app.blueprint(bp)
|
||||||
|
app.run(HOST, PORT, workers=num_workers)
|
||||||
|
|
||||||
|
assert len(process_list) == num_workers
|
||||||
|
|
||||||
|
|
||||||
|
# this function must be outside a test function so that it can be
|
||||||
|
# able to be pickled (local functions cannot be pickled).
|
||||||
|
def handler(request):
|
||||||
|
return text('Hello')
|
||||||
|
|
||||||
|
# Muliprocessing on Windows requires app to be able to be pickled
|
||||||
|
@pytest.mark.parametrize('protocol', [3, 4])
|
||||||
|
def test_pickle_app(app, protocol):
|
||||||
|
app.route('/')(handler)
|
||||||
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
|
up_p_app = pickle.loads(p_app)
|
||||||
|
assert up_p_app
|
||||||
|
request, response = app.test_client.get('/')
|
||||||
|
assert response.text == 'Hello'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('protocol', [3, 4])
|
||||||
|
def test_pickle_app_with_bp(app, protocol):
|
||||||
|
from sanic import Blueprint
|
||||||
|
bp = Blueprint('test_text')
|
||||||
|
bp.route('/')(handler)
|
||||||
|
app.blueprint(bp)
|
||||||
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
|
up_p_app = pickle.loads(p_app)
|
||||||
|
assert up_p_app
|
||||||
|
request, response = app.test_client.get('/')
|
||||||
|
assert app.is_request_stream is False
|
||||||
|
assert response.text == 'Hello'
|
||||||
|
|
|
@ -336,7 +336,7 @@ def test_overload_routes(app):
|
||||||
return text('OK1')
|
return text('OK1')
|
||||||
|
|
||||||
@app.route('/overload', methods=['POST', 'PUT'], name='route_second')
|
@app.route('/overload', methods=['POST', 'PUT'], name='route_second')
|
||||||
async def handler1(request):
|
async def handler2(request):
|
||||||
return text('OK2')
|
return text('OK2')
|
||||||
|
|
||||||
request, response = app.test_client.get(app.url_for('route_first'))
|
request, response = app.test_client.get(app.url_for('route_first'))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from sanic.response import text, redirect
|
from sanic.response import text, redirect
|
||||||
|
|
||||||
|
@ -19,15 +20,15 @@ def redirect_app(app):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@app.route('/1')
|
@app.route('/1')
|
||||||
def handler(request):
|
def handler1(request):
|
||||||
return redirect('/2')
|
return redirect('/2')
|
||||||
|
|
||||||
@app.route('/2')
|
@app.route('/2')
|
||||||
def handler(request):
|
def handler2(request):
|
||||||
return redirect('/3')
|
return redirect('/3')
|
||||||
|
|
||||||
@app.route('/3')
|
@app.route('/3')
|
||||||
def handler(request):
|
def handler3(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@app.route('/redirect_with_header_injection')
|
@app.route('/redirect_with_header_injection')
|
||||||
|
@ -107,3 +108,25 @@ def test_redirect_with_header_injection(redirect_app):
|
||||||
assert response.status == 302
|
assert response.status == 302
|
||||||
assert "test-header" not in response.headers
|
assert "test-header" not in response.headers
|
||||||
assert not response.text.startswith('test-body')
|
assert not response.text.startswith('test-body')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("test_str", ["sanic-test", "sanictest", "sanic test"])
|
||||||
|
async def test_redirect_with_params(app, test_client, test_str):
|
||||||
|
|
||||||
|
@app.route("/api/v1/test/<test>/")
|
||||||
|
async def init_handler(request, test):
|
||||||
|
assert test == test_str
|
||||||
|
return redirect("/api/v2/test/{}/".format(quote(test)))
|
||||||
|
|
||||||
|
@app.route("/api/v2/test/<test>/")
|
||||||
|
async def target_handler(request, test):
|
||||||
|
assert test == test_str
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
test_cli = await test_client(app)
|
||||||
|
|
||||||
|
response = await test_cli.get("/api/v1/test/{}/".format(quote(test_str)))
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
txt = await response.text()
|
||||||
|
assert txt == "OK"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import pytest
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
@ -34,7 +33,7 @@ async def test_request_cancel_when_connection_lost(loop, app, test_client):
|
||||||
assert app.still_serving_cancelled_request is False
|
assert app.still_serving_cancelled_request is False
|
||||||
|
|
||||||
|
|
||||||
async def test_stream_request_cancel_when_connection_lost(loop, app, test_client):
|
async def test_stream_request_cancel_when_conn_lost(loop, app, test_client):
|
||||||
app.still_serving_cancelled_request = False
|
app.still_serving_cancelled_request = False
|
||||||
|
|
||||||
@app.post('/post/<id>', stream=True)
|
@app.post('/post/<id>', stream=True)
|
||||||
|
|
|
@ -18,7 +18,10 @@ def test_storage(app):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return json({'user': request.get('user'), 'sidekick': request.get('sidekick')})
|
return json({
|
||||||
|
'user': request.get('user'),
|
||||||
|
'sidekick': request.get('sidekick')
|
||||||
|
})
|
||||||
|
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,8 @@ def test_request_stream_handle_exception(app):
|
||||||
# 405
|
# 405
|
||||||
request, response = app.test_client.get('/post/random_id', data=data)
|
request, response = app.test_client.get('/post/random_id', data=data)
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
assert response.text == 'Error: Method GET not allowed for URL /post/random_id'
|
assert response.text == 'Error: Method GET not allowed for URL' \
|
||||||
|
' /post/random_id'
|
||||||
|
|
||||||
|
|
||||||
def test_request_stream_blueprint(app):
|
def test_request_stream_blueprint(app):
|
||||||
|
|
|
@ -78,7 +78,7 @@ class DelayableTCPConnector(TCPConnector):
|
||||||
await asyncio.sleep(self.delay)
|
await asyncio.sleep(self.delay)
|
||||||
t = req.loop.time()
|
t = req.loop.time()
|
||||||
print("sending at {}".format(t), flush=True)
|
print("sending at {}".format(t), flush=True)
|
||||||
conn = next(iter(args)) # first arg is connection
|
next(iter(args)) # first arg is connection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self.orig_send(*args, **kwargs)
|
return await self.orig_send(*args, **kwargs)
|
||||||
|
|
|
@ -97,7 +97,7 @@ def test_json(app):
|
||||||
|
|
||||||
results = json_loads(response.text)
|
results = json_loads(response.text)
|
||||||
|
|
||||||
assert results.get('test') == True
|
assert results.get('test') is True
|
||||||
|
|
||||||
|
|
||||||
def test_empty_json(app):
|
def test_empty_json(app):
|
||||||
|
@ -278,22 +278,23 @@ def test_post_form_urlencoded(app):
|
||||||
payload = 'test=OK'
|
payload = 'test=OK'
|
||||||
headers = {'content-type': 'application/x-www-form-urlencoded'}
|
headers = {'content-type': 'application/x-www-form-urlencoded'}
|
||||||
|
|
||||||
request, response = app.test_client.post('/', data=payload, headers=headers)
|
request, response = app.test_client.post('/', data=payload,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
assert request.form.get('test') == 'OK'
|
assert request.form.get('test') == 'OK'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'payload', [
|
'payload', [
|
||||||
'------sanic\r\n' \
|
'------sanic\r\n'
|
||||||
'Content-Disposition: form-data; name="test"\r\n' \
|
'Content-Disposition: form-data; name="test"\r\n'
|
||||||
'\r\n' \
|
'\r\n'
|
||||||
'OK\r\n' \
|
'OK\r\n'
|
||||||
'------sanic--\r\n',
|
'------sanic--\r\n',
|
||||||
'------sanic\r\n' \
|
'------sanic\r\n'
|
||||||
'content-disposition: form-data; name="test"\r\n' \
|
'content-disposition: form-data; name="test"\r\n'
|
||||||
'\r\n' \
|
'\r\n'
|
||||||
'OK\r\n' \
|
'OK\r\n'
|
||||||
'------sanic--\r\n',
|
'------sanic--\r\n',
|
||||||
])
|
])
|
||||||
def test_post_form_multipart_form_data(app, payload):
|
def test_post_form_multipart_form_data(app, payload):
|
||||||
|
@ -362,3 +363,83 @@ def test_url_attributes_with_ssl(app, path, query, expected_url):
|
||||||
assert parsed.path == request.path
|
assert parsed.path == request.path
|
||||||
assert parsed.query == request.query_string
|
assert parsed.query == request.query_string
|
||||||
assert parsed.netloc == request.host
|
assert parsed.netloc == request.host
|
||||||
|
|
||||||
|
|
||||||
|
def test_form_with_multiple_values(app):
|
||||||
|
|
||||||
|
@app.route('/', methods=['POST'])
|
||||||
|
async def handler(request):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
payload="selectedItems=v1&selectedItems=v2&selectedItems=v3"
|
||||||
|
|
||||||
|
headers = {'content-type': 'application/x-www-form-urlencoded'}
|
||||||
|
|
||||||
|
request, response = app.test_client.post('/', data=payload,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
assert request.form.getlist("selectedItems") == ["v1", "v2", "v3"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_string_representation(app):
|
||||||
|
@app.route('/', methods=["GET"])
|
||||||
|
async def get(request):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
request, _ = app.test_client.get("/")
|
||||||
|
assert repr(request) == '<Request: GET />'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'payload', [
|
||||||
|
'------sanic\r\n'
|
||||||
|
'Content-Disposition: form-data; filename="filename"; name="test"\r\n'
|
||||||
|
'\r\n'
|
||||||
|
'OK\r\n'
|
||||||
|
'------sanic--\r\n',
|
||||||
|
'------sanic\r\n'
|
||||||
|
'content-disposition: form-data; filename="filename"; name="test"\r\n'
|
||||||
|
'\r\n'
|
||||||
|
'content-type: application/json; {"field": "value"}\r\n'
|
||||||
|
'------sanic--\r\n',
|
||||||
|
])
|
||||||
|
def test_request_multipart_files(app, payload):
|
||||||
|
@app.route("/", methods=["POST"])
|
||||||
|
async def post(request):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
headers = {'content-type': 'multipart/form-data; boundary=----sanic'}
|
||||||
|
|
||||||
|
request, _ = app.test_client.post(data=payload, headers=headers)
|
||||||
|
assert request.files.get('test').name == "filename"
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_multipart_file_with_json_content_type(app):
|
||||||
|
@app.route("/", methods=["POST"])
|
||||||
|
async def post(request):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
payload = '------sanic\r\nContent-Disposition: form-data; name="file"; filename="test.json"' \
|
||||||
|
'\r\nContent-Type: application/json\r\n\r\n\r\n------sanic--'
|
||||||
|
|
||||||
|
headers = {'content-type': 'multipart/form-data; boundary=------sanic'}
|
||||||
|
|
||||||
|
request, _ = app.test_client.post(data=payload, headers=headers)
|
||||||
|
assert request.files.get('file').type == 'application/json'
|
||||||
|
|
||||||
|
|
||||||
|
def test_request_multipart_with_multiple_files_and_type(app):
|
||||||
|
@app.route("/", methods=["POST"])
|
||||||
|
async def post(request):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
payload = '------sanic\r\nContent-Disposition: form-data; name="file"; filename="test.json"' \
|
||||||
|
'\r\nContent-Type: application/json\r\n\r\n\r\n' \
|
||||||
|
'------sanic\r\nContent-Disposition: form-data; name="file"; filename="some_file.pdf"\r\n' \
|
||||||
|
'Content-Type: application/pdf\r\n\r\n\r\n------sanic--'
|
||||||
|
headers = {'content-type': 'multipart/form-data; boundary=------sanic'}
|
||||||
|
|
||||||
|
request, _ = app.test_client.post(data=payload, headers=headers)
|
||||||
|
assert len(request.files.getlist('file')) == 2
|
||||||
|
assert request.files.getlist('file')[0].type == 'application/json'
|
||||||
|
assert request.files.getlist('file')[1].type == 'application/pdf'
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import sys
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
from aiofiles import os as async_os
|
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
|
from random import choice
|
||||||
|
from unittest.mock import MagicMock
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from random import choice
|
from aiofiles import os as async_os
|
||||||
|
|
||||||
from sanic.response import (
|
from sanic.response import (
|
||||||
HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json
|
HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json
|
||||||
)
|
)
|
||||||
from sanic.server import HttpProtocol
|
from sanic.server import HttpProtocol
|
||||||
from sanic.testing import HOST, PORT
|
from sanic.testing import HOST, PORT
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
JSON_DATA = {'ok': True}
|
JSON_DATA = {'ok': True}
|
||||||
|
|
||||||
|
@ -38,9 +37,8 @@ async def sample_streaming_fn(response):
|
||||||
|
|
||||||
|
|
||||||
def test_method_not_allowed(app):
|
def test_method_not_allowed(app):
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
async def test(request):
|
async def test_get(request):
|
||||||
return response.json({'hello': 'world'})
|
return response.json({'hello': 'world'})
|
||||||
|
|
||||||
request, response = app.test_client.head('/')
|
request, response = app.test_client.head('/')
|
||||||
|
@ -49,19 +47,18 @@ def test_method_not_allowed(app):
|
||||||
request, response = app.test_client.post('/')
|
request, response = app.test_client.post('/')
|
||||||
assert response.headers['Allow'] == 'GET'
|
assert response.headers['Allow'] == 'GET'
|
||||||
|
|
||||||
|
|
||||||
@app.post('/')
|
@app.post('/')
|
||||||
async def test(request):
|
async def test_post(request):
|
||||||
return response.json({'hello': 'world'})
|
return response.json({'hello': 'world'})
|
||||||
|
|
||||||
request, response = app.test_client.head('/')
|
request, response = app.test_client.head('/')
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST'])
|
assert set(response.headers['Allow'].split(', ')) == {'GET', 'POST'}
|
||||||
assert response.headers['Content-Length'] == '0'
|
assert response.headers['Content-Length'] == '0'
|
||||||
|
|
||||||
request, response = app.test_client.patch('/')
|
request, response = app.test_client.patch('/')
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
assert set(response.headers['Allow'].split(', ')) == set(['GET', 'POST'])
|
assert set(response.headers['Allow'].split(', ')) == {'GET', 'POST'}
|
||||||
assert response.headers['Content-Length'] == '0'
|
assert response.headers['Content-Length'] == '0'
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,19 +72,62 @@ def test_response_header(app):
|
||||||
'CONTENT-TYPE': 'application/json'
|
'CONTENT-TYPE': 'application/json'
|
||||||
})
|
})
|
||||||
|
|
||||||
is_windows = sys.platform in ['win32', 'cygwin']
|
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
assert dict(response.headers) == {
|
assert dict(response.headers) == {
|
||||||
'Connection': 'keep-alive',
|
'Connection': 'keep-alive',
|
||||||
'Keep-Alive': str(app.config.KEEP_ALIVE_TIMEOUT),
|
'Keep-Alive': str(app.config.KEEP_ALIVE_TIMEOUT),
|
||||||
# response body contains an extra \r at the end if its windows
|
'Content-Length': '11',
|
||||||
# TODO: this is the only place this difference shows up in our tests
|
|
||||||
# we should figure out a way to unify testing on both platforms
|
|
||||||
'Content-Length': '12' if is_windows else '11',
|
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_content_length(app):
|
||||||
|
@app.get("/response_with_space")
|
||||||
|
async def response_with_space(request):
|
||||||
|
return json({
|
||||||
|
"message": "Data",
|
||||||
|
"details": "Some Details"
|
||||||
|
}, headers={
|
||||||
|
'CONTENT-TYPE': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.get("/response_without_space")
|
||||||
|
async def response_without_space(request):
|
||||||
|
return json({
|
||||||
|
"message":"Data",
|
||||||
|
"details":"Some Details"
|
||||||
|
}, headers={
|
||||||
|
'CONTENT-TYPE': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/response_with_space")
|
||||||
|
content_length_for_response_with_space = response.headers.get("Content-Length")
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/response_without_space")
|
||||||
|
content_length_for_response_without_space = response.headers.get("Content-Length")
|
||||||
|
|
||||||
|
assert content_length_for_response_with_space == content_length_for_response_without_space
|
||||||
|
|
||||||
|
assert content_length_for_response_with_space == '43'
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_content_length_with_different_data_types(app):
|
||||||
|
@app.get("/")
|
||||||
|
async def get_data_with_different_types(request):
|
||||||
|
# Indentation issues in the Response is intentional. Please do not fix
|
||||||
|
return json({
|
||||||
|
'bool': True,
|
||||||
|
'none': None,
|
||||||
|
'string':'string',
|
||||||
|
'number': -1},
|
||||||
|
headers={
|
||||||
|
'CONTENT-TYPE': 'application/json'
|
||||||
|
})
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/")
|
||||||
|
assert response.headers.get("Content-Length") == '55'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def json_app(app):
|
def json_app(app):
|
||||||
|
|
||||||
|
@ -239,7 +279,8 @@ def get_file_content(static_file_directory, file_name):
|
||||||
return file.read()
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt', 'python.png'])
|
@pytest.mark.parametrize('file_name',
|
||||||
|
['test.file', 'decode me.txt', 'python.png'])
|
||||||
@pytest.mark.parametrize('status', [200, 401])
|
@pytest.mark.parametrize('status', [200, 401])
|
||||||
def test_file_response(app, file_name, static_file_directory, status):
|
def test_file_response(app, file_name, static_file_directory, status):
|
||||||
|
|
||||||
|
@ -256,9 +297,15 @@ def test_file_response(app, file_name, static_file_directory, status):
|
||||||
assert 'Content-Disposition' not in response.headers
|
assert 'Content-Disposition' not in response.headers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('source,dest', [
|
@pytest.mark.parametrize(
|
||||||
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')])
|
'source,dest',
|
||||||
def test_file_response_custom_filename(app, source, dest, static_file_directory):
|
[
|
||||||
|
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'),
|
||||||
|
('python.png', 'logo.png')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_file_response_custom_filename(app, source, dest,
|
||||||
|
static_file_directory):
|
||||||
|
|
||||||
@app.route('/files/<filename>', methods=['GET'])
|
@app.route('/files/<filename>', methods=['GET'])
|
||||||
def file_route(request, filename):
|
def file_route(request, filename):
|
||||||
|
@ -269,7 +316,8 @@ def test_file_response_custom_filename(app, source, dest, static_file_directory)
|
||||||
request, response = app.test_client.get('/files/{}'.format(source))
|
request, response = app.test_client.get('/files/{}'.format(source))
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, source)
|
assert response.body == get_file_content(static_file_directory, source)
|
||||||
assert response.headers['Content-Disposition'] == 'attachment; filename="{}"'.format(dest)
|
assert response.headers['Content-Disposition'] == \
|
||||||
|
'attachment; filename="{}"'.format(dest)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
|
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
|
||||||
|
@ -300,7 +348,8 @@ def test_file_head_response(app, file_name, static_file_directory):
|
||||||
get_file_content(static_file_directory, file_name))
|
get_file_content(static_file_directory, file_name))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt', 'python.png'])
|
@pytest.mark.parametrize('file_name',
|
||||||
|
['test.file', 'decode me.txt', 'python.png'])
|
||||||
def test_file_stream_response(app, file_name, static_file_directory):
|
def test_file_stream_response(app, file_name, static_file_directory):
|
||||||
|
|
||||||
@app.route('/files/<filename>', methods=['GET'])
|
@app.route('/files/<filename>', methods=['GET'])
|
||||||
|
@ -316,9 +365,15 @@ def test_file_stream_response(app, file_name, static_file_directory):
|
||||||
assert 'Content-Disposition' not in response.headers
|
assert 'Content-Disposition' not in response.headers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('source,dest', [
|
@pytest.mark.parametrize(
|
||||||
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')])
|
'source,dest',
|
||||||
def test_file_stream_response_custom_filename(app, source, dest, static_file_directory):
|
[
|
||||||
|
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'),
|
||||||
|
('python.png', 'logo.png')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_file_stream_response_custom_filename(app, source, dest,
|
||||||
|
static_file_directory):
|
||||||
|
|
||||||
@app.route('/files/<filename>', methods=['GET'])
|
@app.route('/files/<filename>', methods=['GET'])
|
||||||
def file_route(request, filename):
|
def file_route(request, filename):
|
||||||
|
@ -329,7 +384,8 @@ def test_file_stream_response_custom_filename(app, source, dest, static_file_dir
|
||||||
request, response = app.test_client.get('/files/{}'.format(source))
|
request, response = app.test_client.get('/files/{}'.format(source))
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, source)
|
assert response.body == get_file_content(static_file_directory, source)
|
||||||
assert response.headers['Content-Disposition'] == 'attachment; filename="{}"'.format(dest)
|
assert response.headers['Content-Disposition'] == \
|
||||||
|
'attachment; filename="{}"'.format(dest)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
|
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
|
||||||
|
@ -350,8 +406,10 @@ def test_file_stream_head_response(app, file_name, static_file_directory):
|
||||||
headers=headers,
|
headers=headers,
|
||||||
content_type=guess_type(file_path)[0] or 'text/plain')
|
content_type=guess_type(file_path)[0] or 'text/plain')
|
||||||
else:
|
else:
|
||||||
return file_stream(file_path, chunk_size=32, headers=headers,
|
return file_stream(
|
||||||
mime_type=guess_type(file_path)[0] or 'text/plain')
|
file_path, chunk_size=32, headers=headers,
|
||||||
|
mime_type=guess_type(file_path)[0] or 'text/plain'
|
||||||
|
)
|
||||||
|
|
||||||
request, response = app.test_client.head('/files/{}'.format(file_name))
|
request, response = app.test_client.head('/files/{}'.format(file_name))
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
|
@ -64,12 +64,12 @@ def test_shorthand_routes_multiple(app):
|
||||||
def test_route_strict_slash(app):
|
def test_route_strict_slash(app):
|
||||||
|
|
||||||
@app.get('/get', strict_slashes=True)
|
@app.get('/get', strict_slashes=True)
|
||||||
def handler(request):
|
def handler1(request):
|
||||||
assert request.stream is None
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@app.post('/post/', strict_slashes=True)
|
@app.post('/post/', strict_slashes=True)
|
||||||
def handler(request):
|
def handler2(request):
|
||||||
assert request.stream is None
|
assert request.stream is None
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
@ -133,11 +133,11 @@ def test_route_strict_slash_default_value_can_be_overwritten():
|
||||||
def test_route_slashes_overload(app):
|
def test_route_slashes_overload(app):
|
||||||
|
|
||||||
@app.get('/hello/')
|
@app.get('/hello/')
|
||||||
def handler(request):
|
def handler_get(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
@app.post('/hello/')
|
@app.post('/hello/')
|
||||||
def handler(request):
|
def handler_post(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
request, response = app.test_client.get('/hello')
|
request, response = app.test_client.get('/hello')
|
||||||
|
@ -408,7 +408,8 @@ def test_dynamic_route_uuid(app):
|
||||||
results.append(unique_id)
|
results.append(unique_id)
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
request, response = app.test_client.get('/quirky/123e4567-e89b-12d3-a456-426655440000')
|
url = '/quirky/123e4567-e89b-12d3-a456-426655440000'
|
||||||
|
request, response = app.test_client.get(url)
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
assert type(results[0]) is uuid.UUID
|
assert type(results[0]) is uuid.UUID
|
||||||
|
|
||||||
|
@ -532,11 +533,11 @@ def test_route_duplicate(app):
|
||||||
|
|
||||||
with pytest.raises(RouteExists):
|
with pytest.raises(RouteExists):
|
||||||
@app.route('/test/<dynamic>/')
|
@app.route('/test/<dynamic>/')
|
||||||
async def handler1(request, dynamic):
|
async def handler3(request, dynamic):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@app.route('/test/<dynamic>/')
|
@app.route('/test/<dynamic>/')
|
||||||
async def handler2(request, dynamic):
|
async def handler4(request, dynamic):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -882,12 +883,12 @@ def test_unmergeable_overload_routes(app):
|
||||||
assert response.text == 'OK1'
|
assert response.text == 'OK1'
|
||||||
|
|
||||||
@app.route('/overload_part', methods=['GET'])
|
@app.route('/overload_part', methods=['GET'])
|
||||||
async def handler1(request):
|
async def handler3(request):
|
||||||
return text('OK1')
|
return text('OK1')
|
||||||
|
|
||||||
with pytest.raises(RouteExists):
|
with pytest.raises(RouteExists):
|
||||||
@app.route('/overload_part')
|
@app.route('/overload_part')
|
||||||
async def handler2(request):
|
async def handler4(request):
|
||||||
return text('Duplicated')
|
return text('Duplicated')
|
||||||
|
|
||||||
request, response = app.test_client.get('/overload_part')
|
request, response = app.test_client.get('/overload_part')
|
||||||
|
|
|
@ -11,12 +11,15 @@ async def stop(app, loop):
|
||||||
|
|
||||||
calledq = Queue()
|
calledq = Queue()
|
||||||
|
|
||||||
|
|
||||||
def set_loop(app, loop):
|
def set_loop(app, loop):
|
||||||
loop.add_signal_handler = MagicMock()
|
loop.add_signal_handler = MagicMock()
|
||||||
|
|
||||||
|
|
||||||
def after(app, loop):
|
def after(app, loop):
|
||||||
calledq.put(loop.add_signal_handler.called)
|
calledq.put(loop.add_signal_handler.called)
|
||||||
|
|
||||||
|
|
||||||
def test_register_system_signals(app):
|
def test_register_system_signals(app):
|
||||||
"""Test if sanic register system signals"""
|
"""Test if sanic register system signals"""
|
||||||
|
|
||||||
|
@ -29,7 +32,7 @@ def test_register_system_signals(app):
|
||||||
app.listener('after_server_stop')(after)
|
app.listener('after_server_stop')(after)
|
||||||
|
|
||||||
app.run(HOST, PORT)
|
app.run(HOST, PORT)
|
||||||
assert calledq.get() == True
|
assert calledq.get() is True
|
||||||
|
|
||||||
|
|
||||||
def test_dont_register_system_signals(app):
|
def test_dont_register_system_signals(app):
|
||||||
|
@ -44,4 +47,4 @@ def test_dont_register_system_signals(app):
|
||||||
app.listener('after_server_stop')(after)
|
app.listener('after_server_stop')(after)
|
||||||
|
|
||||||
app.run(HOST, PORT, register_sys_signals=False)
|
app.run(HOST, PORT, register_sys_signals=False)
|
||||||
assert calledq.get() == False
|
assert calledq.get() is False
|
||||||
|
|
|
@ -23,7 +23,8 @@ def get_file_content(static_file_directory, file_name):
|
||||||
return file.read()
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt', 'python.png'])
|
@pytest.mark.parametrize('file_name',
|
||||||
|
['test.file', 'decode me.txt', 'python.png'])
|
||||||
def test_static_file(app, static_file_directory, file_name):
|
def test_static_file(app, static_file_directory, file_name):
|
||||||
app.static(
|
app.static(
|
||||||
'/testing.file', get_file_path(static_file_directory, file_name))
|
'/testing.file', get_file_path(static_file_directory, file_name))
|
||||||
|
@ -83,11 +84,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
|
||||||
'Range': 'bytes=12-19'
|
'Range': 'bytes=12-19'
|
||||||
}
|
}
|
||||||
request, response = app.test_client.get('/testing.file', headers=headers)
|
request, response = app.test_client.get('/testing.file', headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
static_file_directory, file_name))[12:19]
|
static_file_directory, file_name))[12:20]
|
||||||
assert int(response.headers[
|
assert int(response.headers[
|
||||||
'Content-Length']) == len(static_content)
|
'Content-Length']) == len(static_content)
|
||||||
assert response.body == static_content
|
assert response.body == static_content
|
||||||
|
@ -103,7 +104,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
|
||||||
'Range': 'bytes=12-'
|
'Range': 'bytes=12-'
|
||||||
}
|
}
|
||||||
request, response = app.test_client.get('/testing.file', headers=headers)
|
request, response = app.test_client.get('/testing.file', headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -123,7 +124,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
|
||||||
'Range': 'bytes=-12'
|
'Range': 'bytes=-12'
|
||||||
}
|
}
|
||||||
request, response = app.test_client.get('/testing.file', headers=headers)
|
request, response = app.test_client.get('/testing.file', headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -143,8 +144,8 @@ def test_static_content_range_empty(app, file_name, static_file_directory):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' not in response.headers
|
assert 'Content-Range' not in response.headers
|
||||||
assert int(response.headers[
|
assert int(response.headers['Content-Length']) == \
|
||||||
'Content-Length']) == len(get_file_content(static_file_directory, file_name))
|
len(get_file_content(static_file_directory, file_name))
|
||||||
assert response.body == bytes(
|
assert response.body == bytes(
|
||||||
get_file_content(static_file_directory, file_name))
|
get_file_content(static_file_directory, file_name))
|
||||||
|
|
||||||
|
@ -166,7 +167,8 @@ def test_static_content_range_error(app, file_name, static_file_directory):
|
||||||
len(get_file_content(static_file_directory, file_name)),)
|
len(get_file_content(static_file_directory, file_name)),)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt', 'python.png'])
|
@pytest.mark.parametrize('file_name',
|
||||||
|
['test.file', 'decode me.txt', 'python.png'])
|
||||||
def test_static_file_specified_host(app, static_file_directory, file_name):
|
def test_static_file_specified_host(app, static_file_directory, file_name):
|
||||||
app.static(
|
app.static(
|
||||||
'/testing.file',
|
'/testing.file',
|
||||||
|
|
|
@ -13,12 +13,16 @@ URL_FOR_ARGS1 = dict(arg1=['v1', 'v2'])
|
||||||
URL_FOR_VALUE1 = '/myurl?arg1=v1&arg1=v2'
|
URL_FOR_VALUE1 = '/myurl?arg1=v1&arg1=v2'
|
||||||
URL_FOR_ARGS2 = dict(arg1=['v1', 'v2'], _anchor='anchor')
|
URL_FOR_ARGS2 = dict(arg1=['v1', 'v2'], _anchor='anchor')
|
||||||
URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor'
|
URL_FOR_VALUE2 = '/myurl?arg1=v1&arg1=v2#anchor'
|
||||||
URL_FOR_ARGS3 = dict(arg1='v1', _anchor='anchor', _scheme='http',
|
URL_FOR_ARGS3 = dict(
|
||||||
_server='{}:{}'.format(test_host, test_port), _external=True)
|
arg1='v1', _anchor='anchor', _scheme='http',
|
||||||
URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
|
_server='{}:{}'.format(test_host, test_port), _external=True
|
||||||
|
)
|
||||||
|
URL_FOR_VALUE3 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host,
|
||||||
|
test_port)
|
||||||
URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True,
|
URL_FOR_ARGS4 = dict(arg1='v1', _anchor='anchor', _external=True,
|
||||||
_server='http://{}:{}'.format(test_host, test_port))
|
_server='http://{}:{}'.format(test_host, test_port))
|
||||||
URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host, test_port)
|
URL_FOR_VALUE4 = 'http://{}:{}/myurl?arg1=v1#anchor'.format(test_host,
|
||||||
|
test_port)
|
||||||
|
|
||||||
|
|
||||||
def _generate_handlers_from_names(app, l):
|
def _generate_handlers_from_names(app, l):
|
||||||
|
|
|
@ -25,7 +25,8 @@ def get_file_content(static_file_directory, file_name):
|
||||||
return file.read()
|
return file.read()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt', 'python.png'])
|
@pytest.mark.parametrize('file_name',
|
||||||
|
['test.file', 'decode me.txt', 'python.png'])
|
||||||
def test_static_file(app, static_file_directory, file_name):
|
def test_static_file(app, static_file_directory, file_name):
|
||||||
app.static(
|
app.static(
|
||||||
'/testing.file', get_file_path(static_file_directory, file_name))
|
'/testing.file', get_file_path(static_file_directory, file_name))
|
||||||
|
@ -211,11 +212,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
|
||||||
assert uri == app.url_for('static', name='static', filename='any')
|
assert uri == app.url_for('static', name='static', filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
static_file_directory, file_name))[12:19]
|
static_file_directory, file_name))[12:20]
|
||||||
assert int(response.headers[
|
assert int(response.headers[
|
||||||
'Content-Length']) == len(static_content)
|
'Content-Length']) == len(static_content)
|
||||||
assert response.body == static_content
|
assert response.body == static_content
|
||||||
|
@ -232,11 +233,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
|
||||||
filename='any')
|
filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
static_file_directory, file_name))[12:19]
|
static_file_directory, file_name))[12:20]
|
||||||
assert int(response.headers[
|
assert int(response.headers[
|
||||||
'Content-Length']) == len(static_content)
|
'Content-Length']) == len(static_content)
|
||||||
assert response.body == static_content
|
assert response.body == static_content
|
||||||
|
@ -262,7 +263,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
|
||||||
assert uri == app.url_for('static', name='static', filename='any')
|
assert uri == app.url_for('static', name='static', filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -283,7 +284,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
|
||||||
filename='any')
|
filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -313,7 +314,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
|
||||||
assert uri == app.url_for('static', name='static', filename='any')
|
assert uri == app.url_for('static', name='static', filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -334,7 +335,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
|
||||||
filename='any')
|
filename='any')
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 200
|
assert response.status == 206
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' in response.headers
|
assert 'Content-Range' in response.headers
|
||||||
static_content = bytes(get_file_content(
|
static_content = bytes(get_file_content(
|
||||||
|
@ -364,8 +365,8 @@ def test_static_content_range_empty(app, file_name, static_file_directory):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' not in response.headers
|
assert 'Content-Range' not in response.headers
|
||||||
assert int(response.headers[
|
assert int(response.headers['Content-Length']) == \
|
||||||
'Content-Length']) == len(get_file_content(static_file_directory, file_name))
|
len(get_file_content(static_file_directory, file_name))
|
||||||
assert response.body == bytes(
|
assert response.body == bytes(
|
||||||
get_file_content(static_file_directory, file_name))
|
get_file_content(static_file_directory, file_name))
|
||||||
|
|
||||||
|
@ -384,8 +385,8 @@ def test_static_content_range_empty(app, file_name, static_file_directory):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert 'Content-Length' in response.headers
|
assert 'Content-Length' in response.headers
|
||||||
assert 'Content-Range' not in response.headers
|
assert 'Content-Range' not in response.headers
|
||||||
assert int(response.headers[
|
assert int(response.headers['Content-Length']) == \
|
||||||
'Content-Length']) == len(get_file_content(static_file_directory, file_name))
|
len(get_file_content(static_file_directory, file_name))
|
||||||
assert response.body == bytes(
|
assert response.body == bytes(
|
||||||
get_file_content(static_file_directory, file_name))
|
get_file_content(static_file_directory, file_name))
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ from sanic.response import text
|
||||||
def test_vhosts(app):
|
def test_vhosts(app):
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def handler(request):
|
async def handler1(request):
|
||||||
return text("You're at example.com!")
|
return text("You're at example.com!")
|
||||||
|
|
||||||
@app.route('/', host="subdomain.example.com")
|
@app.route('/', host="subdomain.example.com")
|
||||||
async def handler(request):
|
async def handler2(request):
|
||||||
return text("You're at subdomain.example.com!")
|
return text("You're at subdomain.example.com!")
|
||||||
|
|
||||||
headers = {"Host": "example.com"}
|
headers = {"Host": "example.com"}
|
||||||
|
@ -38,11 +38,11 @@ def test_vhosts_with_list(app):
|
||||||
def test_vhosts_with_defaults(app):
|
def test_vhosts_with_defaults(app):
|
||||||
|
|
||||||
@app.route('/', host="hello.com")
|
@app.route('/', host="hello.com")
|
||||||
async def handler(request):
|
async def handler1(request):
|
||||||
return text("Hello, world!")
|
return text("Hello, world!")
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def handler(request):
|
async def handler2(request):
|
||||||
return text("default")
|
return text("default")
|
||||||
|
|
||||||
headers = {"Host": "hello.com"}
|
headers = {"Host": "hello.com"}
|
||||||
|
|
|
@ -129,7 +129,7 @@ def test_with_middleware_response(app):
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@app.middleware('request')
|
@app.middleware('request')
|
||||||
async def process_response(request):
|
async def process_request(request):
|
||||||
results.append(request)
|
results.append(request)
|
||||||
|
|
||||||
@app.middleware('response')
|
@app.middleware('response')
|
||||||
|
@ -162,7 +162,8 @@ def test_with_custom_class_methods(app):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
self._iternal_method()
|
self._iternal_method()
|
||||||
return text('I am get method and global var is {}'.format(self.global_var))
|
return text('I am get method and global var '
|
||||||
|
'is {}'.format(self.global_var))
|
||||||
|
|
||||||
app.add_route(DummyView.as_view(), '/')
|
app.add_route(DummyView.as_view(), '/')
|
||||||
request, response = app.test_client.get('/')
|
request, response = app.test_client.get('/')
|
||||||
|
|
|
@ -102,8 +102,8 @@ def test_run_max_requests_exceeded(worker):
|
||||||
|
|
||||||
assert not worker.alive
|
assert not worker.alive
|
||||||
worker.notify.assert_called_with()
|
worker.notify.assert_called_with()
|
||||||
worker.log.info.assert_called_with("Max requests exceeded, shutting down: %s",
|
worker.log.info.assert_called_with("Max requests exceeded, shutting "
|
||||||
worker)
|
"down: %s", worker)
|
||||||
|
|
||||||
|
|
||||||
def test_worker_close(worker):
|
def test_worker_close(worker):
|
||||||
|
@ -125,7 +125,8 @@ def test_worker_close(worker):
|
||||||
worker.loop = loop
|
worker.loop = loop
|
||||||
server = mock.Mock()
|
server = mock.Mock()
|
||||||
server.close = mock.Mock(wraps=lambda *a, **kw: None)
|
server.close = mock.Mock(wraps=lambda *a, **kw: None)
|
||||||
server.wait_closed = mock.Mock(wraps=asyncio.coroutine(lambda *a, **kw: None))
|
server.wait_closed = mock.Mock(wraps=asyncio.coroutine(
|
||||||
|
lambda *a, **kw: None))
|
||||||
worker.servers = {
|
worker.servers = {
|
||||||
server: {"requests_count": 14},
|
server: {"requests_count": 14},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user