Merge pull request #33 from huge-success/master

Merge upstream master branch
This commit is contained in:
7 2018-11-16 13:02:16 +08:00 committed by GitHub
commit e955e833c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 173 additions and 15 deletions

32
.appveyor.yml Normal file
View File

@ -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

View File

@ -1,7 +1,7 @@
Sanic 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 <https://magic.io/blog/uvloop-blazing-fast-python-networking/>`_. 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 <https://magic.io/blog/uvloop-blazing-fast-python-networking/>`_.
@ -51,6 +51,8 @@ Documentation
:target: https://codecov.io/gh/huge-success/sanic :target: https://codecov.io/gh/huge-success/sanic
.. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master .. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master
:target: https://travis-ci.org/huge-success/sanic :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 .. |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

View File

@ -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. - [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. - [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](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. - [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
- [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support. - [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support.
- [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper. - [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-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-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. - [sanic-sse](https://github.com/inn0kenty/sanic_sse): [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) implementation for Sanic.

View File

@ -9,17 +9,32 @@ A simple example using default settings would be like this:
```python ```python
from sanic import Sanic from sanic import Sanic
from sanic.log import logger
from sanic.response import text
app = Sanic('test') app = Sanic('test')
@app.route('/') @app.route('/')
async def test(request): async def test(request):
return response.text('Hello World!') logger.info('Here is your log')
return text('Hello World!')
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True, access_log=True) 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 To use your own logging config, simply use `logging.config.dictConfig`, or
pass `log_config` when you initialize `Sanic` app: 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**: There are three `loggers` used in sanic, and **must be defined if you want to create your own logging configuration**:
- root:<br> - sanic.root:<br>
Used to log internal messages. Used to log internal messages.
- sanic.error:<br> - sanic.error:<br>

View File

@ -85,7 +85,7 @@ class Request(dict):
self.transport = transport self.transport = transport
# Init but do not inhale # Init but do not inhale
self.body = [] self.body_init()
self.parsed_json = None self.parsed_json = None
self.parsed_form = None self.parsed_form = None
self.parsed_files = None self.parsed_files = None
@ -106,6 +106,15 @@ class Request(dict):
return True return True
return False 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 @property
def json(self): def json(self):
if self.parsed_json is None: if self.parsed_json is None:

View File

@ -300,7 +300,7 @@ class HttpProtocol(asyncio.Protocol):
self.request.stream.put(body) self.request.stream.put(body)
) )
return return
self.request.body.append(body) self.request.body_push(body)
def on_message_complete(self): def on_message_complete(self):
# Entire request (headers and whole body) is received. # Entire request (headers and whole body) is received.
@ -313,7 +313,7 @@ class HttpProtocol(asyncio.Protocol):
self.request.stream.put(None) self.request.stream.put(None)
) )
return return
self.request.body = b"".join(self.request.body) self.request.body_finish()
self.execute_request_handler() self.execute_request_handler()
def execute_request_handler(self): def execute_request_handler(self):

View File

@ -25,7 +25,7 @@ class SanicTestClient:
) )
logger.info(url) logger.info(url)
conn = aiohttp.TCPConnector(verify_ssl=False) conn = aiohttp.TCPConnector(ssl=False)
async with aiohttp.ClientSession( async with aiohttp.ClientSession(
cookies=cookies, connector=conn cookies=cookies, connector=conn
) as session: ) as session:

View File

@ -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

View File

@ -1,4 +1,4 @@
from sanic.helpers import has_message_body from sanic import helpers
def test_has_message_body(): def test_has_message_body():
@ -11,4 +11,26 @@ def test_has_message_body():
(400, True), (400, True),
) )
for status_code, expected in tests: 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

View File

@ -11,6 +11,7 @@ import sanic
from sanic.response import text from sanic.response import text
from sanic.log import LOGGING_CONFIG_DEFAULTS from sanic.log import LOGGING_CONFIG_DEFAULTS
from sanic import Sanic from sanic import Sanic
from sanic.log import logger
logging_format = '''module: %(module)s; \ logging_format = '''module: %(module)s; \
@ -46,7 +47,7 @@ def test_log(app):
def test_logging_defaults(): def test_logging_defaults():
reset_logging() # reset_logging()
app = Sanic("test_logging") app = Sanic("test_logging")
for fmt in [h.formatter for h in logging.getLogger('sanic.root').handlers]: 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(): def test_logging_pass_customer_logconfig():
reset_logging() # reset_logging()
modified_config = LOGGING_CONFIG_DEFAULTS modified_config = LOGGING_CONFIG_DEFAULTS
modified_config['formatters']['generic']['format'] = '%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s' 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 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(): def test_logging_modified_root_logger_config():
reset_logging() reset_logging()
@ -113,4 +133,3 @@ def test_logging_modified_root_logger_config():
app = Sanic("test_logging", log_config=modified_config) app = Sanic("test_logging", log_config=modified_config)
assert logging.getLogger('sanic.root').getEffectiveLevel() == logging.DEBUG assert logging.getLogger('sanic.root').getEffectiveLevel() == logging.DEBUG

View File

@ -31,6 +31,10 @@ def test_multiprocessing(app):
assert len(process_list) == num_workers 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): def test_multiprocessing_with_blueprint(app):
from sanic import Blueprint from sanic import Blueprint
# Selects a number at random so we can spot check # Selects a number at random so we can spot check

View File

@ -173,7 +173,7 @@ class DelayableSanicTestClient(SanicTestClient):
return response return response
Config.REQUEST_TIMEOUT = 2 Config.REQUEST_TIMEOUT = 0.6
request_timeout_default_app = Sanic('test_request_timeout_default') request_timeout_default_app = Sanic('test_request_timeout_default')
request_no_timeout_app = Sanic('test_request_no_timeout') request_no_timeout_app = Sanic('test_request_no_timeout')
@ -189,14 +189,14 @@ async def handler2(request):
def test_default_server_error_request_timeout(): 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') request, response = client.get('/1')
assert response.status == 408 assert response.status == 408
assert response.text == 'Error: Request Timeout' assert response.text == 'Error: Request Timeout'
def test_default_server_error_request_dont_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') request, response = client.get('/1')
assert response.status == 200 assert response.status == 200
assert response.text == 'OK' assert response.text == 'OK'