diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 00000000..368270c5
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,32 @@
+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
+
+notifications:
+ - provider: Email
+ on_build_success: false
+ on_build_status_changed: false
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/docs/sanic/extensions.md b/docs/sanic/extensions.md
index c0728627..63153b94 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.
@@ -31,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.
diff --git a/docs/sanic/logging.md b/docs/sanic/logging.md
index 49805d0e..e1231eb9 100644
--- a/docs/sanic/logging.md
+++ b/docs/sanic/logging.md
@@ -9,17 +9,32 @@ 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] [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 +64,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/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/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:
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
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
diff --git a/tests/test_logging.py b/tests/test_logging.py
index 95c55de0..d2b43c1a 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,7 +47,7 @@ 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('sanic.root').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'
@@ -104,6 +105,25 @@ def test_log_connection_lost(app, debug, monkeypatch):
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')
+
+
def test_logging_modified_root_logger_config():
reset_logging()
@@ -113,4 +133,3 @@ def test_logging_modified_root_logger_config():
app = Sanic("test_logging", log_config=modified_config)
assert logging.getLogger('sanic.root').getEffectiveLevel() == logging.DEBUG
-
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'