diff --git a/.gitignore b/.gitignore index 73e923d3..4a834a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ settings.py docs/_build/ docs/_api/ build/* +.DS_Store diff --git a/.travis.yml b/.travis.yml index d8e17093..afac549a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ sudo: false +dist: precise language: python cache: directories: diff --git a/README.rst b/README.rst index 410bd0b8..a9bfa982 100644 --- a/README.rst +++ b/README.rst @@ -55,6 +55,16 @@ Documentation :target: https://pypi.python.org/pypi/sanic/ .. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg :target: https://pypi.python.org/pypi/sanic/ + + +Examples +-------- +`Non-Core examples `_. Examples of plugins and Sanic that are outside the scope of Sanic core. + +`Extensions `_. Sanic extensions created by the community. + +`Projects `_. Sanic in production use. + TODO ---- diff --git a/docs/sanic/testing.md b/docs/sanic/testing.md index b8427a00..0aca9184 100644 --- a/docs/sanic/testing.md +++ b/docs/sanic/testing.md @@ -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). -# pytest-sanic +## pytest-sanic [pytest-sanic](https://github.com/yunstanford/pytest-sanic) is a pytest plugin, it helps you to test your code asynchronously. Just write tests like, diff --git a/docs/sanic/versioning.md b/docs/sanic/versioning.md index 85cbd278..ab6dab22 100644 --- a/docs/sanic/versioning.md +++ b/docs/sanic/versioning.md @@ -10,11 +10,11 @@ You can pass a version number to the routes directly. from sanic import response -@app.route('/text', verion=1) +@app.route('/text', version=1) def handle_request(request): return response.text('Hello world! Version 1') -@app.route('/text', verion=2) +@app.route('/text', version=2) def handle_request(request): return response.text('Hello world! Version 2') diff --git a/sanic/__init__.py b/sanic/__init__.py index 4cc0710f..8f35a283 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -1,6 +1,6 @@ from sanic.app import Sanic from sanic.blueprints import Blueprint -__version__ = '0.5.4' +__version__ = '0.6.0' __all__ = ['Sanic', 'Blueprint'] diff --git a/sanic/app.py b/sanic/app.py index f1e8be7e..f0ccad86 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -33,9 +33,7 @@ class Sanic: logging.config.dictConfig(log_config) # Only set up a default log handler if the # end-user application didn't set anything up. - if not (logging.root.handlers and - log.level == logging.NOTSET and - log_config): + if not logging.root.handlers and log.level == logging.NOTSET: formatter = logging.Formatter( "%(asctime)s: %(levelname)s: %(message)s") handler = logging.StreamHandler() diff --git a/sanic/exceptions.py b/sanic/exceptions.py index 21ab2a94..0edb0562 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -208,44 +208,39 @@ class Unauthorized(SanicException): """ Unauthorized exception (401 HTTP status code). + :param message: Message describing the exception. :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 - the Digest scheme. (optional) + + When present, kwargs is used to complete the WWW-Authentication header. Examples:: # With a Basic auth-scheme, realm MUST be present: - challenge = {"realm": "Restricted Area"} - raise Unauthorized("Auth required.", "Basic", challenge) + raise Unauthorized("Auth required.", "Basic", realm="Restricted Area") # With a Digest auth-scheme, things are a bit more complicated: - challenge = { - "realm": "Restricted Area", - "qop": "auth, auth-int", - "algorithm": "MD5", - "nonce": "abcdef", - "opaque": "zyxwvu" - } - raise Unauthorized("Auth required.", "Digest", challenge) + raise Unauthorized("Auth required.", + "Digest", + realm="Restricted Area", + qop="auth, auth-int", + algorithm="MD5", + nonce="abcdef", + opaque="zyxwvu") - # With a Bearer auth-scheme, realm is optional: - challenge = {"realm": "Restricted Area"} - raise Unauthorized("Auth required.", "Bearer", challenge) + # With a Bearer auth-scheme, realm is optional so you can write: + raise Unauthorized("Auth required.", "Bearer") + + # or, if you want to specify the realm: + raise Unauthorized("Auth required.", "Bearer", realm="Restricted Area") """ - pass - - def __init__(self, message, scheme, challenge=None): + def __init__(self, message, scheme, **kwargs): super().__init__(message) - chal = "" - - if challenge is not None: - values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()] - chal = ', '.join(values) + values = ["{!s}={!r}".format(k, v) for k, v in kwargs.items()] + challenge = ', '.join(values) self.headers = { - "WWW-Authenticate": "{} {}".format(scheme, chal).rstrip() + "WWW-Authenticate": "{} {}".format(scheme, challenge).rstrip() } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 620e7891..1521c9ed 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -33,18 +33,17 @@ def exception_app(): @app.route('/401/basic') def handler_401_basic(request): - raise Unauthorized("Unauthorized", "Basic", {"realm": "Sanic"}) + raise Unauthorized("Unauthorized", "Basic", realm="Sanic") @app.route('/401/digest') def handler_401_digest(request): - challenge = { - "realm": "Sanic", - "qop": "auth, auth-int", - "algorithm": "MD5", - "nonce": "abcdef", - "opaque": "zyxwvu", - } - raise Unauthorized("Unauthorized", "Digest", challenge) + raise Unauthorized("Unauthorized", + "Digest", + realm="Sanic", + qop="auth, auth-int", + algorithm="MD5", + nonce="abcdef", + opaque="zyxwvu") @app.route('/401/bearer') def handler_401_bearer(request): @@ -122,7 +121,7 @@ def test_forbidden_exception(exception_app): request, response = exception_app.test_client.get('/403') assert response.status == 403 - + def test_unauthorized_exception(exception_app): """Test the built-in Unauthorized exception""" 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') assert response.status == 401 - + auth_header = response.headers.get('WWW-Authenticate') assert auth_header is not None assert auth_header.startswith('Digest') diff --git a/tests/test_exceptions_handler.py b/tests/test_exceptions_handler.py index 006c2cc4..6a959382 100644 --- a/tests/test_exceptions_handler.py +++ b/tests/test_exceptions_handler.py @@ -24,7 +24,7 @@ def handler_3(request): @exception_handler_app.route('/4') def handler_4(request): - foo = bar + foo = bar # noqa -- F821 undefined name 'bar' is done to throw exception return text(foo) diff --git a/tests/test_logging.py b/tests/test_logging.py index fc26ca93..d6911d86 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,5 +1,7 @@ -import asyncio import uuid +from importlib import reload + +from sanic.config import LOGGING from sanic.response import text from sanic import Sanic from io import StringIO @@ -10,6 +12,11 @@ function: %(funcName)s(); \ message: %(message)s''' +def reset_logging(): + logging.shutdown() + reload(logging) + + def test_log(): log_stream = StringIO() for handler in logging.root.handlers[:]: @@ -32,5 +39,19 @@ def test_log(): log_text = log_stream.getvalue() 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__": test_log()