Merge branch 'master' into master
This commit is contained in:
commit
74cc7be922
62
CONTRIBUTING.md
Normal file
62
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# 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:
|
||||||
|
1. All pull requests must pass unit tests
|
||||||
|
2. All pull requests must be reviewed and approved by at least
|
||||||
|
one current collaborator on the project
|
||||||
|
3. All pull requests must pass flake8 checks
|
||||||
|
4. If you decide to remove/change anything from any common interface
|
||||||
|
a deprecation message should accompany it.
|
||||||
|
5. 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.
|
|
@ -83,3 +83,4 @@ Out of the box there are just a few predefined values which can be overwritten w
|
||||||
| ----------------- | --------- | --------------------------------- |
|
| ----------------- | --------- | --------------------------------- |
|
||||||
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
|
||||||
| REQUEST_TIMEOUT | 60 | How long a request can take (sec) |
|
| REQUEST_TIMEOUT | 60 | How long a request can take (sec) |
|
||||||
|
| KEEP_ALIVE | True | Disables keep-alive when False |
|
||||||
|
|
|
@ -4,10 +4,39 @@ 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
|
don't feel comfortable contributing code, adding docstrings to the source files
|
||||||
is very appreciated.
|
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
|
## Running tests
|
||||||
|
|
||||||
* `python -m pip install pytest`
|
To run the tests for sanic it is recommended to use tox like so:
|
||||||
* `python -m pytest tests`
|
|
||||||
|
```bash
|
||||||
|
tox
|
||||||
|
```
|
||||||
|
|
||||||
|
See it's that simple!
|
||||||
|
|
||||||
|
## Pull requests!
|
||||||
|
|
||||||
|
So the pull request approval rules are pretty simple:
|
||||||
|
1. 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
|
## Documentation
|
||||||
|
|
||||||
|
|
41
examples/dask_distributed.py
Normal file
41
examples/dask_distributed.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
|
||||||
|
from tornado.platform.asyncio import BaseAsyncIOLoop, to_asyncio_future
|
||||||
|
from distributed import LocalCluster, Client
|
||||||
|
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def square(x):
|
||||||
|
return x**2
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('after_server_start')
|
||||||
|
async def setup(app, loop):
|
||||||
|
# configure tornado use asyncio's loop
|
||||||
|
ioloop = BaseAsyncIOLoop(loop)
|
||||||
|
|
||||||
|
# init distributed client
|
||||||
|
app.client = Client('tcp://localhost:8786', loop=ioloop, start=False)
|
||||||
|
await to_asyncio_future(app.client._start())
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('before_server_stop')
|
||||||
|
async def stop(app, loop):
|
||||||
|
await to_asyncio_future(app.client._shutdown())
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<value:int>')
|
||||||
|
async def test(request, value):
|
||||||
|
future = app.client.submit(square, value)
|
||||||
|
result = await to_asyncio_future(future._result())
|
||||||
|
return response.text(f'The square of {value} is {result}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Distributed cluster should run somewhere else
|
||||||
|
with LocalCluster(scheduler_port=8786, nanny=False, n_workers=2,
|
||||||
|
threads_per_worker=1) as cluster:
|
||||||
|
app.run(host="0.0.0.0", port=8000)
|
|
@ -1,9 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Example intercepting uncaught exceptions using Sanic's error handler framework.
|
Example intercepting uncaught exceptions using Sanic's error handler framework.
|
||||||
|
|
||||||
This may be useful for developers wishing to use Sentry, Airbrake, etc.
|
This may be useful for developers wishing to use Sentry, Airbrake, etc.
|
||||||
or a custom system to log and monitor unexpected errors in production.
|
or a custom system to log and monitor unexpected errors in production.
|
||||||
|
|
||||||
First we create our own class inheriting from Handler in sanic.exceptions,
|
First we create our own class inheriting from Handler in sanic.exceptions,
|
||||||
and pass in an instance of it when we create our Sanic instance. Inside this
|
and pass in an instance of it when we create our Sanic instance. Inside this
|
||||||
class' default handler, we can do anything including sending exceptions to
|
class' default handler, we can do anything including sending exceptions to
|
||||||
|
@ -39,7 +37,7 @@ server's error_handler to an instance of our CustomHandler
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json
|
from sanic import response
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@ -52,7 +50,7 @@ async def test(request):
|
||||||
# Here, something occurs which causes an unexpected exception
|
# Here, something occurs which causes an unexpected exception
|
||||||
# This exception will flow to our custom handler.
|
# This exception will flow to our custom handler.
|
||||||
1 / 0
|
1 / 0
|
||||||
return json({"test": True})
|
return response.json({"test": True})
|
||||||
|
|
||||||
|
|
||||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
|
@ -1,18 +1,27 @@
|
||||||
## To use this example:
|
# Render templates in a Flask like way from a "template" directory in the project
|
||||||
# curl -d '{"name": "John Doe"}' localhost:8000
|
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import html
|
from sanic import response
|
||||||
from jinja2 import Template
|
from jinja2 import Evironment, PackageLoader, select_autoescape
|
||||||
|
|
||||||
template = Template('Hello {{ name }}!')
|
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
# Load the template environment with async support
|
||||||
|
template_env = Environment(
|
||||||
|
loader=jinja2.PackageLoader('yourapplication', 'templates'),
|
||||||
|
autoescape=jinja2.select_autoescape(['html', 'xml']),
|
||||||
|
enable_async=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load the template from file
|
||||||
|
template = template_env.get_template("example_template.html")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def test(request):
|
async def test(request):
|
||||||
data = request.json
|
data = request.json
|
||||||
return html(template.render(**data))
|
rendered_template = await template.render_async(**data)
|
||||||
|
return response.html(rendered_template)
|
||||||
|
|
||||||
|
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8080, debug=True)
|
26
examples/modify_header_example.py
Normal file
26
examples/modify_header_example.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"""
|
||||||
|
Modify header or status in response
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def handle_request(request):
|
||||||
|
return response.json(
|
||||||
|
{'message': 'Hello world!'},
|
||||||
|
headers={'X-Served-By': 'sanic'},
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/unauthorized')
|
||||||
|
def handle_request(request):
|
||||||
|
return response.json(
|
||||||
|
{'message': 'You are not authorized'},
|
||||||
|
headers={'X-Served-By': 'sanic'},
|
||||||
|
status=404
|
||||||
|
)
|
||||||
|
|
||||||
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
|
@ -1,6 +1,5 @@
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic import response
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
|
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
|
||||||
|
@ -18,6 +17,6 @@ sanic = Sanic()
|
||||||
@sanic.route("/")
|
@sanic.route("/")
|
||||||
def test(request):
|
def test(request):
|
||||||
log.info("received request; responding with 'hey'")
|
log.info("received request; responding with 'hey'")
|
||||||
return text("hey")
|
return response.text("hey")
|
||||||
|
|
||||||
sanic.run(host="0.0.0.0", port=8000)
|
sanic.run(host="0.0.0.0", port=8000)
|
||||||
|
|
85
examples/plotly_example/plotlyjs_example.py
Normal file
85
examples/plotly_example/plotlyjs_example.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
from sanic_session import InMemorySessionInterface
|
||||||
|
from sanic_jinja2 import SanicJinja2
|
||||||
|
|
||||||
|
import json
|
||||||
|
import plotly
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
jinja = SanicJinja2(app)
|
||||||
|
session = InMemorySessionInterface(cookie_name=app.name, prefix=app.name)
|
||||||
|
|
||||||
|
@app.middleware('request')
|
||||||
|
async def print_on_request(request):
|
||||||
|
print(request.headers)
|
||||||
|
await session.open(request)
|
||||||
|
|
||||||
|
@app.middleware('response')
|
||||||
|
async def print_on_response(request, response):
|
||||||
|
await session.save(request, response)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def index(request):
|
||||||
|
rng = pd.date_range('1/1/2011', periods=7500, freq='H')
|
||||||
|
ts = pd.Series(np.random.randn(len(rng)), index=rng)
|
||||||
|
|
||||||
|
graphs = [
|
||||||
|
dict(
|
||||||
|
data=[
|
||||||
|
dict(
|
||||||
|
x=[1, 2, 3],
|
||||||
|
y=[10, 20, 30],
|
||||||
|
type='scatter'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
layout=dict(
|
||||||
|
title='first graph'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
dict(
|
||||||
|
data=[
|
||||||
|
dict(
|
||||||
|
x=[1, 3, 5],
|
||||||
|
y=[10, 50, 30],
|
||||||
|
type='bar'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
layout=dict(
|
||||||
|
title='second graph'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
dict(
|
||||||
|
data=[
|
||||||
|
dict(
|
||||||
|
x=ts.index, # Can use the pandas data structures directly
|
||||||
|
y=ts
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add "ids" to each of the graphs to pass up to the client
|
||||||
|
# for templating
|
||||||
|
ids = ['graph-{}'.format(i) for i, _ in enumerate(graphs)]
|
||||||
|
|
||||||
|
# Convert the figures to JSON
|
||||||
|
# PlotlyJSONEncoder appropriately converts pandas, datetime, etc
|
||||||
|
# objects to their JSON equivalents
|
||||||
|
graphJSON = json.dumps(graphs, cls=plotly.utils.PlotlyJSONEncoder)
|
||||||
|
|
||||||
|
return jinja.render('index.html', request,
|
||||||
|
ids=ids,
|
||||||
|
graphJSON=graphJSON)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=8000, debug=True)
|
5
examples/plotly_example/requirements.txt
Normal file
5
examples/plotly_example/requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pandas==0.19.2
|
||||||
|
plotly==2.0.7
|
||||||
|
sanic==0.5.0
|
||||||
|
sanic-jinja2==0.5.1
|
||||||
|
sanic-session==0.1.3
|
0
examples/plotly_example/templates/index.html
Normal file
0
examples/plotly_example/templates/index.html
Normal file
|
@ -1,6 +1,6 @@
|
||||||
from sanic import Sanic
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from sanic.response import text
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
from sanic.config import Config
|
from sanic.config import Config
|
||||||
from sanic.exceptions import RequestTimeout
|
from sanic.exceptions import RequestTimeout
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ app = Sanic(__name__)
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def test(request):
|
async def test(request):
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
return text('Hello, world!')
|
return response.text('Hello, world!')
|
||||||
|
|
||||||
|
|
||||||
@app.exception(RequestTimeout)
|
@app.exception(RequestTimeout)
|
||||||
def timeout(request, exception):
|
def timeout(request, exception):
|
||||||
return text('RequestTimeout from error_handler.', 408)
|
return response.text('RequestTimeout from error_handler.', 408)
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=8000)
|
app.run(host='0.0.0.0', port=8000)
|
|
@ -5,7 +5,7 @@ motor==1.1
|
||||||
sanic==0.2.0
|
sanic==0.2.0
|
||||||
"""
|
"""
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json
|
from sanic import response
|
||||||
|
|
||||||
|
|
||||||
app = Sanic('motor_mongodb')
|
app = Sanic('motor_mongodb')
|
||||||
|
@ -25,7 +25,7 @@ async def get(request):
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc['id'] = str(doc['_id'])
|
doc['id'] = str(doc['_id'])
|
||||||
del doc['_id']
|
del doc['_id']
|
||||||
return json(docs)
|
return response.json(docs)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/post', methods=['POST'])
|
@app.route('/post', methods=['POST'])
|
||||||
|
@ -34,8 +34,8 @@ async def new(request):
|
||||||
print(doc)
|
print(doc)
|
||||||
db = get_db()
|
db = get_db()
|
||||||
object_id = await db.test_col.save(doc)
|
object_id = await db.test_col.save(doc)
|
||||||
return json({'object_id': str(object_id)})
|
return response.json({'object_id': str(object_id)})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host='127.0.0.1', port=8000)
|
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import json
|
from sanic import response
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test(request):
|
async def test(request):
|
||||||
return json({"test": True})
|
return response.json({"test": True})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.log import log
|
from sanic.log import log
|
||||||
from sanic.response import json, text, file
|
from sanic import response
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
@ -10,17 +10,17 @@ app = Sanic(__name__)
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
async def test_async(request):
|
async def test_async(request):
|
||||||
return json({"test": True})
|
return response.json({"test": True})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/sync", methods=['GET', 'POST'])
|
@app.route("/sync", methods=['GET', 'POST'])
|
||||||
def test_sync(request):
|
def test_sync(request):
|
||||||
return json({"test": True})
|
return response.json({"test": True})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/dynamic/<name>/<id:int>")
|
@app.route("/dynamic/<name>/<id:int>")
|
||||||
def test_params(request, name, id):
|
def test_params(request, name, id):
|
||||||
return text("yeehaww {} {}".format(name, id))
|
return response.text("yeehaww {} {}".format(name, id))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/exception")
|
@app.route("/exception")
|
||||||
|
@ -31,11 +31,11 @@ def exception(request):
|
||||||
async def test_await(request):
|
async def test_await(request):
|
||||||
import asyncio
|
import asyncio
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
return text("I'm feeling sleepy")
|
return response.text("I'm feeling sleepy")
|
||||||
|
|
||||||
@app.route("/file")
|
@app.route("/file")
|
||||||
async def test_file(request):
|
async def test_file(request):
|
||||||
return await file(os.path.abspath("setup.py"))
|
return await response.file(os.path.abspath("setup.py"))
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------- #
|
# ----------------------------------------------- #
|
||||||
|
@ -44,7 +44,7 @@ async def test_file(request):
|
||||||
|
|
||||||
@app.exception(ServerError)
|
@app.exception(ServerError)
|
||||||
async def test(request, exception):
|
async def test(request, exception):
|
||||||
return json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code)
|
return response.json({"exception": "{}".format(exception), "status": exception.status_code}, status=exception.status_code)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------- #
|
# ----------------------------------------------- #
|
||||||
|
@ -53,17 +53,17 @@ async def test(request, exception):
|
||||||
|
|
||||||
@app.route("/json")
|
@app.route("/json")
|
||||||
def post_json(request):
|
def post_json(request):
|
||||||
return json({"received": True, "message": request.json})
|
return response.json({"received": True, "message": request.json})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/form")
|
@app.route("/form")
|
||||||
def post_json(request):
|
def post_json(request):
|
||||||
return json({"received": True, "form_data": request.form, "test": request.form.get('test')})
|
return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/query_string")
|
@app.route("/query_string")
|
||||||
def query_string(request):
|
def query_string(request):
|
||||||
return json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string})
|
return response.json({"parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string})
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------- #
|
# ----------------------------------------------- #
|
||||||
|
|
18
examples/url_for_example.py
Normal file
18
examples/url_for_example.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def index(request):
|
||||||
|
# generate a URL for the endpoint `post_handler`
|
||||||
|
url = app.url_for('post_handler', post_id=5)
|
||||||
|
# the URL is `/posts/5`, redirect to it
|
||||||
|
return response.redirect(url)
|
||||||
|
|
||||||
|
@app.route('/posts/<post_id>')
|
||||||
|
async def post_handler(request, post_id):
|
||||||
|
return response.text('Post - {}'.format(post_id))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host="0.0.0.0", port=8000, debug=True)
|
|
@ -1,4 +1,4 @@
|
||||||
from sanic.response import text
|
from sanic import response
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
@ -15,25 +15,25 @@ bp = Blueprint("bp", host="bp.example.com")
|
||||||
"somethingelse.com",
|
"somethingelse.com",
|
||||||
"therestofyourdomains.com"])
|
"therestofyourdomains.com"])
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("Some defaults")
|
return response.text("Some defaults")
|
||||||
|
|
||||||
@app.route('/', host="example.com")
|
@app.route('/', host="example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("Answer")
|
return response.text("Answer")
|
||||||
|
|
||||||
@app.route('/', host="sub.example.com")
|
@app.route('/', host="sub.example.com")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("42")
|
return response.text("42")
|
||||||
|
|
||||||
@bp.route("/question")
|
@bp.route("/question")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("What is the meaning of life?")
|
return response.text("What is the meaning of life?")
|
||||||
|
|
||||||
@bp.route("/answer")
|
@bp.route("/answer")
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return text("42")
|
return response.text("42")
|
||||||
|
|
||||||
app.register_blueprint(bp)
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host="0.0.0.0", port=8000)
|
app.run(host="0.0.0.0", port=8000)
|
|
@ -17,7 +17,7 @@ from sanic.handlers import ErrorHandler
|
||||||
from sanic.log import log
|
from sanic.log import log
|
||||||
from sanic.response import HTTPResponse, StreamingHTTPResponse
|
from sanic.response import HTTPResponse, StreamingHTTPResponse
|
||||||
from sanic.router import Router
|
from sanic.router import Router
|
||||||
from sanic.server import serve, serve_multiple, HttpProtocol
|
from sanic.server import serve, serve_multiple, HttpProtocol, Signal
|
||||||
from sanic.static import register as static_register
|
from sanic.static import register as static_register
|
||||||
from sanic.testing import SanicTestClient
|
from sanic.testing import SanicTestClient
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
@ -293,7 +293,7 @@ class Sanic:
|
||||||
attach_to=middleware_or_request)
|
attach_to=middleware_or_request)
|
||||||
|
|
||||||
# Static Files
|
# Static Files
|
||||||
def static(self, uri, file_or_directory, pattern='.+',
|
def static(self, uri, file_or_directory, pattern=r'/?.+',
|
||||||
use_modified_since=True, use_content_range=False):
|
use_modified_since=True, use_content_range=False):
|
||||||
"""Register a root to serve files from. The input can either be a
|
"""Register a root to serve files from. The input can either be a
|
||||||
file or a directory. See
|
file or a directory. See
|
||||||
|
@ -687,11 +687,13 @@ class Sanic:
|
||||||
'port': port,
|
'port': port,
|
||||||
'sock': sock,
|
'sock': sock,
|
||||||
'ssl': ssl,
|
'ssl': ssl,
|
||||||
|
'signal': Signal(),
|
||||||
'debug': debug,
|
'debug': debug,
|
||||||
'request_handler': self.handle_request,
|
'request_handler': self.handle_request,
|
||||||
'error_handler': self.error_handler,
|
'error_handler': self.error_handler,
|
||||||
'request_timeout': self.config.REQUEST_TIMEOUT,
|
'request_timeout': self.config.REQUEST_TIMEOUT,
|
||||||
'request_max_size': self.config.REQUEST_MAX_SIZE,
|
'request_max_size': self.config.REQUEST_MAX_SIZE,
|
||||||
|
'keep_alive': self.config.KEEP_ALIVE,
|
||||||
'loop': loop,
|
'loop': loop,
|
||||||
'register_sys_signals': register_sys_signals,
|
'register_sys_signals': register_sys_signals,
|
||||||
'backlog': backlog,
|
'backlog': backlog,
|
||||||
|
|
|
@ -116,7 +116,7 @@ if type(_addr) is str and not os.path.exists(_addr):
|
||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
class Config(dict):
|
||||||
def __init__(self, defaults=None, load_env=True):
|
def __init__(self, defaults=None, load_env=True, keep_alive=True):
|
||||||
super().__init__(defaults or {})
|
super().__init__(defaults or {})
|
||||||
self.LOGO = """
|
self.LOGO = """
|
||||||
▄▄▄▄▄
|
▄▄▄▄▄
|
||||||
|
@ -141,6 +141,7 @@ class Config(dict):
|
||||||
"""
|
"""
|
||||||
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
||||||
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
||||||
|
self.KEEP_ALIVE = keep_alive
|
||||||
|
|
||||||
if load_env:
|
if load_env:
|
||||||
self.load_environment_vars()
|
self.load_environment_vars()
|
||||||
|
@ -208,11 +209,11 @@ class Config(dict):
|
||||||
self[key] = getattr(obj, key)
|
self[key] = getattr(obj, key)
|
||||||
|
|
||||||
def load_environment_vars(self):
|
def load_environment_vars(self):
|
||||||
|
"""
|
||||||
|
Looks for any SANIC_ prefixed environment variables and applies
|
||||||
|
them to the configuration if present.
|
||||||
|
"""
|
||||||
for k, v in os.environ.items():
|
for k, v in os.environ.items():
|
||||||
"""
|
|
||||||
Looks for any SANIC_ prefixed environment variables and applies
|
|
||||||
them to the configuration if present.
|
|
||||||
"""
|
|
||||||
if k.startswith(SANIC_PREFIX):
|
if k.startswith(SANIC_PREFIX):
|
||||||
_, config_key = k.split(SANIC_PREFIX, 1)
|
_, config_key = k.split(SANIC_PREFIX, 1)
|
||||||
self[config_key] = v
|
self[config_key] = v
|
||||||
|
|
|
@ -78,9 +78,10 @@ class Request(dict):
|
||||||
:return: token related to request
|
:return: token related to request
|
||||||
"""
|
"""
|
||||||
auth_header = self.headers.get('Authorization')
|
auth_header = self.headers.get('Authorization')
|
||||||
if auth_header is not None:
|
if 'Token ' in auth_header:
|
||||||
return auth_header.split()[1]
|
return auth_header.partition('Token ')[-1]
|
||||||
return auth_header
|
else:
|
||||||
|
return auth_header
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def form(self):
|
def form(self):
|
||||||
|
|
|
@ -210,7 +210,7 @@ class HTTPResponse(BaseHTTPResponse):
|
||||||
# Speeds up response rate 6% over pulling from all
|
# Speeds up response rate 6% over pulling from all
|
||||||
status = COMMON_STATUS_CODES.get(self.status)
|
status = COMMON_STATUS_CODES.get(self.status)
|
||||||
if not status:
|
if not status:
|
||||||
status = ALL_STATUS_CODES.get(self.status)
|
status = ALL_STATUS_CODES.get(self.status, b'UNKNOWN RESPONSE')
|
||||||
|
|
||||||
return (b'HTTP/%b %d %b\r\n'
|
return (b'HTTP/%b %d %b\r\n'
|
||||||
b'Connection: %b\r\n'
|
b'Connection: %b\r\n'
|
||||||
|
|
|
@ -16,6 +16,7 @@ REGEX_TYPES = {
|
||||||
'int': (int, r'\d+'),
|
'int': (int, r'\d+'),
|
||||||
'number': (float, r'[0-9\\.]+'),
|
'number': (float, r'[0-9\\.]+'),
|
||||||
'alpha': (str, r'[A-Za-z]+'),
|
'alpha': (str, r'[A-Za-z]+'),
|
||||||
|
'path': (str, r'[^/].*?'),
|
||||||
}
|
}
|
||||||
|
|
||||||
ROUTER_CACHE_SIZE = 1024
|
ROUTER_CACHE_SIZE = 1024
|
||||||
|
@ -71,7 +72,8 @@ class Router:
|
||||||
self.routes_always_check = []
|
self.routes_always_check = []
|
||||||
self.hosts = set()
|
self.hosts = set()
|
||||||
|
|
||||||
def parse_parameter_string(self, parameter_string):
|
@classmethod
|
||||||
|
def parse_parameter_string(cls, parameter_string):
|
||||||
"""Parse a parameter string into its constituent name, type, and
|
"""Parse a parameter string into its constituent name, type, and
|
||||||
pattern
|
pattern
|
||||||
|
|
||||||
|
@ -161,10 +163,10 @@ class Router:
|
||||||
parameters.append(parameter)
|
parameters.append(parameter)
|
||||||
|
|
||||||
# Mark the whole route as unhashable if it has the hash key in it
|
# Mark the whole route as unhashable if it has the hash key in it
|
||||||
if re.search('(^|[^^]){1}/', pattern):
|
if re.search(r'(^|[^^]){1}/', pattern):
|
||||||
properties['unhashable'] = True
|
properties['unhashable'] = True
|
||||||
# Mark the route as unhashable if it matches the hash key
|
# Mark the route as unhashable if it matches the hash key
|
||||||
elif re.search(pattern, '/'):
|
elif re.search(r'/', pattern):
|
||||||
properties['unhashable'] = True
|
properties['unhashable'] = True
|
||||||
|
|
||||||
return '({})'.format(pattern)
|
return '({})'.format(pattern)
|
||||||
|
|
|
@ -73,7 +73,8 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
|
|
||||||
def __init__(self, *, loop, request_handler, error_handler,
|
def __init__(self, *, loop, request_handler, error_handler,
|
||||||
signal=Signal(), connections=set(), request_timeout=60,
|
signal=Signal(), connections=set(), request_timeout=60,
|
||||||
request_max_size=None, request_class=None, has_log=True):
|
request_max_size=None, request_class=None, has_log=True,
|
||||||
|
keep_alive=True):
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.transport = None
|
self.transport = None
|
||||||
self.request = None
|
self.request = None
|
||||||
|
@ -92,10 +93,13 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
self._timeout_handler = None
|
self._timeout_handler = None
|
||||||
self._last_request_time = None
|
self._last_request_time = None
|
||||||
self._request_handler_task = None
|
self._request_handler_task = None
|
||||||
|
self._keep_alive = keep_alive
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def keep_alive(self):
|
def keep_alive(self):
|
||||||
return self.parser.should_keep_alive() and not self.signal.stopped
|
return (self._keep_alive
|
||||||
|
and not self.signal.stopped
|
||||||
|
and self.parser.should_keep_alive())
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
# Connection
|
# Connection
|
||||||
|
@ -357,7 +361,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
request_timeout=60, ssl=None, sock=None, request_max_size=None,
|
request_timeout=60, ssl=None, sock=None, request_max_size=None,
|
||||||
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
|
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
|
||||||
register_sys_signals=True, run_async=False, connections=None,
|
register_sys_signals=True, run_async=False, connections=None,
|
||||||
signal=Signal(), request_class=None, has_log=True):
|
signal=Signal(), request_class=None, has_log=True, keep_alive=True):
|
||||||
|
signal=Signal(), request_class=None, keep_alive=True):
|
||||||
"""Start asynchronous HTTP Server on an individual process.
|
"""Start asynchronous HTTP Server on an individual process.
|
||||||
|
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
|
@ -406,7 +411,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
request_timeout=request_timeout,
|
request_timeout=request_timeout,
|
||||||
request_max_size=request_max_size,
|
request_max_size=request_max_size,
|
||||||
request_class=request_class,
|
request_class=request_class,
|
||||||
has_log=has_log
|
has_log=has_log,
|
||||||
|
keep_alive=keep_alive,
|
||||||
)
|
)
|
||||||
|
|
||||||
server_coroutine = loop.create_server(
|
server_coroutine = loop.create_server(
|
||||||
|
|
|
@ -48,14 +48,18 @@ def register(app, uri, file_or_directory, pattern,
|
||||||
# Merge served directory and requested file if provided
|
# Merge served directory and requested file if provided
|
||||||
# Strip all / that in the beginning of the URL to help prevent python
|
# Strip all / that in the beginning of the URL to help prevent python
|
||||||
# from herping a derp and treating the uri as an absolute path
|
# from herping a derp and treating the uri as an absolute path
|
||||||
file_path = file_or_directory
|
root_path = file_path = file_or_directory
|
||||||
if file_uri:
|
if file_uri:
|
||||||
file_path = path.join(
|
file_path = path.join(
|
||||||
file_or_directory, sub('^[/]*', '', file_uri))
|
file_or_directory, sub('^[/]*', '', file_uri))
|
||||||
|
|
||||||
# URL decode the path sent by the browser otherwise we won't be able to
|
# URL decode the path sent by the browser otherwise we won't be able to
|
||||||
# match filenames which got encoded (filenames with spaces etc)
|
# match filenames which got encoded (filenames with spaces etc)
|
||||||
file_path = unquote(file_path)
|
file_path = path.abspath(unquote(file_path))
|
||||||
|
if not file_path.startswith(path.abspath(unquote(root_path))):
|
||||||
|
raise FileNotFound('File not found',
|
||||||
|
path=file_or_directory,
|
||||||
|
relative_url=file_uri)
|
||||||
try:
|
try:
|
||||||
headers = {}
|
headers = {}
|
||||||
# Check if the client has been sent this file before
|
# Check if the client has been sent this file before
|
||||||
|
|
|
@ -3,6 +3,7 @@ import sys
|
||||||
import signal
|
import signal
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -50,8 +51,8 @@ class GunicornWorker(base.Worker):
|
||||||
debug=is_debug,
|
debug=is_debug,
|
||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
ssl=self.ssl_context,
|
ssl=self.ssl_context,
|
||||||
run_async=True
|
run_async=True)
|
||||||
)
|
self._server_settings['signal'] = self.signal
|
||||||
self._server_settings.pop('sock')
|
self._server_settings.pop('sock')
|
||||||
trigger_events(self._server_settings.get('before_start', []),
|
trigger_events(self._server_settings.get('before_start', []),
|
||||||
self.loop)
|
self.loop)
|
||||||
|
@ -97,7 +98,6 @@ class GunicornWorker(base.Worker):
|
||||||
self.servers.append(await serve(
|
self.servers.append(await serve(
|
||||||
sock=sock,
|
sock=sock,
|
||||||
connections=self.connections,
|
connections=self.connections,
|
||||||
signal=self.signal,
|
|
||||||
**self._server_settings
|
**self._server_settings
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,16 @@ def test_token():
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
# uuid4 generated token.
|
# uuid4 generated token.
|
||||||
|
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Authorization': '{}'.format(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/', headers=headers)
|
||||||
|
|
||||||
|
assert request.token == token
|
||||||
|
|
||||||
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
|
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
|
||||||
headers = {
|
headers = {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
|
@ -151,6 +161,18 @@ def test_token():
|
||||||
|
|
||||||
assert request.token == token
|
assert request.token == token
|
||||||
|
|
||||||
|
token = 'a1d895e0-553a-421a-8e22-5ff8ecb48cbf'
|
||||||
|
headers = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Authorization': 'Bearer Token {}'.format(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/', headers=headers)
|
||||||
|
|
||||||
|
assert request.token == token
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# POST
|
# POST
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
|
|
@ -238,6 +238,30 @@ def test_dynamic_route_regex():
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_dynamic_route_path():
|
||||||
|
app = Sanic('test_dynamic_route_path')
|
||||||
|
|
||||||
|
@app.route('/<path:path>/info')
|
||||||
|
async def handler(request, path):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/path/1/info')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/info')
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
|
@app.route('/<path:path>')
|
||||||
|
async def handler1(request, path):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/info')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/whatever/you/set')
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_route_unhashable():
|
def test_dynamic_route_unhashable():
|
||||||
app = Sanic('test_dynamic_route_unhashable')
|
app = Sanic('test_dynamic_route_unhashable')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user