Merge pull request #24 from huge-success/master

merge upstream master branch
This commit is contained in:
7 2018-10-06 21:22:54 -07:00 committed by GitHub
commit cc83c1f0cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 812 additions and 706 deletions

View File

@ -1,7 +1,7 @@
[run] [run]
branch = True branch = True
source = sanic source = sanic
omit = site-packages, sanic/utils.py omit = site-packages, sanic/utils.py, sanic/__main__.py
[html] [html]
directory = coverage directory = coverage

View File

@ -31,7 +31,7 @@ deploy:
provider: pypi provider: pypi
user: channelcat user: channelcat
password: password:
secure: OgADRQH3+dTL5swGzXkeRJDNbLpFzwqYnXB4iLD0Npvzj9QnKyQVvkbaeq6VmV9dpEFb5ULaAKYQq19CrXYDm28yanUSn6jdJ4SukaHusi7xt07U6H7pmoX/uZ2WZYqCSLM8cSp8TXY/3oV3rY5Jfj/AibE5XTbim5/lrhsvW6NR+ALzxc0URRPAHDZEPpojTCjSTjpY0aDsaKWg4mXVRMFfY3O68j6KaIoukIZLuoHfePLKrbZxaPG5VxNhMHEaICdxVxE/dO+7pQmQxXuIsEOHK1QiVJ9YrSGcNqgEqhN36kYP8dqMeVB07sv8Xa6o/Uax2/wXS2HEJvuwP1YD6WkoZuo9ZB85bcMdg7BV9jJDbVFVPJwc75BnTLHrMa3Q1KrRlKRDBUXBUsQivPuWhFNwUgvEayq2qSI3aRQR4Z0O+DfboEhXYojSoD64/EWBTZ7vhgbvOTGEdukUQSYrKj9P8jc1s8exomTsAiqdFxTUpzfiammUSL+M93lP4urtahl1jjXFX7gd3DzdEEb0NsGkx5lm/qdsty8/TeAvKUmC+RVU6T856W6MqN0P+yGbpWUARcSE7fwztC3SPxwAuxvIN3BHmRhOUHoORPNG2VpfbnscIzBKJR4v0JKzbpi0IDa66K+tCGsCEvQuL4cxVOtoUySPWNSUAyUWWUrGM2k= secure: h7oNDjA/ObDBGK7xt55SV0INHOclMJW/byxMrNxvCZ0JxiRk7WBNtWYt0WJjyf5lO/L0/sfgiAk0GIdFon57S24njSLPAq/a4ptkWZ68s2A+TaF6ezJSZvE9V8khivjoeub90TzfX6P5aukRja1CSxXKJm+v0V8hGE4CZGyCgEDvK3JqIakpXllSDl19DhVftCS/lQZD7AXrZlg1kZnPCMtB5IbCVR4L2bfrSJVNptBi2CqqxacY2MOLu+jv5FzJ2BGVIJ2zoIJS2T+JmGJzpiamF6y8Amv0667i9lg2DXWCtI3PsQzCmwa3F/ZsI+ohUAvJC5yvzP7SyTJyXifRBdJ9O137QkNAHFoJOOY3B4GSnTo8/boajKXEqGiV4h2EgwNjBaR0WJl0pB7HHUCBMkNRWqo6ACB8eCr04tXWXPvkGIc+wPjq960hsUZea1O31MuktYc9Ot6eiFqm7OKoItdi7LxCen1eTj93ePgkiEnVZ+p/04Hh1U7CX31UJMNu5kCvZPIANnAuDsS2SK7Qkr88OAuWL0wmrBcXKOcnVkJtZ5mzx8T54bI1RrSYtFDBLFfOPb0GucSziMBtQpE76qPEauVwIXBk3RnR8N57xBR/lvTaIk758tf+haO0llEO5rVls1zLNZ+VlTzXy7hX0OZbdopIAcCFBFWqWMAdXQc=
on: on:
tags: true tags: true
distributions: "sdist bdist_wheel" distributions: "sdist bdist_wheel"

View File

@ -1,3 +1,80 @@
Version 0.8
-----------
0.8.3
- Changes:
- Ownership changed to org 'huge-success'
0.8.0
- Changes:
- Add Server-Sent Events extension (Innokenty Lebedev)
- Graceful handling of request_handler_task cancellation (Ashley Sommer)
- Sanitize URL before redirection (aveao)
- Add url_bytes to request (johndoe46)
- py37 support for travisci (yunstanford)
- Auto reloader support for OSX (garyo)
- Add UUID route support (Volodymyr Maksymiv)
- Add pausable response streams (Ashley Sommer)
- Add weakref to request slots (vopankov)
- remove ubuntu 12.04 from test fixture due to deprecation (yunstanford)
- Allow streaming handlers in add_route (kinware)
- use travis_retry for tox (Raphael Deem)
- update aiohttp version for test client (yunstanford)
- add redirect import for clarity (yingshaoxo)
- Update HTTP Entity headers (Arnulfo Solís)
- Add register_listener method (Stephan Fitzpatrick)
- Remove uvloop/ujson dependencies for Windows (abuckenheimer)
- Content-length header on 204/304 responses (Arnulfo Solís)
- Extend WebSocketProtocol arguments and add docs (Bob Olde Hampsink, yunstanford)
- Update development status from pre-alpha to beta (Maksim Anisenkov)
- KeepAlive Timout log level changed to debug (Arnulfo Solís)
- Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim Aniskenov)
- Install Python 3.5 and 3.6 on docker container for tests (Shahin Azad)
- Add support for blueprint groups and nesting (Elias Tarhini)
- Remove uvloop for windows setup (Aleksandr Kurlov)
- Auto Reload (Yaser Amari)
- Documentation updates/fixups (multiple contributors)
- Fixes:
- Fix: auto_reload in Linux (Ashley Sommer)
- Fix: broken tests for aiohttp >= 3.3.0 (Ashley Sommer)
- Fix: disable auto_reload by default on windows (abuckenheimer)
- Fix (1143): Turn off access log with gunicorn (hqy)
- Fix (1268): Support status code for file response (Cosmo Borsky)
- Fix (1266): Add content_type flag to Sanic.static (Cosmo Borsky)
- Fix: subprotocols parameter missing from add_websocket_route (ciscorn)
- Fix (1242): Responses for CI header (yunstanford)
- Fix (1237): add version constraint for websockets (yunstanford)
- Fix (1231): memory leak - always release resource (Phillip Xu)
- Fix (1221): make request truthy if transport exists (Raphael Deem)
- Fix failing tests for aiohttp>=3.1.0 (Ashley Sommer)
- Fix try_everything examples (PyManiacGR, kot83)
- Fix (1158): default to auto_reload in debug mode (Raphael Deem)
- Fix (1136): ErrorHandler.response handler call too restrictive (Julien Castiaux)
- Fix: raw requires bytes-like object (cloudship)
- Fix (1120): passing a list in to a route decorator's host arg (Timothy Ebiuwhe)
- Fix: Bug in multipart/form-data parser (DirkGuijt)
- Fix: Exception for missing parameter when value is null (NyanKiyoshi)
- Fix: Parameter check (Howie Hu)
- Fix (1089): Routing issue with named parameters and different methods (yunstanford)
- Fix (1085): Signal handling in multi-worker mode (yunstanford)
- Fix: single quote in readme.rst (Cosven)
- Fix: method typos (Dmitry Dygalo)
- Fix: log_response correct output for ip and port (Wibowo Arindrarto)
- Fix (1042): Exception Handling (Raphael Deem)
- Fix: Chinese URIs (Howie Hu)
- Fix (1079): timeout bug when self.transport is None (Raphael Deem)
- Fix (1074): fix strict_slashes when route has slash (Raphael Deem)
- Fix (1050): add samesite cookie to cookie keys (Raphael Deem)
- Fix (1065): allow add_task after server starts (Raphael Deem)
- Fix (1061): double quotes in unauthorized exception (Raphael Deem)
- Fix (1062): inject the app in add_task method (Raphael Deem)
- Fix: update environment.yml for readthedocs (Eli Uriegas)
- Fix: Cancel request task when response timeout is triggered (Jeong YunWon)
- Fix (1052): Method not allowed response for RFC7231 compliance (Raphael Deem)
- Fix: IPv6 Address and Socket Data Format (Dan Palmer)
Note: Changelog was unmaintained between 0.1 and 0.7
Version 0.1 Version 0.1
----------- -----------
- 0.1.7 - 0.1.7

View File

