From 3e3bce422ec20f9f00a8aaaa24c9447960b43b4e Mon Sep 17 00:00:00 2001 From: jacob Date: Tue, 6 Nov 2018 21:27:01 +0800 Subject: [PATCH 01/10] Add test for sanic.root logger and update the docs of logging --- docs/sanic/logging.md | 40 ++++++++++++++++++++++++++++++++++++++-- sanic/log.py | 2 +- tests/test_logging.py | 31 ++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/docs/sanic/logging.md b/docs/sanic/logging.md index 49805d0e..cc32e5bd 100644 --- a/docs/sanic/logging.md +++ b/docs/sanic/logging.md @@ -9,17 +9,53 @@ A simple example using default settings would be like this: ```python from sanic import Sanic +from sanic.log import logger +from sanic.response import text app = Sanic('test') @app.route('/') async def test(request): - return response.text('Hello World!') + logger.info('Here is your log') + return text('Hello World!') if __name__ == "__main__": app.run(debug=True, access_log=True) ``` +After the server is running, you can see some messages looks like: +``` +[2018-11-06 21:16:53 +0800] [24622] [DEBUG] + ▄▄▄▄▄ + ▀▀▀██████▄▄▄ _______________ + ▄▄▄▄▄ █████████▄ / \ + ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! | + ▀▀█████▄▄ ▀██████▄██ | _________________/ + ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/ + ▀▀▀▄ ▀▀███ ▀ ▄▄ + ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ + ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ +▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀ +▌ ▐▀████▐███▒▒▒▒▒▐██▌ +▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀ + ▀▀█████████▀ + ▄▄██▀██████▀█ + ▄██▀ ▀▀▀ █ + ▄█ ▐▌ + ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄ +▌ ▐ ▀▀▄▄▄▀ + ▀▀▄▄▀ + +[2018-11-06 21:16:53 +0800] [24622] [INFO] Goin' Fast @ http://127.0.0.1:8000 +[2018-11-06 21:16:53 +0800] [24667] [INFO] Starting worker [24667] +``` + +You can send a request to server and it will print the log messages: +``` +[2018-11-06 21:18:53 +0800] [25685] [INFO] Here is your log +[2018-11-06 21:18:53 +0800] - (sanic.access)[INFO][127.0.0.1:57038]: GET http://localhost:8000/ 200 12 +``` + To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize `Sanic` app: @@ -49,7 +85,7 @@ By default, log_config parameter is set to use sanic.log.LOGGING_CONFIG_DEFAULTS There are three `loggers` used in sanic, and **must be defined if you want to create your own logging configuration**: -- root:
+- sanic.root:
Used to log internal messages. - sanic.error:
diff --git a/sanic/log.py b/sanic/log.py index cb8ca524..08fc835d 100644 --- a/sanic/log.py +++ b/sanic/log.py @@ -6,7 +6,7 @@ LOGGING_CONFIG_DEFAULTS = dict( version=1, disable_existing_loggers=False, loggers={ - "root": {"level": "INFO", "handlers": ["console"]}, + "sanic.root": {"level": "INFO", "handlers": ["console"]}, "sanic.error": { "level": "INFO", "handlers": ["error_console"], diff --git a/tests/test_logging.py b/tests/test_logging.py index 3af3f122..7f4bf8c2 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -11,6 +11,7 @@ import sanic from sanic.response import text from sanic.log import LOGGING_CONFIG_DEFAULTS from sanic import Sanic +from sanic.log import logger logging_format = '''module: %(module)s; \ @@ -46,10 +47,10 @@ def test_log(app): def test_logging_defaults(): - reset_logging() + # reset_logging() app = Sanic("test_logging") - for fmt in [h.formatter for h in logging.getLogger('root').handlers]: + for fmt in [h.formatter for h in logging.getLogger('sanic.root').handlers]: assert fmt._fmt == LOGGING_CONFIG_DEFAULTS['formatters']['generic']['format'] for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]: @@ -60,7 +61,7 @@ def test_logging_defaults(): def test_logging_pass_customer_logconfig(): - reset_logging() + # reset_logging() modified_config = LOGGING_CONFIG_DEFAULTS modified_config['formatters']['generic']['format'] = '%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s' @@ -68,7 +69,7 @@ def test_logging_pass_customer_logconfig(): app = Sanic("test_logging", log_config=modified_config) - for fmt in [h.formatter for h in logging.getLogger('root').handlers]: + for fmt in [h.formatter for h in logging.getLogger('sanic.root').handlers]: assert fmt._fmt == modified_config['formatters']['generic']['format'] for fmt in [h.formatter for h in logging.getLogger('sanic.error').handlers]: @@ -82,7 +83,7 @@ def test_logging_pass_customer_logconfig(): def test_log_connection_lost(app, debug, monkeypatch): """ Should not log Connection lost exception on non debug """ stream = StringIO() - root = logging.getLogger('root') + root = logging.getLogger('sanic.root') root.addHandler(logging.StreamHandler(stream)) monkeypatch.setattr(sanic.server, 'logger', root) @@ -102,3 +103,23 @@ def test_log_connection_lost(app, debug, monkeypatch): assert 'Connection lost before response written @' in log else: assert 'Connection lost before response written @' not in log + + +def test_logger(caplog): + rand_string = str(uuid.uuid4()) + + app = Sanic() + + @app.get('/') + def log_info(request): + logger.info(rand_string) + return text('hello') + + with caplog.at_level(logging.INFO): + request, response = app.test_client.get('/') + + assert caplog.record_tuples[0] == ('sanic.root', logging.INFO, 'Goin\' Fast @ http://127.0.0.1:42101') + assert caplog.record_tuples[1] == ('sanic.root', logging.INFO, 'http://127.0.0.1:42101/') + assert caplog.record_tuples[2] == ('sanic.root', logging.INFO, rand_string) + assert caplog.record_tuples[-1] == ('sanic.root', logging.INFO, 'Server Stopped') + From 2629fab649785c441eeed9401464153047ce5be4 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sat, 10 Nov 2018 05:50:22 -0800 Subject: [PATCH 02/10] add .appveyor.yml for windows ci support --- .appveyor.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..75440426 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,37 @@ +version: "{branch}.{build}" + +environment: + matrix: + - TOXENV: py35-no-ext + PYTHON: "C:\\Python35-x64" + PYTHON_VERSION: "3.5.x" + PYTHON_ARCH: "64" + + - TOXENV: py36-no-ext + PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "64" + + - TOXENV: py37-no-ext + PYTHON: "C:\\Python37-x64" + PYTHON_VERSION: "3.7.x" + PYTHON_ARCH: "64" + +init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + +install: + - pip install tox + +build: off + +test_script: tox + +cache: + # Not including the .tox directory since it takes longer to download/extract + # the cache archive than for tox to clean install from the pip cache. + - "%LOCALAPPDATA%\pip\Cache -> tox.ini" + +notifications: + - provider: Email + on_build_success: false + on_build_status_changed: false From 8b5d137d8f04413e5d19c27e3db65df709dbef8b Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sat, 10 Nov 2018 06:11:01 -0800 Subject: [PATCH 03/10] fix .appveyor.yml --- .appveyor.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 75440426..368270c5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -26,11 +26,6 @@ build: off test_script: tox -cache: - # Not including the .tox directory since it takes longer to download/extract - # the cache archive than for tox to clean install from the pip cache. - - "%LOCALAPPDATA%\pip\Cache -> tox.ini" - notifications: - provider: Email on_build_success: false From f89ba1d39f35b5f3d7b7be6466de0c6ab92935f4 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Sun, 11 Nov 2018 17:57:57 +0100 Subject: [PATCH 04/10] Add tests for `is_entity_header` and `is_hop_by_hop_header` helper functions (#1410) --- tests/test_helpers.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index cf859b96..eb08624e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,4 +1,4 @@ -from sanic.helpers import has_message_body +from sanic import helpers def test_has_message_body(): @@ -11,4 +11,26 @@ def test_has_message_body(): (400, True), ) for status_code, expected in tests: - assert has_message_body(status_code) is expected + assert helpers.has_message_body(status_code) is expected + + +def test_is_entity_header(): + tests = ( + ("allow", True), + ("extension-header", True), + ("", False), + ("test", False), + ) + for header, expected in tests: + assert helpers.is_entity_header(header) is expected + + +def test_is_hop_by_hop_header(): + tests = ( + ("connection", True), + ("upgrade", True), + ("", False), + ("test", False), + ) + for header, expected in tests: + assert helpers.is_hop_by_hop_header(header) is expected From dd5bac61cbc025a423a20e65b26c217bd8d9a735 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 12 Nov 2018 16:09:12 +0800 Subject: [PATCH 05/10] Update document for logging --- docs/sanic/logging.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/docs/sanic/logging.md b/docs/sanic/logging.md index cc32e5bd..e1231eb9 100644 --- a/docs/sanic/logging.md +++ b/docs/sanic/logging.md @@ -25,27 +25,6 @@ if __name__ == "__main__": After the server is running, you can see some messages looks like: ``` -[2018-11-06 21:16:53 +0800] [24622] [DEBUG] - ▄▄▄▄▄ - ▀▀▀██████▄▄▄ _______________ - ▄▄▄▄▄ █████████▄ / \ - ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! | - ▀▀█████▄▄ ▀██████▄██ | _________________/ - ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/ - ▀▀▀▄ ▀▀███ ▀ ▄▄ - ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ - ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ -▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀ -▌ ▐▀████▐███▒▒▒▒▒▐██▌ -▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀ - ▀▀█████████▀ - ▄▄██▀██████▀█ - ▄██▀ ▀▀▀ █ - ▄█ ▐▌ - ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄ -▌ ▐ ▀▀▄▄▄▀ - ▀▀▄▄▀ - [2018-11-06 21:16:53 +0800] [24622] [INFO] Goin' Fast @ http://127.0.0.1:8000 [2018-11-06 21:16:53 +0800] [24667] [INFO] Starting worker [24667] ``` From e58ea8c7b4fd541741223e3203c8fcede7b53c0e Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sat, 10 Nov 2018 06:26:55 -0800 Subject: [PATCH 06/10] fix unit test for windows ci fix unit tests for windows ci add appveyor build status badge add readthedoc build status badge --- README.rst | 4 +++- tests/test_multiprocessing.py | 4 ++++ tests/test_request_timeout.py | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 31f036b0..311be757 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Sanic ===== -|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |Codecov| |PyPI| |PyPI version| |Code style black| +|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |AppVeyor Build Status| |Documentation| |Codecov| |PyPI| |PyPI version| |Code style black| Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by `this article `_. @@ -51,6 +51,8 @@ Documentation :target: https://codecov.io/gh/huge-success/sanic .. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master :target: https://travis-ci.org/huge-success/sanic +.. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true + :target: https://ci.appveyor.com/project/huge-success/sanic .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest :target: http://sanic.readthedocs.io/en/latest/?badge=latest .. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py index ac45ad61..45d6aaf2 100644 --- a/tests/test_multiprocessing.py +++ b/tests/test_multiprocessing.py @@ -31,6 +31,10 @@ def test_multiprocessing(app): assert len(process_list) == num_workers +@pytest.mark.skipif( + not hasattr(signal, 'SIGALRM'), + reason='SIGALRM is not implemented for this platform', +) def test_multiprocessing_with_blueprint(app): from sanic import Blueprint # Selects a number at random so we can spot check diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 163547cb..e674e451 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -173,7 +173,7 @@ class DelayableSanicTestClient(SanicTestClient): return response -Config.REQUEST_TIMEOUT = 2 +Config.REQUEST_TIMEOUT = 0.6 request_timeout_default_app = Sanic('test_request_timeout_default') request_no_timeout_app = Sanic('test_request_no_timeout') @@ -189,14 +189,14 @@ async def handler2(request): def test_default_server_error_request_timeout(): - client = DelayableSanicTestClient(request_timeout_default_app, None, 3) + client = DelayableSanicTestClient(request_timeout_default_app, None, 2) request, response = client.get('/1') assert response.status == 408 assert response.text == 'Error: Request Timeout' def test_default_server_error_request_dont_timeout(): - client = DelayableSanicTestClient(request_no_timeout_app, None, 1) + client = DelayableSanicTestClient(request_no_timeout_app, None, 0.2) request, response = client.get('/1') assert response.status == 200 assert response.text == 'OK' From c8b0e7f2a7f0d73424a68716b8dd6d11fa170fd5 Mon Sep 17 00:00:00 2001 From: Richard K Date: Mon, 12 Nov 2018 13:11:41 -0200 Subject: [PATCH 07/10] Created methods to append and finish body content on Request (#1379) * created methods to append and finish body content on request.py so the underlying body instance can have certain flexibility; modified server.py to reflect these changes * - made some adjustments (including the Request.body_init method) as requested by @ahopkins; - created a new test with a custom Request class implementation of the flexibility provided by the new methods; --- sanic/request.py | 11 +++++++- sanic/server.py | 4 +-- tests/test_custom_request.py | 53 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/test_custom_request.py diff --git a/sanic/request.py b/sanic/request.py index 013a27ea..e775596a 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -85,7 +85,7 @@ class Request(dict): self.transport = transport # Init but do not inhale - self.body = [] + self.body_init() self.parsed_json = None self.parsed_form = None self.parsed_files = None @@ -106,6 +106,15 @@ class Request(dict): return True return False + def body_init(self): + self.body = [] + + def body_push(self, data): + self.body.append(data) + + def body_finish(self): + self.body = b"".join(self.body) + @property def json(self): if self.parsed_json is None: diff --git a/sanic/server.py b/sanic/server.py index 0d0bf29c..dc197941 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -300,7 +300,7 @@ class HttpProtocol(asyncio.Protocol): self.request.stream.put(body) ) return - self.request.body.append(body) + self.request.body_push(body) def on_message_complete(self): # Entire request (headers and whole body) is received. @@ -313,7 +313,7 @@ class HttpProtocol(asyncio.Protocol): self.request.stream.put(None) ) return - self.request.body = b"".join(self.request.body) + self.request.body_finish() self.execute_request_handler() def execute_request_handler(self): diff --git a/tests/test_custom_request.py b/tests/test_custom_request.py new file mode 100644 index 00000000..d0ae48e7 --- /dev/null +++ b/tests/test_custom_request.py @@ -0,0 +1,53 @@ +from io import BytesIO + +from sanic import Sanic +from sanic.request import Request +from sanic.response import json_dumps, text + + +class CustomRequest(Request): + __slots__ = ("body_buffer",) + + def body_init(self): + self.body_buffer = BytesIO() + + def body_push(self, data): + self.body_buffer.write(data) + + def body_finish(self): + self.body = self.body_buffer.getvalue() + self.body_buffer.close() + + +def test_custom_request(): + app = Sanic(request_class=CustomRequest) + + @app.route("/post", methods=["POST"]) + async def post_handler(request): + return text("OK") + + @app.route("/get") + async def get_handler(request): + return text("OK") + + payload = {"test": "OK"} + headers = {"content-type": "application/json"} + + request, response = app.test_client.post( + "/post", data=json_dumps(payload), headers=headers + ) + + assert isinstance(request.body_buffer, BytesIO) + assert request.body_buffer.closed + assert request.body == b'{"test":"OK"}' + assert request.json.get("test") == "OK" + assert response.text == "OK" + assert response.status == 200 + + request, response = app.test_client.get("/get") + + assert isinstance(request.body_buffer, BytesIO) + assert request.body_buffer.closed + assert request.body == b"" + assert response.text == "OK" + assert response.status == 200 From 90b9d732448d44924cf3f1b056139b140d422747 Mon Sep 17 00:00:00 2001 From: Lewis Date: Tue, 13 Nov 2018 14:39:29 +0900 Subject: [PATCH 08/10] ADD: Sanic-JWT-Extended extension --- docs/sanic/extensions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sanic/extensions.md b/docs/sanic/extensions.md index c0728627..bcea2299 100644 --- a/docs/sanic/extensions.md +++ b/docs/sanic/extensions.md @@ -8,6 +8,7 @@ A list of Sanic extensions created by the community. - [Compress](https://github.com/subyraman/sanic_compress): Allows you to easily gzip Sanic responses. A port of Flask-Compress. - [Jinja2](https://github.com/lixxu/sanic-jinja2): Support for Jinja2 template. - [Sanic JWT](https://github.com/ahopkins/sanic-jwt): Authentication, JWT, and permission scoping for Sanic. +- [Sanic-JWT-Extended](https://github.com/devArtoria/Sanic-JWT-Extended): Provides extended JWT support for Sanic - [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI. - [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support. - [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper. From efb9a420451073e0caf842dcdaf793a2d1d0adb0 Mon Sep 17 00:00:00 2001 From: Nir Galon Date: Wed, 14 Nov 2018 15:16:14 +0200 Subject: [PATCH 09/10] Change deprecated verify_ssl to ssl (#1155) --- sanic/testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/testing.py b/sanic/testing.py index eda52d61..19f87095 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -25,7 +25,7 @@ class SanicTestClient: ) logger.info(url) - conn = aiohttp.TCPConnector(verify_ssl=False) + conn = aiohttp.TCPConnector(ssl=False) async with aiohttp.ClientSession( cookies=cookies, connector=conn ) as session: From 096c44b910443e74c821dbf21adf2f86ccda8611 Mon Sep 17 00:00:00 2001 From: Tim&Anna <191996155@qq.com> Date: Wed, 14 Nov 2018 21:16:43 +0800 Subject: [PATCH 10/10] Update extensions.md (#1263) * Update extensions.md add an extension: sanic-script * Update extensions.md --- docs/sanic/extensions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sanic/extensions.md b/docs/sanic/extensions.md index bcea2299..63153b94 100644 --- a/docs/sanic/extensions.md +++ b/docs/sanic/extensions.md @@ -32,4 +32,5 @@ 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-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-script](https://github.com/tim2anna/sanic-script): An extension for Sanic that adds support for writing commands to your application. - [sanic-sse](https://github.com/inn0kenty/sanic_sse): [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) implementation for Sanic.