merge master into local branch

This commit is contained in:
MichaelYusko 2017-08-03 12:11:47 +03:00
commit 7216bf7835
11 changed files with 70 additions and 45 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ settings.py
docs/_build/ docs/_build/
docs/_api/ docs/_api/
build/* build/*
.DS_Store

View File

@ -1,4 +1,5 @@
sudo: false sudo: false
dist: precise
language: python language: python
cache: cache:
directories: directories:

View File

@ -55,6 +55,16 @@ Documentation
:target: https://pypi.python.org/pypi/sanic/ :target: https://pypi.python.org/pypi/sanic/
.. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg .. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg
:target: https://pypi.python.org/pypi/sanic/ :target: https://pypi.python.org/pypi/sanic/
Examples
--------
`Non-Core examples <https://github.com/channelcat/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.
`Projects <https://github.com/channelcat/sanic/wiki/Projects/>`_. Sanic in production use.
TODO TODO
---- ----

View File

@ -59,7 +59,7 @@ the available arguments to aiohttp can be found
[in the documentation for ClientSession](https://aiohttp.readthedocs.io/en/stable/client_reference.html#client-session). [in the documentation for ClientSession](https://aiohttp.readthedocs.io/en/stable/client_reference.html#client-session).
# pytest-sanic ## pytest-sanic
[pytest-sanic](https://github.com/yunstanford/pytest-sanic) is a pytest plugin, it helps you to test your code asynchronously. [pytest-sanic](https://github.com/yunstanford/pytest-sanic) is a pytest plugin, it helps you to test your code asynchronously.
Just write tests like, Just write tests like,

View File

@ -10,11 +10,11 @@ You can pass a version number to the routes directly.
from sanic import response from sanic import response
@app.route('/text', verion=1) @app.route('/text', version=1)
def handle_request(request): def handle_request(request):
return response.text('Hello world! Version 1') return response.text('Hello world! Version 1')
@app.route('/text', verion=2) @app.route('/text', version=2)
def handle_request(request): def handle_request(request):
return response.text('Hello world! Version 2') return response.text('Hello world! Version 2')

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.5.4' __version__ = '0.6.0'
__all__ = ['Sanic', 'Blueprint'] __all__ = ['Sanic', 'Blueprint']

View File

@ -33,9 +33,7 @@ class Sanic:
logging.config.dictConfig(log_config) logging.config.dictConfig(log_config)
# Only set up a default log handler if the # Only set up a default log handler if the
# end-user application didn't set anything up. # end-user application didn't set anything up.
if not (logging.root.handlers and if not logging.root.handlers and log.level == logging.NOTSET:
log.level == logging.NOTSET and
log_config):
formatter = logging.Formatter( formatter = logging.Formatter(
"%(asctime)s: %(levelname)s: %(message)s") "%(asctime)s: %(levelname)s: %(message)s")
handler = logging.StreamHandler() handler = logging.StreamHandler()

View File

@ -208,44 +208,39 @@ class Unauthorized(SanicException):
""" """
Unauthorized exception (401 HTTP status code). Unauthorized exception (401 HTTP status code).
:param message: Message describing the exception.
:param scheme: Name of the authentication scheme to be used. :param scheme: Name of the authentication scheme to be used.
:param challenge: A dict containing values to add to the WWW-Authenticate
header that is generated. This is especially useful when dealing with When present, kwargs is used to complete the WWW-Authentication header.
the Digest scheme. (optional)
Examples:: Examples::
# With a Basic auth-scheme, realm MUST be present: # With a Basic auth-scheme, realm MUST be present:
challenge = {"realm": "Restricted Area"} raise Unauthorized("Auth required.", "Basic", realm="Restricted Area")
raise Unauthorized("Auth required.", "Basic", challenge)
# With a Digest auth-scheme, things are a bit more complicated: # With a Digest auth-scheme, things are a bit more complicated:
challenge = { raise Unauthorized("Auth required.",
"realm": "Restricted Area", "Digest",
"qop": "auth, auth-int", realm="Restricted Area",
"algorithm": "MD5", qop="auth, auth-int",
"nonce": "abcdef", algorithm="MD5",
"opaque": "zyxwvu" nonce="abcdef",
} opaque="zyxwvu")
raise Unauthorized("Auth required.", "Digest", challenge)
# With a Bearer auth-scheme, realm is optional: # With a Bearer auth-scheme, realm is optional so you can write:
challenge = {"realm": "Restricted Area"} raise Unauthorized("Auth required.", "Bearer")
raise Unauthorized("Auth required.", "Bearer", challenge)
# or, if you want to specify the realm:
raise Unauthorized("Auth required.", "Bearer", realm="Restricted Area")
""" """
pass def __init__(self, message, scheme, **kwargs):
def __init__(self, message, scheme, challenge=None):
super().__init__(message) super().__init__(message)
chal = "" values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()]
challenge = ', '.join(values)
if challenge is not None:
values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()]
chal = ', '.join(values)
self.headers = { self.headers = {
"WWW-Authenticate": "{} {}".format(scheme, chal).rstrip() "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip()
} }

View File

@ -33,18 +33,17 @@ def exception_app():
@app.route('/401/basic') @app.route('/401/basic')
def handler_401_basic(request): def handler_401_basic(request):
raise Unauthorized("Unauthorized", "Basic", {"realm": "Sanic"}) raise Unauthorized("Unauthorized", "Basic", realm="Sanic")
@app.route('/401/digest') @app.route('/401/digest')
def handler_401_digest(request): def handler_401_digest(request):
challenge = { raise Unauthorized("Unauthorized",
"realm": "Sanic", "Digest",
"qop": "auth, auth-int", realm="Sanic",
"algorithm": "MD5", qop="auth, auth-int",
"nonce": "abcdef", algorithm="MD5",
"opaque": "zyxwvu", nonce="abcdef",
} opaque="zyxwvu")
raise Unauthorized("Unauthorized", "Digest", challenge)
@app.route('/401/bearer') @app.route('/401/bearer')
def handler_401_bearer(request): def handler_401_bearer(request):
@ -122,7 +121,7 @@ def test_forbidden_exception(exception_app):
request, response = exception_app.test_client.get('/403') request, response = exception_app.test_client.get('/403')
assert response.status == 403 assert response.status == 403
def test_unauthorized_exception(exception_app): def test_unauthorized_exception(exception_app):
"""Test the built-in Unauthorized exception""" """Test the built-in Unauthorized exception"""
request, response = exception_app.test_client.get('/401/basic') request, response = exception_app.test_client.get('/401/basic')
@ -132,7 +131,7 @@ def test_unauthorized_exception(exception_app):
request, response = exception_app.test_client.get('/401/digest') request, response = exception_app.test_client.get('/401/digest')
assert response.status == 401 assert response.status == 401
auth_header = response.headers.get('WWW-Authenticate') auth_header = response.headers.get('WWW-Authenticate')
assert auth_header is not None assert auth_header is not None
assert auth_header.startswith('Digest') assert auth_header.startswith('Digest')

View File

@ -24,7 +24,7 @@ def handler_3(request):
@exception_handler_app.route('/4') @exception_handler_app.route('/4')
def handler_4(request): def handler_4(request):
foo = bar foo = bar # noqa -- F821 undefined name 'bar' is done to throw exception
return text(foo) return text(foo)

View File

@ -1,5 +1,7 @@
import asyncio
import uuid import uuid
from importlib import reload
from sanic.config import LOGGING
from sanic.response import text from sanic.response import text
from sanic import Sanic from sanic import Sanic
from io import StringIO from io import StringIO
@ -10,6 +12,11 @@ function: %(funcName)s(); \
message: %(message)s''' message: %(message)s'''
def reset_logging():
logging.shutdown()
reload(logging)
def test_log(): def test_log():
log_stream = StringIO() log_stream = StringIO()
for handler in logging.root.handlers[:]: for handler in logging.root.handlers[:]:
@ -32,5 +39,19 @@ def test_log():
log_text = log_stream.getvalue() log_text = log_stream.getvalue()
assert rand_string in log_text assert rand_string in log_text
def test_default_log_fmt():
reset_logging()
Sanic()
for fmt in [h.formatter for h in logging.getLogger('sanic').handlers]:
assert fmt._fmt == LOGGING['formatters']['simple']['format']
reset_logging()
Sanic(log_config=None)
for fmt in [h.formatter for h in logging.getLogger('sanic').handlers]:
assert fmt._fmt == "%(asctime)s: %(levelname)s: %(message)s"
if __name__ == "__main__": if __name__ == "__main__":
test_log() test_log()