@ -7,9 +7,9 @@ Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's ba
On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy. On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.
Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contributions are welcome! Sanic is developed `on GitHub <https://github.com/huge-success/sanic/>`_. We also have `a community discussion board <https://community.sanicframework.org/>`_. Contributions are welcome!
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/channelcat/sanic/issues/396>`_ that we use to track those projects! If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/huge-success/sanic/issues/396>`_ that we use to track those projects!
Hello World Example Hello World Example
------------------- -------------------
@ -47,8 +47,8 @@ Documentation
.. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg .. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg
:target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |Build Status| image:: https://travis-ci.org/channelcat/sanic.svg?branch=master .. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master
:target: https://travis-ci.org/channelcat/sanic :target: https://travis-ci.org/huge-success/sanic
.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest
:target: http://sanic.readthedocs.io/en/latest/?badge=latest :target: http://sanic.readthedocs.io/en/latest/?badge=latest
.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg .. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg
@ -57,23 +57,21 @@ Documentation
:target: https://pypi.python.org/pypi/sanic/ :target: https://pypi.python.org/pypi/sanic/
Questions and Discussion
------------------------
`Ask a question or join the conversation <https://community.sanicframework.org/>`_.
Examples Examples
-------- --------
`Non-Core examples <https://github.com/channelcat/sanic/wiki/Examples/>`_. Examples of plugins and Sanic that are outside the scope of Sanic core. `Non-Core examples <https://github.com/huge-success/sanic/wiki/Examples/>`_. Examples of plugins and Sanic that are outside the scope of Sanic core.
`Extensions <https://github.com/channelcat/sanic/wiki/Extensions/>`_. Sanic extensions created by the community. `Extensions <https://github.com/huge-success/sanic/wiki/Extensions/>`_. Sanic extensions created by the community.
`Projects <https://github.com/channelcat/sanic/wiki/Projects/>`_. Sanic in production use. `Projects <https://github.com/huge-success/sanic/wiki/Projects/>`_. Sanic in production use.
TODO
----
* http2
Limitations
-----------
* No wheels for uvloop and httptools on Windows :(
Final Thoughts Final Thoughts
-------------- --------------

View File

@ -31,3 +31,4 @@ A list of Sanic extensions created by the community.
- [Sanic-Auth](https://github.com/pyx/sanic-auth): A minimal backend agnostic session-based user authentication mechanism for Sanic. - [Sanic-Auth](https://github.com/pyx/sanic-auth): A minimal backend agnostic session-based user authentication mechanism for Sanic.
- [Sanic-CookieSession](https://github.com/pyx/sanic-cookiesession): A client-side only, cookie-based session, similar to the built-in session in Flask. - [Sanic-CookieSession](https://github.com/pyx/sanic-cookiesession): A client-side only, cookie-based session, similar to the built-in session in Flask.
- [Sanic-WTF](https://github.com/pyx/sanic-wtf): Sanic-WTF makes using WTForms with Sanic and CSRF (Cross-Site Request Forgery) protection a little bit easier. - [Sanic-WTF](https://github.com/pyx/sanic-wtf): Sanic-WTF makes using WTForms with Sanic and CSRF (Cross-Site Request Forgery) protection a little bit easier.
- [sanic-sse](https://github.com/inn0kenty/sanic_sse): [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) implementation for Sanic.

View File

@ -37,7 +37,7 @@ async def handler(request):
if body is None: if body is None:
break break
body = body.decode('utf-8').replace('1', 'A') body = body.decode('utf-8').replace('1', 'A')
response.write(body) await response.write(body)
return stream(streaming) return stream(streaming)
@ -85,8 +85,8 @@ app = Sanic(__name__)
@app.route("/") @app.route("/")
async def test(request): async def test(request):
async def sample_streaming_fn(response): async def sample_streaming_fn(response):
response.write('foo,') await response.write('foo,')
response.write('bar') await response.write('bar')
return stream(sample_streaming_fn, content_type='text/csv') return stream(sample_streaming_fn, content_type='text/csv')
``` ```
@ -100,7 +100,7 @@ async def index(request):
conn = await asyncpg.connect(database='test') conn = await asyncpg.connect(database='test')
async with conn.transaction(): async with conn.transaction():
async for record in conn.cursor('SELECT generate_series(0, 10)'): async for record in conn.cursor('SELECT generate_series(0, 10)'):
response.write(record[0]) await response.write(record[0])
return stream(stream_from_db) return stream(stream_from_db)
``` ```

View File

@ -12,9 +12,10 @@ dependencies:
- zlib=1.2.8=0 - zlib=1.2.8=0
- pip: - pip:
- uvloop>=0.5.3 - uvloop>=0.5.3
- httptools>=0.0.9 - httptools>=0.0.10
- ujson>=1.35 - ujson>=1.35
- aiofiles>=0.3.0 - aiofiles>=0.3.0
- websockets>=3.2 - websockets>=6.0
- sphinxcontrib-asyncio>=0.2.0 - sphinxcontrib-asyncio>=0.2.0
- multidict>=4.0<5.0
- https://github.com/channelcat/docutils-fork/zipball/master - https://github.com/channelcat/docutils-fork/zipball/master

View File

@ -1,7 +1,5 @@
from sanic import Sanic from sanic import Blueprint, Sanic
from sanic import Blueprint from sanic.response import file, json
from sanic.response import json
app = Sanic(__name__) app = Sanic(__name__)
blueprint = Blueprint('name', url_prefix='/my_blueprint') blueprint = Blueprint('name', url_prefix='/my_blueprint')
@ -19,7 +17,12 @@ async def foo2(request):
return json({'msg': 'hi from blueprint2'}) return json({'msg': 'hi from blueprint2'})
@blueprint3.websocket('/foo') @blueprint3.route('/foo')
async def index(request):
return await file('websocket.html')
@app.websocket('/feed')
async def foo3(request, ws): async def foo3(request, ws):
while True: while True:
data = 'hello!' data = 'hello!'

View File

@ -30,7 +30,7 @@ async def handler(request):
if body is None: if body is None:
break break
body = body.decode('utf-8').replace('1', 'A') body = body.decode('utf-8').replace('1', 'A')
response.write(body) await response.write(body)
return stream(streaming) return stream(streaming)

View File

@ -0,0 +1,42 @@
from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text
app = Sanic('some_name')
class SimpleView(HTTPMethodView):
def get(self, request):
return text('I am get method')
def post(self, request):
return text('I am post method')
def put(self, request):
return text('I am put method')
def patch(self, request):
return text('I am patch method')
def delete(self, request):
return text('I am delete method')
class SimpleAsyncView(HTTPMethodView):
async def get(self, request):
return text('I am async get method')
async def post(self, request):
return text('I am async post method')
async def put(self, request):
return text('I am async put method')
app.add_route(SimpleView.as_view(), '/')
app.add_route(SimpleAsyncView.as_view(), '/async')
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,12 +1,13 @@
aiofiles aiofiles
aiohttp>=2.3.0 aiohttp>=2.3.0,<=3.2.1
chardet<=2.3.0 chardet<=2.3.0
beautifulsoup4 beautifulsoup4
coverage coverage
httptools httptools>=0.0.10
flake8 flake8
pytest==3.3.2 pytest==3.3.2
tox tox
ujson; sys_platform != "win32" and implementation_name == "cpython" ujson; sys_platform != "win32" and implementation_name == "cpython"
uvloop; sys_platform != "win32" and implementation_name == "cpython" uvloop; sys_platform != "win32" and implementation_name == "cpython"
gunicorn gunicorn
multidict>=4.0,<5.0

View File

@ -1,5 +1,6 @@
aiofiles aiofiles
httptools httptools>=0.0.10
ujson; sys_platform != "win32" and implementation_name == "cpython" ujson; sys_platform != "win32" and implementation_name == "cpython"
uvloop; sys_platform != "win32" and implementation_name == "cpython" uvloop; sys_platform != "win32" and implementation_name == "cpython"
websockets>=5.0,<6.0 websockets>=6.0,<7.0
multidict>=4.0,<5.0

View File

@ -1,6 +1,6 @@
from sanic.app import Sanic from sanic.app import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
__version__ = '0.7.0' __version__ = '0.8.3'
__all__ = ['Sanic', 'Blueprint'] __all__ = ['Sanic', 'Blueprint']

View File

@ -315,13 +315,13 @@ class Sanic:
return response return response
def add_websocket_route(self, handler, uri, host=None, def add_websocket_route(self, handler, uri, host=None,
strict_slashes=None, name=None): strict_slashes=None, subprotocols=None, name=None):
"""A helper method to register a function as a websocket route.""" """A helper method to register a function as a websocket route."""
if strict_slashes is None: if strict_slashes is None:
strict_slashes = self.strict_slashes strict_slashes = self.strict_slashes
return self.websocket(uri, host=host, strict_slashes=strict_slashes, return self.websocket(uri, host=host, strict_slashes=strict_slashes,
name=name)(handler) subprotocols=subprotocols, name=name)(handler)
def enable_websocket(self, enable=True): def enable_websocket(self, enable=True):
"""Enable or disable the support for websocket. """Enable or disable the support for websocket.
@ -386,13 +386,14 @@ class Sanic:
def static(self, uri, file_or_directory, pattern=r'/?.+', def static(self, uri, file_or_directory, pattern=r'/?.+',
use_modified_since=True, use_content_range=False, use_modified_since=True, use_content_range=False,
stream_large_files=False, name='static', host=None, stream_large_files=False, name='static', host=None,
strict_slashes=None): strict_slashes=None, content_type=None):
"""Register a root to serve files from. The input can either be a """Register a root to serve files from. The input can either be a
file or a directory. See file or a directory. See
""" """
static_register(self, uri, file_or_directory, pattern, static_register(self, uri, file_or_directory, pattern,
use_modified_since, use_content_range, use_modified_since, use_content_range,
stream_large_files, name, host, strict_slashes) stream_large_files, name, host, strict_slashes,
content_type)
def blueprint(self, blueprint, **options): def blueprint(self, blueprint, **options):
"""Register a blueprint on the application. """Register a blueprint on the application.
@ -570,6 +571,10 @@ class Sanic:
:return: Nothing :return: Nothing
""" """
# Define `response` var here to remove warnings about
# allocation before assignment below.
response = None
cancelled = False
try: try:
# -------------------------------------------- # # -------------------------------------------- #
# Request Middleware # Request Middleware
@ -596,6 +601,13 @@ class Sanic:
response = handler(request, *args, **kwargs) response = handler(request, *args, **kwargs)
if isawaitable(response): if isawaitable(response):
response = await response response = await response
except CancelledError:
# If response handler times out, the server handles the error
# and cancels the handle_request job.
# In this case, the transport is already closed and we cannot
# issue a response.
response = None
cancelled = True
except Exception as e: except Exception as e:
# -------------------------------------------- # # -------------------------------------------- #
# Response Generation Failed # Response Generation Failed
@ -621,13 +633,22 @@ class Sanic:
# -------------------------------------------- # # -------------------------------------------- #
# Response Middleware # Response Middleware
# -------------------------------------------- # # -------------------------------------------- #
# Don't run response middleware if response is None
if response is not None:
try: try:
response = await self._run_response_middleware(request, response = await self._run_response_middleware(request,
response) response)
except CancelledError:
# Response middleware can timeout too, as above.
response = None
cancelled = True
except BaseException: except BaseException:
error_logger.exception( error_logger.exception(
'Exception occurred in one of response middleware handlers' 'Exception occurred in one of response '
'middleware handlers'
) )
if cancelled:
raise CancelledError()
# pass the response to the correct callback # pass the response to the correct callback
if isinstance(response, StreamingHTTPResponse): if isinstance(response, StreamingHTTPResponse):
@ -670,8 +691,8 @@ class Sanic:
""" """
# Default auto_reload to false # Default auto_reload to false
auto_reload = False auto_reload = False
# If debug is set, default it to true # If debug is set, default it to true (unless on windows)
if debug: if debug and os.name == 'posix':
auto_reload = True auto_reload = True
# Allow for overriding either of the defaults # Allow for overriding either of the defaults
auto_reload = kwargs.get("auto_reload", auto_reload) auto_reload = kwargs.get("auto_reload", auto_reload)
@ -687,11 +708,12 @@ class Sanic:
warnings.simplefilter('default') warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.", warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning) DeprecationWarning)
# compatibility old access_log params
self.config.ACCESS_LOG = access_log
server_settings = self._helper( server_settings = self._helper(
host=host, port=port, debug=debug, ssl=ssl, sock=sock, host=host, port=port, debug=debug, ssl=ssl, sock=sock,
workers=workers, protocol=protocol, backlog=backlog, workers=workers, protocol=protocol, backlog=backlog,
register_sys_signals=register_sys_signals, register_sys_signals=register_sys_signals, auto_reload=auto_reload)
access_log=access_log, auto_reload=auto_reload)
try: try:
self.is_running = True self.is_running = True
@ -745,12 +767,12 @@ class Sanic:
warnings.simplefilter('default') warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.", warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning) DeprecationWarning)
# compatibility old access_log params
self.config.ACCESS_LOG = access_log
server_settings = self._helper( server_settings = self._helper(
host=host, port=port, debug=debug, ssl=ssl, sock=sock, host=host, port=port, debug=debug, ssl=ssl, sock=sock,
loop=get_event_loop(), protocol=protocol, loop=get_event_loop(), protocol=protocol,
backlog=backlog, run_async=True, backlog=backlog, run_async=True)
access_log=access_log)
# Trigger before_start events # Trigger before_start events
await self.trigger_events( await self.trigger_events(
@ -795,8 +817,7 @@ class Sanic:
def _helper(self, host=None, port=None, debug=False, def _helper(self, host=None, port=None, debug=False,
ssl=None, sock=None, workers=1, loop=None, ssl=None, sock=None, workers=1, loop=None,
protocol=HttpProtocol, backlog=100, stop_event=None, protocol=HttpProtocol, backlog=100, stop_event=None,
register_sys_signals=True, run_async=False, access_log=True, register_sys_signals=True, run_async=False, auto_reload=False):
auto_reload=False):
"""Helper function used by `run` and `create_server`.""" """Helper function used by `run` and `create_server`."""
if isinstance(ssl, dict): if isinstance(ssl, dict):
# try common aliaseses # try common aliaseses
@ -837,7 +858,7 @@ class Sanic:
'loop': loop, 'loop': loop,
'register_sys_signals': register_sys_signals, 'register_sys_signals': register_sys_signals,
'backlog': backlog, 'backlog': backlog,
'access_log': access_log, 'access_log': self.config.ACCESS_LOG,
'websocket_max_size': self.config.WEBSOCKET_MAX_SIZE, 'websocket_max_size': self.config.WEBSOCKET_MAX_SIZE,
'websocket_max_queue': self.config.WEBSOCKET_MAX_QUEUE, 'websocket_max_queue': self.config.WEBSOCKET_MAX_QUEUE,
'websocket_read_limit': self.config.WEBSOCKET_READ_LIMIT, 'websocket_read_limit': self.config.WEBSOCKET_READ_LIMIT,

View File

@ -39,6 +39,7 @@ class Config(dict):
self.WEBSOCKET_READ_LIMIT = 2 ** 16 self.WEBSOCKET_READ_LIMIT = 2 ** 16
self.WEBSOCKET_WRITE_LIMIT = 2 ** 16 self.WEBSOCKET_WRITE_LIMIT = 2 ** 16
self.GRACEFUL_SHUTDOWN_TIMEOUT = 15.0 # 15 sec self.GRACEFUL_SHUTDOWN_TIMEOUT = 15.0 # 15 sec
self.ACCESS_LOG = True
if load_env: if load_env:
prefix = SANIC_PREFIX if load_env is True else load_env prefix = SANIC_PREFIX if load_env is True else load_env

View File

@ -47,16 +47,15 @@ class CookieJar(dict):
super().__init__() super().__init__()
self.headers = headers self.headers = headers
self.cookie_headers = {} self.cookie_headers = {}
self.header_key = "Set-Cookie"
def __setitem__(self, key, value): def __setitem__(self, key, value):
# If this cookie doesn't exist, add it to the header keys # If this cookie doesn't exist, add it to the header keys
cookie_header = self.cookie_headers.get(key) if not self.cookie_headers.get(key):
if not cookie_header:
cookie = Cookie(key, value) cookie = Cookie(key, value)
cookie['path'] = '/' cookie['path'] = '/'
cookie_header = MultiHeader("Set-Cookie") self.cookie_headers[key] = self.header_key
self.cookie_headers[key] = cookie_header self.headers.add(self.header_key, cookie)
self.headers[cookie_header] = cookie
return super().__setitem__(key, cookie) return super().__setitem__(key, cookie)
else: else:
self[key].value = value self[key].value = value
@ -67,7 +66,11 @@ class CookieJar(dict):
self[key]['max-age'] = 0 self[key]['max-age'] = 0
else: else:
cookie_header = self.cookie_headers[key] cookie_header = self.cookie_headers[key]
del self.headers[cookie_header] # remove it from header
cookies = self.headers.popall(cookie_header)
for cookie in cookies:
if cookie.key != key:
self.headers.add(cookie_header, cookie)
del self.cookie_headers[key] del self.cookie_headers[key]
return super().__delitem__(key) return super().__delitem__(key)
@ -124,18 +127,3 @@ class Cookie(dict):
output.append('%s=%s' % (self._keys[key], value)) output.append('%s=%s' % (self._keys[key], value))
return "; ".join(output).encode(encoding) return "; ".join(output).encode(encoding)
# ------------------------------------------------------------ #
# Header Trickery
# ------------------------------------------------------------ #
class MultiHeader:
"""String-holding object which allow us to set a header within response
that has a unique key, but may contain duplicate header names
"""
def __init__(self, name):
self.name = name
def encode(self):
return self.name.encode()

View File

@ -74,7 +74,14 @@ def kill_process_children_unix(pid):
with open(children_proc_path) as children_list_file_2: with open(children_proc_path) as children_list_file_2:
children_list_pid_2 = children_list_file_2.read().split() children_list_pid_2 = children_list_file_2.read().split()
for _pid in children_list_pid_2: for _pid in children_list_pid_2:
try:
os.kill(int(_pid), signal.SIGTERM) os.kill(int(_pid), signal.SIGTERM)
except ProcessLookupError:
continue
try:
os.kill(int(child_pid), signal.SIGTERM)
except ProcessLookupError:
continue
def kill_process_children_osx(pid): def kill_process_children_osx(pid):
@ -94,7 +101,7 @@ def kill_process_children(pid):
""" """
if sys.platform == 'darwin': if sys.platform == 'darwin':
kill_process_children_osx(pid) kill_process_children_osx(pid)
elif sys.platform == 'posix': elif sys.platform == 'linux':
kill_process_children_unix(pid) kill_process_children_unix(pid)
else: else:
pass # should signal error here pass # should signal error here
@ -136,8 +143,8 @@ def watchdog(sleep_interval):
continue continue
elif mtime > old_time: elif mtime > old_time:
kill_process_children(worker_process.pid) kill_process_children(worker_process.pid)
worker_process.terminate()
worker_process = restart_with_reloader() worker_process = restart_with_reloader()
mtimes[filename] = mtime mtimes[filename] = mtime
break break

View File

@ -48,10 +48,11 @@ class Request(dict):
'app', 'headers', 'version', 'method', '_cookies', 'transport', 'app', 'headers', 'version', 'method', '_cookies', 'transport',
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', 'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
'_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr', '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr',
'_socket', '_port', '__weakref__' '_socket', '_port', '__weakref__', 'raw_url'
) )
def __init__(self, url_bytes, headers, version, method, transport): def __init__(self, url_bytes, headers, version, method, transport):
self.raw_url = url_bytes
# TODO: Content-Encoding detection # TODO: Content-Encoding detection
self._parsed_url = parse_url(url_bytes) self._parsed_url = parse_url(url_bytes)
self.app = None self.app = None

View File

@ -1,5 +1,6 @@
from mimetypes import guess_type from mimetypes import guess_type
from os import path from os import path
from urllib.parse import quote_plus
try: try:
from ujson import dumps as json_dumps from ujson import dumps as json_dumps
@ -7,6 +8,7 @@ except BaseException:
from json import dumps as json_dumps 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 sanic import http from sanic import http
from sanic.cookies import CookieJar from sanic.cookies import CookieJar
@ -44,7 +46,7 @@ class BaseHTTPResponse:
class StreamingHTTPResponse(BaseHTTPResponse): class StreamingHTTPResponse(BaseHTTPResponse):
__slots__ = ( __slots__ = (
'transport', 'streaming_fn', 'status', 'protocol', 'streaming_fn', 'status',
'content_type', 'headers', '_cookies' 'content_type', 'headers', '_cookies'
) )
@ -53,10 +55,10 @@ class StreamingHTTPResponse(BaseHTTPResponse):
self.content_type = content_type self.content_type = content_type
self.streaming_fn = streaming_fn self.streaming_fn = streaming_fn
self.status = status self.status = status
self.headers = headers or {} self.headers = CIMultiDict(headers or {})
self._cookies = None self._cookies = None
def write(self, data): async def write(self, data):
"""Writes a chunk of data to the streaming response. """Writes a chunk of data to the streaming response.
:param data: bytes-ish data to be written. :param data: bytes-ish data to be written.
@ -64,8 +66,9 @@ class StreamingHTTPResponse(BaseHTTPResponse):
if type(data) != bytes: if type(data) != bytes:
data = self._encode_body(data) data = self._encode_body(data)
self.transport.write( self.protocol.push_data(
b"%x\r\n%b\r\n" % (len(data), data)) b"%x\r\n%b\r\n" % (len(data), data))
await self.protocol.drain()
async def stream( async def stream(
self, version="1.1", keep_alive=False, keep_alive_timeout=None): self, version="1.1", keep_alive=False, keep_alive_timeout=None):
@ -75,10 +78,12 @@ class StreamingHTTPResponse(BaseHTTPResponse):
headers = self.get_headers( headers = self.get_headers(
version, keep_alive=keep_alive, version, keep_alive=keep_alive,
keep_alive_timeout=keep_alive_timeout) keep_alive_timeout=keep_alive_timeout)
self.transport.write(headers) self.protocol.push_data(headers)
await self.protocol.drain()
await self.streaming_fn(self) await self.streaming_fn(self)
self.transport.write(b'0\r\n\r\n') self.protocol.push_data(b'0\r\n\r\n')
# no need to await drain here after this write, because it is the
# very last thing we write and nothing needs to wait for it.
def get_headers( def get_headers(
self, version="1.1", keep_alive=False, keep_alive_timeout=None): self, version="1.1", keep_alive=False, keep_alive_timeout=None):
@ -124,7 +129,7 @@ class HTTPResponse(BaseHTTPResponse):
self.body = body_bytes self.body = body_bytes
self.status = status self.status = status
self.headers = headers or {} self.headers = CIMultiDict(headers or {})
self._cookies = None self._cookies = None
def output( def output(
@ -231,8 +236,8 @@ def html(body, status=200, headers=None):
content_type="text/html; charset=utf-8") content_type="text/html; charset=utf-8")
async def file( async def file(location, status=200, mime_type=None, headers=None,
location, mime_type=None, headers=None, filename=None, _range=None): filename=None, _range=None):
"""Return a response object with file data. """Return a response object with file data.
:param location: Location of file on system. :param location: Location of file on system.
@ -258,15 +263,14 @@ async def file(
out_stream = await _file.read() out_stream = await _file.read()
mime_type = mime_type or guess_type(filename)[0] or 'text/plain' mime_type = mime_type or guess_type(filename)[0] or 'text/plain'
return HTTPResponse(status=200, return HTTPResponse(status=status,
headers=headers, headers=headers,
content_type=mime_type, content_type=mime_type,
body_bytes=out_stream) body_bytes=out_stream)
async def file_stream( async def file_stream(location, status=200, chunk_size=4096, mime_type=None,
location, chunk_size=4096, mime_type=None, headers=None, headers=None, filename=None, _range=None):
filename=None, _range=None):
"""Return a streaming response object with file data. """Return a streaming response object with file data.
:param location: Location of file on system. :param location: Location of file on system.
@ -297,13 +301,13 @@ async def file_stream(
if len(content) < 1: if len(content) < 1:
break break
to_send -= len(content) to_send -= len(content)
response.write(content) await response.write(content)
else: else:
while True: while True:
content = await _file.read(chunk_size) content = await _file.read(chunk_size)
if len(content) < 1: if len(content) < 1:
break break
response.write(content) await response.write(content)
finally: finally:
await _file.close() await _file.close()
return # Returning from this fn closes the stream return # Returning from this fn closes the stream
@ -313,7 +317,7 @@ async def file_stream(
headers['Content-Range'] = 'bytes %s-%s/%s' % ( headers['Content-Range'] = 'bytes %s-%s/%s' % (
_range.start, _range.end, _range.total) _range.start, _range.end, _range.total)
return StreamingHTTPResponse(streaming_fn=_streaming_fn, return StreamingHTTPResponse(streaming_fn=_streaming_fn,
status=200, status=status,
headers=headers, headers=headers,
content_type=mime_type) content_type=mime_type)
@ -359,8 +363,11 @@ def redirect(to, headers=None, status=302,
""" """
headers = headers or {} headers = headers or {}
# URL Quote the URL before redirecting
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'] = to headers['Location'] = safe_to
return HTTPResponse( return HTTPResponse(
status=status, status=status,

View File

@ -18,6 +18,7 @@ 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
try: try:
import uvloop import uvloop
@ -39,25 +40,6 @@ class Signal:
stopped = False stopped = False
class CIDict(dict):
"""Case Insensitive dict where all keys are converted to lowercase
This does not maintain the inputted case when calling items() or keys()
in favor of speed, since headers are case insensitive
"""
def get(self, key, default=None):
return super().get(key.casefold(), default)
def __getitem__(self, key):
return super().__getitem__(key.casefold())
def __setitem__(self, key, value):
return super().__setitem__(key.casefold(), value)
def __contains__(self, key):
return super().__contains__(key.casefold())
class HttpProtocol(asyncio.Protocol): class HttpProtocol(asyncio.Protocol):
__slots__ = ( __slots__ = (
# event loop, connection # event loop, connection
@ -73,7 +55,8 @@ class HttpProtocol(asyncio.Protocol):
# connection management # connection management
'_total_request_size', '_request_timeout_handler', '_total_request_size', '_request_timeout_handler',
'_response_timeout_handler', '_keep_alive_timeout_handler', '_response_timeout_handler', '_keep_alive_timeout_handler',
'_last_request_time', '_last_response_time', '_is_stream_handler') '_last_request_time', '_last_response_time', '_is_stream_handler',
'_not_paused')
def __init__(self, *, loop, request_handler, error_handler, def __init__(self, *, loop, request_handler, error_handler,
signal=Signal(), connections=set(), request_timeout=60, signal=Signal(), connections=set(), request_timeout=60,
@ -100,6 +83,7 @@ class HttpProtocol(asyncio.Protocol):
self.request_class = request_class or Request self.request_class = request_class or Request
self.is_request_stream = is_request_stream self.is_request_stream = is_request_stream
self._is_stream_handler = False self._is_stream_handler = False
self._not_paused = asyncio.Event(loop=loop)
self._total_request_size = 0 self._total_request_size = 0
self._request_timeout_handler = None self._request_timeout_handler = None
self._response_timeout_handler = None self._response_timeout_handler = None
@ -114,6 +98,7 @@ class HttpProtocol(asyncio.Protocol):
if 'requests_count' not in self.state: if 'requests_count' not in self.state:
self.state['requests_count'] = 0 self.state['requests_count'] = 0
self._debug = debug self._debug = debug
self._not_paused.set()
@property @property
def keep_alive(self): def keep_alive(self):
@ -142,6 +127,12 @@ class HttpProtocol(asyncio.Protocol):
if self._keep_alive_timeout_handler: if self._keep_alive_timeout_handler:
self._keep_alive_timeout_handler.cancel() self._keep_alive_timeout_handler.cancel()
def pause_writing(self):
self._not_paused.clear()
def resume_writing(self):
self._not_paused.set()
def request_timeout_callback(self): def request_timeout_callback(self):
# See the docstring in the RequestTimeout exception, to see # See the docstring in the RequestTimeout exception, to see
# exactly what this timeout is checking for. # exactly what this timeout is checking for.
@ -159,10 +150,7 @@ class HttpProtocol(asyncio.Protocol):
self._request_stream_task.cancel() self._request_stream_task.cancel()
if self._request_handler_task: if self._request_handler_task:
self._request_handler_task.cancel() self._request_handler_task.cancel()
try: self.write_error(RequestTimeout('Request Timeout'))
raise RequestTimeout('Request Timeout')
except RequestTimeout as exception:
self.write_error(exception)
def response_timeout_callback(self): def response_timeout_callback(self):
# Check if elapsed time since response was initiated exceeds our # Check if elapsed time since response was initiated exceeds our
@ -179,10 +167,7 @@ class HttpProtocol(asyncio.Protocol):
self._request_stream_task.cancel() self._request_stream_task.cancel()
if self._request_handler_task: if self._request_handler_task:
self._request_handler_task.cancel() self._request_handler_task.cancel()
try: self.write_error(ServiceUnavailable('Response Timeout'))
raise ServiceUnavailable('Response Timeout')
except ServiceUnavailable as exception:
self.write_error(exception)
def keep_alive_timeout_callback(self): def keep_alive_timeout_callback(self):
# Check if elapsed time since last response exceeds our configured # Check if elapsed time since last response exceeds our configured
@ -208,8 +193,7 @@ class HttpProtocol(asyncio.Protocol):
# memory limits # memory limits
self._total_request_size += len(data) self._total_request_size += len(data)
if self._total_request_size > self.request_max_size: if self._total_request_size > self.request_max_size:
exception = PayloadTooLarge('Payload Too Large') self.write_error(PayloadTooLarge('Payload Too Large'))
self.write_error(exception)
# Create parser if this is the first time we're receiving data # Create parser if this is the first time we're receiving data
if self.parser is None: if self.parser is None:
@ -227,8 +211,7 @@ class HttpProtocol(asyncio.Protocol):
message = 'Bad Request' message = 'Bad Request'
if self._debug: if self._debug:
message += '\n' + traceback.format_exc() message += '\n' + traceback.format_exc()
exception = InvalidUsage(message) self.write_error(InvalidUsage(message))
self.write_error(exception)
def on_url(self, url): def on_url(self, url):
if not self.url: if not self.url:
@ -242,8 +225,7 @@ class HttpProtocol(asyncio.Protocol):
if value is not None: if value is not None:
if self._header_fragment == b'Content-Length' \ if self._header_fragment == b'Content-Length' \
and int(value) > self.request_max_size: and int(value) > self.request_max_size:
exception = PayloadTooLarge('Payload Too Large') self.write_error(PayloadTooLarge('Payload Too Large'))
self.write_error(exception)
try: try:
value = value.decode() value = value.decode()
except UnicodeDecodeError: except UnicodeDecodeError:
@ -256,7 +238,7 @@ class HttpProtocol(asyncio.Protocol):
def on_headers_complete(self): def on_headers_complete(self):
self.request = self.request_class( self.request = self.request_class(
url_bytes=self.url, url_bytes=self.url,
headers=CIDict(self.headers), headers=CIMultiDict(self.headers),
version=self.parser.get_http_version(), version=self.parser.get_http_version(),
method=self.parser.get_method().decode(), method=self.parser.get_method().decode(),
transport=self.transport transport=self.transport
@ -369,6 +351,12 @@ class HttpProtocol(asyncio.Protocol):
self._last_response_time = current_time self._last_response_time = current_time
self.cleanup() self.cleanup()
async def drain(self):
await self._not_paused.wait()
def push_data(self, data):
self.transport.write(data)
async def stream_response(self, response): async def stream_response(self, response):
""" """
Streams a response to the client asynchronously. Attaches Streams a response to the client asynchronously. Attaches
@ -378,9 +366,10 @@ class HttpProtocol(asyncio.Protocol):
if self._response_timeout_handler: if self._response_timeout_handler:
self._response_timeout_handler.cancel() self._response_timeout_handler.cancel()
self._response_timeout_handler = None self._response_timeout_handler = None
try: try:
keep_alive = self.keep_alive keep_alive = self.keep_alive
response.transport = self.transport response.protocol = self
await response.stream( await response.stream(
self.request.version, keep_alive, self.keep_alive_timeout) self.request.version, keep_alive, self.keep_alive_timeout)
self.log_response(response) self.log_response(response)
@ -435,7 +424,7 @@ class HttpProtocol(asyncio.Protocol):
self.log_response(response) self.log_response(response)
try: try:
self.transport.close() self.transport.close()
except AttributeError as e: except AttributeError:
logger.debug('Connection lost before server could close it.') logger.debug('Connection lost before server could close it.')
def bail_out(self, message, from_error=False): def bail_out(self, message, from_error=False):
@ -445,8 +434,7 @@ class HttpProtocol(asyncio.Protocol):
self.transport.get_extra_info('peername')) self.transport.get_extra_info('peername'))
logger.debug('Exception:\n%s', traceback.format_exc()) logger.debug('Exception:\n%s', traceback.format_exc())
else: else:
exception = ServerError(message) self.write_error(ServerError(message))
self.write_error(exception)
logger.error(message) logger.error(message)
def cleanup(self): def cleanup(self):

View File

@ -19,7 +19,7 @@ from sanic.response import file, file_stream, HTTPResponse
def register(app, uri, file_or_directory, pattern, def register(app, uri, file_or_directory, pattern,
use_modified_since, use_content_range, use_modified_since, use_content_range,
stream_large_files, name='static', host=None, stream_large_files, name='static', host=None,
strict_slashes=None): strict_slashes=None, content_type=None):
# TODO: Though sanic is not a file server, I feel like we should at least # TODO: Though sanic is not a file server, I feel like we should at least
# make a good effort here. Modified-since is nice, but we could # make a good effort here. Modified-since is nice, but we could
# also look into etags, expires, and caching # also look into etags, expires, and caching
@ -41,6 +41,7 @@ def register(app, uri, file_or_directory, pattern,
If this is an integer, this represents the If this is an integer, this represents the
threshold size to switch to file_stream() threshold size to switch to file_stream()
:param name: user defined name used for url_for :param name: user defined name used for url_for
:param content_type: user defined content type for header
""" """
# If we're not trying to match a file directly, # If we're not trying to match a file directly,
# serve from the folder # serve from the folder
@ -95,10 +96,10 @@ def register(app, uri, file_or_directory, pattern,
del headers['Content-Length'] del headers['Content-Length']
for key, value in _range.headers.items(): for key, value in _range.headers.items():
headers[key] = value headers[key] = value
headers['Content-Type'] = content_type \
or guess_type(file_path)[0] or 'text/plain'
if request.method == 'HEAD': if request.method == 'HEAD':
return HTTPResponse( return HTTPResponse(headers=headers)
headers=headers,
content_type=guess_type(file_path)[0] or 'text/plain')
else: else:
if stream_large_files: if stream_large_files:
if isinstance(stream_large_files, int): if isinstance(stream_large_files, int):

View File

@ -57,17 +57,11 @@ class WebSocketProtocol(HttpProtocol):
async def websocket_handshake(self, request, subprotocols=None): async def websocket_handshake(self, request, subprotocols=None):
# let the websockets package do the handshake with the client # let the websockets package do the handshake with the client
headers = [] headers = {}
def get_header(k):
return request.headers.get(k, '')
def set_header(k, v):
headers.append((k, v))
try: try:
key = handshake.check_request(get_header) key = handshake.check_request(request.headers)
handshake.build_response(set_header, key) handshake.build_response(headers, key)
except InvalidHandshake: except InvalidHandshake:
raise InvalidUsage('Invalid websocket request') raise InvalidUsage('Invalid websocket request')
@ -79,12 +73,12 @@ class WebSocketProtocol(HttpProtocol):
for p in client_subprotocols: for p in client_subprotocols:
if p in subprotocols: if p in subprotocols:
subprotocol = p subprotocol = p
set_header('Sec-Websocket-Protocol', subprotocol) headers['Sec-Websocket-Protocol'] = subprotocol
break break
# write the 101 response back to the client # write the 101 response back to the client
rv = b'HTTP/1.1 101 Switching Protocols\r\n' rv = b'HTTP/1.1 101 Switching Protocols\r\n'
for k, v in headers: for k, v in headers.items():
rv += k.encode('utf-8') + b': ' + v.encode('utf-8') + b'\r\n' rv += k.encode('utf-8') + b': ' + v.encode('utf-8') + b'\r\n'
rv += b'\r\n' rv += b'\r\n'
request.transport.write(rv) request.transport.write(rv)

View File

@ -56,11 +56,12 @@ ujson = 'ujson>=1.35' + env_dependency
uvloop = 'uvloop>=0.5.3' + env_dependency uvloop = 'uvloop>=0.5.3' + env_dependency
requirements = [ requirements = [
'httptools>=0.0.9', 'httptools>=0.0.10',
uvloop, uvloop,
ujson, ujson,
'aiofiles>=0.3.0', 'aiofiles>=0.3.0',
'websockets>=5.0,<6.0', 'websockets>=6.0,<7.0',
'multidict>=4.0,<5.0',
] ]
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")): if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
print("Installing without uJSON") print("Installing without uJSON")

8
tests/conftest.py Normal file
View File

@ -0,0 +1,8 @@
import pytest
from sanic import Sanic
@pytest.fixture
def app(request):
return Sanic(request.node.name)

26
tests/static/test.html Normal file
View File

@ -0,0 +1,26 @@
<html>
<body>
<pre>
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \
▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! |
▀▀█████▄▄ ▀██████▄██ | _________________/
▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
▀▀▀▄ ▀▀███ ▀ ▄▄
▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
██▀▄▄▄██▀▄███▀ ▀▀████ ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀
▌ ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
▀▀█████████▀
▄▄██▀██████▀█
▄██▀ ▀▀▀ █
▄█ ▐▌
▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀
</pre>
</body>
</html>

View File

@ -1,9 +1,7 @@
import asyncio import asyncio
from sanic import Sanic
def test_bad_request_response(): def test_bad_request_response(app):
app = Sanic('test_bad_request_response')
lines = [] lines = []
@app.listener('after_server_start') @app.listener('after_server_start')
async def _request(sanic, loop): async def _request(sanic, loop):

View File

@ -1,10 +1,10 @@
import asyncio import asyncio
import inspect import inspect
import os
import pytest import pytest
from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.response import json, text from sanic.response import text
from sanic.exceptions import NotFound, ServerError, InvalidUsage from sanic.exceptions import NotFound, ServerError, InvalidUsage
from sanic.constants import HTTP_METHODS from sanic.constants import HTTP_METHODS
@ -13,9 +13,16 @@ from sanic.constants import HTTP_METHODS
# GET # GET
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def get_file_path(static_file_directory, file_name):
return os.path.join(static_file_directory, file_name)
def get_file_content(static_file_directory, file_name):
"""The content of the static file to check"""
with open(get_file_path(static_file_directory, file_name), 'rb') as file:
return file.read()
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_versioned_routes_get(method): def test_versioned_routes_get(app, method):
app = Sanic('test_shorhand_routes_get')
bp = Blueprint('test_text') bp = Blueprint('test_text')
method = method.lower() method = method.lower()
@ -37,8 +44,7 @@ def test_versioned_routes_get(method):
assert response.status == 200 assert response.status == 200
def test_bp(): def test_bp(app):
app = Sanic('test_text')
bp = Blueprint('test_text') bp = Blueprint('test_text')
@bp.route('/') @bp.route('/')
@ -51,8 +57,7 @@ def test_bp():
assert response.text == 'Hello' assert response.text == 'Hello'
def test_bp_strict_slash(): def test_bp_strict_slash(app):
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text') bp = Blueprint('test_text')
@bp.get('/get', strict_slashes=True) @bp.get('/get', strict_slashes=True)
@ -78,8 +83,7 @@ def test_bp_strict_slash():
request, response = app.test_client.post('/post') request, response = app.test_client.post('/post')
assert response.status == 404 assert response.status == 404
def test_bp_strict_slash_default_value(): def test_bp_strict_slash_default_value(app):
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text', strict_slashes=True) bp = Blueprint('test_text', strict_slashes=True)
@bp.get('/get') @bp.get('/get')
@ -98,8 +102,7 @@ def test_bp_strict_slash_default_value():
request, response = app.test_client.post('/post') request, response = app.test_client.post('/post')
assert response.status == 404 assert response.status == 404
def test_bp_strict_slash_without_passing_default_value(): def test_bp_strict_slash_without_passing_default_value(app):
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text') bp = Blueprint('test_text')
@bp.get('/get') @bp.get('/get')
@ -118,8 +121,7 @@ def test_bp_strict_slash_without_passing_default_value():
request, response = app.test_client.post('/post') request, response = app.test_client.post('/post')
assert response.text == 'OK' assert response.text == 'OK'
def test_bp_strict_slash_default_value_can_be_overwritten(): def test_bp_strict_slash_default_value_can_be_overwritten(app):
app = Sanic('test_route_strict_slash')
bp = Blueprint('test_text', strict_slashes=True) bp = Blueprint('test_text', strict_slashes=True)
@bp.get('/get', strict_slashes=False) @bp.get('/get', strict_slashes=False)
@ -138,8 +140,7 @@ def test_bp_strict_slash_default_value_can_be_overwritten():
request, response = app.test_client.post('/post') request, response = app.test_client.post('/post')
assert response.text == 'OK' assert response.text == 'OK'
def test_bp_with_url_prefix(): def test_bp_with_url_prefix(app):
app = Sanic('test_text')
bp = Blueprint('test_text', url_prefix='/test1') bp = Blueprint('test_text', url_prefix='/test1')
@bp.route('/') @bp.route('/')
@ -152,8 +153,7 @@ def test_bp_with_url_prefix():
assert response.text == 'Hello' assert response.text == 'Hello'
def test_several_bp_with_url_prefix(): def test_several_bp_with_url_prefix(app):
app = Sanic('test_text')
bp = Blueprint('test_text', url_prefix='/test1') bp = Blueprint('test_text', url_prefix='/test1')
bp2 = Blueprint('test_text2', url_prefix='/test2') bp2 = Blueprint('test_text2', url_prefix='/test2')
@ -173,8 +173,7 @@ def test_several_bp_with_url_prefix():
request, response = app.test_client.get('/test2/') request, response = app.test_client.get('/test2/')
assert response.text == 'Hello2' assert response.text == 'Hello2'
def test_bp_with_host(): def test_bp_with_host(app):
app = Sanic('test_bp_host')
bp = Blueprint('test_bp_host', url_prefix='/test1', host="example.com") bp = Blueprint('test_bp_host', url_prefix='/test1', host="example.com")
@bp.route('/') @bp.route('/')
@ -200,8 +199,7 @@ def test_bp_with_host():
assert response.text == 'Hello subdomain!' assert response.text == 'Hello subdomain!'
def test_several_bp_with_host(): def test_several_bp_with_host(app):
app = Sanic('test_text')
bp = Blueprint('test_text', bp = Blueprint('test_text',
url_prefix='/test', url_prefix='/test',
host="example.com") host="example.com")
@ -244,8 +242,7 @@ def test_several_bp_with_host():
headers=headers) headers=headers)
assert response.text == 'Hello3' assert response.text == 'Hello3'
def test_bp_middleware(): def test_bp_middleware(app):
app = Sanic('test_middleware')
blueprint = Blueprint('test_middleware') blueprint = Blueprint('test_middleware')
@blueprint.middleware('response') @blueprint.middleware('response')
@ -263,8 +260,7 @@ def test_bp_middleware():
assert response.status == 200 assert response.status == 200
assert response.text == 'OK' assert response.text == 'OK'
def test_bp_exception_handler(): def test_bp_exception_handler(app):
app = Sanic('test_middleware')
blueprint = Blueprint('test_middleware') blueprint = Blueprint('test_middleware')
@blueprint.route('/1') @blueprint.route('/1')
@ -296,8 +292,7 @@ def test_bp_exception_handler():
request, response = app.test_client.get('/3') request, response = app.test_client.get('/3')
assert response.status == 200 assert response.status == 200
def test_bp_listeners(): def test_bp_listeners(app):
app = Sanic('test_middleware')
blueprint = Blueprint('test_middleware') blueprint = Blueprint('test_middleware')
order = [] order = []
@ -332,12 +327,11 @@ def test_bp_listeners():
assert order == [1,2,3,4,5,6] assert order == [1,2,3,4,5,6]
def test_bp_static(): def test_bp_static(app):
current_file = inspect.getfile(inspect.currentframe()) current_file = inspect.getfile(inspect.currentframe())
with open(current_file, 'rb') as file: with open(current_file, 'rb') as file:
current_file_contents = file.read() current_file_contents = file.read()
app = Sanic('test_static')
blueprint = Blueprint('test_static') blueprint = Blueprint('test_static')
blueprint.static('/testing.file', current_file) blueprint.static('/testing.file', current_file)
@ -348,8 +342,28 @@ def test_bp_static():
assert response.status == 200 assert response.status == 200
assert response.body == current_file_contents assert response.body == current_file_contents
def test_bp_shorthand(): @pytest.mark.parametrize('file_name', ['test.html'])
app = Sanic('test_shorhand_routes') def test_bp_static_content_type(app, file_name):
# This is done here, since no other test loads a file here
current_file = inspect.getfile(inspect.currentframe())
current_directory = os.path.dirname(os.path.abspath(current_file))
static_directory = os.path.join(current_directory, 'static')
blueprint = Blueprint('test_static')
blueprint.static(
'/testing.file',
get_file_path(static_directory, file_name),
content_type='text/html; charset=utf-8'
)
app.blueprint(blueprint)
request, response = app.test_client.get('/testing.file')
assert response.status == 200
assert response.body == get_file_content(static_directory, file_name)
assert response.headers['Content-Type'] == 'text/html; charset=utf-8'
def test_bp_shorthand(app):
blueprint = Blueprint('test_shorhand_routes') blueprint = Blueprint('test_shorhand_routes')
ev = asyncio.Event() ev = asyncio.Event()
@ -447,9 +461,7 @@ def test_bp_shorthand():
assert response.status == 101 assert response.status == 101
assert ev.is_set() assert ev.is_set()
def test_bp_group(): def test_bp_group(app):
app = Sanic('test_nested_bp_groups')
deep_0 = Blueprint('deep_0', url_prefix='/deep') deep_0 = Blueprint('deep_0', url_prefix='/deep')
deep_1 = Blueprint('deep_1', url_prefix = '/deep1') deep_1 = Blueprint('deep_1', url_prefix = '/deep1')

View File

@ -5,8 +5,7 @@ from tempfile import NamedTemporaryFile
from sanic import Sanic from sanic import Sanic
def test_load_from_object(): def test_load_from_object(app):
app = Sanic('test_load_from_object')
class Config: class Config:
not_for_config = 'should not be used' not_for_config = 'should not be used'
CONFIG_VALUE = 'should be used' CONFIG_VALUE = 'should be used'
@ -16,26 +15,29 @@ def test_load_from_object():
assert app.config.CONFIG_VALUE == 'should be used' assert app.config.CONFIG_VALUE == 'should be used'
assert 'not_for_config' not in app.config assert 'not_for_config' not in app.config
def test_auto_load_env(): def test_auto_load_env():
environ["SANIC_TEST_ANSWER"] = "42" environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic() app = Sanic()
assert app.config.TEST_ANSWER == 42 assert app.config.TEST_ANSWER == 42
del environ["SANIC_TEST_ANSWER"] del environ["SANIC_TEST_ANSWER"]
def test_dont_load_env(): def test_dont_load_env():
environ["SANIC_TEST_ANSWER"] = "42" environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(load_env=False) app = Sanic(load_env=False)
assert getattr(app.config, 'TEST_ANSWER', None) == None assert getattr(app.config, 'TEST_ANSWER', None) is None
del environ["SANIC_TEST_ANSWER"] del environ["SANIC_TEST_ANSWER"]
def test_load_env_prefix(): def test_load_env_prefix():
environ["MYAPP_TEST_ANSWER"] = "42" environ["MYAPP_TEST_ANSWER"] = "42"
app = Sanic(load_env='MYAPP_') app = Sanic(load_env='MYAPP_')
assert app.config.TEST_ANSWER == 42 assert app.config.TEST_ANSWER == 42
del environ["MYAPP_TEST_ANSWER"] del environ["MYAPP_TEST_ANSWER"]
def test_load_from_file():
app = Sanic('test_load_from_file') def test_load_from_file(app):
config = b""" config = b"""
VALUE = 'some value' VALUE = 'some value'
condition = 1 == 1 condition = 1 == 1
@ -53,14 +55,12 @@ if condition:
assert 'condition' not in app.config assert 'condition' not in app.config
def test_load_from_missing_file(): def test_load_from_missing_file(app):
app = Sanic('test_load_from_missing_file')
with pytest.raises(IOError): with pytest.raises(IOError):
app.config.from_pyfile('non-existent file') app.config.from_pyfile('non-existent file')
def test_load_from_envvar(): def test_load_from_envvar(app):
app = Sanic('test_load_from_envvar')
config = b"VALUE = 'some value'" config = b"VALUE = 'some value'"
with NamedTemporaryFile() as config_file: with NamedTemporaryFile() as config_file:
config_file.write(config) config_file.write(config)
@ -71,15 +71,17 @@ def test_load_from_envvar():
assert app.config.VALUE == 'some value' assert app.config.VALUE == 'some value'
def test_load_from_missing_envvar(): def test_load_from_missing_envvar(app):
app = Sanic('test_load_from_missing_envvar') with pytest.raises(RuntimeError) as e:
with pytest.raises(RuntimeError):
app.config.from_envvar('non-existent variable') app.config.from_envvar('non-existent variable')
assert str(e.value) == ("The environment variable 'non-existent "
"variable' is not set and thus configuration "
"could not be loaded.")
def test_overwrite_exisiting_config(): def test_overwrite_exisiting_config(app):
app = Sanic('test_overwrite_exisiting_config')
app.config.DEFAULT = 1 app.config.DEFAULT = 1
class Config: class Config:
DEFAULT = 2 DEFAULT = 2
@ -87,7 +89,7 @@ def test_overwrite_exisiting_config():
assert app.config.DEFAULT == 2 assert app.config.DEFAULT == 2
def test_missing_config(): def test_missing_config(app):
app = Sanic('test_missing_config') with pytest.raises(AttributeError) as e:
with pytest.raises(AttributeError):
app.config.NON_EXISTENT app.config.NON_EXISTENT
assert str(e.value) == ("Config has no 'NON_EXISTENT'")

View File

@ -9,8 +9,7 @@ import pytest
# GET # GET
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def test_cookies(): def test_cookies(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -25,12 +24,12 @@ def test_cookies():
assert response.text == 'Cookies are: working!' assert response.text == 'Cookies are: working!'
assert response_cookies['right_back'].value == 'at you' assert response_cookies['right_back'].value == 'at you'
@pytest.mark.parametrize("httponly,expected", [ @pytest.mark.parametrize("httponly,expected", [
(False, False), (False, False),
(True, True), (True, True),
]) ])
def test_false_cookies_encoded(httponly, expected): def test_false_cookies_encoded(app, httponly, expected):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -48,8 +47,7 @@ def test_false_cookies_encoded(httponly, expected):
(False, False), (False, False),
(True, True), (True, True),
]) ])
def test_false_cookies(httponly, expected): def test_false_cookies(app, httponly, expected):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -64,8 +62,7 @@ def test_false_cookies(httponly, expected):
assert ('HttpOnly' in response_cookies['right_back'].output()) == expected assert ('HttpOnly' in response_cookies['right_back'].output()) == expected
def test_http2_cookies(): def test_http2_cookies(app):
app = Sanic('test_http2_cookies')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -77,8 +74,7 @@ def test_http2_cookies():
assert response.text == 'Cookies are: working!' assert response.text == 'Cookies are: working!'
def test_cookie_options(): def test_cookie_options(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -95,8 +91,7 @@ def test_cookie_options():
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'] == True
def test_cookie_deletion(): def test_cookie_deletion(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):

View File

@ -1,18 +1,16 @@
from sanic import Sanic
from sanic.response import text from sanic.response import text
from threading import Event from threading import Event
import asyncio import asyncio
from queue import Queue from queue import Queue
def test_create_task(): def test_create_task(app):
e = Event() e = Event()
async def coro(): async def coro():
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
e.set() e.set()
app = Sanic('test_create_task')
app.add_task(coro) app.add_task(coro)
@app.route('/early') @app.route('/early')
@ -30,8 +28,7 @@ def test_create_task():
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(): def test_create_task_with_app_arg(app):
app = Sanic('test_add_task')
q = Queue() q = Queue()
@app.route('/') @app.route('/')
@ -44,4 +41,4 @@ def test_create_task_with_app_arg():
app.add_task(coro) app.add_task(coro)
request, response = app.test_client.get('/') request, response = app.test_client.get('/')
assert q.get() == 'test_add_task' assert q.get() == 'test_create_task_with_app_arg'

View File

@ -1,9 +1,6 @@
from sanic import Sanic
from sanic.server import HttpProtocol from sanic.server import HttpProtocol
from sanic.response import text from sanic.response import text
app = Sanic('test_custom_porotocol')
class CustomHttpProtocol(HttpProtocol): class CustomHttpProtocol(HttpProtocol):
@ -16,12 +13,12 @@ class CustomHttpProtocol(HttpProtocol):
self.transport.close() self.transport.close()
def test_use_custom_protocol(app):
@app.route('/1') @app.route('/1')
async def handler_1(request): async def handler_1(request):
return 'OK' return 'OK'
def test_use_custom_protocol():
server_kwargs = { server_kwargs = {
'protocol': CustomHttpProtocol 'protocol': CustomHttpProtocol
} }

View File

@ -10,8 +10,7 @@ import pytest
("put", "text", "OK2 test"), ("put", "text", "OK2 test"),
("delete", "status", 405), ("delete", "status", 405),
]) ])
def test_overload_dynamic_routes(method, attr, expected): def test_overload_dynamic_routes(app, method, attr, expected):
app = Sanic('test_dynamic_route')
@app.route('/overload/<param>', methods=['GET']) @app.route('/overload/<param>', methods=['GET'])
async def handler1(request, param): async def handler1(request, param):
@ -25,8 +24,7 @@ def test_overload_dynamic_routes(method, attr, expected):
assert getattr(response, attr) == expected assert getattr(response, attr) == expected
def test_overload_dynamic_routes_exist(): def test_overload_dynamic_routes_exist(app):
app = Sanic('test_dynamic_route')
@app.route('/overload/<param>', methods=['GET']) @app.route('/overload/<param>', methods=['GET'])
async def handler1(request, param): async def handler1(request, param):

View File

@ -81,8 +81,7 @@ def exception_app():
return app return app
def test_catch_exception_list(): def test_catch_exception_list(app):
app = Sanic('exception_list')
@app.exception([SanicExceptionTestException, NotFound]) @app.exception([SanicExceptionTestException, NotFound])
def exception_list(request, exception): def exception_list(request, exception):

View File

@ -9,14 +9,39 @@ import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST, PORT from sanic.testing import SanicTestClient, HOST, PORT
try:
try:
import packaging # direct use
except ImportError:
# setuptools v39.0 and above.
try:
from setuptools.extern import packaging
except ImportError:
# Before setuptools v39.0
from pkg_resources.extern import packaging
version = packaging.version
except ImportError:
raise RuntimeError("The 'packaging' library is missing.")
aiohttp_version = version.parse(aiohttp.__version__)
class ReuseableTCPConnector(TCPConnector): class ReuseableTCPConnector(TCPConnector):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReuseableTCPConnector, self).__init__(*args, **kwargs) super(ReuseableTCPConnector, self).__init__(*args, **kwargs)
self.old_proto = None self.old_proto = None
if aiohttp.__version__ >= '3.0': if aiohttp_version >= version.parse('3.3.0'):
async def connect(self, req, traces, timeout):
new_conn = await super(ReuseableTCPConnector, self)\
.connect(req, traces, timeout)
if self.old_proto is not None:
if self.old_proto != new_conn._protocol:
raise RuntimeError(
"We got a new connection, wanted the same one!")
print(new_conn.__dict__)
self.old_proto = new_conn._protocol
return new_conn
elif aiohttp_version >= version.parse('3.0.0'):
async def connect(self, req, traces=None): async def connect(self, req, traces=None):
new_conn = await super(ReuseableTCPConnector, self)\ new_conn = await super(ReuseableTCPConnector, self)\
.connect(req, traces=traces) .connect(req, traces=traces)
@ -28,7 +53,6 @@ class ReuseableTCPConnector(TCPConnector):
self.old_proto = new_conn._protocol self.old_proto = new_conn._protocol
return new_conn return new_conn
else: else:
async def connect(self, req): async def connect(self, req):
new_conn = await super(ReuseableTCPConnector, self)\ new_conn = await super(ReuseableTCPConnector, self)\
.connect(req) .connect(req)

View File

@ -23,7 +23,7 @@ def reset_logging():
reload(logging) reload(logging)
def test_log(): def test_log(app):
log_stream = StringIO() log_stream = StringIO()
for handler in logging.root.handlers[:]: for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler) logging.root.removeHandler(handler)
@ -33,7 +33,6 @@ def test_log():
stream=log_stream stream=log_stream
) )
log = logging.getLogger() log = logging.getLogger()
app = Sanic('test_logging')
rand_string = str(uuid.uuid4()) rand_string = str(uuid.uuid4())
@app.route('/') @app.route('/')
@ -80,9 +79,8 @@ def test_logging_pass_customer_logconfig():
@pytest.mark.parametrize('debug', (True, False, )) @pytest.mark.parametrize('debug', (True, False, ))
def test_log_connection_lost(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 """
app = Sanic('connection_lost')
stream = StringIO() stream = StringIO()
root = logging.getLogger('root') root = logging.getLogger('root')
root.addHandler(logging.StreamHandler(stream)) root.addHandler(logging.StreamHandler(stream))

View File

@ -1,7 +1,5 @@
from json import loads as json_loads, dumps as json_dumps
from sanic import Sanic
from sanic.request import Request from sanic.request import Request
from sanic.response import json, text, HTTPResponse from sanic.response import text, HTTPResponse
from sanic.exceptions import NotFound from sanic.exceptions import NotFound
@ -9,9 +7,7 @@ from sanic.exceptions import NotFound
# GET # GET
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def test_middleware_request(): def test_middleware_request(app):
app = Sanic('test_middleware_request')
results = [] results = []
@app.middleware @app.middleware
@ -28,9 +24,7 @@ def test_middleware_request():
assert type(results[0]) is Request assert type(results[0]) is Request
def test_middleware_response(): def test_middleware_response(app):
app = Sanic('test_middleware_response')
results = [] results = []
@app.middleware('request') @app.middleware('request')
@ -54,8 +48,7 @@ def test_middleware_response():
assert isinstance(results[2], HTTPResponse) assert isinstance(results[2], HTTPResponse)
def test_middleware_response_exception(): def test_middleware_response_exception(app):
app = Sanic('test_middleware_response_exception')
result = {'status_code': None} result = {'status_code': None}
@app.middleware('response') @app.middleware('response')
@ -75,8 +68,7 @@ def test_middleware_response_exception():
assert response.text == 'OK' assert response.text == 'OK'
assert result['status_code'] == 404 assert result['status_code'] == 404
def test_middleware_override_request(): def test_middleware_override_request(app):
app = Sanic('test_middleware_override_request')
@app.middleware @app.middleware
async def halt_request(request): async def halt_request(request):
@ -92,8 +84,7 @@ def test_middleware_override_request():
assert response.text == 'OK' assert response.text == 'OK'
def test_middleware_override_response(): def test_middleware_override_response(app):
app = Sanic('test_middleware_override_response')
@app.middleware('response') @app.middleware('response')
async def process_response(request, response): async def process_response(request, response):
@ -109,10 +100,7 @@ def test_middleware_override_response():
assert response.text == 'OK' assert response.text == 'OK'
def test_middleware_order(app):
def test_middleware_order():
app = Sanic('test_middleware_order')
order = [] order = []
@app.middleware('request') @app.middleware('request')

View File

@ -2,15 +2,13 @@ import multiprocessing
import random import random
import signal import signal
from sanic import Sanic
from sanic.testing import HOST, PORT from sanic.testing import HOST, PORT
def test_multiprocessing(): def test_multiprocessing(app):
"""Tests that the number of children we produce is correct""" """Tests that the number of children we produce is correct"""
# Selects a number at random so we can spot check # Selects a number at random so we can spot check
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1)) num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
app = Sanic('test_multiprocessing')
process_list = set() process_list = set()
def stop_on_alarm(*args): def stop_on_alarm(*args):

View File

@ -4,7 +4,6 @@
import asyncio import asyncio
import pytest import pytest
from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.response import text from sanic.response import text
from sanic.exceptions import URLBuildError from sanic.exceptions import URLBuildError
@ -16,8 +15,7 @@ from sanic.constants import HTTP_METHODS
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_versioned_named_routes_get(method): def test_versioned_named_routes_get(app, method):
app = Sanic('test_shorhand_routes_get')
bp = Blueprint('test_bp', url_prefix='/bp') bp = Blueprint('test_bp', url_prefix='/bp')
method = method.lower() method = method.lower()
@ -57,8 +55,7 @@ def test_versioned_named_routes_get(method):
app.url_for('handler') app.url_for('handler')
def test_shorthand_default_routes_get(): def test_shorthand_default_routes_get(app):
app = Sanic('test_shorhand_routes_get')
@app.get('/get') @app.get('/get')
def handler(request): def handler(request):
@ -68,8 +65,7 @@ def test_shorthand_default_routes_get():
assert app.url_for('handler') == '/get' assert app.url_for('handler') == '/get'
def test_shorthand_named_routes_get(): def test_shorthand_named_routes_get(app):
app = Sanic('test_shorhand_routes_get')
bp = Blueprint('test_bp', url_prefix='/bp') bp = Blueprint('test_bp', url_prefix='/bp')
@app.get('/get', name='route_get') @app.get('/get', name='route_get')
@ -93,8 +89,7 @@ def test_shorthand_named_routes_get():
app.url_for('test_bp.handler2') app.url_for('test_bp.handler2')
def test_shorthand_named_routes_post(): def test_shorthand_named_routes_post(app):
app = Sanic('test_shorhand_routes_post')
@app.post('/post', name='route_name') @app.post('/post', name='route_name')
def handler(request): def handler(request):
@ -106,8 +101,7 @@ def test_shorthand_named_routes_post():
app.url_for('handler') app.url_for('handler')
def test_shorthand_named_routes_put(): def test_shorthand_named_routes_put(app):
app = Sanic('test_shorhand_routes_put')
@app.put('/put', name='route_put') @app.put('/put', name='route_put')
def handler(request): def handler(request):
@ -121,8 +115,7 @@ def test_shorthand_named_routes_put():
app.url_for('handler') app.url_for('handler')
def test_shorthand_named_routes_delete(): def test_shorthand_named_routes_delete(app):
app = Sanic('test_shorhand_routes_delete')
@app.delete('/delete', name='route_delete') @app.delete('/delete', name='route_delete')
def handler(request): def handler(request):
@ -136,8 +129,7 @@ def test_shorthand_named_routes_delete():
app.url_for('handler') app.url_for('handler')
def test_shorthand_named_routes_patch(): def test_shorthand_named_routes_patch(app):
app = Sanic('test_shorhand_routes_patch')
@app.patch('/patch', name='route_patch') @app.patch('/patch', name='route_patch')
def handler(request): def handler(request):
@ -151,8 +143,7 @@ def test_shorthand_named_routes_patch():
app.url_for('handler') app.url_for('handler')
def test_shorthand_named_routes_head(): def test_shorthand_named_routes_head(app):
app = Sanic('test_shorhand_routes_head')
@app.head('/head', name='route_head') @app.head('/head', name='route_head')
def handler(request): def handler(request):
@ -166,8 +157,7 @@ def test_shorthand_named_routes_head():
app.url_for('handler') app.url_for('handler')
def test_shorthand_named_routes_options(): def test_shorthand_named_routes_options(app):
app = Sanic('test_shorhand_routes_options')
@app.options('/options', name='route_options') @app.options('/options', name='route_options')
def handler(request): def handler(request):
@ -181,8 +171,7 @@ def test_shorthand_named_routes_options():
app.url_for('handler') app.url_for('handler')
def test_named_static_routes(): def test_named_static_routes(app):
app = Sanic('test_dynamic_route')
@app.route('/test', name='route_test') @app.route('/test', name='route_test')
async def handler1(request): async def handler1(request):
@ -205,9 +194,7 @@ def test_named_static_routes():
app.url_for('handler2') app.url_for('handler2')
def test_named_dynamic_route(): def test_named_dynamic_route(app):
app = Sanic('test_dynamic_route')
results = [] results = []
@app.route('/folder/<name>', name='route_dynamic') @app.route('/folder/<name>', name='route_dynamic')
@ -221,8 +208,7 @@ def test_named_dynamic_route():
app.url_for('handler') app.url_for('handler')
def test_dynamic_named_route_regex(): def test_dynamic_named_route_regex(app):
app = Sanic('test_dynamic_route_regex')
@app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>', name='route_re') @app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>', name='route_re')
async def handler(request, folder_id): async def handler(request, folder_id):
@ -235,8 +221,7 @@ def test_dynamic_named_route_regex():
app.url_for('handler') app.url_for('handler')
def test_dynamic_named_route_path(): def test_dynamic_named_route_path(app):
app = Sanic('test_dynamic_route_path')
@app.route('/<path:path>/info', name='route_dynamic_path') @app.route('/<path:path>/info', name='route_dynamic_path')
async def handler(request, path): async def handler(request, path):
@ -249,8 +234,7 @@ def test_dynamic_named_route_path():
app.url_for('handler') app.url_for('handler')
def test_dynamic_named_route_unhashable(): def test_dynamic_named_route_unhashable(app):
app = Sanic('test_dynamic_route_unhashable')
@app.route('/folder/<unhashable:[A-Za-z0-9/]+>/end/', @app.route('/folder/<unhashable:[A-Za-z0-9/]+>/end/',
name='route_unhashable') name='route_unhashable')
@ -265,8 +249,7 @@ def test_dynamic_named_route_unhashable():
app.url_for('handler') app.url_for('handler')
def test_websocket_named_route(): def test_websocket_named_route(app):
app = Sanic('test_websocket_route')
ev = asyncio.Event() ev = asyncio.Event()
@app.websocket('/ws', name='route_ws') @app.websocket('/ws', name='route_ws')
@ -280,8 +263,7 @@ def test_websocket_named_route():
app.url_for('handler') app.url_for('handler')
def test_websocket_named_route_with_subprotocols(): def test_websocket_named_route_with_subprotocols(app):
app = Sanic('test_websocket_route')
results = [] results = []
@app.websocket('/ws', subprotocols=['foo', 'bar'], name='route_ws') @app.websocket('/ws', subprotocols=['foo', 'bar'], name='route_ws')
@ -294,8 +276,7 @@ def test_websocket_named_route_with_subprotocols():
app.url_for('handler') app.url_for('handler')
def test_static_add_named_route(): def test_static_add_named_route(app):
app = Sanic('test_static_add_route')
async def handler1(request): async def handler1(request):
return text('OK1') return text('OK1')
@ -319,9 +300,7 @@ def test_static_add_named_route():
app.url_for('handler2') app.url_for('handler2')
def test_dynamic_add_named_route(): def test_dynamic_add_named_route(app):
app = Sanic('test_dynamic_add_route')
results = [] results = []
async def handler(request, name): async def handler(request, name):
@ -335,8 +314,7 @@ def test_dynamic_add_named_route():
app.url_for('handler') app.url_for('handler')
def test_dynamic_add_named_route_unhashable(): def test_dynamic_add_named_route_unhashable(app):
app = Sanic('test_dynamic_add_route_unhashable')
async def handler(request, unhashable): async def handler(request, unhashable):
return text('OK') return text('OK')
@ -351,8 +329,7 @@ def test_dynamic_add_named_route_unhashable():
app.url_for('handler') app.url_for('handler')
def test_overload_routes(): def test_overload_routes(app):
app = Sanic('test_dynamic_route')
@app.route('/overload', methods=['GET'], name='route_first') @app.route('/overload', methods=['GET'], name='route_first')
async def handler1(request): async def handler1(request):

View File

@ -1,49 +1,45 @@
from sanic import Sanic
from sanic.exceptions import PayloadTooLarge from sanic.exceptions import PayloadTooLarge
from sanic.response import text from sanic.response import text
def test_payload_too_large_from_error_handler(): def test_payload_too_large_from_error_handler(app):
data_received_app = Sanic('data_received') app.config.REQUEST_MAX_SIZE = 1
data_received_app.config.REQUEST_MAX_SIZE = 1
@data_received_app.route('/1') @app.route('/1')
async def handler1(request): async def handler1(request):
return text('OK') return text('OK')
@data_received_app.exception(PayloadTooLarge) @app.exception(PayloadTooLarge)
def handler_exception(request, exception): def handler_exception(request, exception):
return text('Payload Too Large from error_handler.', 413) return text('Payload Too Large from error_handler.', 413)
response = data_received_app.test_client.get('/1', gather_request=False) response = app.test_client.get('/1', gather_request=False)
assert response.status == 413 assert response.status == 413
assert response.text == 'Payload Too Large from error_handler.' assert response.text == 'Payload Too Large from error_handler.'
def test_payload_too_large_at_data_received_default(): def test_payload_too_large_at_data_received_default(app):
data_received_default_app = Sanic('data_received_default') app.config.REQUEST_MAX_SIZE = 1
data_received_default_app.config.REQUEST_MAX_SIZE = 1
@data_received_default_app.route('/1') @app.route('/1')
async def handler2(request): async def handler2(request):
return text('OK') return text('OK')
response = data_received_default_app.test_client.get( response = app.test_client.get(
'/1', gather_request=False) '/1', gather_request=False)
assert response.status == 413 assert response.status == 413
assert response.text == 'Error: Payload Too Large' assert response.text == 'Error: Payload Too Large'
def test_payload_too_large_at_on_header_default(): def test_payload_too_large_at_on_header_default(app):
on_header_default_app = Sanic('on_header') app.config.REQUEST_MAX_SIZE = 500
on_header_default_app.config.REQUEST_MAX_SIZE = 500
@on_header_default_app.post('/1') @app.post('/1')
async def handler3(request): async def handler3(request):
return text('OK') return text('OK')
data = 'a' * 1000 data = 'a' * 1000
response = on_header_default_app.test_client.post( response = app.test_client.post(
'/1', gather_request=False, data=data) '/1', gather_request=False, data=data)
assert response.status == 413 assert response.status == 413
assert response.text == 'Error: Payload Too Large' assert response.text == 'Error: Payload Too Large'

View File

@ -1,12 +1,10 @@
import pytest import pytest
from sanic import Sanic
from sanic.response import text, redirect from sanic.response import text, redirect
@pytest.fixture @pytest.fixture
def redirect_app(): def redirect_app(app):
app = Sanic('test_redirection')
@app.route('/redirect_init') @app.route('/redirect_init')
async def redirect_init(request): async def redirect_init(request):
@ -32,6 +30,10 @@ def redirect_app():
def handler(request): def handler(request):
return text('OK') return text('OK')
@app.route('/redirect_with_header_injection')
async def redirect_with_header_injection(request):
return redirect("/unsafe\ntest-header: test-value\n\ntest-body")
return app return app
@ -92,3 +94,16 @@ def test_chained_redirect(redirect_app):
assert response.url.endswith('/3') assert response.url.endswith('/3')
except AttributeError: except AttributeError:
assert response.url.path.endswith('/3') assert response.url.path.endswith('/3')
def test_redirect_with_header_injection(redirect_app):
"""
Test redirection to a URL with header and body injections.
"""
request, response = redirect_app.test_client.get(
"/redirect_with_header_injection",
allow_redirects=False)
assert response.status == 302
assert "test-header" not in response.headers
assert not response.text.startswith('test-body')

View File

@ -1,6 +1,5 @@
import random import random
from sanic import Sanic
from sanic.response import json from sanic.response import json
try: try:
@ -9,8 +8,7 @@ except ImportError:
from json import loads from json import loads
def test_storage(): def test_storage(app):
app = Sanic('test_text')
@app.middleware('request') @app.middleware('request')
def store(request): def store(request):
@ -29,8 +27,7 @@ def test_storage():
assert response_json.get('sidekick') is None assert response_json.get('sidekick') is None
def test_app_injection(): def test_app_injection(app):
app = Sanic('test_app_injection')
expected = random.choice(range(0, 100)) expected = random.choice(range(0, 100))
@app.listener('after_server_start') @app.listener('after_server_start')

View File

@ -1,5 +1,4 @@
import asyncio import asyncio
from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.views import CompositionView from sanic.views import CompositionView
from sanic.views import HTTPMethodView from sanic.views import HTTPMethodView
@ -9,11 +8,9 @@ from sanic.response import stream, text
data = "abc" * 100000 data = "abc" * 100000
def test_request_stream_method_view(): def test_request_stream_method_view(app):
'''for self.is_request_stream = True''' '''for self.is_request_stream = True'''
app = Sanic('test_request_stream_method_view')
class SimpleView(HTTPMethodView): class SimpleView(HTTPMethodView):
def get(self, request): def get(self, request):
@ -44,11 +41,9 @@ def test_request_stream_method_view():
assert response.text == data assert response.text == data
def test_request_stream_app(): def test_request_stream_app(app):
'''for self.is_request_stream = True and decorators''' '''for self.is_request_stream = True and decorators'''
app = Sanic('test_request_stream_app')
@app.get('/get') @app.get('/get')
async def get(request): async def get(request):
assert request.stream is None assert request.stream is None
@ -83,7 +78,7 @@ def test_request_stream_app():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
@app.put('/_put') @app.put('/_put')
@ -100,7 +95,7 @@ def test_request_stream_app():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
@app.patch('/_patch') @app.patch('/_patch')
@ -117,7 +112,7 @@ def test_request_stream_app():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
assert app.is_request_stream is True assert app.is_request_stream is True
@ -163,11 +158,9 @@ def test_request_stream_app():
assert response.text == data assert response.text == data
def test_request_stream_handle_exception(): def test_request_stream_handle_exception(app):
'''for handling exceptions properly''' '''for handling exceptions properly'''
app = Sanic('test_request_stream_exception')
@app.post('/post/<id>', stream=True) @app.post('/post/<id>', stream=True)
async def post(request, id): async def post(request, id):
assert isinstance(request.stream, asyncio.Queue) assert isinstance(request.stream, asyncio.Queue)
@ -177,7 +170,7 @@ def test_request_stream_handle_exception():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
# 404 # 404
@ -191,10 +184,8 @@ def test_request_stream_handle_exception():
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(): def test_request_stream_blueprint(app):
'''for self.is_request_stream = True''' '''for self.is_request_stream = True'''
app = Sanic('test_request_stream_blueprint')
bp = Blueprint('test_blueprint_request_stream_blueprint') bp = Blueprint('test_blueprint_request_stream_blueprint')
@app.get('/get') @app.get('/get')
@ -231,7 +222,7 @@ def test_request_stream_blueprint():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
@bp.put('/_put') @bp.put('/_put')
@ -248,7 +239,7 @@ def test_request_stream_blueprint():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
@bp.patch('/_patch') @bp.patch('/_patch')
@ -265,7 +256,7 @@ def test_request_stream_blueprint():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
app.blueprint(bp) app.blueprint(bp)
@ -313,11 +304,9 @@ def test_request_stream_blueprint():
assert response.text == data assert response.text == data
def test_request_stream_composition_view(): def test_request_stream_composition_view(app):
'''for self.is_request_stream = True''' '''for self.is_request_stream = True'''
app = Sanic('test_request_stream__composition_view')
def get_handler(request): def get_handler(request):
assert request.stream is None assert request.stream is None
return text('OK') return text('OK')
@ -348,11 +337,9 @@ def test_request_stream_composition_view():
assert response.text == data assert response.text == data
def test_request_stream(): def test_request_stream(app):
'''test for complex application''' '''test for complex application'''
bp = Blueprint('test_blueprint_request_stream') bp = Blueprint('test_blueprint_request_stream')
app = Sanic('test_request_stream')
class SimpleView(HTTPMethodView): class SimpleView(HTTPMethodView):
@ -380,7 +367,7 @@ def test_request_stream():
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
response.write(body.decode('utf-8')) await response.write(body.decode('utf-8'))
return stream(streaming) return stream(streaming)
@app.get('/get') @app.get('/get')

View File

@ -5,9 +5,24 @@ import asyncio
from sanic.response import text from sanic.response import text
from sanic.config import Config from sanic.config import Config
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector, ClientResponse
from sanic.testing import SanicTestClient, HOST, PORT from sanic.testing import SanicTestClient, HOST, PORT
try:
try:
import packaging # direct use
except ImportError:
# setuptools v39.0 and above.
try:
from setuptools.extern import packaging
except ImportError:
# Before setuptools v39.0
from pkg_resources.extern import packaging
version = packaging.version
except ImportError:
raise RuntimeError("The 'packaging' library is missing.")
aiohttp_version = version.parse(aiohttp.__version__)
class DelayableTCPConnector(TCPConnector): class DelayableTCPConnector(TCPConnector):
@ -38,6 +53,9 @@ class DelayableTCPConnector(TCPConnector):
self.orig_start = getattr(resp, 'start') self.orig_start = getattr(resp, 'start')
try: try:
if aiohttp_version >= version.parse("3.3.0"):
ret = await self.orig_start(connection)
else:
ret = await self.orig_start(connection, ret = await self.orig_start(connection,
read_until_eof) read_until_eof)
except Exception as e: except Exception as e:
@ -58,14 +76,30 @@ class DelayableTCPConnector(TCPConnector):
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 conn = next(iter(args)) # first arg is connection
if aiohttp.__version__ >= "3.1.0":
if aiohttp_version >= version.parse("3.1.0"):
try: try:
delayed_resp = await self.orig_send(*args, **kwargs) delayed_resp = await self.orig_send(*args, **kwargs)
except Exception as e: except Exception as e:
if aiohttp_version >= version.parse("3.3.0"):
return aiohttp.ClientResponse(req.method, req.url, return aiohttp.ClientResponse(req.method, req.url,
writer=None, continue100=None, timer=None, writer=None,
request_info=None, auto_decompress=None, traces=[], continue100=None,
loop=req.loop, session=None) timer=None,
request_info=None,
traces=[],
loop=req.loop,
session=None)
else:
return aiohttp.ClientResponse(req.method, req.url,
writer=None,
continue100=None,
timer=None,
request_info=None,
auto_decompress=None,
traces=[],
loop=req.loop,
session=None)
else: else:
try: try:
delayed_resp = self.orig_send(*args, **kwargs) delayed_resp = self.orig_send(*args, **kwargs)
@ -73,7 +107,7 @@ class DelayableTCPConnector(TCPConnector):
return aiohttp.ClientResponse(req.method, req.url) return aiohttp.ClientResponse(req.method, req.url)
return delayed_resp return delayed_resp
if aiohttp.__version__ >= "3.1.0": if aiohttp_version >= version.parse("3.1.0"):
# aiohttp changed the request.send method to async # aiohttp changed the request.send method to async
async def send(self, *args, **kwargs): async def send(self, *args, **kwargs):
gen = self.delayed_send(*args, **kwargs) gen = self.delayed_send(*args, **kwargs)
@ -96,12 +130,25 @@ class DelayableTCPConnector(TCPConnector):
self._post_connect_delay = _post_connect_delay self._post_connect_delay = _post_connect_delay
self._pre_request_delay = _pre_request_delay self._pre_request_delay = _pre_request_delay
if aiohttp.__version__ >= '3.0': if aiohttp_version >= version.parse("3.3.0"):
async def connect(self, req, traces, timeout):
d_req = DelayableTCPConnector.\
RequestContextManager(req, self._pre_request_delay)
conn = await super(DelayableTCPConnector, self).\
connect(req, traces, timeout)
if self._post_connect_delay and self._post_connect_delay > 0:
await asyncio.sleep(self._post_connect_delay,
loop=self._loop)
req.send = d_req.send
t = req.loop.time()
print("Connected at {}".format(t), flush=True)
return conn
elif aiohttp_version >= version.parse("3.0.0"):
async def connect(self, req, traces=None): async def connect(self, req, traces=None):
d_req = DelayableTCPConnector.\ d_req = DelayableTCPConnector.\
RequestContextManager(req, self._pre_request_delay) RequestContextManager(req, self._pre_request_delay)
conn = await super(DelayableTCPConnector, self).connect(req, traces=traces) conn = await super(DelayableTCPConnector, self).\
connect(req, traces=traces)
if self._post_connect_delay and self._post_connect_delay > 0: if self._post_connect_delay and self._post_connect_delay > 0:
await asyncio.sleep(self._post_connect_delay, await asyncio.sleep(self._post_connect_delay,
loop=self._loop) loop=self._loop)

View File

@ -5,7 +5,6 @@ import ssl
import pytest import pytest
from sanic import Sanic
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.response import json, text from sanic.response import json, text
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE from sanic.request import DEFAULT_HTTP_CONTENT_TYPE
@ -16,8 +15,7 @@ from sanic.testing import HOST, PORT
# GET # GET
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def test_sync(): def test_sync(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -27,8 +25,8 @@ def test_sync():
assert response.text == 'Hello' assert response.text == 'Hello'
def test_remote_address():
app = Sanic('test_text') def test_remote_address(app):
@app.route('/') @app.route('/')
def handler(request): def handler(request):
@ -38,8 +36,8 @@ def test_remote_address():
assert response.text == '127.0.0.1' assert response.text == '127.0.0.1'
def test_text():
app = Sanic('test_text') def test_text(app):
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -50,8 +48,7 @@ def test_text():
assert response.text == 'Hello' assert response.text == 'Hello'
def test_headers(): def test_headers(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -63,8 +60,7 @@ def test_headers():
assert response.headers.get('spam') == 'great' assert response.headers.get('spam') == 'great'
def test_non_str_headers(): def test_non_str_headers(app):
app = Sanic('test_text')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -75,8 +71,8 @@ def test_non_str_headers():
assert response.headers.get('answer') == '42' assert response.headers.get('answer') == '42'
def test_invalid_response():
app = Sanic('test_invalid_response') def test_invalid_response(app):
@app.exception(ServerError) @app.exception(ServerError)
def handler_exception(request, exception): def handler_exception(request, exception):
@ -91,8 +87,7 @@ def test_invalid_response():
assert response.text == "Internal Server Error." assert response.text == "Internal Server Error."
def test_json(): def test_json(app):
app = Sanic('test_json')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -105,8 +100,7 @@ def test_json():
assert results.get('test') == True assert results.get('test') == True
def test_empty_json(): def test_empty_json(app):
app = Sanic('test_json')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -118,8 +112,7 @@ def test_empty_json():
assert response.text == 'null' assert response.text == 'null'
def test_invalid_json(): def test_invalid_json(app):
app = Sanic('test_json')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -131,8 +124,7 @@ def test_invalid_json():
assert response.status == 400 assert response.status == 400
def test_query_string(): def test_query_string(app):
app = Sanic('test_query_string')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -145,8 +137,7 @@ def test_query_string():
assert request.args.get('test2') == 'false' assert request.args.get('test2') == 'false'
def test_uri_template(): def test_uri_template(app):
app = Sanic('test_uri_template')
@app.route('/foo/<id:int>/bar/<name:[A-z]+>') @app.route('/foo/<id:int>/bar/<name:[A-z]+>')
async def handler(request): async def handler(request):
@ -156,8 +147,7 @@ def test_uri_template():
assert request.uri_template == '/foo/<id:int>/bar/<name:[A-z]+>' assert request.uri_template == '/foo/<id:int>/bar/<name:[A-z]+>'
def test_token(): def test_token(app):
app = Sanic('test_post_token')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -204,8 +194,7 @@ def test_token():
assert request.token is None assert request.token is None
def test_content_type(): def test_content_type(app):
app = Sanic('test_content_type')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -223,8 +212,7 @@ def test_content_type():
assert response.text == 'application/json' assert response.text == 'application/json'
def test_remote_addr(): def test_remote_addr(app):
app = Sanic('test_content_type')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -249,8 +237,7 @@ def test_remote_addr():
assert response.text == '127.0.0.1' assert response.text == '127.0.0.1'
def test_match_info(): def test_match_info(app):
app = Sanic('test_match_info')
@app.route('/api/v1/user/<user_id>/') @app.route('/api/v1/user/<user_id>/')
async def handler(request, user_id): async def handler(request, user_id):
@ -266,8 +253,7 @@ def test_match_info():
# POST # POST
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def test_post_json(): def test_post_json(app):
app = Sanic('test_post_json')
@app.route('/', methods=['POST']) @app.route('/', methods=['POST'])
async def handler(request): async def handler(request):
@ -283,8 +269,7 @@ def test_post_json():
assert response.text == 'OK' assert response.text == 'OK'
def test_post_form_urlencoded(): def test_post_form_urlencoded(app):
app = Sanic('test_post_form_urlencoded')
@app.route('/', methods=['POST']) @app.route('/', methods=['POST'])
async def handler(request): async def handler(request):
@ -311,8 +296,7 @@ def test_post_form_urlencoded():
'OK\r\n' \ 'OK\r\n' \
'------sanic--\r\n', '------sanic--\r\n',
]) ])
def test_post_form_multipart_form_data(payload): def test_post_form_multipart_form_data(app, payload):
app = Sanic('test_post_form_multipart_form_data')
@app.route('/', methods=['POST']) @app.route('/', methods=['POST'])
async def handler(request): async def handler(request):
@ -331,8 +315,7 @@ def test_post_form_multipart_form_data(payload):
('/bar/baz', '', 'http://{}:{}/bar/baz'), ('/bar/baz', '', 'http://{}:{}/bar/baz'),
('/moo/boo', 'arg1=val1', 'http://{}:{}/moo/boo?arg1=val1') ('/moo/boo', 'arg1=val1', 'http://{}:{}/moo/boo?arg1=val1')
]) ])
def test_url_attributes_no_ssl(path, query, expected_url): def test_url_attributes_no_ssl(app, path, query, expected_url):
app = Sanic('test_url_attrs_no_ssl')
async def handler(request): async def handler(request):
return text('OK') return text('OK')
@ -356,9 +339,7 @@ def test_url_attributes_no_ssl(path, query, expected_url):
('/bar/baz', '', 'https://{}:{}/bar/baz'), ('/bar/baz', '', 'https://{}:{}/bar/baz'),
('/moo/boo', 'arg1=val1', 'https://{}:{}/moo/boo?arg1=val1') ('/moo/boo', 'arg1=val1', 'https://{}:{}/moo/boo?arg1=val1')
]) ])
def test_url_attributes_with_ssl(path, query, expected_url): def test_url_attributes_with_ssl(app, path, query, expected_url):
app = Sanic('test_url_attrs_with_ssl')
current_dir = os.path.dirname(os.path.realpath(__file__)) current_dir = os.path.dirname(os.path.realpath(__file__))
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain( context.load_cert_chain(

View File

@ -8,17 +8,16 @@ from urllib.parse import unquote
import pytest import pytest
from random import choice from random import choice
from sanic import Sanic
from sanic.response import HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json from sanic.response import HTTPResponse, stream, StreamingHTTPResponse, file, file_stream, json
from sanic.server import HttpProtocol
from sanic.testing import HOST, PORT from sanic.testing import HOST, PORT
from unittest.mock import MagicMock from unittest.mock import MagicMock
JSON_DATA = {'ok': True} JSON_DATA = {'ok': True}
def test_response_body_not_a_string(): def test_response_body_not_a_string(app):
"""Test when a response body sent from the application is not a string""" """Test when a response body sent from the application is not a string"""
app = Sanic('response_body_not_a_string')
random_num = choice(range(1000)) random_num = choice(range(1000))
@app.route('/hello') @app.route('/hello')
@ -30,13 +29,12 @@ def test_response_body_not_a_string():
async def sample_streaming_fn(response): async def sample_streaming_fn(response):
response.write('foo,') await response.write('foo,')
await asyncio.sleep(.001) await asyncio.sleep(.001)
response.write('bar') await response.write('bar')
def test_method_not_allowed(): def test_method_not_allowed(app):
app = Sanic('method_not_allowed')
@app.get('/') @app.get('/')
async def test(request): async def test(request):
@ -64,9 +62,27 @@ def test_method_not_allowed():
assert response.headers['Content-Length'] == '0' assert response.headers['Content-Length'] == '0'
def test_response_header(app):
@app.get('/')
async def test(request):
return json({
"ok": True
}, headers={
'CONTENT-TYPE': 'application/json'
})
request, response = app.test_client.get('/')
assert dict(response.headers) == {
'Connection': 'keep-alive',
'Keep-Alive': '2',
'Content-Length': '11',
'Content-Type': 'application/json',
}
@pytest.fixture @pytest.fixture
def json_app(): def json_app(app):
app = Sanic('json')
@app.route("/") @app.route("/")
async def test(request): async def test(request):
@ -124,8 +140,7 @@ def test_no_content(json_app):
@pytest.fixture @pytest.fixture
def streaming_app(): def streaming_app(app):
app = Sanic('streaming')
@app.route("/") @app.route("/")
async def test(request): async def test(request):
@ -170,20 +185,30 @@ def test_stream_response_includes_chunked_header():
def test_stream_response_writes_correct_content_to_transport(streaming_app): def test_stream_response_writes_correct_content_to_transport(streaming_app):
response = StreamingHTTPResponse(sample_streaming_fn) response = StreamingHTTPResponse(sample_streaming_fn)
response.transport = MagicMock(asyncio.Transport) response.protocol = MagicMock(HttpProtocol)
response.protocol.transport = MagicMock(asyncio.Transport)
async def mock_drain():
pass
def mock_push_data(data):
response.protocol.transport.write(data)
response.protocol.push_data = mock_push_data
response.protocol.drain = mock_drain
@streaming_app.listener('after_server_start') @streaming_app.listener('after_server_start')
async def run_stream(app, loop): async def run_stream(app, loop):
await response.stream() await response.stream()
assert response.transport.write.call_args_list[1][0][0] == ( assert response.protocol.transport.write.call_args_list[1][0][0] == (
b'4\r\nfoo,\r\n' b'4\r\nfoo,\r\n'
) )
assert response.transport.write.call_args_list[2][0][0] == ( assert response.protocol.transport.write.call_args_list[2][0][0] == (
b'3\r\nbar\r\n' b'3\r\nbar\r\n'
) )
assert response.transport.write.call_args_list[3][0][0] == ( assert response.protocol.transport.write.call_args_list[3][0][0] == (
b'0\r\n\r\n' b'0\r\n\r\n'
) )
@ -208,25 +233,25 @@ def 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_response(file_name, static_file_directory): @pytest.mark.parametrize('status', [200, 401])
app = Sanic('test_file_helper') def test_file_response(app, file_name, static_file_directory, status):
@app.route('/files/<filename>', methods=['GET']) @app.route('/files/<filename>', methods=['GET'])
def file_route(request, filename): def file_route(request, filename):
file_path = os.path.join(static_file_directory, filename) file_path = os.path.join(static_file_directory, filename)
file_path = os.path.abspath(unquote(file_path)) file_path = os.path.abspath(unquote(file_path))
return file(file_path, mime_type=guess_type(file_path)[0] or 'text/plain') return file(file_path, status=status,
mime_type=guess_type(file_path)[0] or 'text/plain')
request, response = app.test_client.get('/files/{}'.format(file_name)) request, response = app.test_client.get('/files/{}'.format(file_name))
assert response.status == 200 assert response.status == status
assert response.body == get_file_content(static_file_directory, file_name) assert response.body == get_file_content(static_file_directory, file_name)
assert 'Content-Disposition' not in response.headers assert 'Content-Disposition' not in response.headers
@pytest.mark.parametrize('source,dest', [ @pytest.mark.parametrize('source,dest', [
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')]) ('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')])
def test_file_response_custom_filename(source, dest, static_file_directory): def test_file_response_custom_filename(app, source, dest, static_file_directory):
app = Sanic('test_file_helper')
@app.route('/files/<filename>', methods=['GET']) @app.route('/files/<filename>', methods=['GET'])
def file_route(request, filename): def file_route(request, filename):
@ -241,8 +266,7 @@ def test_file_response_custom_filename(source, dest, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_file_head_response(file_name, static_file_directory): def test_file_head_response(app, file_name, static_file_directory):
app = Sanic('test_file_helper')
@app.route('/files/<filename>', methods=['GET', 'HEAD']) @app.route('/files/<filename>', methods=['GET', 'HEAD'])
async def file_route(request, filename): async def file_route(request, filename):
@ -270,8 +294,7 @@ def test_file_head_response(file_name, static_file_directory):
@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(file_name, static_file_directory): def test_file_stream_response(app, file_name, static_file_directory):
app = Sanic('test_file_helper')
@app.route('/files/<filename>', methods=['GET']) @app.route('/files/<filename>', methods=['GET'])
def file_route(request, filename): def file_route(request, filename):
@ -288,8 +311,7 @@ def test_file_stream_response(file_name, static_file_directory):
@pytest.mark.parametrize('source,dest', [ @pytest.mark.parametrize('source,dest', [
('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')]) ('test.file', 'my_file.txt'), ('decode me.txt', 'readme.md'), ('python.png', 'logo.png')])
def test_file_stream_response_custom_filename(source, dest, static_file_directory): def test_file_stream_response_custom_filename(app, source, dest, static_file_directory):
app = Sanic('test_file_helper')
@app.route('/files/<filename>', methods=['GET']) @app.route('/files/<filename>', methods=['GET'])
def file_route(request, filename): def file_route(request, filename):
@ -304,8 +326,7 @@ def test_file_stream_response_custom_filename(source, dest, static_file_director
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_file_stream_head_response(file_name, static_file_directory): def test_file_stream_head_response(app, file_name, static_file_directory):
app = Sanic('test_file_helper')
@app.route('/files/<filename>', methods=['GET', 'HEAD']) @app.route('/files/<filename>', methods=['GET', 'HEAD'])
async def file_route(request, filename): async def file_route(request, filename):

View File

@ -7,6 +7,7 @@ from sanic.config import Config
Config.RESPONSE_TIMEOUT = 1 Config.RESPONSE_TIMEOUT = 1
response_timeout_app = Sanic('test_response_timeout') response_timeout_app = Sanic('test_response_timeout')
response_timeout_default_app = Sanic('test_response_timeout_default') response_timeout_default_app = Sanic('test_response_timeout_default')
response_handler_cancelled_app = Sanic('test_response_handler_cancelled')
@response_timeout_app.route('/1') @response_timeout_app.route('/1')
@ -36,3 +37,29 @@ def test_default_server_error_response_timeout():
request, response = response_timeout_default_app.test_client.get('/1') request, response = response_timeout_default_app.test_client.get('/1')
assert response.status == 503 assert response.status == 503
assert response.text == 'Error: Response Timeout' assert response.text == 'Error: Response Timeout'
response_handler_cancelled_app.flag = False
@response_handler_cancelled_app.exception(asyncio.CancelledError)
def handler_cancelled(request, exception):
# If we get a CancelledError, it means sanic has already sent a response,
# we should not ever have to handle a CancelledError.
response_handler_cancelled_app.flag = True
return text("App received CancelledError!", 500)
# The client will never receive this response, because the socket
# is already closed when we get a CancelledError.
@response_handler_cancelled_app.route('/1')
async def handler_3(request):
await asyncio.sleep(2)
return text('OK')
def test_response_handler_cancelled():
request, response = response_handler_cancelled_app.test_client.get('/1')
assert response.status == 503
assert response.text == 'Error: Response Timeout'
assert response_handler_cancelled_app.flag is False

View File

@ -12,9 +12,7 @@ from sanic.constants import HTTP_METHODS
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_versioned_routes_get(method): def test_versioned_routes_get(app, method):
app = Sanic('test_shorhand_routes_get')
method = method.lower() method = method.lower()
func = getattr(app, method) func = getattr(app, method)
@ -32,8 +30,7 @@ def test_versioned_routes_get(method):
assert response.status == 200 assert response.status == 200
def test_shorthand_routes_get(): def test_shorthand_routes_get(app):
app = Sanic('test_shorhand_routes_get')
@app.get('/get') @app.get('/get')
def handler(request): def handler(request):
@ -46,8 +43,7 @@ def test_shorthand_routes_get():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_multiple(): def test_shorthand_routes_multiple(app):
app = Sanic('test_shorthand_routes_multiple')
@app.get('/get') @app.get('/get')
def get_handler(request): def get_handler(request):
@ -65,8 +61,7 @@ def test_shorthand_routes_multiple():
assert response.status == 200 assert response.status == 200
def test_route_strict_slash(): def test_route_strict_slash(app):
app = Sanic('test_route_strict_slash')
@app.get('/get', strict_slashes=True) @app.get('/get', strict_slashes=True)
def handler(request): def handler(request):
@ -93,9 +88,8 @@ def test_route_strict_slash():
assert response.status == 404 assert response.status == 404
def test_route_invalid_parameter_syntax(): def test_route_invalid_parameter_syntax(app):
with pytest.raises(ValueError): with pytest.raises(ValueError):
app = Sanic('test_route_invalid_param_syntax')
@app.get('/get/<:string>', strict_slashes=True) @app.get('/get/<:string>', strict_slashes=True)
def handler(request): def handler(request):
@ -115,8 +109,7 @@ def test_route_strict_slash_default_value():
assert response.status == 404 assert response.status == 404
def test_route_strict_slash_without_passing_default_value(): def test_route_strict_slash_without_passing_default_value(app):
app = Sanic('test_route_strict_slash')
@app.get('/get') @app.get('/get')
def handler(request): def handler(request):
@ -137,8 +130,7 @@ def test_route_strict_slash_default_value_can_be_overwritten():
assert response.text == 'OK' assert response.text == 'OK'
def test_route_slashes_overload(): def test_route_slashes_overload(app):
app = Sanic('test_route_slashes_overload')
@app.get('/hello/') @app.get('/hello/')
def handler(request): def handler(request):
@ -161,8 +153,7 @@ def test_route_slashes_overload():
assert response.text == 'OK' assert response.text == 'OK'
def test_route_optional_slash(): def test_route_optional_slash(app):
app = Sanic('test_route_optional_slash')
@app.get('/get') @app.get('/get')
def handler(request): def handler(request):
@ -174,9 +165,8 @@ def test_route_optional_slash():
request, response = app.test_client.get('/get/') request, response = app.test_client.get('/get/')
assert response.text == 'OK' assert response.text == 'OK'
def test_route_strict_slashes_set_to_false_and_host_is_a_list(): def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
#Part of regression test for issue #1120 #Part of regression test for issue #1120
app = Sanic('test_route_strict_slashes_set_to_false_and_host_is_a_list')
site1 = 'localhost:{}'.format(app.test_client.port) site1 = 'localhost:{}'.format(app.test_client.port)
@ -209,8 +199,7 @@ def test_route_strict_slashes_set_to_false_and_host_is_a_list():
request, response = app.test_client.delete('http://' + site1 +'/delete') request, response = app.test_client.delete('http://' + site1 +'/delete')
assert response.text == 'OK' assert response.text == 'OK'
def test_shorthand_routes_post(): def test_shorthand_routes_post(app):
app = Sanic('test_shorhand_routes_post')
@app.post('/post') @app.post('/post')
def handler(request): def handler(request):
@ -223,8 +212,7 @@ def test_shorthand_routes_post():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_put(): def test_shorthand_routes_put(app):
app = Sanic('test_shorhand_routes_put')
@app.put('/put') @app.put('/put')
def handler(request): def handler(request):
@ -240,8 +228,7 @@ def test_shorthand_routes_put():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_delete(): def test_shorthand_routes_delete(app):
app = Sanic('test_shorhand_routes_delete')
@app.delete('/delete') @app.delete('/delete')
def handler(request): def handler(request):
@ -257,8 +244,7 @@ def test_shorthand_routes_delete():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_patch(): def test_shorthand_routes_patch(app):
app = Sanic('test_shorhand_routes_patch')
@app.patch('/patch') @app.patch('/patch')
def handler(request): def handler(request):
@ -274,8 +260,7 @@ def test_shorthand_routes_patch():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_head(): def test_shorthand_routes_head(app):
app = Sanic('test_shorhand_routes_head')
@app.head('/head') @app.head('/head')
def handler(request): def handler(request):
@ -291,8 +276,7 @@ def test_shorthand_routes_head():
assert response.status == 405 assert response.status == 405
def test_shorthand_routes_options(): def test_shorthand_routes_options(app):
app = Sanic('test_shorhand_routes_options')
@app.options('/options') @app.options('/options')
def handler(request): def handler(request):
@ -308,8 +292,7 @@ def test_shorthand_routes_options():
assert response.status == 405 assert response.status == 405
def test_static_routes(): def test_static_routes(app):
app = Sanic('test_dynamic_route')
@app.route('/test') @app.route('/test')
async def handler1(request): async def handler1(request):
@ -326,9 +309,7 @@ def test_static_routes():
assert response.text == 'OK2' assert response.text == 'OK2'
def test_dynamic_route(): def test_dynamic_route(app):
app = Sanic('test_dynamic_route')
results = [] results = []
@app.route('/folder/<name>') @app.route('/folder/<name>')
@ -342,9 +323,7 @@ def test_dynamic_route():
assert results[0] == 'test123' assert results[0] == 'test123'
def test_dynamic_route_string(): def test_dynamic_route_string(app):
app = Sanic('test_dynamic_route_string')
results = [] results = []
@app.route('/folder/<name:string>') @app.route('/folder/<name:string>')
@ -363,9 +342,7 @@ def test_dynamic_route_string():
assert results[1] == 'favicon.ico' assert results[1] == 'favicon.ico'
def test_dynamic_route_int(): def test_dynamic_route_int(app):
app = Sanic('test_dynamic_route_int')
results = [] results = []
@app.route('/folder/<folder_id:int>') @app.route('/folder/<folder_id:int>')
@ -381,9 +358,7 @@ def test_dynamic_route_int():
assert response.status == 404 assert response.status == 404
def test_dynamic_route_number(): def test_dynamic_route_number(app):
app = Sanic('test_dynamic_route_number')
results = [] results = []
@app.route('/weight/<weight:number>') @app.route('/weight/<weight:number>')
@ -402,8 +377,7 @@ def test_dynamic_route_number():
assert response.status == 404 assert response.status == 404
def test_dynamic_route_regex(): def test_dynamic_route_regex(app):
app = Sanic('test_dynamic_route_regex')
@app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>') @app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>')
async def handler(request, folder_id): async def handler(request, folder_id):
@ -422,9 +396,8 @@ def test_dynamic_route_regex():
assert response.status == 200 assert response.status == 200
def test_dynamic_route_uuid(): def test_dynamic_route_uuid(app):
import uuid import uuid
app = Sanic('test_dynamic_route_uuid')
results = [] results = []
@ -444,8 +417,7 @@ def test_dynamic_route_uuid():
assert response.status == 404 assert response.status == 404
def test_dynamic_route_path(): def test_dynamic_route_path(app):
app = Sanic('test_dynamic_route_path')
@app.route('/<path:path>/info') @app.route('/<path:path>/info')
async def handler(request, path): async def handler(request, path):
@ -468,8 +440,7 @@ def test_dynamic_route_path():
assert response.status == 200 assert response.status == 200
def test_dynamic_route_unhashable(): def test_dynamic_route_unhashable(app):
app = Sanic('test_dynamic_route_unhashable')
@app.route('/folder/<unhashable:[A-Za-z0-9/]+>/end/') @app.route('/folder/<unhashable:[A-Za-z0-9/]+>/end/')
async def handler(request, unhashable): async def handler(request, unhashable):
@ -488,8 +459,7 @@ def test_dynamic_route_unhashable():
assert response.status == 404 assert response.status == 404
def test_websocket_route(): def test_websocket_route(app):
app = Sanic('test_websocket_route')
ev = asyncio.Event() ev = asyncio.Event()
@app.websocket('/ws') @app.websocket('/ws')
@ -506,8 +476,7 @@ def test_websocket_route():
assert ev.is_set() assert ev.is_set()
def test_websocket_route_with_subprotocols(): def test_websocket_route_with_subprotocols(app):
app = Sanic('test_websocket_route')
results = [] results = []
@app.websocket('/ws', subprotocols=['foo', 'bar']) @app.websocket('/ws', subprotocols=['foo', 'bar'])
@ -548,8 +517,7 @@ def test_websocket_route_with_subprotocols():
assert results == ['bar', 'bar', None, None] assert results == ['bar', 'bar', None, None]
def test_route_duplicate(): def test_route_duplicate(app):
app = Sanic('test_route_duplicate')
with pytest.raises(RouteExists): with pytest.raises(RouteExists):
@app.route('/test') @app.route('/test')
@ -570,8 +538,7 @@ def test_route_duplicate():
pass pass
def test_method_not_allowed(): def test_method_not_allowed(app):
app = Sanic('test_method_not_allowed')
@app.route('/test', methods=['GET']) @app.route('/test', methods=['GET'])
async def handler(request): async def handler(request):
@ -584,8 +551,7 @@ def test_method_not_allowed():
assert response.status == 405 assert response.status == 405
def test_static_add_route(): def test_static_add_route(app):
app = Sanic('test_static_add_route')
async def handler1(request): async def handler1(request):
return text('OK1') return text('OK1')
@ -603,8 +569,7 @@ def test_static_add_route():
assert response.text == 'OK2' assert response.text == 'OK2'
def test_dynamic_add_route(): def test_dynamic_add_route(app):
app = Sanic('test_dynamic_add_route')
results = [] results = []
@ -619,8 +584,7 @@ def test_dynamic_add_route():
assert results[0] == 'test123' assert results[0] == 'test123'
def test_dynamic_add_route_string(): def test_dynamic_add_route_string(app):
app = Sanic('test_dynamic_add_route_string')
results = [] results = []
@ -640,9 +604,7 @@ def test_dynamic_add_route_string():
assert results[1] == 'favicon.ico' assert results[1] == 'favicon.ico'
def test_dynamic_add_route_int(): def test_dynamic_add_route_int(app):
app = Sanic('test_dynamic_add_route_int')
results = [] results = []
async def handler(request, folder_id): async def handler(request, folder_id):
@ -659,9 +621,7 @@ def test_dynamic_add_route_int():
assert response.status == 404 assert response.status == 404
def test_dynamic_add_route_number(): def test_dynamic_add_route_number(app):
app = Sanic('test_dynamic_add_route_number')
results = [] results = []
async def handler(request, weight): async def handler(request, weight):
@ -681,8 +641,7 @@ def test_dynamic_add_route_number():
assert response.status == 404 assert response.status == 404
def test_dynamic_add_route_regex(): def test_dynamic_add_route_regex(app):
app = Sanic('test_dynamic_route_int')
async def handler(request, folder_id): async def handler(request, folder_id):
return text('OK') return text('OK')
@ -702,8 +661,7 @@ def test_dynamic_add_route_regex():
assert response.status == 200 assert response.status == 200
def test_dynamic_add_route_unhashable(): def test_dynamic_add_route_unhashable(app):
app = Sanic('test_dynamic_add_route_unhashable')
async def handler(request, unhashable): async def handler(request, unhashable):
return text('OK') return text('OK')
@ -723,8 +681,7 @@ def test_dynamic_add_route_unhashable():
assert response.status == 404 assert response.status == 404
def test_add_route_duplicate(): def test_add_route_duplicate(app):
app = Sanic('test_add_route_duplicate')
with pytest.raises(RouteExists): with pytest.raises(RouteExists):
async def handler1(request): async def handler1(request):
@ -747,8 +704,7 @@ def test_add_route_duplicate():
app.add_route(handler2, '/test/<dynamic>/') app.add_route(handler2, '/test/<dynamic>/')
def test_add_route_method_not_allowed(): def test_add_route_method_not_allowed(app):
app = Sanic('test_add_route_method_not_allowed')
async def handler(request): async def handler(request):
return text('OK') return text('OK')
@ -762,8 +718,7 @@ def test_add_route_method_not_allowed():
assert response.status == 405 assert response.status == 405
def test_remove_static_route(): def test_remove_static_route(app):
app = Sanic('test_remove_static_route')
async def handler1(request): async def handler1(request):
return text('OK1') return text('OK1')
@ -790,8 +745,7 @@ def test_remove_static_route():
assert response.status == 404 assert response.status == 404
def test_remove_dynamic_route(): def test_remove_dynamic_route(app):
app = Sanic('test_remove_dynamic_route')
async def handler(request, name): async def handler(request, name):
return text('OK') return text('OK')
@ -806,15 +760,13 @@ def test_remove_dynamic_route():
assert response.status == 404 assert response.status == 404
def test_remove_inexistent_route(): def test_remove_inexistent_route(app):
app = Sanic('test_remove_inexistent_route')
with pytest.raises(RouteDoesNotExist): with pytest.raises(RouteDoesNotExist):
app.remove_route('/test') app.remove_route('/test')
def test_removing_slash(): def test_removing_slash(app):
app = Sanic(__name__)
@app.get('/rest/<resource>') @app.get('/rest/<resource>')
def get(_): def get(_):
@ -827,8 +779,7 @@ def test_removing_slash():
assert len(app.router.routes_all.keys()) == 2 assert len(app.router.routes_all.keys()) == 2
def test_remove_unhashable_route(): def test_remove_unhashable_route(app):
app = Sanic('test_remove_unhashable_route')
async def handler(request, unhashable): async def handler(request, unhashable):
return text('OK') return text('OK')
@ -856,8 +807,7 @@ def test_remove_unhashable_route():
assert response.status == 404 assert response.status == 404
def test_remove_route_without_clean_cache(): def test_remove_route_without_clean_cache(app):
app = Sanic('test_remove_static_route')
async def handler(request): async def handler(request):
return text('OK') return text('OK')
@ -884,8 +834,7 @@ def test_remove_route_without_clean_cache():
assert response.status == 200 assert response.status == 200
def test_overload_routes(): def test_overload_routes(app):
app = Sanic('test_dynamic_route')
@app.route('/overload', methods=['GET']) @app.route('/overload', methods=['GET'])
async def handler1(request): async def handler1(request):
@ -913,8 +862,7 @@ def test_overload_routes():
return text('Duplicated') return text('Duplicated')
def test_unmergeable_overload_routes(): def test_unmergeable_overload_routes(app):
app = Sanic('test_dynamic_route')
@app.route('/overload_whole', methods=None) @app.route('/overload_whole', methods=None)
async def handler1(request): async def handler1(request):
@ -947,8 +895,7 @@ def test_unmergeable_overload_routes():
assert response.status == 405 assert response.status == 405
def test_unicode_routes(): def test_unicode_routes(app):
app = Sanic('test_unicode_routes')
@app.get('/你好') @app.get('/你好')
def handler1(request): def handler1(request):
@ -965,8 +912,7 @@ def test_unicode_routes():
assert response.text == 'OK2 你好' assert response.text == 'OK2 你好'
def test_uri_with_different_method_and_different_params(): def test_uri_with_different_method_and_different_params(app):
app = Sanic('test_uri')
@app.route('/ads/<ad_id>', methods=['GET']) @app.route('/ads/<ad_id>', methods=['GET'])
async def ad_get(request, ad_id): async def ad_get(request, ad_id):

View File

@ -1,11 +1,7 @@
from io import StringIO
from random import choice
from string import ascii_letters
import signal import signal
import pytest import pytest
from sanic import Sanic
from sanic.testing import HOST, PORT from sanic.testing import HOST, PORT
AVAILABLE_LISTENERS = [ AVAILABLE_LISTENERS = [
@ -37,54 +33,46 @@ def start_stop_app(random_name_app, **run_kwargs):
@pytest.mark.parametrize('listener_name', AVAILABLE_LISTENERS) @pytest.mark.parametrize('listener_name', AVAILABLE_LISTENERS)
def test_single_listener(listener_name): def test_single_listener(app, listener_name):
"""Test that listeners on their own work""" """Test that listeners on their own work"""
random_name_app = Sanic(''.join( output = []
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
output = list()
# Register listener # Register listener
random_name_app.listener(listener_name)( app.listener(listener_name)(
create_listener(listener_name, output)) create_listener(listener_name, output))
start_stop_app(random_name_app) start_stop_app(app)
assert random_name_app.name + listener_name == output.pop() assert app.name + listener_name == output.pop()
@pytest.mark.parametrize('listener_name', AVAILABLE_LISTENERS) @pytest.mark.parametrize('listener_name', AVAILABLE_LISTENERS)
def test_register_listener(listener_name): def test_register_listener(app, listener_name):
""" """
Test that listeners on their own work with Test that listeners on their own work with
app.register_listener method app.register_listener method
""" """
random_name_app = Sanic(''.join( output = []
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
output = list()
# Register listener # Register listener
listener = create_listener(listener_name, output) listener = create_listener(listener_name, output)
random_name_app.register_listener(listener, app.register_listener(listener,
event=listener_name) event=listener_name)
start_stop_app(random_name_app) start_stop_app(app)
assert random_name_app.name + listener_name == output.pop() assert app.name + listener_name == output.pop()
def test_all_listeners(): def test_all_listeners(app):
random_name_app = Sanic(''.join( output = []
[choice(ascii_letters) for _ in range(choice(range(5, 10)))]))
output = list()
for listener_name in AVAILABLE_LISTENERS: for listener_name in AVAILABLE_LISTENERS:
listener = create_listener(listener_name, output) listener = create_listener(listener_name, output)
random_name_app.listener(listener_name)(listener) app.listener(listener_name)(listener)
start_stop_app(random_name_app) start_stop_app(app)
for listener_name in AVAILABLE_LISTENERS: for listener_name in AVAILABLE_LISTENERS:
assert random_name_app.name + listener_name == output.pop() assert app.name + listener_name == output.pop()
async def test_trigger_before_events_create_server(): async def test_trigger_before_events_create_server(app):
class MySanicDb: class MySanicDb:
pass pass
app = Sanic("test_sanic_app")
@app.listener('before_server_start') @app.listener('before_server_start')
async def init_db(app, loop): async def init_db(app, loop):
app.db = MySanicDb() app.db = MySanicDb()

View File

@ -1,4 +1,3 @@
from sanic import Sanic
from sanic.response import HTTPResponse from sanic.response import HTTPResponse
from sanic.testing import HOST, PORT from sanic.testing import HOST, PORT
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -18,9 +17,8 @@ def set_loop(app, loop):
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(): def test_register_system_signals(app):
"""Test if sanic register system signals""" """Test if sanic register system signals"""
app = Sanic('test_register_system_signals')
@app.route('/hello') @app.route('/hello')
async def hello_route(request): async def hello_route(request):
@ -34,9 +32,8 @@ def test_register_system_signals():
assert calledq.get() == True assert calledq.get() == True
def test_dont_register_system_signals(): def test_dont_register_system_signals(app):
"""Test if sanic don't register system signals""" """Test if sanic don't register system signals"""
app = Sanic('test_register_system_signals')
@app.route('/hello') @app.route('/hello')
async def hello_route(request): async def hello_route(request):

View File

@ -3,8 +3,6 @@ import os
import pytest import pytest
from sanic import Sanic
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def static_file_directory(): def static_file_directory():
@ -26,8 +24,7 @@ def 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(static_file_directory, file_name): def test_static_file(app, static_file_directory, file_name):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name)) '/testing.file', get_file_path(static_file_directory, file_name))
@ -36,11 +33,23 @@ def test_static_file(static_file_directory, file_name):
assert response.body == get_file_content(static_file_directory, file_name) assert response.body == get_file_content(static_file_directory, file_name)
@pytest.mark.parametrize('file_name', ['test.html'])
def test_static_file_content_type(app, static_file_directory, file_name):
app.static(
'/testing.file',
get_file_path(static_file_directory, file_name),
content_type='text/html; charset=utf-8'
)
request, response = app.test_client.get('/testing.file')
assert response.status == 200
assert response.body == get_file_content(static_file_directory, file_name)
assert response.headers['Content-Type'] == 'text/html; charset=utf-8'
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
@pytest.mark.parametrize('base_uri', ['/static', '', '/dir']) @pytest.mark.parametrize('base_uri', ['/static', '', '/dir'])
def test_static_directory(file_name, base_uri, static_file_directory): def test_static_directory(app, file_name, base_uri, static_file_directory):
app = Sanic('test_static')
app.static(base_uri, static_file_directory) app.static(base_uri, static_file_directory)
request, response = app.test_client.get( request, response = app.test_client.get(
@ -50,8 +59,7 @@ def test_static_directory(file_name, base_uri, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_head_request(file_name, static_file_directory): def test_static_head_request(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -66,8 +74,7 @@ def test_static_head_request(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_correct(file_name, static_file_directory): def test_static_content_range_correct(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -87,8 +94,7 @@ def test_static_content_range_correct(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_front(file_name, static_file_directory): def test_static_content_range_front(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -108,8 +114,7 @@ def test_static_content_range_front(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_back(file_name, static_file_directory): def test_static_content_range_back(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -129,8 +134,7 @@ def test_static_content_range_back(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_empty(file_name, static_file_directory): def test_static_content_range_empty(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -146,8 +150,7 @@ def test_static_content_range_empty(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_error(file_name, static_file_directory): def test_static_content_range_error(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -164,8 +167,7 @@ def test_static_content_range_error(file_name, static_file_directory):
@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(static_file_directory, file_name): def test_static_file_specified_host(app, static_file_directory, file_name):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', '/testing.file',
get_file_path(static_file_directory, file_name), get_file_path(static_file_directory, file_name),

View File

@ -1,7 +1,6 @@
import pytest as pytest import pytest as pytest
from urllib.parse import urlsplit, parse_qsl from urllib.parse import urlsplit, parse_qsl
from sanic import Sanic
from sanic.response import text from sanic.response import text
from sanic.views import HTTPMethodView from sanic.views import HTTPMethodView
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
@ -30,8 +29,7 @@ def _generate_handlers_from_names(app, l):
@pytest.fixture @pytest.fixture
def simple_app(): def simple_app(app):
app = Sanic('simple_app')
handler_names = list(string.ascii_letters) handler_names = list(string.ascii_letters)
_generate_handlers_from_names(app, handler_names) _generate_handlers_from_names(app, handler_names)
@ -54,8 +52,7 @@ def test_simple_url_for_getting(simple_app):
(URL_FOR_ARGS2, URL_FOR_VALUE2), (URL_FOR_ARGS2, URL_FOR_VALUE2),
(URL_FOR_ARGS3, URL_FOR_VALUE3), (URL_FOR_ARGS3, URL_FOR_VALUE3),
(URL_FOR_ARGS4, URL_FOR_VALUE4)]) (URL_FOR_ARGS4, URL_FOR_VALUE4)])
def test_simple_url_for_getting_with_more_params(args, url): def test_simple_url_for_getting_with_more_params(app, args, url):
app = Sanic('more_url_build')
@app.route('/myurl') @app.route('/myurl')
def passes(request): def passes(request):
@ -67,8 +64,7 @@ def test_simple_url_for_getting_with_more_params(args, url):
assert response.text == 'this should pass' assert response.text == 'this should pass'
def test_fails_if_endpoint_not_found(): def test_fails_if_endpoint_not_found(app):
app = Sanic('fail_url_build')
@app.route('/fail') @app.route('/fail')
def fail(request): def fail(request):
@ -80,14 +76,12 @@ def test_fails_if_endpoint_not_found():
assert str(e.value) == 'Endpoint with name `passes` was not found' assert str(e.value) == 'Endpoint with name `passes` was not found'
def test_fails_url_build_if_param_not_passed(): def test_fails_url_build_if_param_not_passed(app):
url = '/' url = '/'
for letter in string.ascii_letters: for letter in string.ascii_letters:
url += '<{}>/'.format(letter) url += '<{}>/'.format(letter)
app = Sanic('fail_url_build')
@app.route(url) @app.route(url)
def fail(request): def fail(request):
return text('this should fail') return text('this should fail')
@ -103,8 +97,7 @@ def test_fails_url_build_if_param_not_passed():
assert 'Required parameter `Z` was not passed to url_for' in str(e.value) assert 'Required parameter `Z` was not passed to url_for' in str(e.value)
def test_fails_url_build_if_params_not_passed(): def test_fails_url_build_if_params_not_passed(app):
app = Sanic('fail_url_build')
@app.route('/fail') @app.route('/fail')
def fail(request): def fail(request):
@ -126,8 +119,7 @@ PASSING_KWARGS = {
EXPECTED_BUILT_URL = '/4/woof/ba/normal/1.001' EXPECTED_BUILT_URL = '/4/woof/ba/normal/1.001'
def test_fails_with_int_message(): def test_fails_with_int_message(app):
app = Sanic('fail_url_build')
@app.route(COMPLEX_PARAM_URL) @app.route(COMPLEX_PARAM_URL)
def fail(request): def fail(request):
@ -145,8 +137,7 @@ def test_fails_with_int_message():
assert str(e.value) == expected_error assert str(e.value) == expected_error
def test_fails_with_two_letter_string_message(): def test_fails_with_two_letter_string_message(app):
app = Sanic('fail_url_build')
@app.route(COMPLEX_PARAM_URL) @app.route(COMPLEX_PARAM_URL)
def fail(request): def fail(request):
@ -165,8 +156,7 @@ def test_fails_with_two_letter_string_message():
assert str(e.value) == expected_error assert str(e.value) == expected_error
def test_fails_with_number_message(): def test_fails_with_number_message(app):
app = Sanic('fail_url_build')
@app.route(COMPLEX_PARAM_URL) @app.route(COMPLEX_PARAM_URL)
def fail(request): def fail(request):
@ -185,8 +175,7 @@ def test_fails_with_number_message():
assert str(e.value) == expected_error assert str(e.value) == expected_error
def test_adds_other_supplied_values_as_query_string(): def test_adds_other_supplied_values_as_query_string(app):
app = Sanic('passes')
@app.route(COMPLEX_PARAM_URL) @app.route(COMPLEX_PARAM_URL)
def passes(request): def passes(request):
@ -205,8 +194,7 @@ def test_adds_other_supplied_values_as_query_string():
@pytest.fixture @pytest.fixture
def blueprint_app(): def blueprint_app(app):
app = Sanic('blueprints')
first_print = Blueprint('first', url_prefix='/first') first_print = Blueprint('first', url_prefix='/first')
second_print = Blueprint('second', url_prefix='/second') second_print = Blueprint('second', url_prefix='/second')
@ -252,8 +240,7 @@ def test_blueprints_work_with_params(blueprint_app):
@pytest.fixture @pytest.fixture
def methodview_app(): def methodview_app(app):
app = Sanic('methodview')
class ViewOne(HTTPMethodView): class ViewOne(HTTPMethodView):
def get(self, request): def get(self, request):

View File

@ -3,7 +3,6 @@ import os
import pytest import pytest
from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
@ -27,8 +26,7 @@ def 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(static_file_directory, file_name): def test_static_file(app, static_file_directory, file_name):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name)) '/testing.file', get_file_path(static_file_directory, file_name))
app.static( app.static(
@ -102,9 +100,7 @@ def test_static_file(static_file_directory, file_name):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
@pytest.mark.parametrize('base_uri', ['/static', '', '/dir']) @pytest.mark.parametrize('base_uri', ['/static', '', '/dir'])
def test_static_directory(file_name, base_uri, static_file_directory): def test_static_directory(app, file_name, base_uri, static_file_directory):
app = Sanic('test_static')
app.static(base_uri, static_file_directory) app.static(base_uri, static_file_directory)
base_uri2 = base_uri + '/2' base_uri2 = base_uri + '/2'
app.static(base_uri2, static_file_directory, name='uploads') app.static(base_uri2, static_file_directory, name='uploads')
@ -156,10 +152,8 @@ def test_static_directory(file_name, base_uri, static_file_directory):
assert response.body == get_file_content(static_file_directory, file_name) assert response.body == get_file_content(static_file_directory, file_name)
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_head_request(file_name, static_file_directory): def test_static_head_request(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -198,8 +192,7 @@ def test_static_head_request(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_correct(file_name, static_file_directory): def test_static_content_range_correct(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -250,8 +243,7 @@ def test_static_content_range_correct(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_front(file_name, static_file_directory): def test_static_content_range_front(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -302,8 +294,7 @@ def test_static_content_range_front(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_back(file_name, static_file_directory): def test_static_content_range_back(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -354,8 +345,7 @@ def test_static_content_range_back(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_empty(file_name, static_file_directory): def test_static_content_range_empty(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)
@ -401,8 +391,7 @@ def test_static_content_range_empty(file_name, static_file_directory):
@pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt']) @pytest.mark.parametrize('file_name', ['test.file', 'decode me.txt'])
def test_static_content_range_error(file_name, static_file_directory): def test_static_content_range_error(app, file_name, static_file_directory):
app = Sanic('test_static')
app.static( app.static(
'/testing.file', get_file_path(static_file_directory, file_name), '/testing.file', get_file_path(static_file_directory, file_name),
use_content_range=True) use_content_range=True)

View File

@ -1,14 +1,12 @@
from json import loads as json_loads, dumps as json_dumps from json import dumps as json_dumps
from sanic import Sanic from sanic.response import text
from sanic.response import json, text
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
# UTF-8 # UTF-8
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
def test_utf8_query_string(): def test_utf8_query_string(app):
app = Sanic('test_utf8_query_string')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -18,8 +16,7 @@ def test_utf8_query_string():
assert request.args.get('utf8') == '' assert request.args.get('utf8') == ''
def test_utf8_response(): def test_utf8_response(app):
app = Sanic('test_utf8_response')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -29,8 +26,7 @@ def test_utf8_response():
assert response.text == '' assert response.text == ''
def skip_test_utf8_route(): def skip_test_utf8_route(app):
app = Sanic('skip_test_utf8_route')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):
@ -41,8 +37,7 @@ def skip_test_utf8_route():
assert response.text == 'OK' assert response.text == 'OK'
def test_utf8_post_json(): def test_utf8_post_json(app):
app = Sanic('test_utf8_post_json')
@app.route('/') @app.route('/')
async def handler(request): async def handler(request):

View File

@ -1,9 +1,7 @@
from sanic import Sanic from sanic.response import text
from sanic.response import json, text
def test_vhosts(): def test_vhosts(app):
app = Sanic('test_vhosts')
@app.route('/', host="example.com") @app.route('/', host="example.com")
async def handler(request): async def handler(request):
@ -22,8 +20,7 @@ def test_vhosts():
assert response.text == "You're at subdomain.example.com!" assert response.text == "You're at subdomain.example.com!"
def test_vhosts_with_list(): def test_vhosts_with_list(app):
app = Sanic('test_vhosts')
@app.route('/', host=["hello.com", "world.com"]) @app.route('/', host=["hello.com", "world.com"])
async def handler(request): async def handler(request):
@ -37,8 +34,8 @@ def test_vhosts_with_list():
request, response = app.test_client.get('/', headers=headers) request, response = app.test_client.get('/', headers=headers)
assert response.text == "Hello, world!" assert response.text == "Hello, world!"
def test_vhosts_with_defaults():
app = Sanic('test_vhosts') def test_vhosts_with_defaults(app):
@app.route('/', host="hello.com") @app.route('/', host="hello.com")
async def handler(request): async def handler(request):

View File

@ -1,6 +1,5 @@
import pytest as pytest import pytest as pytest
from sanic import Sanic
from sanic.exceptions import InvalidUsage from sanic.exceptions import InvalidUsage
from sanic.response import text, HTTPResponse from sanic.response import text, HTTPResponse
from sanic.views import HTTPMethodView, CompositionView from sanic.views import HTTPMethodView, CompositionView
@ -10,8 +9,7 @@ from sanic.constants import HTTP_METHODS
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_methods(method): def test_methods(app, method):
app = Sanic('test_methods')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -44,8 +42,7 @@ def test_methods(method):
assert response.headers['method'] == method assert response.headers['method'] == method
def test_unexisting_methods(): def test_unexisting_methods(app):
app = Sanic('test_unexisting_methods')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -59,8 +56,7 @@ def test_unexisting_methods():
assert response.text == 'Error: Method POST not allowed for URL /' assert response.text == 'Error: Method POST not allowed for URL /'
def test_argument_methods(): def test_argument_methods(app):
app = Sanic('test_argument_methods')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -74,8 +70,7 @@ def test_argument_methods():
assert response.text == 'I am get method with test123' assert response.text == 'I am get method with test123'
def test_with_bp(): def test_with_bp(app):
app = Sanic('test_with_bp')
bp = Blueprint('test_text') bp = Blueprint('test_text')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -93,8 +88,7 @@ def test_with_bp():
assert response.text == 'I am get method' assert response.text == 'I am get method'
def test_with_bp_with_url_prefix(): def test_with_bp_with_url_prefix(app):
app = Sanic('test_with_bp_with_url_prefix')
bp = Blueprint('test_text', url_prefix='/test1') bp = Blueprint('test_text', url_prefix='/test1')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -110,8 +104,7 @@ def test_with_bp_with_url_prefix():
assert response.text == 'I am get method' assert response.text == 'I am get method'
def test_with_middleware(): def test_with_middleware(app):
app = Sanic('test_with_middleware')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
@ -132,9 +125,7 @@ def test_with_middleware():
assert type(results[0]) is Request assert type(results[0]) is Request
def test_with_middleware_response(): def test_with_middleware_response(app):
app = Sanic('test_with_middleware_response')
results = [] results = []
@app.middleware('request') @app.middleware('request')
@ -161,8 +152,7 @@ def test_with_middleware_response():
assert isinstance(results[2], HTTPResponse) assert isinstance(results[2], HTTPResponse)
def test_with_custom_class_methods(): def test_with_custom_class_methods(app):
app = Sanic('test_with_custom_class_methods')
class DummyView(HTTPMethodView): class DummyView(HTTPMethodView):
global_var = 0 global_var = 0
@ -179,9 +169,7 @@ def test_with_custom_class_methods():
assert response.text == 'I am get method and global var is 10' assert response.text == 'I am get method and global var is 10'
def test_with_decorator(): def test_with_decorator(app):
app = Sanic('test_with_decorator')
results = [] results = []
def stupid_decorator(view): def stupid_decorator(view):
@ -227,9 +215,7 @@ def test_composition_view_rejects_duplicate_methods():
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_composition_view_runs_methods_as_expected(method): def test_composition_view_runs_methods_as_expected(app, method):
app = Sanic('test_composition_view')
view = CompositionView() view = CompositionView()
def first(request): def first(request):
@ -251,9 +237,7 @@ def test_composition_view_runs_methods_as_expected(method):
@pytest.mark.parametrize('method', HTTP_METHODS) @pytest.mark.parametrize('method', HTTP_METHODS)
def test_composition_view_rejects_invalid_methods(method): def test_composition_view_rejects_invalid_methods(app, method):
app = Sanic('test_composition_view')
view = CompositionView() view = CompositionView()
view.add(['GET', 'POST', 'PUT'], lambda x: text('first method')) view.add(['GET', 'POST', 'PUT'], lambda x: text('first method'))