resolve conflict in setup.py
This commit is contained in:
commit
b36dc22b45
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,3 +15,4 @@ docs/_build/
|
||||||
docs/_api/
|
docs/_api/
|
||||||
build/*
|
build/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
dist/*
|
|
@ -18,9 +18,22 @@ So assume you have already cloned the repo and are in the working directory with
|
||||||
a virtual environment already set up, then run:
|
a virtual environment already set up, then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python setup.py develop && pip install -r requirements-dev.txt
|
pip3 install -e . "[.dev]"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Dependency Changes
|
||||||
|
|
||||||
|
`Sanic` doesn't use `requirements*.txt` files to manage any kind of dependencies related to it in order to simplify the
|
||||||
|
effort required in managing the dependencies. Please make sure you have read and understood the following section of
|
||||||
|
the document that explains the way `sanic` manages dependencies inside the `setup.py` file.
|
||||||
|
|
||||||
|
| Dependency Type | Usage | Installation |
|
||||||
|
| ------------------------------------------| -------------------------------------------------------------------------- | --------------------------- |
|
||||||
|
| requirements | Bare minimum dependencies required for sanic to function | pip3 install -e . |
|
||||||
|
| tests_require / extras_require['test'] | Dependencies required to run the Unit Tests for `sanic` | pip3 install -e '[.test]' |
|
||||||
|
| extras_require['dev'] | Additional Development requirements to add contributing | pip3 install -e '[.dev]' |
|
||||||
|
| extras_require['docs'] | Dependencies required to enable building and enhancing sanic documentation | pip3 install -e '[.docs]' |
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
To run the tests for sanic it is recommended to use tox like so:
|
To run the tests for sanic it is recommended to use tox like so:
|
||||||
|
|
18
MANIFEST.in
18
MANIFEST.in
|
@ -1,7 +1,15 @@
|
||||||
include README.rst
|
# Non Code related contents
|
||||||
include MANIFEST.in
|
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include setup.py
|
include README.rst
|
||||||
|
include pyproject.toml
|
||||||
|
|
||||||
recursive-exclude * __pycache__
|
# Setup
|
||||||
recursive-exclude * *.py[co]
|
include setup.py
|
||||||
|
include Makefile
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
include .coveragerc
|
||||||
|
graft tests
|
||||||
|
|
||||||
|
global-exclude __pycache__
|
||||||
|
global-exclude *.py[co]
|
58
Makefile
58
Makefile
|
@ -1,4 +1,58 @@
|
||||||
test:
|
.PHONY: help test test-coverage install docker-test black fix-import beautify
|
||||||
find . -name "*.pyc" -delete
|
|
||||||
|
.DEFAULT: help
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo "test"
|
||||||
|
@echo " Run Sanic Unit Tests"
|
||||||
|
@echo "test-coverage"
|
||||||
|
@echo " Run Sanic Unit Tests with Coverage"
|
||||||
|
@echo "install"
|
||||||
|
@echo " Install Sanic"
|
||||||
|
@echo "docker-test"
|
||||||
|
@echo " Run Sanic Unit Tests using Docker"
|
||||||
|
@echo "black"
|
||||||
|
@echo " Analyze and fix linting issues using Black"
|
||||||
|
@echo "fix-import"
|
||||||
|
@echo " Analyze and fix import order using isort"
|
||||||
|
@echo "beautify [sort_imports=1] [include_tests=1]"
|
||||||
|
@echo " Analyze and fix linting issue using black and optionally fix import sort using isort"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find . ! -path "./.eggs/*" -name "*.pyc" -exec rm {} \;
|
||||||
|
find . ! -path "./.eggs/*" -name "*.pyo" -exec rm {} \;
|
||||||
|
find . ! -path "./.eggs/*" -name ".coverage" -exec rm {} \;
|
||||||
|
rm -rf build/* > /dev/null 2>&1
|
||||||
|
rm -rf dist/* > /dev/null 2>&1
|
||||||
|
|
||||||
|
test: clean
|
||||||
|
python setup.py test
|
||||||
|
|
||||||
|
test-coverage: clean
|
||||||
|
python setup.py test --pytest-args="--cov sanic --cov-report term --cov-append "
|
||||||
|
|
||||||
|
install:
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
docker-test: clean
|
||||||
docker build -t sanic/test-image -f docker/Dockerfile .
|
docker build -t sanic/test-image -f docker/Dockerfile .
|
||||||
docker run -t sanic/test-image tox
|
docker run -t sanic/test-image tox
|
||||||
|
|
||||||
|
beautify: black
|
||||||
|
ifdef sort_imports
|
||||||
|
ifdef include_tests
|
||||||
|
$(warning It is suggested that you do not run sort import on tests)
|
||||||
|
isort -rc sanic tests
|
||||||
|
else
|
||||||
|
$(info Sorting Imports)
|
||||||
|
isort -rc sanic
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
black:
|
||||||
|
black --config ./pyproject.toml sanic tests
|
||||||
|
|
||||||
|
fix-import: black
|
||||||
|
isort -rc sanic
|
||||||
|
|
78
README.rst
78
README.rst
|
@ -1,7 +1,44 @@
|
||||||
Sanic
|
Sanic
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |AppVeyor Build Status| |Documentation| |Codecov| |PyPI| |PyPI version| |Code style black|
|
.. start-badges
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:stub-columns: 1
|
||||||
|
|
||||||
|
* - Build
|
||||||
|
- | |Build Status| |AppVeyor Build Status| |Codecov|
|
||||||
|
* - Docs
|
||||||
|
- |Documentation|
|
||||||
|
* - Package
|
||||||
|
- | |PyPI| |PyPI version| |Wheel| |Supported implementations| |Code style black|
|
||||||
|
* - Support
|
||||||
|
- |Join the chat at https://gitter.im/sanic-python/Lobby|
|
||||||
|
|
||||||
|
.. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg
|
||||||
|
:target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||||
|
.. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg
|
||||||
|
: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
|
||||||
|
: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/
|
||||||
|
.. |Code style black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/ambv/black
|
||||||
|
.. |Wheel| image:: https://img.shields.io/pypi/wheel/sanic.svg
|
||||||
|
:alt: PyPI Wheel
|
||||||
|
:target: https://pypi.python.org/pypi/sanic
|
||||||
|
.. |Supported implementations| image:: https://img.shields.io/pypi/implementation/sanic.svg
|
||||||
|
:alt: Supported implementations
|
||||||
|
:target: https://pypi.python.org/pypi/sanic
|
||||||
|
|
||||||
|
.. end-badges
|
||||||
|
|
||||||
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/>`_.
|
||||||
|
|
||||||
|
@ -11,6 +48,18 @@ Sanic is developed `on GitHub <https://github.com/huge-success/sanic/>`_. We als
|
||||||
|
|
||||||
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/huge-success/sanic/issues/396>`_ that we use to track those projects!
|
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/huge-success/sanic/issues/396>`_ that we use to track those projects!
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
- ``pip3 install sanic``
|
||||||
|
|
||||||
|
To install sanic without uvloop or ujson using bash, you can provide either or both of these environmental variables
|
||||||
|
using any truthy string like `'y', 'yes', 't', 'true', 'on', '1'` and setting the ``SANIC_NO_X`` (``X`` = ``UVLOOP``/``UJSON``)
|
||||||
|
to true will stop that features installation.
|
||||||
|
|
||||||
|
- ``SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip install sanic``
|
||||||
|
|
||||||
|
|
||||||
Hello World Example
|
Hello World Example
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -28,39 +77,12 @@ Hello World Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=8000)
|
app.run(host='0.0.0.0', port=8000)
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
- ``pip install sanic``
|
|
||||||
|
|
||||||
To install sanic without uvloop or ujson using bash, you can provide either or both of these environmental variables
|
|
||||||
using any truthy string like `'y', 'yes', 't', 'true', 'on', '1'` and setting the NO_X to true will stop that features
|
|
||||||
installation.
|
|
||||||
|
|
||||||
- ``SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip install sanic``
|
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
`Documentation on Readthedocs <http://sanic.readthedocs.io/>`_.
|
`Documentation on Readthedocs <http://sanic.readthedocs.io/>`_.
|
||||||
|
|
||||||
.. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg
|
|
||||||
:target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
|
||||||
.. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg
|
|
||||||
: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
|
|
||||||
: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/
|
|
||||||
.. |Code style black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
||||||
:target: https://github.com/ambv/black
|
|
||||||
|
|
||||||
Questions and Discussion
|
Questions and Discussion
|
||||||
------------------------
|
------------------------
|
||||||
|
|
|
@ -38,7 +38,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'Sanic'
|
project = 'Sanic'
|
||||||
copyright = '2016, Sanic contributors'
|
copyright = '2018, Sanic contributors'
|
||||||
author = 'Sanic contributors'
|
author = 'Sanic contributors'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
|
|
@ -7,30 +7,31 @@ Guides
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
sanic/getting_started
|
sanic/getting_started
|
||||||
sanic/routing
|
sanic/config
|
||||||
|
sanic/logging
|
||||||
sanic/request_data
|
sanic/request_data
|
||||||
sanic/response
|
sanic/response
|
||||||
|
sanic/cookies
|
||||||
|
sanic/routing
|
||||||
|
sanic/blueprints
|
||||||
sanic/static_files
|
sanic/static_files
|
||||||
|
sanic/versioning
|
||||||
sanic/exceptions
|
sanic/exceptions
|
||||||
sanic/middleware
|
sanic/middleware
|
||||||
sanic/blueprints
|
|
||||||
sanic/websocket
|
sanic/websocket
|
||||||
sanic/config
|
|
||||||
sanic/cookies
|
|
||||||
sanic/decorators
|
sanic/decorators
|
||||||
sanic/streaming
|
sanic/streaming
|
||||||
sanic/class_based_views
|
sanic/class_based_views
|
||||||
sanic/custom_protocol
|
sanic/custom_protocol
|
||||||
sanic/sockets
|
sanic/sockets
|
||||||
sanic/ssl
|
sanic/ssl
|
||||||
sanic/logging
|
|
||||||
sanic/versioning
|
|
||||||
sanic/debug_mode
|
sanic/debug_mode
|
||||||
sanic/testing
|
sanic/testing
|
||||||
sanic/deploying
|
sanic/deploying
|
||||||
sanic/extensions
|
sanic/extensions
|
||||||
sanic/contributing
|
sanic/contributing
|
||||||
sanic/api_reference
|
sanic/api_reference
|
||||||
|
sanic/examples
|
||||||
|
|
||||||
|
|
||||||
Module Documentation
|
Module Documentation
|
||||||
|
|
|
@ -118,9 +118,9 @@ app = Sanic(__name__)
|
||||||
app.blueprint(api)
|
app.blueprint(api)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using blueprints
|
## Using Blueprints
|
||||||
|
|
||||||
Blueprints have much the same functionality as an application instance.
|
Blueprints have almost the same functionality as an application instance.
|
||||||
|
|
||||||
### WebSocket routes
|
### WebSocket routes
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ async def close_connection(app, loop):
|
||||||
Blueprints can be very useful for API versioning, where one blueprint may point
|
Blueprints can be very useful for API versioning, where one blueprint may point
|
||||||
at `/v1/<routes>`, and another pointing at `/v2/<routes>`.
|
at `/v1/<routes>`, and another pointing at `/v2/<routes>`.
|
||||||
|
|
||||||
When a blueprint is initialised, it can take an optional `url_prefix` argument,
|
When a blueprint is initialised, it can take an optional `version` argument,
|
||||||
which will be prepended to all routes defined on the blueprint. This feature
|
which will be prepended to all routes defined on the blueprint. This feature
|
||||||
can be used to implement our API versioning scheme.
|
can be used to implement our API versioning scheme.
|
||||||
|
|
||||||
|
@ -210,8 +210,8 @@ can be used to implement our API versioning scheme.
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic import Blueprint
|
from sanic import Blueprint
|
||||||
|
|
||||||
blueprint_v1 = Blueprint('v1', url_prefix='/v1')
|
blueprint_v1 = Blueprint('v1', url_prefix='/api', version="v1")
|
||||||
blueprint_v2 = Blueprint('v2', url_prefix='/v2')
|
blueprint_v2 = Blueprint('v2', url_prefix='/api', version="v2")
|
||||||
|
|
||||||
@blueprint_v1.route('/')
|
@blueprint_v1.route('/')
|
||||||
async def api_v1_root(request):
|
async def api_v1_root(request):
|
||||||
|
@ -222,7 +222,7 @@ async def api_v2_root(request):
|
||||||
return text('Welcome to version 2 of our documentation')
|
return text('Welcome to version 2 of our documentation')
|
||||||
```
|
```
|
||||||
|
|
||||||
When we register our blueprints on the app, the routes `/v1` and `/v2` will now
|
When we register our blueprints on the app, the routes `/v1/api` and `/v2/api` will now
|
||||||
point to the individual blueprints, which allows the creation of *sub-sites*
|
point to the individual blueprints, which allows the creation of *sub-sites*
|
||||||
for each API version.
|
for each API version.
|
||||||
|
|
||||||
|
@ -232,8 +232,8 @@ from sanic import Sanic
|
||||||
from blueprints import blueprint_v1, blueprint_v2
|
from blueprints import blueprint_v1, blueprint_v2
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
app.blueprint(blueprint_v1, url_prefix='/v1')
|
app.blueprint(blueprint_v1)
|
||||||
app.blueprint(blueprint_v2, url_prefix='/v2')
|
app.blueprint(blueprint_v2)
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=8000, debug=True)
|
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||||
```
|
```
|
||||||
|
@ -246,7 +246,7 @@ takes the format `<blueprint_name>.<handler_name>`. For example:
|
||||||
```python
|
```python
|
||||||
@blueprint_v1.route('/')
|
@blueprint_v1.route('/')
|
||||||
async def root(request):
|
async def root(request):
|
||||||
url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
|
url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/api/post/5'
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -93,28 +93,52 @@ DB_USER = 'appuser'
|
||||||
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
Out of the box there are just a few predefined values which can be overwritten when creating the application.
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
| ------------------------- | --------- | ------------------------------------------------------ |
|
| ------------------------- | --------- | --------------------------------------------------------- |
|
||||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||||
|
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
|
||||||
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
|
||||||
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
|
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
|
||||||
| KEEP_ALIVE | True | Disables keep-alive when False |
|
| KEEP_ALIVE | True | Disables keep-alive when False |
|
||||||
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
|
||||||
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long take to force close non-idle connection (sec) |
|
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
|
||||||
| ACCESS_LOG | True | Disable or enable access log |
|
| ACCESS_LOG | True | Disable or enable access log |
|
||||||
|
|
||||||
### The different Timeout variables:
|
### The different Timeout variables:
|
||||||
|
|
||||||
A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the `REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates a HTTP 408 response and sends that to the client. Adjust this value higher if your clients routinely pass very large request payloads or upload requests very slowly.
|
#### `REQUEST_TIMEOUT`
|
||||||
|
|
||||||
A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates a HTTP 503 response and sets that to the client. Adjust this value higher if your application is likely to have long-running process that delay the generation of a response.
|
A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the
|
||||||
|
Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the
|
||||||
|
`REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response
|
||||||
|
and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads
|
||||||
|
or upload requests very slowly.
|
||||||
|
|
||||||
### What is Keep Alive? And what does the Keep Alive Timeout value do?
|
#### `RESPONSE_TIMEOUT`
|
||||||
|
|
||||||
Keep-Alive is a HTTP feature indroduced in HTTP 1.1. When sending a HTTP request, the client (usually a web browser application) can set a Keep-Alive header to indicate for the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server.
|
A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the
|
||||||
|
Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT`
|
||||||
|
value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the
|
||||||
|
client. Set this parameter's value higher if your application is likely to have long-running process that delay the
|
||||||
|
generation of a response.
|
||||||
|
|
||||||
The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the Keep-Alive header on the request.
|
#### `KEEP_ALIVE_TIMEOUT`
|
||||||
|
|
||||||
The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds, this is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless you know your clients are using a browser which supports TCP connections held open for that long.
|
##### What is Keep Alive? And what does the Keep Alive Timeout value do?
|
||||||
|
|
||||||
|
`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application)
|
||||||
|
can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response.
|
||||||
|
This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient
|
||||||
|
network traffic for both the client and the server.
|
||||||
|
|
||||||
|
The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application,
|
||||||
|
set it to `False` to cause all client connections to close immediately after a response is sent, regardless of
|
||||||
|
the `Keep-Alive` header on the request.
|
||||||
|
|
||||||
|
The amount of time the server holds the TCP connection open is decided by the server itself.
|
||||||
|
In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds.
|
||||||
|
This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for
|
||||||
|
the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless
|
||||||
|
you know your clients are using a browser which supports TCP connections held open for that long.
|
||||||
|
|
||||||
For reference:
|
For reference:
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Thank you for your interest! Sanic is always looking for contributors. If you
|
|
||||||
don't feel comfortable contributing code, adding docstrings to the source files
|
|
||||||
is very appreciated.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To develop on sanic (and mainly to just run the tests) it is highly recommend to
|
|
||||||
install from sources.
|
|
||||||
|
|
||||||
So assume you have already cloned the repo and are in the working directory with
|
|
||||||
a virtual environment already set up, then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python setup.py develop && pip install -r requirements-dev.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running tests
|
|
||||||
|
|
||||||
To run the tests for sanic it is recommended to use tox like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tox
|
|
||||||
```
|
|
||||||
|
|
||||||
See it's that simple!
|
|
||||||
|
|
||||||
## Pull requests!
|
|
||||||
|
|
||||||
So the pull request approval rules are pretty simple:
|
|
||||||
* All pull requests must pass unit tests
|
|
||||||
* All pull requests must be reviewed and approved by at least
|
|
||||||
one current collaborator on the project
|
|
||||||
* All pull requests must pass flake8 checks
|
|
||||||
* If you decide to remove/change anything from any common interface
|
|
||||||
a deprecation message should accompany it.
|
|
||||||
* If you implement a new feature you should have at least one unit
|
|
||||||
test to accompany it.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Sanic's documentation is built
|
|
||||||
using [sphinx](http://www.sphinx-doc.org/en/1.5.1/). Guides are written in
|
|
||||||
Markdown and can be found in the `docs` folder, while the module reference is
|
|
||||||
automatically generated using `sphinx-apidoc`.
|
|
||||||
|
|
||||||
To generate the documentation from scratch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sphinx-apidoc -fo docs/_api/ sanic
|
|
||||||
sphinx-build -b html docs docs/_build
|
|
||||||
```
|
|
||||||
|
|
||||||
The HTML documentation will be created in the `docs/_build` folder.
|
|
||||||
|
|
||||||
## Warning
|
|
||||||
|
|
||||||
One of the main goals of Sanic is speed. Code that lowers the performance of
|
|
||||||
Sanic without significant gains in usability, security, or features may not be
|
|
||||||
merged. Please don't let this intimidate you! If you have any concerns about an
|
|
||||||
idea, open an issue for discussion and help.
|
|
89
docs/sanic/contributing.rst
Normal file
89
docs/sanic/contributing.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
Thank you for your interest! Sanic is always looking for contributors.
|
||||||
|
If you don’t feel comfortable contributing code, adding docstrings to
|
||||||
|
the source files is very appreciated.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
To develop on sanic (and mainly to just run the tests) it is highly
|
||||||
|
recommend to install from sources.
|
||||||
|
|
||||||
|
So assume you have already cloned the repo and are in the working
|
||||||
|
directory with a virtual environment already set up, then run:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
pip3 install -e . '[.dev]'
|
||||||
|
|
||||||
|
Dependency Changes
|
||||||
|
------------------
|
||||||
|
|
||||||
|
``Sanic`` doesn't use ``requirements*.txt`` files to manage any kind of dependencies related to it in order to simplify the
|
||||||
|
effort required in managing the dependencies. Please make sure you have read and understood the following section of
|
||||||
|
the document that explains the way ``sanic`` manages dependencies inside the ``setup.py`` file.
|
||||||
|
|
||||||
|
+------------------------+-----------------------------------------------+--------------------------------+
|
||||||
|
| Dependency Type | Usage | Installation |
|
||||||
|
+========================+===============================================+================================+
|
||||||
|
| requirements | Bare minimum dependencies required for sanic | ``pip3 install -e .`` |
|
||||||
|
| | to function | |
|
||||||
|
+------------------------+-----------------------------------------------+--------------------------------+
|
||||||
|
| tests_require / | Dependencies required to run the Unit Tests | ``pip3 install -e '.[test]'`` |
|
||||||
|
| extras_require['test'] | for ``sanic`` | |
|
||||||
|
+------------------------+-----------------------------------------------+--------------------------------+
|
||||||
|
| extras_require['dev'] | Additional Development requirements to add | ``pip3 install -e '.[dev]'`` |
|
||||||
|
| | contributing | |
|
||||||
|
+------------------------+-----------------------------------------------+--------------------------------+
|
||||||
|
| extras_require['docs'] | Dependencies required to enable building and | ``pip3 install -e '.[docs]'`` |
|
||||||
|
| | enhancing sanic documentation | |
|
||||||
|
+------------------------+-----------------------------------------------+--------------------------------+
|
||||||
|
|
||||||
|
Running tests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To run the tests for sanic it is recommended to use tox like so:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
tox
|
||||||
|
|
||||||
|
See it’s that simple!
|
||||||
|
|
||||||
|
Pull requests!
|
||||||
|
--------------
|
||||||
|
|
||||||
|
So the pull request approval rules are pretty simple:
|
||||||
|
|
||||||
|
* All pull requests must pass unit tests
|
||||||
|
* All pull requests must be reviewed and approved by at least one current collaborator on the project
|
||||||
|
* All pull requests must pass flake8 checks
|
||||||
|
* If you decide to remove/change anything from any common interface a deprecation message should accompany it.
|
||||||
|
* If you implement a new feature you should have at least one unit test to accompany it.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Sanic’s documentation is built using `sphinx`_. Guides are written in
|
||||||
|
Markdown and can be found in the ``docs`` folder, while the module
|
||||||
|
reference is automatically generated using ``sphinx-apidoc``.
|
||||||
|
|
||||||
|
To generate the documentation from scratch:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
sphinx-apidoc -fo docs/_api/ sanic
|
||||||
|
sphinx-build -b html docs docs/_build
|
||||||
|
|
||||||
|
The HTML documentation will be created in the ``docs/_build`` folder.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
One of the main goals of Sanic is speed. Code that lowers the
|
||||||
|
performance of Sanic without significant gains in usability, security,
|
||||||
|
or features may not be merged. Please don’t let this intimidate you! If
|
||||||
|
you have any concerns about an idea, open an issue for discussion and
|
||||||
|
help.
|
||||||
|
|
||||||
|
.. _sphinx: http://www.sphinx-doc.org/en/1.5.1/
|
|
@ -1,72 +0,0 @@
|
||||||
# Custom Protocols
|
|
||||||
|
|
||||||
*Note: this is advanced usage, and most readers will not need such functionality.*
|
|
||||||
|
|
||||||
You can change the behavior of Sanic's protocol by specifying a custom
|
|
||||||
protocol, which should be a subclass
|
|
||||||
of
|
|
||||||
[asyncio.protocol](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes).
|
|
||||||
This protocol can then be passed as the keyword argument `protocol` to the `sanic.run` method.
|
|
||||||
|
|
||||||
The constructor of the custom protocol class receives the following keyword
|
|
||||||
arguments from Sanic.
|
|
||||||
|
|
||||||
- `loop`: an `asyncio`-compatible event loop.
|
|
||||||
- `connections`: a `set` to store protocol objects. When Sanic receives
|
|
||||||
`SIGINT` or `SIGTERM`, it executes `protocol.close_if_idle` for all protocol
|
|
||||||
objects stored in this set.
|
|
||||||
- `signal`: a `sanic.server.Signal` object with the `stopped` attribute. When
|
|
||||||
Sanic receives `SIGINT` or `SIGTERM`, `signal.stopped` is assigned `True`.
|
|
||||||
- `request_handler`: a coroutine that takes a `sanic.request.Request` object
|
|
||||||
and a `response` callback as arguments.
|
|
||||||
- `error_handler`: a `sanic.exceptions.Handler` which is called when exceptions
|
|
||||||
are raised.
|
|
||||||
- `request_timeout`: the number of seconds before a request times out.
|
|
||||||
- `request_max_size`: an integer specifying the maximum size of a request, in bytes.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
An error occurs in the default protocol if a handler function does not return
|
|
||||||
an `HTTPResponse` object.
|
|
||||||
|
|
||||||
By overriding the `write_response` protocol method, if a handler returns a
|
|
||||||
string it will be converted to an `HTTPResponse object`.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from sanic import Sanic
|
|
||||||
from sanic.server import HttpProtocol
|
|
||||||
from sanic.response import text
|
|
||||||
|
|
||||||
app = Sanic(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomHttpProtocol(HttpProtocol):
|
|
||||||
|
|
||||||
def __init__(self, *, loop, request_handler, error_handler,
|
|
||||||
signal, connections, request_timeout, request_max_size):
|
|
||||||
super().__init__(
|
|
||||||
loop=loop, request_handler=request_handler,
|
|
||||||
error_handler=error_handler, signal=signal,
|
|
||||||
connections=connections, request_timeout=request_timeout,
|
|
||||||
request_max_size=request_max_size)
|
|
||||||
|
|
||||||
def write_response(self, response):
|
|
||||||
if isinstance(response, str):
|
|
||||||
response = text(response)
|
|
||||||
self.transport.write(
|
|
||||||
response.output(self.request.version)
|
|
||||||
)
|
|
||||||
self.transport.close()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
async def string(request):
|
|
||||||
return 'string'
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/1')
|
|
||||||
async def response(request):
|
|
||||||
return text('response')
|
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
|
|
||||||
```
|
|
76
docs/sanic/custom_protocol.rst
Normal file
76
docs/sanic/custom_protocol.rst
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
Custom Protocols
|
||||||
|
================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This is advanced usage, and most readers will not need such functionality.
|
||||||
|
|
||||||
|
You can change the behavior of Sanic's protocol by specifying a custom
|
||||||
|
protocol, which should be a subclass
|
||||||
|
of `asyncio.protocol <https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes>`_.
|
||||||
|
This protocol can then be passed as the keyword argument ``protocol`` to the ``sanic.run`` method.
|
||||||
|
|
||||||
|
The constructor of the custom protocol class receives the following keyword
|
||||||
|
arguments from Sanic.
|
||||||
|
|
||||||
|
- ``loop``: an ``asyncio``-compatible event loop.
|
||||||
|
- ``connections``: a ``set`` to store protocol objects. When Sanic receives
|
||||||
|
``SIGINT`` or ``SIGTERM``, it executes ``protocol.close_if_idle`` for all protocol
|
||||||
|
objects stored in this set.
|
||||||
|
- ``signal``: a ``sanic.server.Signal`` object with the ``stopped`` attribute. When
|
||||||
|
Sanic receives ``SIGINT`` or ``SIGTERM``, ``signal.stopped`` is assigned ``True``.
|
||||||
|
- ``request_handler``: a coroutine that takes a ``sanic.request.Request`` object
|
||||||
|
and a ``response`` callback as arguments.
|
||||||
|
- ``error_handler``: a ``sanic.exceptions.Handler`` which is called when exceptions
|
||||||
|
are raised.
|
||||||
|
- ``request_timeout``: the number of seconds before a request times out.
|
||||||
|
- ``request_max_size``: an integer specifying the maximum size of a request, in bytes.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
An error occurs in the default protocol if a handler function does not return
|
||||||
|
an ``HTTPResponse`` object.
|
||||||
|
|
||||||
|
By overriding the ``write_response`` protocol method, if a handler returns a
|
||||||
|
string it will be converted to an ``HTTPResponse object``.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.server import HttpProtocol
|
||||||
|
from sanic.response import text
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomHttpProtocol(HttpProtocol):
|
||||||
|
|
||||||
|
def __init__(self, *, loop, request_handler, error_handler,
|
||||||
|
signal, connections, request_timeout, request_max_size):
|
||||||
|
super().__init__(
|
||||||
|
loop=loop, request_handler=request_handler,
|
||||||
|
error_handler=error_handler, signal=signal,
|
||||||
|
connections=connections, request_timeout=request_timeout,
|
||||||
|
request_max_size=request_max_size)
|
||||||
|
|
||||||
|
def write_response(self, response):
|
||||||
|
if isinstance(response, str):
|
||||||
|
response = text(response)
|
||||||
|
self.transport.write(
|
||||||
|
response.output(self.request.version)
|
||||||
|
)
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def string(request):
|
||||||
|
return 'string'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/1')
|
||||||
|
async def response(request):
|
||||||
|
return text('response')
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
|
||||||
|
|
159
docs/sanic/examples.rst
Normal file
159
docs/sanic/examples.rst
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
This section of the documentation is a simple collection of example code that can help you get a quick start
|
||||||
|
on your application development. Most of these examples are categorized and provide you with a link to the
|
||||||
|
working code example in the `Sanic Repository <https://github.com/huge-success/sanic/tree/master/examples>`_
|
||||||
|
|
||||||
|
|
||||||
|
Basic Examples
|
||||||
|
--------------
|
||||||
|
|
||||||
|
This section of the examples are a collection of code that provide a simple use case example of the sanic application.
|
||||||
|
|
||||||
|
Simple Apps
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A simple sanic application with a single ``async`` method with ``text`` and ``json`` type response.
|
||||||
|
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/teapot.py
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/simple_server.py
|
||||||
|
|
||||||
|
|
||||||
|
Simple App with ``Sanic Views``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Showcasing the simple mechanism of using :class:`sanic.viewes.HTTPMethodView` as well as a way to extend the same
|
||||||
|
into providing a custom ``async`` behavior for ``view``.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/simple_async_view.py
|
||||||
|
|
||||||
|
|
||||||
|
URL Redirect
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/redirect_example.py
|
||||||
|
|
||||||
|
|
||||||
|
Named URL redirection
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``Sanic`` provides an easy to use way of redirecting the requests via a helper method called ``url_for`` that takes a
|
||||||
|
unique url name as argument and returns you the actual route assigned for it. This will help in simplifying the
|
||||||
|
efforts required in redirecting the user between different section of the application.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/url_for_example.py
|
||||||
|
|
||||||
|
Blueprints
|
||||||
|
~~~~~~~~~~
|
||||||
|
``Sanic`` provides an amazing feature to group your APIs and routes under a logical collection that can easily be
|
||||||
|
imported and plugged into any of your sanic application and it's called ``blueprints``
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/blueprints.py
|
||||||
|
|
||||||
|
Logging Enhancements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Even though ``Sanic`` comes with a battery of Logging support it allows the end users to customize the way logging
|
||||||
|
is handled in the application runtime.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/override_logging.py
|
||||||
|
|
||||||
|
The following sample provides an example code that demonstrates the usage of :func:`sanic.app.Sanic.middleware` in order
|
||||||
|
to provide a mechanism to assign a unique request ID for each of the incoming requests and log them via
|
||||||
|
`aiotask-context <https://github.com/Skyscanner/aiotask-context>`_.
|
||||||
|
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/log_request_id.py
|
||||||
|
|
||||||
|
Sanic Streaming Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``Sanic`` framework comes with in-built support for streaming large files and the following code explains the process
|
||||||
|
to setup a ``Sanic`` application with streaming support.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/request_stream/server.py
|
||||||
|
|
||||||
|
Sample Client app to show the usage of streaming application by a client code.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/request_stream/client.py
|
||||||
|
|
||||||
|
Sanic Concurrency Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
``Sanic`` supports the ability to start an app with multiple worker support. However, it's important to be able to limit
|
||||||
|
the concurrency per process/loop in order to ensure an efficient execution. The following section of the code provides a
|
||||||
|
brief example of how to limit the concurrency with the help of :class:`asyncio.Semaphore`
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/limit_concurrency.py
|
||||||
|
|
||||||
|
|
||||||
|
Sanic Deployment via Docker
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Deploying a ``sanic`` app via ``docker`` and ``docker-compose`` is an easy task to achieve and the following example
|
||||||
|
provides a deployment of the sample ``simple_server.py``
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/Dockerfile
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/docker-compose.yml
|
||||||
|
|
||||||
|
|
||||||
|
Monitoring and Error Handling
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
``Sanic`` provides an extendable bare minimum implementation of a global exception handler via
|
||||||
|
:class:`sanic.handlers.ErrorHandler`. This example shows how to extend it to enable some custom behaviors.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/exception_monitoring.py
|
||||||
|
|
||||||
|
Monitoring using external Service Providers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* `LogDNA <https://logdna.com/>`_
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/logdna_example.py
|
||||||
|
|
||||||
|
* `RayGun <https://raygun.com/>`_
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/raygun_example.py
|
||||||
|
|
||||||
|
* `Rollbar <https://rollbar.com>`_
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/rollbar_example.py
|
||||||
|
|
||||||
|
* `Sentry <http://sentry.io>`_
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/sentry_example.py
|
||||||
|
|
||||||
|
|
||||||
|
Security
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
The following sample code shows a simple decorator based authentication and authorization mechanism that can be setup
|
||||||
|
to secure your ``sanic`` api endpoints.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/authorized_sanic.py
|
||||||
|
|
||||||
|
Sanic Websocket
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``Sanic`` provides an ability to easily add a route and map it to a ``websocket`` handlers.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/websocket.html
|
||||||
|
.. literalinclude:: ../../examples/websocket.py
|
||||||
|
|
||||||
|
vhost Suppport
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/vhosts.py
|
||||||
|
|
||||||
|
Unit Testing With Parallel Test Run Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The following example shows you how to get up and running with unit testing ``sanic`` application with parallel test
|
||||||
|
execution support provided by the ``pytest-xdist`` plugin.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/pytest_xdist.py
|
||||||
|
|
||||||
|
For more examples and useful samples please visit the `Huge-Sanic's GitHub Page <https://github.com/huge-success/sanic/tree/master/examples>`_
|
|
@ -65,7 +65,7 @@ can subclass Sanic's default error handler as such:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.handlers import ErrorHnadler
|
from sanic.handlers import ErrorHandler
|
||||||
|
|
||||||
class CustomErrorHandler(ErrorHandler):
|
class CustomErrorHandler(ErrorHandler):
|
||||||
def default(self, request, exception):
|
def default(self, request, exception):
|
||||||
|
|
|
@ -1,36 +1,74 @@
|
||||||
# Extensions
|
# Extensions
|
||||||
|
|
||||||
A list of Sanic extensions created by the community.
|
A list of Sanic extensions created by the community.
|
||||||
|
|
||||||
|
|
||||||
|
## Extension and Plugin Development
|
||||||
|
|
||||||
- [Sanic-Plugins-Framework](https://github.com/ashleysommer/sanicpluginsframework): Library for easily creating and using Sanic plugins.
|
- [Sanic-Plugins-Framework](https://github.com/ashleysommer/sanicpluginsframework): Library for easily creating and using Sanic plugins.
|
||||||
- [Sessions](https://github.com/subyraman/sanic_session): Support for sessions.
|
- [sanic-script](https://github.com/tim2anna/sanic-script): An extension for Sanic that adds support for writing commands to your application.
|
||||||
Allows using redis, memcache or an in memory store.
|
|
||||||
- [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors.
|
## Security
|
||||||
- [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](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
|
- [Secure](https://github.com/cakinney/secure): Secure 🔒 is a lightweight package that adds optional security headers and cookie attributes for Python web frameworks.
|
||||||
- [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
|
- [Sessions](https://github.com/subyraman/sanic_session): Support for sessions. Allows using redis, memcache or an in memory store.
|
||||||
- [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support.
|
- [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors.
|
||||||
- [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper.
|
- [Sanic-JWT-Extended](https://github.com/devArtoria/Sanic-JWT-Extended): Provides extended JWT support for
|
||||||
- [Sanic CRUD](https://github.com/Typhon66/sanic_crud): CRUD REST API generation with peewee models.
|
|
||||||
- [UserAgent](https://github.com/lixxu/sanic-useragent): Add `user_agent` to request
|
- [UserAgent](https://github.com/lixxu/sanic-useragent): Add `user_agent` to request
|
||||||
- [Limiter](https://github.com/bohea/sanic-limiter): Rate limiting for sanic.
|
- [Limiter](https://github.com/bohea/sanic-limiter): Rate limiting for sanic.
|
||||||
- [Sanic EnvConfig](https://github.com/jamesstidard/sanic-envconfig): Pull environment variables into your sanic config.
|
|
||||||
- [Babel](https://github.com/lixxu/sanic-babel): Adds i18n/l10n support to Sanic applications with the help of the
|
|
||||||
`Babel` library
|
|
||||||
- [Dispatch](https://github.com/ashleysommer/sanic-dispatcher): A dispatcher inspired by `DispatcherMiddleware` in werkzeug. Can act as a Sanic-to-WSGI adapter.
|
|
||||||
- [Sanic-OAuth](https://github.com/Sniedes722/Sanic-OAuth): OAuth Library for connecting to & creating your own token providers.
|
- [Sanic-OAuth](https://github.com/Sniedes722/Sanic-OAuth): OAuth Library for connecting to & creating your own token providers.
|
||||||
- [sanic-oauth](https://gitlab.com/SirEdvin/sanic-oauth): OAuth Library with many provider and OAuth1/OAuth2 support.
|
- [sanic-oauth](https://gitlab.com/SirEdvin/sanic-oauth): OAuth Library with many provider and OAuth1/OAuth2 support.
|
||||||
- [Sanic-nginx-docker-example](https://github.com/itielshwartz/sanic-nginx-docker-example): Simple and easy to use example of Sanic behined nginx using docker-compose.
|
|
||||||
- [sanic-graphql](https://github.com/graphql-python/sanic-graphql): GraphQL integration with Sanic
|
|
||||||
- [sanic-prometheus](https://github.com/dkruchinin/sanic-prometheus): Prometheus metrics for Sanic
|
|
||||||
- [Sanic-RestPlus](https://github.com/ashleysommer/sanic-restplus): A port of Flask-RestPlus for Sanic. Full-featured REST API with SwaggerUI generation.
|
|
||||||
- [sanic-transmute](https://github.com/yunstanford/sanic-transmute): A Sanic extension that generates APIs from python function and classes, and also generates Swagger UI/documentation automatically.
|
|
||||||
- [pytest-sanic](https://github.com/yunstanford/pytest-sanic): A pytest plugin for Sanic. It helps you to test your code asynchronously.
|
|
||||||
- [jinja2-sanic](https://github.com/yunstanford/jinja2-sanic): a jinja2 template renderer for Sanic.([Documentation](http://jinja2-sanic.readthedocs.io/en/latest/))
|
|
||||||
- [GINO](https://github.com/fantix/gino): An asyncio ORM on top of SQLAlchemy core, delivered with a Sanic extension. ([Documentation](https://python-gino.readthedocs.io/))
|
|
||||||
- [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.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
|
||||||
|
- [Sanic-RestPlus](https://github.com/ashleysommer/sanic-restplus): A port of Flask-RestPlus for Sanic. Full-featured REST API with SwaggerUI generation.
|
||||||
|
- [sanic-transmute](https://github.com/yunstanford/sanic-transmute): A Sanic extension that generates APIs from python function and classes, and also generates Swagger UI/documentation automatically.
|
||||||
|
|
||||||
|
## ORM and Database Integration
|
||||||
|
|
||||||
|
- [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper.
|
||||||
|
- [Sanic CRUD](https://github.com/Typhon66/sanic_crud): CRUD REST API generation with peewee models.
|
||||||
|
- [sanic-graphql](https://github.com/graphql-python/sanic-graphql): GraphQL integration with Sanic
|
||||||
|
- [GINO](https://github.com/fantix/gino): An asyncio ORM on top of SQLAlchemy core, delivered with a Sanic extension. ([Documentation](https://python-gino.readthedocs.io/))
|
||||||
|
|
||||||
|
## Unit Testing
|
||||||
|
|
||||||
|
- [pytest-sanic](https://github.com/yunstanford/pytest-sanic): A pytest plugin for Sanic. It helps you to test your code asynchronously.
|
||||||
|
|
||||||
|
## Project Creation Template
|
||||||
|
|
||||||
|
- [cookiecutter-sanic](https://github.com/harshanarayana/cookiecutter-sanic) Get your sanic application up and running in a matter of second in a well defined project structure.
|
||||||
|
Batteries included for deployment, unit testing, automated release management and changelog generation.
|
||||||
|
|
||||||
|
## Templating
|
||||||
|
|
||||||
- [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.
|
- [Jinja2](https://github.com/lixxu/sanic-jinja2): Support for Jinja2 template.
|
||||||
|
- [jinja2-sanic](https://github.com/yunstanford/jinja2-sanic): a jinja2 template renderer for Sanic.([Documentation](http://jinja2-sanic.readthedocs.io/en/latest/))
|
||||||
|
|
||||||
|
## API Helper Utilities
|
||||||
|
|
||||||
- [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.
|
||||||
|
- [Compress](https://github.com/subyraman/sanic_compress): Allows you to easily gzip Sanic responses. A port of Flask-Compress.
|
||||||
|
- [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support.
|
||||||
|
- [Sanic EnvConfig](https://github.com/jamesstidard/sanic-envconfig): Pull environment variables into your sanic config.
|
||||||
|
|
||||||
|
## i18n/l10n Support
|
||||||
|
- [Babel](https://github.com/lixxu/sanic-babel): Adds i18n/l10n support to Sanic applications with the help of the `Babel` library
|
||||||
|
|
||||||
|
## Custom Middlewares
|
||||||
|
|
||||||
|
- [Dispatch](https://github.com/ashleysommer/sanic-dispatcher): A dispatcher inspired by `DispatcherMiddleware` in werkzeug. Can act as a Sanic-to-WSGI adapter.
|
||||||
|
|
||||||
|
## Monitoring and Reporting
|
||||||
|
|
||||||
|
- [sanic-prometheus](https://github.com/dkruchinin/sanic-prometheus): Prometheus metrics for Sanic
|
||||||
|
|
||||||
|
|
||||||
|
## Sample Applications
|
||||||
|
|
||||||
|
- [Sanic-nginx-docker-example](https://github.com/itielshwartz/sanic-nginx-docker-example): Simple and easy to use example of Sanic behined nginx using docker-compose.
|
||||||
|
|
|
@ -7,9 +7,17 @@ syntax, so earlier versions of python won't work.
|
||||||
## 1. Install Sanic
|
## 1. Install Sanic
|
||||||
|
|
||||||
```
|
```
|
||||||
python3 -m pip install sanic
|
pip3 install sanic
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To install sanic without `uvloop` or `ujson` using bash, you can provide either or both of these environmental variables
|
||||||
|
using any truthy string like `'y', 'yes', 't', 'true', 'on', '1'` and setting the `SANIC_NO_X` (`X` = `UVLOOP`/`UJSON`)
|
||||||
|
to true will stop that features installation.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip3 install sanic
|
||||||
|
```
|
||||||
|
|
||||||
## 2. Create a file called `main.py`
|
## 2. Create a file called `main.py`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
# Logging
|
|
||||||
|
|
||||||
|
|
||||||
Sanic allows you to do different types of logging (access log, error log) on the requests based on the [python3 logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on python3 logging if you want to create a new configuration.
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
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):
|
|
||||||
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:
|
|
||||||
|
|
||||||
```python
|
|
||||||
app = Sanic('test', log_config=LOGGING_CONFIG)
|
|
||||||
```
|
|
||||||
|
|
||||||
And to close logging, simply assign access_log=False:
|
|
||||||
|
|
||||||
```python
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(access_log=False)
|
|
||||||
```
|
|
||||||
|
|
||||||
This would skip calling logging functions when handling requests.
|
|
||||||
And you could even do further in production to gain extra speed:
|
|
||||||
|
|
||||||
```python
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# disable debug messages
|
|
||||||
app.run(debug=False, access_log=False)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
By default, log_config parameter is set to use sanic.log.LOGGING_CONFIG_DEFAULTS dictionary for configuration.
|
|
||||||
|
|
||||||
There are three `loggers` used in sanic, and **must be defined if you want to create your own logging configuration**:
|
|
||||||
|
|
||||||
- sanic.root:<br>
|
|
||||||
Used to log internal messages.
|
|
||||||
|
|
||||||
- sanic.error:<br>
|
|
||||||
Used to log error logs.
|
|
||||||
|
|
||||||
- sanic.access:<br>
|
|
||||||
Used to log access logs.
|
|
||||||
|
|
||||||
#### Log format:
|
|
||||||
|
|
||||||
In addition to default parameters provided by python (asctime, levelname, message),
|
|
||||||
Sanic provides additional parameters for access logger with:
|
|
||||||
|
|
||||||
- host (str)<br>
|
|
||||||
request.ip
|
|
||||||
|
|
||||||
|
|
||||||
- request (str)<br>
|
|
||||||
request.method + " " + request.url
|
|
||||||
|
|
||||||
|
|
||||||
- status (int)<br>
|
|
||||||
response.status
|
|
||||||
|
|
||||||
|
|
||||||
- byte (int)<br>
|
|
||||||
len(response.body)
|
|
||||||
|
|
||||||
|
|
||||||
The default access log format is
|
|
||||||
```python
|
|
||||||
%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d
|
|
||||||
```
|
|
103
docs/sanic/logging.rst
Normal file
103
docs/sanic/logging.rst
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
Logging
|
||||||
|
=======
|
||||||
|
|
||||||
|
Sanic allows you to do different types of logging (access log, error
|
||||||
|
log) on the requests based on the `python3 logging API`_. You should
|
||||||
|
have some basic knowledge on python3 logging if you want to create a new
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
A simple example using default settings would be like this:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.log import logger
|
||||||
|
from sanic.response import text
|
||||||
|
|
||||||
|
app = Sanic('test')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def test(request):
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
app = Sanic('test', log_config=LOGGING_CONFIG)
|
||||||
|
|
||||||
|
And to close logging, simply assign access_log=False:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(access_log=False)
|
||||||
|
|
||||||
|
This would skip calling logging functions when handling requests. And
|
||||||
|
you could even do further in production to gain extra speed:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# disable debug messages
|
||||||
|
app.run(debug=False, access_log=False)
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, ``log_config`` parameter is set to use
|
||||||
|
``sanic.log.LOGGING_CONFIG_DEFAULTS`` dictionary for configuration.
|
||||||
|
|
||||||
|
There are three ``loggers`` used in sanic, and **must be defined if you
|
||||||
|
want to create your own logging configuration**:
|
||||||
|
|
||||||
|
================ ==============================
|
||||||
|
Logger Name Usecase
|
||||||
|
================ ==============================
|
||||||
|
``sanic.root`` Used to log internal messages.
|
||||||
|
``sanic.error`` Used to log error logs.
|
||||||
|
``sanic.access`` Used to log access logs.
|
||||||
|
================ ==============================
|
||||||
|
|
||||||
|
Log format:
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
In addition to default parameters provided by python (``asctime``,
|
||||||
|
``levelname``, ``message``), Sanic provides additional parameters for
|
||||||
|
access logger with:
|
||||||
|
|
||||||
|
===================== ========================================== ========
|
||||||
|
Log Context Parameter Parameter Value Datatype
|
||||||
|
===================== ========================================== ========
|
||||||
|
``host`` ``request.ip`` str
|
||||||
|
``request`` ``request.method`` + " " + ``request.url`` str
|
||||||
|
``status`` ``response.status`` int
|
||||||
|
``byte`` ``len(response.body)`` int
|
||||||
|
===================== ========================================== ========
|
||||||
|
|
||||||
|
The default access log format is ``%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d``
|
||||||
|
|
||||||
|
.. _python3 logging API: https://docs.python.org/3/howto/logging.html
|
|
@ -164,24 +164,24 @@ url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
|
||||||
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
|
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
|
||||||
# /posts/5?arg_one=one&arg_one=two
|
# /posts/5?arg_one=one&arg_one=two
|
||||||
```
|
```
|
||||||
- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example:
|
- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not supported now and will be ignored). For example:
|
||||||
```python
|
```python
|
||||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
|
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
|
||||||
# /posts/5?arg_one=one#anchor
|
# /posts/5?arg_one=one#anchor
|
||||||
|
|
||||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
|
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
|
||||||
# //server/posts/5?arg_one=one
|
# //server/posts/5?arg_one=one
|
||||||
# _external requires passed argument _server or SERVER_NAME in app.config or url will be same as no _external
|
# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
|
||||||
|
|
||||||
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
|
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
|
||||||
# http://server/posts/5?arg_one=one
|
# http://server/posts/5?arg_one=one
|
||||||
# when specifying _scheme, _external must be True
|
# when specifying _scheme, _external must be True
|
||||||
|
|
||||||
# you can pass all special arguments one time
|
# you can pass all special arguments at once
|
||||||
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
|
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
|
||||||
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
|
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
|
||||||
```
|
```
|
||||||
- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown.
|
- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be raised.
|
||||||
|
|
||||||
## WebSocket routes
|
## WebSocket routes
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ async def feed(request, ws):
|
||||||
app.add_websocket_route(my_websocket_handler, '/feed')
|
app.add_websocket_route(my_websocket_handler, '/feed')
|
||||||
```
|
```
|
||||||
|
|
||||||
Handlers for a WebSocket route are passed the request as first argument, and a
|
Handlers to a WebSocket route are invoked with the request as first argument, and a
|
||||||
WebSocket protocol object as second argument. The protocol object has `send`
|
WebSocket protocol object as second argument. The protocol object has `send`
|
||||||
and `recv` methods to send and receive data respectively.
|
and `recv` methods to send and receive data respectively.
|
||||||
|
|
||||||
|
@ -243,7 +243,8 @@ app.blueprint(bp)
|
||||||
|
|
||||||
## User defined route name
|
## User defined route name
|
||||||
|
|
||||||
You can pass `name` to change the route name to avoid using the default name (`handler.__name__`).
|
A custom route name can be used by passing a `name` argument while registering the route which will
|
||||||
|
override the default route name generated using the `handler.__name__` attribute.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
|
@ -305,8 +306,8 @@ def handler(request):
|
||||||
|
|
||||||
## Build URL for static files
|
## Build URL for static files
|
||||||
|
|
||||||
You can use `url_for` for static file url building now.
|
Sanic supports using `url_for` method to build static file urls. In case if the static url
|
||||||
If it's for file directly, `filename` can be ignored.
|
is pointing to a directory, `filename` parameter to the `url_for` can be ignored. q
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Request Streaming
|
## Request Streaming
|
||||||
|
|
||||||
Sanic allows you to get request data by stream, as below. When the request ends, `request.stream.get()` returns `None`. Only post, put and patch decorator have stream argument.
|
Sanic allows you to get request data by stream, as below. When the request ends, `await request.stream.read()` returns `None`. Only post, put and patch decorator have stream argument.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
@ -22,7 +22,7 @@ class SimpleView(HTTPMethodView):
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
@ -33,7 +33,7 @@ class SimpleView(HTTPMethodView):
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
body = body.decode('utf-8').replace('1', 'A')
|
body = body.decode('utf-8').replace('1', 'A')
|
||||||
|
@ -45,7 +45,7 @@ async def handler(request):
|
||||||
async def bp_handler(request):
|
async def bp_handler(request):
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8').replace('1', 'A')
|
result += body.decode('utf-8').replace('1', 'A')
|
||||||
|
@ -55,7 +55,7 @@ async def bp_handler(request):
|
||||||
async def post_handler(request):
|
async def post_handler(request):
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
WebSocket
|
WebSocket
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Sanic supports websockets, to setup a WebSocket:
|
Sanic provides an easy to user abstraction on top of `websockets`. To setup a WebSocket:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ decorator:
|
||||||
app.add_websocket_route(feed, '/feed')
|
app.add_websocket_route(feed, '/feed')
|
||||||
|
|
||||||
|
|
||||||
Handlers for a WebSocket route are passed the request as first argument, and a
|
Handlers for a WebSocket route is invoked with the request as first argument, and a
|
||||||
WebSocket protocol object as second argument. The protocol object has ``send``
|
WebSocket protocol object as second argument. The protocol object has ``send``
|
||||||
and ``recv`` methods to send and receive data respectively.
|
and ``recv`` methods to send and receive data respectively.
|
||||||
|
|
||||||
|
|
61
examples/logdna_example.py
Normal file
61
examples/logdna_example.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
from os import getenv
|
||||||
|
from platform import node
|
||||||
|
from uuid import getnode as get_mac
|
||||||
|
|
||||||
|
from logdna import LogDNAHandler
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
from sanic.request import Request
|
||||||
|
|
||||||
|
log = logging.getLogger('logdna')
|
||||||
|
log.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def get_my_ip_address(remote_server="google.com"):
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
s.connect((remote_server, 80))
|
||||||
|
return s.getsockname()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_mac_address():
|
||||||
|
h = iter(hex(get_mac())[2:].zfill(12))
|
||||||
|
return ":".join(i + next(h) for i in h)
|
||||||
|
|
||||||
|
|
||||||
|
logdna_options = {
|
||||||
|
"app": __name__,
|
||||||
|
"index_meta": True,
|
||||||
|
"hostname": node(),
|
||||||
|
"ip": get_my_ip_address(),
|
||||||
|
"mac": get_mac_address()
|
||||||
|
}
|
||||||
|
|
||||||
|
logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options)
|
||||||
|
|
||||||
|
logdna = logging.getLogger(__name__)
|
||||||
|
logdna.setLevel(logging.INFO)
|
||||||
|
logdna.addHandler(logdna_handler)
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.middleware
|
||||||
|
def log_request(request: Request):
|
||||||
|
logdna.info("I was Here with a new Request to URL: {}".format(request.url))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def default(request):
|
||||||
|
return json({
|
||||||
|
"response": "I was here"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=getenv("PORT", 8080)
|
||||||
|
)
|
37
examples/raygun_example.py
Normal file
37
examples/raygun_example.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
from raygun4py.raygunprovider import RaygunSender
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.exceptions import SanicException
|
||||||
|
from sanic.handlers import ErrorHandler
|
||||||
|
|
||||||
|
|
||||||
|
class RaygunExceptionReporter(ErrorHandler):
|
||||||
|
|
||||||
|
def __init__(self, raygun_api_key=None):
|
||||||
|
super().__init__()
|
||||||
|
if raygun_api_key is None:
|
||||||
|
raygun_api_key = getenv("RAYGUN_API_KEY")
|
||||||
|
|
||||||
|
self.sender = RaygunSender(raygun_api_key)
|
||||||
|
|
||||||
|
def default(self, request, exception):
|
||||||
|
self.sender.send_exception(exception=exception)
|
||||||
|
return super().default(request, exception)
|
||||||
|
|
||||||
|
|
||||||
|
raygun_error_reporter = RaygunExceptionReporter()
|
||||||
|
app = Sanic(__name__, error_handler=raygun_error_reporter)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/raise")
|
||||||
|
async def test(request):
|
||||||
|
raise SanicException('You Broke It!')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=getenv("PORT", 8080)
|
||||||
|
)
|
30
examples/rollbar_example.py
Normal file
30
examples/rollbar_example.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import rollbar
|
||||||
|
|
||||||
|
from sanic.handlers import ErrorHandler
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.exceptions import SanicException
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
rollbar.init(getenv("ROLLBAR_API_KEY"))
|
||||||
|
|
||||||
|
|
||||||
|
class RollbarExceptionHandler(ErrorHandler):
|
||||||
|
|
||||||
|
def default(self, request, exception):
|
||||||
|
rollbar.report_message(str(exception))
|
||||||
|
return super().default(request, exception)
|
||||||
|
|
||||||
|
|
||||||
|
app = Sanic(__name__, error_handler=RollbarExceptionHandler())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/raise")
|
||||||
|
def create_error(request):
|
||||||
|
raise SanicException("I was here and I don't like where I am")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=getenv("PORT", 8080)
|
||||||
|
)
|
35
examples/sentry_example.py
Normal file
35
examples/sentry_example.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
from sentry_sdk import init as sentry_init
|
||||||
|
from sentry_sdk.integrations.sanic import SanicIntegration
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
|
||||||
|
sentry_init(
|
||||||
|
dsn=getenv("SENTRY_DSN"),
|
||||||
|
integrations=[SanicIntegration()],
|
||||||
|
)
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
@app.route("/working")
|
||||||
|
async def working_path(request):
|
||||||
|
return json({
|
||||||
|
"response": "Working API Response"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
|
@app.route("/raise-error")
|
||||||
|
async def raise_error(request):
|
||||||
|
raise Exception("Testing Sentry Integration")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=getenv("PORT", 8080)
|
||||||
|
)
|
304
release.py
Executable file
304
release.py
Executable file
|
@ -0,0 +1,304 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from argparse import ArgumentParser, Namespace
|
||||||
|
from collections import OrderedDict
|
||||||
|
from configparser import RawConfigParser
|
||||||
|
from datetime import datetime
|
||||||
|
from json import dumps
|
||||||
|
from os import path
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
from jinja2 import Environment, BaseLoader
|
||||||
|
from requests import patch
|
||||||
|
|
||||||
|
GIT_COMMANDS = {
|
||||||
|
"get_tag": ["git describe --tags --abbrev=0"],
|
||||||
|
"commit_version_change": [
|
||||||
|
"git add . && git commit -m 'Bumping up version from "
|
||||||
|
"{current_version} to {new_version}'"
|
||||||
|
],
|
||||||
|
"create_new_tag": [
|
||||||
|
"git tag -a {new_version} -m 'Bumping up version from "
|
||||||
|
"{current_version} to {new_version}'"
|
||||||
|
],
|
||||||
|
"push_tag": ["git push origin {new_version}"],
|
||||||
|
"get_change_log": [
|
||||||
|
'git log --no-merges --pretty=format:"%h::: %cn::: %s" '
|
||||||
|
"{current_version}.."
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RELEASE_NOTE_TEMPLATE = """
|
||||||
|
# {{ release_name }} - {% now 'utc', '%Y-%m-%d' %}
|
||||||
|
|
||||||
|
To see the exhaustive list of pull requests included in this release see:
|
||||||
|
https://github.com/huge-success/sanic/milestone/{{milestone}}?closed=1
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
{% for row in changelogs %}
|
||||||
|
* {{ row -}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
{% for author in authors %}
|
||||||
|
* {{ author -}}
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
JINJA_RELEASE_NOTE_TEMPLATE = Environment(
|
||||||
|
loader=BaseLoader, extensions=["jinja2_time.TimeExtension"]
|
||||||
|
).from_string(RELEASE_NOTE_TEMPLATE)
|
||||||
|
|
||||||
|
RELEASE_NOTE_UPDATE_URL = (
|
||||||
|
"https://api.github.com/repos/huge-success/sanic/releases/tags/"
|
||||||
|
"{new_version}?access_token={token}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_shell_command(command: list):
|
||||||
|
try:
|
||||||
|
process = Popen(
|
||||||
|
command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True
|
||||||
|
)
|
||||||
|
output, error = process.communicate()
|
||||||
|
return_code = process.returncode
|
||||||
|
return output.decode("utf-8"), error, return_code
|
||||||
|
except:
|
||||||
|
return None, None, -1
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_default_calendar_release_version():
|
||||||
|
return datetime.now().strftime("%y.%m.0")
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_current_version(config_file: str) -> str:
|
||||||
|
if path.isfile(config_file):
|
||||||
|
config_parser = RawConfigParser()
|
||||||
|
with open(config_file) as cfg:
|
||||||
|
config_parser.read_file(cfg)
|
||||||
|
return (
|
||||||
|
config_parser.get("version", "current_version")
|
||||||
|
or _fetch_default_calendar_release_version()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return _fetch_default_calendar_release_version()
|
||||||
|
|
||||||
|
|
||||||
|
def _change_micro_version(current_version: str):
|
||||||
|
version_string = current_version.split(".")
|
||||||
|
version_string[-1] = str((int(version_string[-1]) + 1))
|
||||||
|
return ".".join(version_string)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_new_version(
|
||||||
|
config_file: str = "./setup.cfg",
|
||||||
|
current_version: str = None,
|
||||||
|
micro_release: bool = False,
|
||||||
|
):
|
||||||
|
if micro_release:
|
||||||
|
if current_version:
|
||||||
|
return _change_micro_version(current_version)
|
||||||
|
elif config_file:
|
||||||
|
return _change_micro_version(_fetch_current_version(config_file))
|
||||||
|
else:
|
||||||
|
return _fetch_default_calendar_release_version()
|
||||||
|
else:
|
||||||
|
return _fetch_default_calendar_release_version()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_current_tag(git_command_name="get_tag"):
|
||||||
|
global GIT_COMMANDS
|
||||||
|
command = GIT_COMMANDS.get(git_command_name)
|
||||||
|
out, err, ret = _run_shell_command(command)
|
||||||
|
if len(str(out)):
|
||||||
|
return str(out).split("\n")[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _update_release_version_for_sanic(
|
||||||
|
current_version, new_version, config_file
|
||||||
|
):
|
||||||
|
config_parser = RawConfigParser()
|
||||||
|
with open(config_file) as cfg:
|
||||||
|
config_parser.read_file(cfg)
|
||||||
|
config_parser.set("version", "current_version", new_version)
|
||||||
|
|
||||||
|
version_file = config_parser.get("version", "file")
|
||||||
|
current_version_line = config_parser.get(
|
||||||
|
"version", "current_version_pattern"
|
||||||
|
).format(current_version=current_version)
|
||||||
|
new_version_line = config_parser.get(
|
||||||
|
"version", "new_version_pattern"
|
||||||
|
).format(new_version=new_version)
|
||||||
|
|
||||||
|
with open(version_file) as init_file:
|
||||||
|
data = init_file.read()
|
||||||
|
|
||||||
|
new_data = data.replace(current_version_line, new_version_line)
|
||||||
|
with open(version_file, "w") as init_file:
|
||||||
|
init_file.write(new_data)
|
||||||
|
|
||||||
|
with open(config_file, "w") as config:
|
||||||
|
config_parser.write(config)
|
||||||
|
|
||||||
|
command = GIT_COMMANDS.get("commit_version_change")
|
||||||
|
command[0] = command[0].format(
|
||||||
|
new_version=new_version, current_version=current_version
|
||||||
|
)
|
||||||
|
_, err, ret = _run_shell_command(command)
|
||||||
|
if int(ret) != 0:
|
||||||
|
print(
|
||||||
|
"Failed to Commit Version upgrade changes to Sanic: {}".format(
|
||||||
|
err.decode("utf-8")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_change_log(current_version: str = None):
|
||||||
|
global GIT_COMMANDS
|
||||||
|
command = GIT_COMMANDS.get("get_change_log")
|
||||||
|
command[0] = command[0].format(current_version=current_version)
|
||||||
|
output, error, ret = _run_shell_command(command=command)
|
||||||
|
if not len(str(output)):
|
||||||
|
print("Unable to Fetch Change log details to update the Release Note")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
commit_details = OrderedDict()
|
||||||
|
commit_details["authors"] = dict()
|
||||||
|
commit_details["commits"] = list()
|
||||||
|
|
||||||
|
for line in str(output).split("\n"):
|
||||||
|
commit, author, description = line.split(":::")
|
||||||
|
if "GitHub" not in author:
|
||||||
|
commit_details["authors"][author] = 1
|
||||||
|
commit_details["commits"].append(" - ".join([commit, description]))
|
||||||
|
|
||||||
|
return commit_details
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_markdown_document(
|
||||||
|
milestone, release_name, current_version, release_version
|
||||||
|
):
|
||||||
|
global JINJA_RELEASE_NOTE_TEMPLATE
|
||||||
|
release_name = release_name or release_version
|
||||||
|
change_log = _generate_change_log(current_version=current_version)
|
||||||
|
return JINJA_RELEASE_NOTE_TEMPLATE.render(
|
||||||
|
release_name=release_name,
|
||||||
|
milestone=milestone,
|
||||||
|
changelogs=change_log["commits"],
|
||||||
|
authors=change_log["authors"].keys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tag_release(new_version, current_version, milestone, release_name, token):
|
||||||
|
global GIT_COMMANDS
|
||||||
|
global RELEASE_NOTE_UPDATE_URL
|
||||||
|
for command_name in ["create_new_tag", "push_tag"]:
|
||||||
|
command = GIT_COMMANDS.get(command_name)
|
||||||
|
command[0] = command[0].format(
|
||||||
|
new_version=new_version, current_version=current_version
|
||||||
|
)
|
||||||
|
out, error, ret = _run_shell_command(command=command)
|
||||||
|
if int(ret) != 0:
|
||||||
|
print("Failed to execute the command: {}".format(command[0]))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
change_log = _generate_markdown_document(
|
||||||
|
milestone, release_name, current_version, new_version
|
||||||
|
)
|
||||||
|
|
||||||
|
body = {"name": release_name or new_version, "body": change_log}
|
||||||
|
|
||||||
|
headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
response = patch(
|
||||||
|
RELEASE_NOTE_UPDATE_URL.format(new_version=new_version, token=token),
|
||||||
|
data=dumps(body),
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
|
def release(args: Namespace):
|
||||||
|
current_tag = _get_current_tag()
|
||||||
|
current_version = _fetch_current_version(args.config)
|
||||||
|
if current_tag and current_version not in current_tag:
|
||||||
|
print(
|
||||||
|
"Tag mismatch between what's in git and what was provided by "
|
||||||
|
"--current-version. Existing: {}, Give: {}".format(
|
||||||
|
current_tag, current_version
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
new_version = args.release_version or _get_new_version(
|
||||||
|
args.config, current_version, args.micro_release
|
||||||
|
)
|
||||||
|
_update_release_version_for_sanic(
|
||||||
|
current_version=current_version,
|
||||||
|
new_version=new_version,
|
||||||
|
config_file=args.config,
|
||||||
|
)
|
||||||
|
_tag_release(
|
||||||
|
current_version=current_version,
|
||||||
|
new_version=new_version,
|
||||||
|
milestone=args.milestone,
|
||||||
|
release_name=args.release_name,
|
||||||
|
token=args.token,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli = ArgumentParser(description="Sanic Release Manager")
|
||||||
|
cli.add_argument(
|
||||||
|
"--release-version",
|
||||||
|
"-r",
|
||||||
|
help="New Version to use for Release",
|
||||||
|
default=_fetch_default_calendar_release_version(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--current-version",
|
||||||
|
"-cv",
|
||||||
|
help="Current Version to default in case if you don't want to "
|
||||||
|
"use the version configuration files",
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--config",
|
||||||
|
"-c",
|
||||||
|
help="Configuration file used for release",
|
||||||
|
default="./setup.cfg",
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--token",
|
||||||
|
"-t",
|
||||||
|
help="Git access token with necessary access to Huge Sanic Org",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--milestone",
|
||||||
|
"-ms",
|
||||||
|
help="Git Release milestone information to include in relase note",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--release-name",
|
||||||
|
"-n",
|
||||||
|
help="Release Name to use if any",
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
cli.add_argument(
|
||||||
|
"--micro-release",
|
||||||
|
"-m",
|
||||||
|
help="Micro Release with patches only",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
args = cli.parse_args()
|
||||||
|
release(args)
|
|
@ -1,13 +0,0 @@
|
||||||
aiofiles
|
|
||||||
aiohttp>=2.3.0,<=3.2.1
|
|
||||||
chardet<=2.3.0
|
|
||||||
beautifulsoup4
|
|
||||||
coverage
|
|
||||||
httptools>=0.0.10
|
|
||||||
flake8
|
|
||||||
pytest==3.3.2
|
|
||||||
tox
|
|
||||||
ujson; sys_platform != "win32" and implementation_name == "cpython"
|
|
||||||
uvloop; sys_platform != "win32" and implementation_name == "cpython"
|
|
||||||
gunicorn
|
|
||||||
multidict>=4.0,<5.0
|
|
|
@ -1,4 +0,0 @@
|
||||||
sphinx
|
|
||||||
sphinx_rtd_theme
|
|
||||||
recommonmark
|
|
||||||
sphinxcontrib-asyncio
|
|
|
@ -1,6 +0,0 @@
|
||||||
aiofiles
|
|
||||||
httptools>=0.0.10
|
|
||||||
ujson; sys_platform != "win32" and implementation_name == "cpython"
|
|
||||||
uvloop; sys_platform != "win32" and implementation_name == "cpython"
|
|
||||||
websockets>=6.0,<7.0
|
|
||||||
multidict>=4.0,<5.0
|
|
|
@ -2,6 +2,6 @@ from sanic.app import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
|
||||||
__version__ = "0.8.3"
|
__version__ = "18.12.0"
|
||||||
|
|
||||||
__all__ = ["Sanic", "Blueprint"]
|
__all__ = ["Sanic", "Blueprint"]
|
||||||
|
|
196
sanic/app.py
196
sanic/app.py
|
@ -204,6 +204,17 @@ class Sanic:
|
||||||
def get(
|
def get(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **GET** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **GET** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"GET"}),
|
methods=frozenset({"GET"}),
|
||||||
|
@ -222,6 +233,17 @@ class Sanic:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **POST** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **POST** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"POST"}),
|
methods=frozenset({"POST"}),
|
||||||
|
@ -241,6 +263,17 @@ class Sanic:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **PUT** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **PUT** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"PUT"}),
|
methods=frozenset({"PUT"}),
|
||||||
|
@ -266,6 +299,17 @@ class Sanic:
|
||||||
def options(
|
def options(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **OPTIONS** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **OPTIONS** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"OPTIONS"}),
|
methods=frozenset({"OPTIONS"}),
|
||||||
|
@ -284,6 +328,17 @@ class Sanic:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **DELETE** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **PATCH** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"PATCH"}),
|
methods=frozenset({"PATCH"}),
|
||||||
|
@ -297,6 +352,17 @@ class Sanic:
|
||||||
def delete(
|
def delete(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **DELETE** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **DELETE** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=frozenset({"DELETE"}),
|
methods=frozenset({"DELETE"}),
|
||||||
|
@ -430,7 +496,22 @@ class Sanic:
|
||||||
subprotocols=None,
|
subprotocols=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
"""A helper method to register a function as a websocket route."""
|
"""
|
||||||
|
A helper method to register a function as a websocket route.
|
||||||
|
|
||||||
|
:param handler: a callable function or instance of a class
|
||||||
|
that can handle the websocket request
|
||||||
|
:param host: Host IP or FQDN details
|
||||||
|
:param uri: URL path that will be mapped to the websocket
|
||||||
|
handler
|
||||||
|
:param strict_slashes: If the API endpoint needs to terminate
|
||||||
|
with a "/" or not
|
||||||
|
:param subprotocols: Subprotocols to be used with websocket
|
||||||
|
handshake
|
||||||
|
:param name: A unique name assigned to the URL so that it can
|
||||||
|
be used with :func:`url_for`
|
||||||
|
:return: Objected decorated by :func:`websocket`
|
||||||
|
"""
|
||||||
if strict_slashes is None:
|
if strict_slashes is None:
|
||||||
strict_slashes = self.strict_slashes
|
strict_slashes = self.strict_slashes
|
||||||
|
|
||||||
|
@ -459,6 +540,16 @@ class Sanic:
|
||||||
self.websocket_enabled = enable
|
self.websocket_enabled = enable
|
||||||
|
|
||||||
def remove_route(self, uri, clean_cache=True, host=None):
|
def remove_route(self, uri, clean_cache=True, host=None):
|
||||||
|
"""
|
||||||
|
This method provides the app user a mechanism by which an already
|
||||||
|
existing route can be removed from the :class:`Sanic` object
|
||||||
|
|
||||||
|
:param uri: URL Path to be removed from the app
|
||||||
|
:param clean_cache: Instruct sanic if it needs to clean up the LRU
|
||||||
|
route cache
|
||||||
|
:param host: IP address or FQDN specific to the host
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.router.remove(uri, clean_cache, host)
|
self.router.remove(uri, clean_cache, host)
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
|
@ -481,6 +572,21 @@ class Sanic:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def register_middleware(self, middleware, attach_to="request"):
|
def register_middleware(self, middleware, attach_to="request"):
|
||||||
|
"""
|
||||||
|
Register an application level middleware that will be attached
|
||||||
|
to all the API URLs registered under this application.
|
||||||
|
|
||||||
|
This method is internally invoked by the :func:`middleware`
|
||||||
|
decorator provided at the app level.
|
||||||
|
|
||||||
|
:param middleware: Callback method to be attached to the
|
||||||
|
middleware
|
||||||
|
:param attach_to: The state at which the middleware needs to be
|
||||||
|
invoked in the lifecycle of an *HTTP Request*.
|
||||||
|
**request** - Invoke before the request is processed
|
||||||
|
**response** - Invoke before the response is returned back
|
||||||
|
:return: decorated method
|
||||||
|
"""
|
||||||
if attach_to == "request":
|
if attach_to == "request":
|
||||||
self.request_middleware.append(middleware)
|
self.request_middleware.append(middleware)
|
||||||
if attach_to == "response":
|
if attach_to == "response":
|
||||||
|
@ -489,10 +595,14 @@ class Sanic:
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def middleware(self, middleware_or_request):
|
def middleware(self, middleware_or_request):
|
||||||
"""Decorate and register middleware to be called before a request.
|
|
||||||
Can either be called as @app.middleware or @app.middleware('request')
|
|
||||||
"""
|
"""
|
||||||
|
Decorate and register middleware to be called before a request.
|
||||||
|
Can either be called as *@app.middleware* or
|
||||||
|
*@app.middleware('request')*
|
||||||
|
|
||||||
|
:param: middleware_or_request: Optional parameter to use for
|
||||||
|
identifying which type of middleware is being registered.
|
||||||
|
"""
|
||||||
# Detect which way this was called, @middleware or @middleware('AT')
|
# Detect which way this was called, @middleware or @middleware('AT')
|
||||||
if callable(middleware_or_request):
|
if callable(middleware_or_request):
|
||||||
return self.register_middleware(middleware_or_request)
|
return self.register_middleware(middleware_or_request)
|
||||||
|
@ -516,8 +626,30 @@ class Sanic:
|
||||||
strict_slashes=None,
|
strict_slashes=None,
|
||||||
content_type=None,
|
content_type=None,
|
||||||
):
|
):
|
||||||
"""Register a root to serve files from. The input can either be a
|
"""
|
||||||
file or a directory. See
|
Register a root to serve files from. The input can either be a
|
||||||
|
file or a directory. This method will enable an easy and simple way
|
||||||
|
to setup the :class:`Route` necessary to serve the static files.
|
||||||
|
|
||||||
|
:param uri: URL path to be used for serving static content
|
||||||
|
:param file_or_directory: Path for the Static file/directory with
|
||||||
|
static files
|
||||||
|
:param pattern: Regex Pattern identifying the valid static files
|
||||||
|
:param use_modified_since: If true, send file modified time, and return
|
||||||
|
not modified if the browser's matches the server's
|
||||||
|
:param use_content_range: If true, process header for range requests
|
||||||
|
and sends the file part that is requested
|
||||||
|
:param stream_large_files: If true, use the
|
||||||
|
:func:`StreamingHTTPResponse.file_stream` handler rather
|
||||||
|
than the :func:`HTTPResponse.file` handler to send the file.
|
||||||
|
If this is an integer, this represents the threshold size to
|
||||||
|
switch to :func:`StreamingHTTPResponse.file_stream`
|
||||||
|
:param name: user defined name used for url_for
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`Sanic` to check if the request
|
||||||
|
URLs need to terminate with a */*
|
||||||
|
:param content_type: user defined content type for header
|
||||||
|
:return: None
|
||||||
"""
|
"""
|
||||||
static_register(
|
static_register(
|
||||||
self,
|
self,
|
||||||
|
@ -555,7 +687,17 @@ class Sanic:
|
||||||
blueprint.register(self, options)
|
blueprint.register(self, options)
|
||||||
|
|
||||||
def register_blueprint(self, *args, **kwargs):
|
def register_blueprint(self, *args, **kwargs):
|
||||||
# TODO: deprecate 1.0
|
"""
|
||||||
|
Proxy method provided for invoking the :func:`blueprint` method
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
To be deprecated in 1.0. Use :func:`blueprint` instead.
|
||||||
|
|
||||||
|
:param args: Blueprint object or (list, tuple) thereof
|
||||||
|
:param kwargs: option dictionary with blueprint defaults
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
warnings.simplefilter("default")
|
warnings.simplefilter("default")
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
@ -700,6 +842,9 @@ class Sanic:
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def converted_response_type(self, response):
|
def converted_response_type(self, response):
|
||||||
|
"""
|
||||||
|
No implementation provided.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def handle_request(self, request, write_callback, stream_callback):
|
async def handle_request(self, request, write_callback, stream_callback):
|
||||||
|
@ -835,14 +980,6 @@ class Sanic:
|
||||||
access_log=True,
|
access_log=True,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
if "loop" in kwargs:
|
|
||||||
raise TypeError(
|
|
||||||
"loop is not a valid argument. To use an existing loop, "
|
|
||||||
"change to create_server().\nSee more: "
|
|
||||||
"https://sanic.readthedocs.io/en/latest/sanic/deploying.html"
|
|
||||||
"#asynchronous-support"
|
|
||||||
)
|
|
||||||
|
|
||||||
"""Run the HTTP Server and listen until keyboard interrupt or term
|
"""Run the HTTP Server and listen until keyboard interrupt or term
|
||||||
signal. On termination, drain connections before closing.
|
signal. On termination, drain connections before closing.
|
||||||
|
|
||||||
|
@ -852,14 +989,22 @@ class Sanic:
|
||||||
:param ssl: SSLContext, or location of certificate and key
|
:param ssl: SSLContext, or location of certificate and key
|
||||||
for SSL encryption of worker(s)
|
for SSL encryption of worker(s)
|
||||||
:param sock: Socket for the server to accept connections from
|
:param sock: Socket for the server to accept connections from
|
||||||
:param workers: Number of processes
|
:param workers: Number of processes received before it is respected
|
||||||
received before it is respected
|
:param backlog: a number of unaccepted connections that the system
|
||||||
:param backlog:
|
will allow before refusing new connections
|
||||||
:param stop_event:
|
:param stop_event: event to be triggered before stopping the app
|
||||||
:param register_sys_signals:
|
:param register_sys_signals: Register SIG* events
|
||||||
:param protocol: Subclass of asyncio protocol class
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
|
if "loop" in kwargs:
|
||||||
|
raise TypeError(
|
||||||
|
"loop is not a valid argument. To use an existing loop, "
|
||||||
|
"change to create_server().\nSee more: "
|
||||||
|
"https://sanic.readthedocs.io/en/latest/sanic/deploying.html"
|
||||||
|
"#asynchronous-support"
|
||||||
|
)
|
||||||
|
|
||||||
# Default auto_reload to false
|
# Default auto_reload to false
|
||||||
auto_reload = False
|
auto_reload = False
|
||||||
# If debug is set, default it to true (unless on windows)
|
# If debug is set, default it to true (unless on windows)
|
||||||
|
@ -943,10 +1088,16 @@ class Sanic:
|
||||||
stop_event=None,
|
stop_event=None,
|
||||||
access_log=True,
|
access_log=True,
|
||||||
):
|
):
|
||||||
"""Asynchronous version of `run`.
|
"""
|
||||||
|
Asynchronous version of :func:`run`.
|
||||||
|
|
||||||
NOTE: This does not support multiprocessing and is not the preferred
|
This method will take care of the operations necessary to invoke
|
||||||
way to run a Sanic application.
|
the *before_start* events via :func:`trigger_events` method invocation
|
||||||
|
before starting the *sanic* app in Async mode.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This does not support multiprocessing and is not the preferred
|
||||||
|
way to run a :class:`Sanic` application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if sock is None:
|
if sock is None:
|
||||||
|
@ -1071,6 +1222,7 @@ class Sanic:
|
||||||
"response_timeout": self.config.RESPONSE_TIMEOUT,
|
"response_timeout": self.config.RESPONSE_TIMEOUT,
|
||||||
"keep_alive_timeout": self.config.KEEP_ALIVE_TIMEOUT,
|
"keep_alive_timeout": self.config.KEEP_ALIVE_TIMEOUT,
|
||||||
"request_max_size": self.config.REQUEST_MAX_SIZE,
|
"request_max_size": self.config.REQUEST_MAX_SIZE,
|
||||||
|
"request_buffer_queue_size": self.config.REQUEST_BUFFER_QUEUE_SIZE,
|
||||||
"keep_alive": self.config.KEEP_ALIVE,
|
"keep_alive": self.config.KEEP_ALIVE,
|
||||||
"loop": loop,
|
"loop": loop,
|
||||||
"register_sys_signals": register_sys_signals,
|
"register_sys_signals": register_sys_signals,
|
||||||
|
|
|
@ -38,11 +38,17 @@ class Blueprint:
|
||||||
version=None,
|
version=None,
|
||||||
strict_slashes=False,
|
strict_slashes=False,
|
||||||
):
|
):
|
||||||
"""Create a new blueprint
|
"""
|
||||||
|
In *Sanic* terminology, a **Blueprint** is a logical collection of
|
||||||
|
URLs that perform a specific set of tasks which can be identified by
|
||||||
|
a unique name.
|
||||||
|
|
||||||
:param name: unique name of the blueprint
|
:param name: unique name of the blueprint
|
||||||
:param url_prefix: URL to be prefixed before all route URLs
|
:param url_prefix: URL to be prefixed before all route URLs
|
||||||
:param strict_slashes: strict to trailing slash
|
:param host: IP Address of FQDN for the sanic server to use.
|
||||||
|
:param version: Blueprint Version
|
||||||
|
:param strict_slashes: Enforce the API urls are requested with a
|
||||||
|
training */*
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url_prefix = url_prefix
|
self.url_prefix = url_prefix
|
||||||
|
@ -59,8 +65,9 @@ class Blueprint:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def group(*blueprints, url_prefix=""):
|
def group(*blueprints, url_prefix=""):
|
||||||
"""Create a list of blueprints, optionally
|
"""
|
||||||
grouping them under a general URL prefix.
|
Create a list of blueprints, optionally grouping them under a
|
||||||
|
general URL prefix.
|
||||||
|
|
||||||
:param blueprints: blueprints to be registered as a group
|
:param blueprints: blueprints to be registered as a group
|
||||||
:param url_prefix: URL route to be prepended to all sub-prefixes
|
:param url_prefix: URL route to be prepended to all sub-prefixes
|
||||||
|
@ -83,7 +90,14 @@ class Blueprint:
|
||||||
return bps
|
return bps
|
||||||
|
|
||||||
def register(self, app, options):
|
def register(self, app, options):
|
||||||
"""Register the blueprint to the sanic app."""
|
"""
|
||||||
|
Register the blueprint to the sanic app.
|
||||||
|
|
||||||
|
:param app: Instance of :class:`sanic.app.Sanic` class
|
||||||
|
:param options: Options to be used while registering the
|
||||||
|
blueprint into the app.
|
||||||
|
*url_prefix* - URL Prefix to override the blueprint prefix
|
||||||
|
"""
|
||||||
|
|
||||||
url_prefix = options.get("url_prefix", self.url_prefix)
|
url_prefix = options.get("url_prefix", self.url_prefix)
|
||||||
|
|
||||||
|
@ -160,6 +174,15 @@ class Blueprint:
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
:param methods: list of acceptable HTTP methods.
|
:param methods: list of acceptable HTTP methods.
|
||||||
|
:param host: IP Address of FQDN for the sanic server to use.
|
||||||
|
:param strict_slashes: Enforce the API urls are requested with a
|
||||||
|
training */*
|
||||||
|
:param stream: If the route should provide a streaming support
|
||||||
|
:param version: Blueprint Version
|
||||||
|
:param name: Unique name to identify the Route
|
||||||
|
|
||||||
|
:return a decorated method that when invoked will return an object
|
||||||
|
of type :class:`FutureRoute`
|
||||||
"""
|
"""
|
||||||
if strict_slashes is None:
|
if strict_slashes is None:
|
||||||
strict_slashes = self.strict_slashes
|
strict_slashes = self.strict_slashes
|
||||||
|
@ -196,9 +219,10 @@ class Blueprint:
|
||||||
or class instance with a view_class method.
|
or class instance with a view_class method.
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
:param methods: list of acceptable HTTP methods.
|
:param methods: list of acceptable HTTP methods.
|
||||||
:param host:
|
:param host: IP Address of FQDN for the sanic server to use.
|
||||||
:param strict_slashes:
|
:param strict_slashes: Enforce the API urls are requested with a
|
||||||
:param version:
|
training */*
|
||||||
|
:param version: Blueprint Version
|
||||||
:param name: user defined route name for url_for
|
:param name: user defined route name for url_for
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
|
@ -233,6 +257,11 @@ class Blueprint:
|
||||||
"""Create a blueprint websocket route from a decorated function.
|
"""Create a blueprint websocket route from a decorated function.
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
|
:param host: IP Address of FQDN for the sanic server to use.
|
||||||
|
:param strict_slashes: Enforce the API urls are requested with a
|
||||||
|
training */*
|
||||||
|
:param version: Blueprint Version
|
||||||
|
:param name: Unique name to identify the Websocket Route
|
||||||
"""
|
"""
|
||||||
if strict_slashes is None:
|
if strict_slashes is None:
|
||||||
strict_slashes = self.strict_slashes
|
strict_slashes = self.strict_slashes
|
||||||
|
@ -254,6 +283,9 @@ class Blueprint:
|
||||||
:param handler: function for handling uri requests. Accepts function,
|
:param handler: function for handling uri requests. Accepts function,
|
||||||
or class instance with a view_class method.
|
or class instance with a view_class method.
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
|
:param host: IP Address of FQDN for the sanic server to use.
|
||||||
|
:param version: Blueprint Version
|
||||||
|
:param name: Unique name to identify the Websocket Route
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
self.websocket(uri=uri, host=host, version=version, name=name)(handler)
|
self.websocket(uri=uri, host=host, version=version, name=name)(handler)
|
||||||
|
@ -272,7 +304,14 @@ class Blueprint:
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def middleware(self, *args, **kwargs):
|
def middleware(self, *args, **kwargs):
|
||||||
"""Create a blueprint middleware from a decorated function."""
|
"""
|
||||||
|
Create a blueprint middleware from a decorated function.
|
||||||
|
|
||||||
|
:param args: Positional arguments to be used while invoking the
|
||||||
|
middleware
|
||||||
|
:param kwargs: optional keyword args that can be used with the
|
||||||
|
middleware.
|
||||||
|
"""
|
||||||
|
|
||||||
def register_middleware(_middleware):
|
def register_middleware(_middleware):
|
||||||
future_middleware = FutureMiddleware(_middleware, args, kwargs)
|
future_middleware = FutureMiddleware(_middleware, args, kwargs)
|
||||||
|
@ -288,7 +327,17 @@ class Blueprint:
|
||||||
return register_middleware
|
return register_middleware
|
||||||
|
|
||||||
def exception(self, *args, **kwargs):
|
def exception(self, *args, **kwargs):
|
||||||
"""Create a blueprint exception from a decorated function."""
|
"""
|
||||||
|
This method enables the process of creating a global exception
|
||||||
|
handler for the current blueprint under question.
|
||||||
|
|
||||||
|
:param args: List of Python exceptions to be caught by the handler
|
||||||
|
:param kwargs: Additional optional arguments to be passed to the
|
||||||
|
exception handler
|
||||||
|
|
||||||
|
:return a decorated method to handle global exceptions for any
|
||||||
|
route registered under this blueprint.
|
||||||
|
"""
|
||||||
|
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
exception = FutureException(handler, args, kwargs)
|
exception = FutureException(handler, args, kwargs)
|
||||||
|
@ -319,9 +368,20 @@ class Blueprint:
|
||||||
def get(
|
def get(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **GET** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **GET** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["GET"],
|
methods=frozenset({"GET"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
version=version,
|
version=version,
|
||||||
|
@ -337,9 +397,20 @@ class Blueprint:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **POST** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **POST** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["POST"],
|
methods=frozenset({"POST"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
|
@ -356,9 +427,20 @@ class Blueprint:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **PUT** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **PUT** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["PUT"],
|
methods=frozenset({"PUT"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
|
@ -369,9 +451,20 @@ class Blueprint:
|
||||||
def head(
|
def head(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **HEAD** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **HEAD** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["HEAD"],
|
methods=frozenset({"HEAD"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
version=version,
|
version=version,
|
||||||
|
@ -381,9 +474,20 @@ class Blueprint:
|
||||||
def options(
|
def options(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **OPTIONS** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **OPTIONS** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["OPTIONS"],
|
methods=frozenset({"OPTIONS"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
version=version,
|
version=version,
|
||||||
|
@ -399,9 +503,20 @@ class Blueprint:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **PATCH** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **PATCH** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["PATCH"],
|
methods=frozenset({"PATCH"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
|
@ -412,9 +527,20 @@ class Blueprint:
|
||||||
def delete(
|
def delete(
|
||||||
self, uri, host=None, strict_slashes=None, version=None, name=None
|
self, uri, host=None, strict_slashes=None, version=None, name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Add an API URL under the **DELETE** *HTTP* method
|
||||||
|
|
||||||
|
:param uri: URL to be tagged to **DELETE** method of *HTTP*
|
||||||
|
:param host: Host IP or FQDN for the service to use
|
||||||
|
:param strict_slashes: Instruct :class:`sanic.app.Sanic` to check
|
||||||
|
if the request URLs need to terminate with a */*
|
||||||
|
:param version: API Version
|
||||||
|
:param name: Unique name that can be used to identify the Route
|
||||||
|
:return: Object decorated with :func:`route` method
|
||||||
|
"""
|
||||||
return self.route(
|
return self.route(
|
||||||
uri,
|
uri,
|
||||||
methods=["DELETE"],
|
methods=frozenset({"DELETE"}),
|
||||||
host=host,
|
host=host,
|
||||||
strict_slashes=strict_slashes,
|
strict_slashes=strict_slashes,
|
||||||
version=version,
|
version=version,
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Config(dict):
|
||||||
▀▀▄▄▀
|
▀▀▄▄▀
|
||||||
"""
|
"""
|
||||||
self.REQUEST_MAX_SIZE = 100000000 # 100 megabytes
|
self.REQUEST_MAX_SIZE = 100000000 # 100 megabytes
|
||||||
|
self.REQUEST_BUFFER_QUEUE_SIZE = 100
|
||||||
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
||||||
self.RESPONSE_TIMEOUT = 60 # 60 seconds
|
self.RESPONSE_TIMEOUT = 60 # 60 seconds
|
||||||
self.KEEP_ALIVE = keep_alive
|
self.KEEP_ALIVE = keep_alive
|
||||||
|
|
|
@ -106,6 +106,18 @@ class Cookie(dict):
|
||||||
return super().__setitem__(key, value)
|
return super().__setitem__(key, value)
|
||||||
|
|
||||||
def encode(self, encoding):
|
def encode(self, encoding):
|
||||||
|
"""
|
||||||
|
Encode the cookie content in a specific type of encoding instructed
|
||||||
|
by the developer. Leverages the :func:`str.encode` method provided
|
||||||
|
by python.
|
||||||
|
|
||||||
|
This method can be used to encode and embed ``utf-8`` content into
|
||||||
|
the cookies.
|
||||||
|
|
||||||
|
:param encoding: Encoding to be used with the cookie
|
||||||
|
:return: Cookie encoded in a codec of choosing.
|
||||||
|
:except: UnicodeEncodeError
|
||||||
|
"""
|
||||||
output = ["%s=%s" % (self.key, _quote(self.value))]
|
output = ["%s=%s" % (self.key, _quote(self.value))]
|
||||||
for key, value in self.items():
|
for key, value in self.items():
|
||||||
if key == "max-age":
|
if key == "max-age":
|
||||||
|
|
|
@ -123,7 +123,7 @@ _sanic_exceptions = {}
|
||||||
|
|
||||||
def add_status_code(code):
|
def add_status_code(code):
|
||||||
"""
|
"""
|
||||||
Decorator used for adding exceptions to _sanic_exceptions.
|
Decorator used for adding exceptions to :class:`SanicException`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def class_decorator(cls):
|
def class_decorator(cls):
|
||||||
|
|
|
@ -19,6 +19,18 @@ from sanic.response import html, text
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler:
|
class ErrorHandler:
|
||||||
|
"""
|
||||||
|
Provide :class:`sanic.app.Sanic` application with a mechanism to handle
|
||||||
|
and process any and all uncaught exceptions in a way the application
|
||||||
|
developer will set fit.
|
||||||
|
|
||||||
|
This error handling framework is built into the core that can be extended
|
||||||
|
by the developers to perform a wide range of tasks from recording the error
|
||||||
|
stats to reporting them to an external service that can be used for
|
||||||
|
realtime alerting system.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
handlers = None
|
handlers = None
|
||||||
cached_handlers = None
|
cached_handlers = None
|
||||||
_missing = object()
|
_missing = object()
|
||||||
|
@ -58,9 +70,34 @@ class ErrorHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
def add(self, exception, handler):
|
def add(self, exception, handler):
|
||||||
|
"""
|
||||||
|
Add a new exception handler to an already existing handler object.
|
||||||
|
|
||||||
|
:param exception: Type of exception that need to be handled
|
||||||
|
:param handler: Reference to the method that will handle the exception
|
||||||
|
|
||||||
|
:type exception: :class:`sanic.exceptions.SanicException` or
|
||||||
|
:class:`Exception`
|
||||||
|
:type handler: ``function``
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.handlers.append((exception, handler))
|
self.handlers.append((exception, handler))
|
||||||
|
|
||||||
def lookup(self, exception):
|
def lookup(self, exception):
|
||||||
|
"""
|
||||||
|
Lookup the existing instance of :class:`ErrorHandler` and fetch the
|
||||||
|
registered handler for a specific type of exception.
|
||||||
|
|
||||||
|
This method leverages a dict lookup to speedup the retrieval process.
|
||||||
|
|
||||||
|
:param exception: Type of exception
|
||||||
|
|
||||||
|
:type exception: :class:`sanic.exceptions.SanicException` or
|
||||||
|
:class:`Exception`
|
||||||
|
|
||||||
|
:return: Registered function if found ``None`` otherwise
|
||||||
|
"""
|
||||||
handler = self.cached_handlers.get(type(exception), self._missing)
|
handler = self.cached_handlers.get(type(exception), self._missing)
|
||||||
if handler is self._missing:
|
if handler is self._missing:
|
||||||
for exception_class, handler in self.handlers:
|
for exception_class, handler in self.handlers:
|
||||||
|
@ -75,9 +112,15 @@ class ErrorHandler:
|
||||||
"""Fetches and executes an exception handler and returns a response
|
"""Fetches and executes an exception handler and returns a response
|
||||||
object
|
object
|
||||||
|
|
||||||
:param request: Request
|
:param request: Instance of :class:`sanic.request.Request`
|
||||||
:param exception: Exception to handle
|
:param exception: Exception to handle
|
||||||
:return: Response object
|
|
||||||
|
:type request: :class:`sanic.request.Request`
|
||||||
|
:type exception: :class:`sanic.exceptions.SanicException` or
|
||||||
|
:class:`Exception`
|
||||||
|
|
||||||
|
:return: Wrap the return value obtained from :func:`default`
|
||||||
|
or registered handler for that type of exception.
|
||||||
"""
|
"""
|
||||||
handler = self.lookup(exception)
|
handler = self.lookup(exception)
|
||||||
response = None
|
response = None
|
||||||
|
@ -109,6 +152,20 @@ class ErrorHandler:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def default(self, request, exception):
|
def default(self, request, exception):
|
||||||
|
"""
|
||||||
|
Provide a default behavior for the objects of :class:`ErrorHandler`.
|
||||||
|
If a developer chooses to extent the :class:`ErrorHandler` they can
|
||||||
|
provide a custom implementation for this method to behave in a way
|
||||||
|
they see fit.
|
||||||
|
|
||||||
|
:param request: Incoming request
|
||||||
|
:param exception: Exception object
|
||||||
|
|
||||||
|
:type request: :class:`sanic.request.Request`
|
||||||
|
:type exception: :class:`sanic.exceptions.SanicException` or
|
||||||
|
:class:`Exception`
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
self.log(format_exc())
|
self.log(format_exc())
|
||||||
try:
|
try:
|
||||||
url = repr(request.url)
|
url = repr(request.url)
|
||||||
|
@ -133,7 +190,23 @@ class ErrorHandler:
|
||||||
|
|
||||||
|
|
||||||
class ContentRangeHandler:
|
class ContentRangeHandler:
|
||||||
"""Class responsible for parsing request header"""
|
"""
|
||||||
|
A mechanism to parse and process the incoming request headers to
|
||||||
|
extract the content range information.
|
||||||
|
|
||||||
|
:param request: Incoming api request
|
||||||
|
:param stats: Stats related to the content
|
||||||
|
|
||||||
|
:type request: :class:`sanic.request.Request`
|
||||||
|
:type stats: :class:`posix.stat_result`
|
||||||
|
|
||||||
|
:ivar start: Content Range start
|
||||||
|
:ivar end: Content Range end
|
||||||
|
:ivar size: Length of the content
|
||||||
|
:ivar total: Total size identified by the :class:`posix.stat_result`
|
||||||
|
instance
|
||||||
|
:ivar ContentRangeHandler.headers: Content range header ``dict``
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = ("start", "end", "size", "total", "headers")
|
__slots__ = ("start", "end", "size", "total", "headers")
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -47,6 +48,23 @@ class RequestParameters(dict):
|
||||||
return super().get(name, default)
|
return super().get(name, default)
|
||||||
|
|
||||||
|
|
||||||
|
class StreamBuffer:
|
||||||
|
def __init__(self, buffer_size=100):
|
||||||
|
self._queue = asyncio.Queue(buffer_size)
|
||||||
|
|
||||||
|
async def read(self):
|
||||||
|
""" Stop reading when gets None """
|
||||||
|
payload = await self._queue.get()
|
||||||
|
self._queue.task_done()
|
||||||
|
return payload
|
||||||
|
|
||||||
|
async def put(self, payload):
|
||||||
|
await self._queue.put(payload)
|
||||||
|
|
||||||
|
def is_full(self):
|
||||||
|
return self._queue.full()
|
||||||
|
|
||||||
|
|
||||||
class Request(dict):
|
class Request(dict):
|
||||||
"""Properties of an HTTP request such as URL, headers, etc."""
|
"""Properties of an HTTP request such as URL, headers, etc."""
|
||||||
|
|
||||||
|
|
|
@ -331,6 +331,17 @@ class Router:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_dynamic_route_exists(pattern, routes_to_check, parameters):
|
def check_dynamic_route_exists(pattern, routes_to_check, parameters):
|
||||||
|
"""
|
||||||
|
Check if a URL pattern exists in a list of routes provided based on
|
||||||
|
the comparison of URL pattern and the parameters.
|
||||||
|
|
||||||
|
:param pattern: URL parameter pattern
|
||||||
|
:param routes_to_check: list of dynamic routes either hashable or
|
||||||
|
unhashable routes.
|
||||||
|
:param parameters: List of :class:`Parameter` items
|
||||||
|
:return: Tuple of index and route if matching route exists else
|
||||||
|
-1 for index and None for route
|
||||||
|
"""
|
||||||
for ndx, route in enumerate(routes_to_check):
|
for ndx, route in enumerate(routes_to_check):
|
||||||
if route.pattern == pattern and route.parameters == parameters:
|
if route.pattern == pattern and route.parameters == parameters:
|
||||||
return ndx, route
|
return ndx, route
|
||||||
|
|
|
@ -22,7 +22,7 @@ from sanic.exceptions import (
|
||||||
ServiceUnavailable,
|
ServiceUnavailable,
|
||||||
)
|
)
|
||||||
from sanic.log import access_logger, logger
|
from sanic.log import access_logger, logger
|
||||||
from sanic.request import Request
|
from sanic.request import Request, StreamBuffer
|
||||||
from sanic.response import HTTPResponse
|
from sanic.response import HTTPResponse
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +42,10 @@ class Signal:
|
||||||
|
|
||||||
|
|
||||||
class HttpProtocol(asyncio.Protocol):
|
class HttpProtocol(asyncio.Protocol):
|
||||||
|
"""
|
||||||
|
This class provides a basic HTTP implementation of the sanic framework.
|
||||||
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
# event loop, connection
|
# event loop, connection
|
||||||
"loop",
|
"loop",
|
||||||
|
@ -59,6 +63,7 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
"response_timeout",
|
"response_timeout",
|
||||||
"keep_alive_timeout",
|
"keep_alive_timeout",
|
||||||
"request_max_size",
|
"request_max_size",
|
||||||
|
"request_buffer_queue_size",
|
||||||
"request_class",
|
"request_class",
|
||||||
"is_request_stream",
|
"is_request_stream",
|
||||||
"router",
|
"router",
|
||||||
|
@ -89,11 +94,12 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
request_handler,
|
request_handler,
|
||||||
error_handler,
|
error_handler,
|
||||||
signal=Signal(),
|
signal=Signal(),
|
||||||
connections=set(),
|
connections=None,
|
||||||
request_timeout=60,
|
request_timeout=60,
|
||||||
response_timeout=60,
|
response_timeout=60,
|
||||||
keep_alive_timeout=5,
|
keep_alive_timeout=5,
|
||||||
request_max_size=None,
|
request_max_size=None,
|
||||||
|
request_buffer_queue_size=100,
|
||||||
request_class=None,
|
request_class=None,
|
||||||
access_log=True,
|
access_log=True,
|
||||||
keep_alive=True,
|
keep_alive=True,
|
||||||
|
@ -112,10 +118,11 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.router = router
|
self.router = router
|
||||||
self.signal = signal
|
self.signal = signal
|
||||||
self.access_log = access_log
|
self.access_log = access_log
|
||||||
self.connections = connections
|
self.connections = connections or set()
|
||||||
self.request_handler = request_handler
|
self.request_handler = request_handler
|
||||||
self.error_handler = error_handler
|
self.error_handler = error_handler
|
||||||
self.request_timeout = request_timeout
|
self.request_timeout = request_timeout
|
||||||
|
self.request_buffer_queue_size = request_buffer_queue_size
|
||||||
self.response_timeout = response_timeout
|
self.response_timeout = response_timeout
|
||||||
self.keep_alive_timeout = keep_alive_timeout
|
self.keep_alive_timeout = keep_alive_timeout
|
||||||
self.request_max_size = request_max_size
|
self.request_max_size = request_max_size
|
||||||
|
@ -141,6 +148,13 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def keep_alive(self):
|
def keep_alive(self):
|
||||||
|
"""
|
||||||
|
Check if the connection needs to be kept alive based on the params
|
||||||
|
attached to the `_keep_alive` attribute, :attr:`Signal.stopped`
|
||||||
|
and :func:`HttpProtocol.parser.should_keep_alive`
|
||||||
|
|
||||||
|
:return: ``True`` if connection is to be kept alive ``False`` else
|
||||||
|
"""
|
||||||
return (
|
return (
|
||||||
self._keep_alive
|
self._keep_alive
|
||||||
and not self.signal.stopped
|
and not self.signal.stopped
|
||||||
|
@ -213,8 +227,13 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.write_error(ServiceUnavailable("Response Timeout"))
|
self.write_error(ServiceUnavailable("Response Timeout"))
|
||||||
|
|
||||||
def keep_alive_timeout_callback(self):
|
def keep_alive_timeout_callback(self):
|
||||||
# Check if elapsed time since last response exceeds our configured
|
"""
|
||||||
# maximum keep alive timeout value
|
Check if elapsed time since last response exceeds our configured
|
||||||
|
maximum keep alive timeout value and if so, close the transport
|
||||||
|
pipe and let the response writer handle the error.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
time_elapsed = current_time - self._last_response_time
|
time_elapsed = current_time - self._last_response_time
|
||||||
if time_elapsed < self.keep_alive_timeout:
|
if time_elapsed < self.keep_alive_timeout:
|
||||||
time_left = self.keep_alive_timeout - time_elapsed
|
time_left = self.keep_alive_timeout - time_elapsed
|
||||||
|
@ -298,17 +317,27 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.request
|
self.request
|
||||||
)
|
)
|
||||||
if self._is_stream_handler:
|
if self._is_stream_handler:
|
||||||
self.request.stream = asyncio.Queue()
|
self.request.stream = StreamBuffer(
|
||||||
|
self.request_buffer_queue_size
|
||||||
|
)
|
||||||
self.execute_request_handler()
|
self.execute_request_handler()
|
||||||
|
|
||||||
def on_body(self, body):
|
def on_body(self, body):
|
||||||
if self.is_request_stream and self._is_stream_handler:
|
if self.is_request_stream and self._is_stream_handler:
|
||||||
self._request_stream_task = self.loop.create_task(
|
self._request_stream_task = self.loop.create_task(
|
||||||
self.request.stream.put(body)
|
self.body_append(body)
|
||||||
)
|
)
|
||||||
return
|
else:
|
||||||
self.request.body_push(body)
|
self.request.body_push(body)
|
||||||
|
|
||||||
|
async def body_append(self, body):
|
||||||
|
if self.request.stream.is_full():
|
||||||
|
self.transport.pause_reading()
|
||||||
|
await self.request.stream.put(body)
|
||||||
|
self.transport.resume_reading()
|
||||||
|
else:
|
||||||
|
await self.request.stream.put(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.
|
||||||
# We can cancel and remove the request timeout handler now.
|
# We can cancel and remove the request timeout handler now.
|
||||||
|
@ -324,6 +353,12 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self.execute_request_handler()
|
self.execute_request_handler()
|
||||||
|
|
||||||
def execute_request_handler(self):
|
def execute_request_handler(self):
|
||||||
|
"""
|
||||||
|
Invoke the request handler defined by the
|
||||||
|
:func:`sanic.app.Sanic.handle_request` method
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._response_timeout_handler = self.loop.call_later(
|
self._response_timeout_handler = self.loop.call_later(
|
||||||
self.response_timeout, self.response_timeout_callback
|
self.response_timeout, self.response_timeout_callback
|
||||||
)
|
)
|
||||||
|
@ -338,6 +373,17 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
# Responding
|
# Responding
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
def log_response(self, response):
|
def log_response(self, response):
|
||||||
|
"""
|
||||||
|
Helper method provided to enable the logging of responses in case if
|
||||||
|
the :attr:`HttpProtocol.access_log` is enabled.
|
||||||
|
|
||||||
|
:param response: Response generated for the current request
|
||||||
|
|
||||||
|
:type response: :class:`sanic.response.HTTPResponse` or
|
||||||
|
:class:`sanic.response.StreamingHTTPResponse`
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if self.access_log:
|
if self.access_log:
|
||||||
extra = {"status": getattr(response, "status", 0)}
|
extra = {"status": getattr(response, "status", 0)}
|
||||||
|
|
||||||
|
@ -492,6 +538,20 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
logger.debug("Connection lost before server could close it.")
|
logger.debug("Connection lost before server could close it.")
|
||||||
|
|
||||||
def bail_out(self, message, from_error=False):
|
def bail_out(self, message, from_error=False):
|
||||||
|
"""
|
||||||
|
In case if the transport pipes are closed and the sanic app encounters
|
||||||
|
an error while writing data to the transport pipe, we log the error
|
||||||
|
with proper details.
|
||||||
|
|
||||||
|
:param message: Error message to display
|
||||||
|
:param from_error: If the bail out was invoked while handling an
|
||||||
|
exception scenario.
|
||||||
|
|
||||||
|
:type message: str
|
||||||
|
:type from_error: bool
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if from_error or self.transport.is_closing():
|
if from_error or self.transport.is_closing():
|
||||||
logger.error(
|
logger.error(
|
||||||
"Transport closed @ %s and exception "
|
"Transport closed @ %s and exception "
|
||||||
|
@ -575,6 +635,7 @@ def serve(
|
||||||
ssl=None,
|
ssl=None,
|
||||||
sock=None,
|
sock=None,
|
||||||
request_max_size=None,
|
request_max_size=None,
|
||||||
|
request_buffer_queue_size=100,
|
||||||
reuse_port=False,
|
reuse_port=False,
|
||||||
loop=None,
|
loop=None,
|
||||||
protocol=HttpProtocol,
|
protocol=HttpProtocol,
|
||||||
|
@ -635,6 +696,7 @@ def serve(
|
||||||
outgoing bytes, the low-water limit is a
|
outgoing bytes, the low-water limit is a
|
||||||
quarter of the high-water limit.
|
quarter of the high-water limit.
|
||||||
:param is_request_stream: disable/enable Request.stream
|
:param is_request_stream: disable/enable Request.stream
|
||||||
|
:param request_buffer_queue_size: streaming request buffer queue size
|
||||||
:param router: Router object
|
:param router: Router object
|
||||||
:param graceful_shutdown_timeout: How long take to Force close non-idle
|
:param graceful_shutdown_timeout: How long take to Force close non-idle
|
||||||
connection
|
connection
|
||||||
|
|
11
setup.cfg
11
setup.cfg
|
@ -1,11 +1,8 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
# https://github.com/ambv/black#slices
|
|
||||||
# https://github.com/ambv/black#line-breaks--binary-operators
|
|
||||||
ignore = E203, W503
|
ignore = E203, W503
|
||||||
|
|
||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
atomic=true
|
atomic = true
|
||||||
default_section = THIRDPARTY
|
default_section = THIRDPARTY
|
||||||
include_trailing_comma = true
|
include_trailing_comma = true
|
||||||
known_first_party = sanic
|
known_first_party = sanic
|
||||||
|
@ -15,3 +12,9 @@ lines_after_imports = 2
|
||||||
lines_between_types = 1
|
lines_between_types = 1
|
||||||
multi_line_output = 3
|
multi_line_output = 3
|
||||||
not_skip = __init__.py
|
not_skip = __init__.py
|
||||||
|
|
||||||
|
[version]
|
||||||
|
current_version = 0.8.3
|
||||||
|
file = sanic/__init__.py
|
||||||
|
current_version_pattern = __version__ = "{current_version}"
|
||||||
|
new_version_pattern = __version__ = "{new_version}"
|
||||||
|
|
128
setup.py
128
setup.py
|
@ -4,74 +4,126 @@ Sanic
|
||||||
import codecs
|
import codecs
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
from setuptools.command.test import test as TestCommand
|
||||||
|
|
||||||
|
|
||||||
def open_local(paths, mode='r', encoding='utf8'):
|
class PyTest(TestCommand):
|
||||||
path = os.path.join(
|
"""
|
||||||
os.path.abspath(os.path.dirname(__file__)),
|
Provide a Test runner to be used from setup.py to run unit tests
|
||||||
*paths
|
"""
|
||||||
)
|
|
||||||
|
user_options = [("pytest-args=", "a", "Arguments to pass to pytest")]
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
TestCommand.initialize_options(self)
|
||||||
|
self.pytest_args = ""
|
||||||
|
|
||||||
|
def run_tests(self):
|
||||||
|
import shlex
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
errno = pytest.main(shlex.split(self.pytest_args))
|
||||||
|
sys.exit(errno)
|
||||||
|
|
||||||
|
|
||||||
|
def open_local(paths, mode="r", encoding="utf8"):
|
||||||
|
path = os.path.join(os.path.abspath(os.path.dirname(__file__)), *paths)
|
||||||
|
|
||||||
return codecs.open(path, mode, encoding)
|
return codecs.open(path, mode, encoding)
|
||||||
|
|
||||||
|
|
||||||
with open_local(['sanic', '__init__.py'], encoding='latin1') as fp:
|
with open_local(["sanic", "__init__.py"], encoding="latin1") as fp:
|
||||||
try:
|
try:
|
||||||
version = re.findall(r"^__version__ = \"([^']+)\"\r?$",
|
version = re.findall(
|
||||||
fp.read(), re.M)[0]
|
r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M
|
||||||
|
)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError('Unable to determine version.')
|
raise RuntimeError("Unable to determine version.")
|
||||||
|
|
||||||
|
with open_local(["README.rst"]) as rm:
|
||||||
with open_local(['README.rst']) as rm:
|
|
||||||
long_description = rm.read()
|
long_description = rm.read()
|
||||||
|
|
||||||
setup_kwargs = {
|
setup_kwargs = {
|
||||||
'name': 'sanic',
|
"name": "sanic",
|
||||||
'version': version,
|
"version": version,
|
||||||
'url': 'http://github.com/channelcat/sanic/',
|
"url": "http://github.com/channelcat/sanic/",
|
||||||
'license': 'MIT',
|
"license": "MIT",
|
||||||
'author': 'Channel Cat',
|
"author": "Channel Cat",
|
||||||
'author_email': 'channelcat@gmail.com',
|
"author_email": "channelcat@gmail.com",
|
||||||
'description': (
|
"description": (
|
||||||
'A microframework based on uvloop, httptools, and learnings of flask'),
|
"A microframework based on uvloop, httptools, and learnings of flask"
|
||||||
'long_description': long_description,
|
),
|
||||||
'packages': ['sanic'],
|
"long_description": long_description,
|
||||||
'platforms': 'any',
|
"packages": ["sanic"],
|
||||||
'classifiers': [
|
"platforms": "any",
|
||||||
'Development Status :: 4 - Beta',
|
"classifiers": [
|
||||||
'Environment :: Web Environment',
|
"Development Status :: 4 - Beta",
|
||||||
'License :: OSI Approved :: MIT License',
|
"Environment :: Web Environment",
|
||||||
'Programming Language :: Python :: 3.5',
|
"License :: OSI Approved :: MIT License",
|
||||||
'Programming Language :: Python :: 3.6',
|
"Programming Language :: Python :: 3.5",
|
||||||
'Programming Language :: Python :: 3.7',
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
env_dependency = '; sys_platform != "win32" and implementation_name == "cpython"'
|
env_dependency = (
|
||||||
ujson = 'ujson>=1.35' + env_dependency
|
'; sys_platform != "win32" ' 'and implementation_name == "cpython"'
|
||||||
uvloop = 'uvloop>=0.5.3' + env_dependency
|
)
|
||||||
|
ujson = "ujson>=1.35" + env_dependency
|
||||||
|
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||||
|
|
||||||
requirements = [
|
requirements = [
|
||||||
'httptools>=0.0.10',
|
"httptools>=0.0.10",
|
||||||
uvloop,
|
uvloop,
|
||||||
ujson,
|
ujson,
|
||||||
'aiofiles>=0.3.0',
|
"aiofiles>=0.3.0",
|
||||||
'websockets>=6.0,<7.0',
|
"websockets>=6.0,<7.0",
|
||||||
'multidict>=4.0,<5.0',
|
"multidict>=4.0,<5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
tests_require = [
|
||||||
|
"pytest==3.3.2",
|
||||||
|
"multidict>=4.0,<5.0",
|
||||||
|
"gunicorn",
|
||||||
|
"pytest-cov",
|
||||||
|
"aiohttp>=2.3.0,<=3.2.1",
|
||||||
|
"beautifulsoup4",
|
||||||
|
uvloop,
|
||||||
|
ujson,
|
||||||
|
"pytest-sanic",
|
||||||
|
"pytest-sugar",
|
||||||
|
]
|
||||||
|
|
||||||
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
|
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
|
||||||
print("Installing without uJSON")
|
print("Installing without uJSON")
|
||||||
requirements.remove(ujson)
|
requirements.remove(ujson)
|
||||||
|
tests_require.remove(ujson)
|
||||||
|
|
||||||
# 'nt' means windows OS
|
# 'nt' means windows OS
|
||||||
if strtobool(os.environ.get("SANIC_NO_UVLOOP", "no")):
|
if strtobool(os.environ.get("SANIC_NO_UVLOOP", "no")):
|
||||||
print("Installing without uvLoop")
|
print("Installing without uvLoop")
|
||||||
requirements.remove(uvloop)
|
requirements.remove(uvloop)
|
||||||
|
tests_require.remove(uvloop)
|
||||||
|
|
||||||
setup_kwargs['install_requires'] = requirements
|
extras_require = {
|
||||||
|
"test": tests_require,
|
||||||
|
"dev": tests_require + ["aiofiles", "tox", "black", "flake8"],
|
||||||
|
"docs": [
|
||||||
|
"sphinx",
|
||||||
|
"sphinx_rtd_theme",
|
||||||
|
"recommonmark",
|
||||||
|
"sphinxcontrib-asyncio",
|
||||||
|
"docutils",
|
||||||
|
"pygments"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_kwargs["install_requires"] = requirements
|
||||||
|
setup_kwargs["tests_require"] = tests_require
|
||||||
|
setup_kwargs["extras_require"] = extras_require
|
||||||
|
setup_kwargs["cmdclass"] = {"test": PyTest}
|
||||||
setup(**setup_kwargs)
|
setup(**setup_kwargs)
|
||||||
|
|
|
@ -4,8 +4,10 @@ from sanic.views import CompositionView
|
||||||
from sanic.views import HTTPMethodView
|
from sanic.views import HTTPMethodView
|
||||||
from sanic.views import stream as stream_decorator
|
from sanic.views import stream as stream_decorator
|
||||||
from sanic.response import stream, text
|
from sanic.response import stream, text
|
||||||
|
from sanic.request import StreamBuffer
|
||||||
|
|
||||||
data = "abc" * 100000
|
|
||||||
|
data = "abc" * 10000000
|
||||||
|
|
||||||
|
|
||||||
def test_request_stream_method_view(app):
|
def test_request_stream_method_view(app):
|
||||||
|
@ -19,10 +21,10 @@ def test_request_stream_method_view(app):
|
||||||
|
|
||||||
@stream_decorator
|
@stream_decorator
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
@ -71,11 +73,11 @@ def test_request_stream_app(app):
|
||||||
|
|
||||||
@app.post('/post/<id>', stream=True)
|
@app.post('/post/<id>', stream=True)
|
||||||
async def post(request, id):
|
async def post(request, id):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -88,11 +90,11 @@ def test_request_stream_app(app):
|
||||||
|
|
||||||
@app.put('/put', stream=True)
|
@app.put('/put', stream=True)
|
||||||
async def put(request):
|
async def put(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -105,11 +107,11 @@ def test_request_stream_app(app):
|
||||||
|
|
||||||
@app.patch('/patch', stream=True)
|
@app.patch('/patch', stream=True)
|
||||||
async def patch(request):
|
async def patch(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -163,11 +165,11 @@ def test_request_stream_handle_exception(app):
|
||||||
|
|
||||||
@app.post('/post/<id>', stream=True)
|
@app.post('/post/<id>', stream=True)
|
||||||
async def post(request, id):
|
async def post(request, id):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -216,11 +218,11 @@ def test_request_stream_blueprint(app):
|
||||||
|
|
||||||
@bp.post('/post/<id>', stream=True)
|
@bp.post('/post/<id>', stream=True)
|
||||||
async def post(request, id):
|
async def post(request, id):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -233,11 +235,11 @@ def test_request_stream_blueprint(app):
|
||||||
|
|
||||||
@bp.put('/put', stream=True)
|
@bp.put('/put', stream=True)
|
||||||
async def put(request):
|
async def put(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -250,11 +252,11 @@ def test_request_stream_blueprint(app):
|
||||||
|
|
||||||
@bp.patch('/patch', stream=True)
|
@bp.patch('/patch', stream=True)
|
||||||
async def patch(request):
|
async def patch(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -313,10 +315,10 @@ def test_request_stream_composition_view(app):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
async def post_handler(request):
|
async def post_handler(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
@ -350,10 +352,10 @@ def test_request_stream(app):
|
||||||
|
|
||||||
@stream_decorator
|
@stream_decorator
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
@ -361,11 +363,11 @@ def test_request_stream(app):
|
||||||
|
|
||||||
@app.post('/stream', stream=True)
|
@app.post('/stream', stream=True)
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
|
|
||||||
async def streaming(response):
|
async def streaming(response):
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
await response.write(body.decode('utf-8'))
|
await response.write(body.decode('utf-8'))
|
||||||
|
@ -378,10 +380,10 @@ def test_request_stream(app):
|
||||||
|
|
||||||
@bp.post('/bp_stream', stream=True)
|
@bp.post('/bp_stream', stream=True)
|
||||||
async def bp_stream(request):
|
async def bp_stream(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
@ -397,10 +399,10 @@ def test_request_stream(app):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
async def post_handler(request):
|
async def post_handler(request):
|
||||||
assert isinstance(request.stream, asyncio.Queue)
|
assert isinstance(request.stream, StreamBuffer)
|
||||||
result = ''
|
result = ''
|
||||||
while True:
|
while True:
|
||||||
body = await request.stream.get()
|
body = await request.stream.read()
|
||||||
if body is None:
|
if body is None:
|
||||||
break
|
break
|
||||||
result += body.decode('utf-8')
|
result += body.decode('utf-8')
|
||||||
|
|
|
@ -22,7 +22,7 @@ def test_versioned_routes_get(app, method):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
else:
|
else:
|
||||||
print(func)
|
print(func)
|
||||||
raise
|
raise Exception("Method: {} is not callable".format(method))
|
||||||
|
|
||||||
client_method = getattr(app.test_client, method)
|
client_method = getattr(app.test_client, method)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user