Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d3fd75ec2 | ||
|
|
a42b254c33 | ||
|
|
d24e1ae110 | ||
|
|
2e7badab4e | ||
|
|
7cf3d49f00 | ||
|
|
25037006bf | ||
|
|
f611eb2c2b | ||
|
|
e12c10b087 | ||
|
|
9527e5ded8 | ||
|
|
23b4b20b4f | ||
|
|
7f9ecd659c | ||
|
|
bb31d465f2 | ||
|
|
834468e8e7 | ||
|
|
4720513672 | ||
|
|
a480110d43 | ||
|
|
0a2c95cc10 | ||
|
|
9d2e32902d | ||
|
|
77b6413526 | ||
|
|
9e502099e0 | ||
|
|
c6a7e44ae7 | ||
|
|
1bf06312b8 | ||
|
|
c35721abbd | ||
|
|
7f3c417078 | ||
|
|
69511c2783 | ||
|
|
158a94d34c | ||
|
|
db58bd68f5 | ||
|
|
e65f08a2c8 | ||
|
|
ab8f616385 | ||
|
|
6dc6f9bbb5 | ||
|
|
7754bb995b | ||
|
|
d1fefce61c | ||
|
|
c3abdab9c4 | ||
|
|
8b13e103fd | ||
|
|
5f94f65f4f | ||
|
|
fc0d69616c | ||
|
|
656f5b93d6 | ||
|
|
436d37c079 | ||
|
|
ed0081fcf7 | ||
|
|
140062f8a3 | ||
|
|
75f5fa7c06 | ||
|
|
9152a1a266 | ||
|
|
ade89ab795 | ||
|
|
95cfdee8b8 | ||
|
|
63a27cc5e2 | ||
|
|
472face796 | ||
|
|
8d537a6d0b | ||
|
|
b3101d339e | ||
|
|
85acddddba | ||
|
|
c9d747d97f | ||
|
|
0bba267808 | ||
|
|
1036242064 | ||
|
|
5fd62098bd | ||
|
|
74cc7be922 | ||
|
|
b3814ca89a | ||
|
|
b75a321e4a | ||
|
|
9caa4fec4a | ||
|
|
a0cba1aee1 | ||
|
|
97018ad62f | ||
|
|
a7d17fae44 | ||
|
|
6ce0050979 | ||
|
|
bc035fca78 | ||
|
|
df914a92e4 | ||
|
|
1b939a6823 | ||
|
|
81b6d988ec | ||
|
|
7e9b65feca | ||
|
|
6f098b3d21 | ||
|
|
5ddb0488f2 | ||
|
|
3e87314adf | ||
|
|
f6d4a06661 | ||
|
|
ff17fc95e6 | ||
|
|
c5a46f1cea | ||
|
|
0b072189c4 | ||
|
|
5b22d1486a | ||
|
|
9eb48c2b0d | ||
|
|
ff0632001c | ||
|
|
28bd09a2ea | ||
|
|
c6aaa9b09c | ||
|
|
20d9ec1fd2 | ||
|
|
2c45c2d3c0 | ||
|
|
18829e648a | ||
|
|
a64c636a33 | ||
|
|
5796f211c1 | ||
|
|
ae09dec05e | ||
|
|
afd51e0823 | ||
|
|
0bbf826b21 | ||
|
|
02d1900e2f | ||
|
|
73da11b04c | ||
|
|
4af07e3731 | ||
|
|
7f60f85cd4 | ||
|
|
4c66cb1854 | ||
|
|
35b92e1511 | ||
|
|
e5d3fe52c5 | ||
|
|
63fe7c0a86 | ||
|
|
c5f137c715 | ||
|
|
66923bc0e3 | ||
|
|
8bf7b5a323 | ||
|
|
36d4d85849 | ||
|
|
5f0e05f3bf | ||
|
|
235e5511eb | ||
|
|
6fb60ae0b1 | ||
|
|
6b2883074b | ||
|
|
7fe418d1b7 | ||
|
|
f872ceb0d9 | ||
|
|
0f10a36b40 | ||
|
|
3c45c9170f | ||
|
|
a0730aeb44 | ||
|
|
adb7331670 | ||
|
|
bf46bcf376 | ||
|
|
f330c3f8c5 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@ settings.py
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
docs/_api/
|
docs/_api/
|
||||||
build/*
|
build/*
|
||||||
|
access.log
|
||||||
|
error.log
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ deploy:
|
|||||||
secure: OgADRQH3+dTL5swGzXkeRJDNbLpFzwqYnXB4iLD0Npvzj9QnKyQVvkbaeq6VmV9dpEFb5ULaAKYQq19CrXYDm28yanUSn6jdJ4SukaHusi7xt07U6H7pmoX/uZ2WZYqCSLM8cSp8TXY/3oV3rY5Jfj/AibE5XTbim5/lrhsvW6NR+ALzxc0URRPAHDZEPpojTCjSTjpY0aDsaKWg4mXVRMFfY3O68j6KaIoukIZLuoHfePLKrbZxaPG5VxNhMHEaICdxVxE/dO+7pQmQxXuIsEOHK1QiVJ9YrSGcNqgEqhN36kYP8dqMeVB07sv8Xa6o/Uax2/wXS2HEJvuwP1YD6WkoZuo9ZB85bcMdg7BV9jJDbVFVPJwc75BnTLHrMa3Q1KrRlKRDBUXBUsQivPuWhFNwUgvEayq2qSI3aRQR4Z0O+DfboEhXYojSoD64/EWBTZ7vhgbvOTGEdukUQSYrKj9P8jc1s8exomTsAiqdFxTUpzfiammUSL+M93lP4urtahl1jjXFX7gd3DzdEEb0NsGkx5lm/qdsty8/TeAvKUmC+RVU6T856W6MqN0P+yGbpWUARcSE7fwztC3SPxwAuxvIN3BHmRhOUHoORPNG2VpfbnscIzBKJR4v0JKzbpi0IDa66K+tCGsCEvQuL4cxVOtoUySPWNSUAyUWWUrGM2k=
|
secure: OgADRQH3+dTL5swGzXkeRJDNbLpFzwqYnXB4iLD0Npvzj9QnKyQVvkbaeq6VmV9dpEFb5ULaAKYQq19CrXYDm28yanUSn6jdJ4SukaHusi7xt07U6H7pmoX/uZ2WZYqCSLM8cSp8TXY/3oV3rY5Jfj/AibE5XTbim5/lrhsvW6NR+ALzxc0URRPAHDZEPpojTCjSTjpY0aDsaKWg4mXVRMFfY3O68j6KaIoukIZLuoHfePLKrbZxaPG5VxNhMHEaICdxVxE/dO+7pQmQxXuIsEOHK1QiVJ9YrSGcNqgEqhN36kYP8dqMeVB07sv8Xa6o/Uax2/wXS2HEJvuwP1YD6WkoZuo9ZB85bcMdg7BV9jJDbVFVPJwc75BnTLHrMa3Q1KrRlKRDBUXBUsQivPuWhFNwUgvEayq2qSI3aRQR4Z0O+DfboEhXYojSoD64/EWBTZ7vhgbvOTGEdukUQSYrKj9P8jc1s8exomTsAiqdFxTUpzfiammUSL+M93lP4urtahl1jjXFX7gd3DzdEEb0NsGkx5lm/qdsty8/TeAvKUmC+RVU6T856W6MqN0P+yGbpWUARcSE7fwztC3SPxwAuxvIN3BHmRhOUHoORPNG2VpfbnscIzBKJR4v0JKzbpi0IDa66K+tCGsCEvQuL4cxVOtoUySPWNSUAyUWWUrGM2k=
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
|
distributions: "sdist bdist_wheel"
|
||||||
|
|||||||
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.
|
||||||
4
MANIFEST.in
Normal file
4
MANIFEST.in
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
include README.rst
|
||||||
|
|
||||||
|
recursive-exclude * __pycache__
|
||||||
|
recursive-exclude * *.py[co]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Sanic
|
Sanic
|
||||||
=================================
|
=====
|
||||||
|
|
||||||
|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |PyPI| |PyPI version|
|
|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |PyPI| |PyPI version|
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ On top of being Flask-like, Sanic supports async request handlers. This means y
|
|||||||
|
|
||||||
Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contributions are welcome!
|
Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contributions are welcome!
|
||||||
|
|
||||||
|
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/channelcat/sanic/issues/396>`_ that we use to track those projects!
|
||||||
|
|
||||||
Benchmarks
|
Benchmarks
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@@ -84,8 +86,9 @@ Documentation
|
|||||||
|
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
* Streamed file processing
|
* Streamed file processing
|
||||||
* http2
|
* http2
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
-----------
|
-----------
|
||||||
* No wheels for uvloop and httptools on Windows :(
|
* No wheels for uvloop and httptools on Windows :(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Guides
|
|||||||
sanic/class_based_views
|
sanic/class_based_views
|
||||||
sanic/custom_protocol
|
sanic/custom_protocol
|
||||||
sanic/ssl
|
sanic/ssl
|
||||||
|
sanic/logging
|
||||||
sanic/testing
|
sanic/testing
|
||||||
sanic/deploying
|
sanic/deploying
|
||||||
sanic/extensions
|
sanic/extensions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Any reasonably complex application will need configuration that is not baked into the acutal code. Settings might be different for different environments or installations.
|
Any reasonably complex application will need configuration that is not baked into the actual code. Settings might be different for different environments or installations.
|
||||||
|
|
||||||
## Basics
|
## Basics
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -56,3 +56,17 @@ for Gunicorn `worker-class` argument:
|
|||||||
```
|
```
|
||||||
gunicorn --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker
|
gunicorn --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Asynchronous support
|
||||||
|
This is suitable if you *need* to share the sanic process with other applications, in particular the `loop`.
|
||||||
|
However be advised that this method does not support using multiple processes, and is not the preferred way
|
||||||
|
to run the app in general.
|
||||||
|
|
||||||
|
Here is an incomplete example (please see `run_async.py` in examples for something more practical):
|
||||||
|
|
||||||
|
```python
|
||||||
|
server = app.create_server(host="0.0.0.0", port=8000)
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
task = asyncio.ensure_future(server)
|
||||||
|
loop.run_forever()
|
||||||
|
```
|
||||||
|
|||||||
128
docs/sanic/logging.md
Normal file
128
docs/sanic/logging.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# 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 do create a new configuration.
|
||||||
|
|
||||||
|
### Quck Start
|
||||||
|
|
||||||
|
A simple example using default setting would be like this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.config import LOGGING
|
||||||
|
|
||||||
|
# The default logging handlers are ['accessStream', 'errorStream']
|
||||||
|
# but we change it to use other handlers here for demo purpose
|
||||||
|
LOGGING['loggers']['network']['handlers'] = [
|
||||||
|
'accessTimedRotatingFile', 'errorTimedRotationgFile']
|
||||||
|
|
||||||
|
app = Sanic('test')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def test(request):
|
||||||
|
return response.text('Hello World!')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(log_config=LOGGING)
|
||||||
|
```
|
||||||
|
|
||||||
|
After the program starts, it will log down all the information/requests in access.log and error.log in your working directory.
|
||||||
|
|
||||||
|
And to close logging, simply assign log_config=None:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(log_config=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
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 internal messages
|
||||||
|
app.run(debug=False, log_config=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
By default, log_config parameter is set to use sanic.config.LOGGING dictionary for configuration. The default configuration provides several predefined `handlers`:
|
||||||
|
|
||||||
|
- internal (using [logging.StreamHandler](https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler))<br>
|
||||||
|
For internal information console outputs.
|
||||||
|
|
||||||
|
|
||||||
|
- accessStream (using [logging.StreamHandler](https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler))<br>
|
||||||
|
For requests information logging in console
|
||||||
|
|
||||||
|
|
||||||
|
- errorStream (using [logging.StreamHandler](https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler))<br>
|
||||||
|
For error message and traceback logging in console.
|
||||||
|
|
||||||
|
|
||||||
|
- accessSysLog (using [logging.handlers.SysLogHandler](https://docs.python.org/3/library/logging.handlers.html#logging.handlers.SysLogHandler))<br>
|
||||||
|
For requests information logging to syslog.
|
||||||
|
Currently supports Windows (via localhost:514), Darwin (/var/run/syslog),
|
||||||
|
Linux (/dev/log) and FreeBSD (/dev/log).<br>
|
||||||
|
You would not be able to access this property if the directory doesn't exist.
|
||||||
|
(Notice that in Docker you have to enable everything by yourself)
|
||||||
|
|
||||||
|
|
||||||
|
- errorSysLog (using [logging.handlers.SysLogHandler](https://docs.python.org/3/library/logging.handlers.html#logging.handlers.SysLogHandler))<br>
|
||||||
|
For error message and traceback logging to syslog.
|
||||||
|
Currently supports Windows (via localhost:514), Darwin (/var/run/syslog),
|
||||||
|
Linux (/dev/log) and FreeBSD (/dev/log).<br>
|
||||||
|
You would not be able to access this property if the directory doesn't exist.
|
||||||
|
(Notice that in Docker you have to enable everything by yourself)
|
||||||
|
|
||||||
|
|
||||||
|
- accessTimedRotatingFile (using [logging.handlers.TimedRotatingFileHandler](https://docs.python.org/3/library/logging.handlers.html#logging.handlers.TimedRotatingFileHandler))<br>
|
||||||
|
For requests information logging to file with daily rotation support.
|
||||||
|
|
||||||
|
|
||||||
|
- errorTimedRotatingFile (using [logging.handlers.TimedRotatingFileHandler](https://docs.python.org/3/library/logging.handlers.html#logging.handlers.TimedRotatingFileHandler))<br>
|
||||||
|
For error message and traceback logging to file with daily rotation support.
|
||||||
|
|
||||||
|
And `filters`:
|
||||||
|
|
||||||
|
- accessFilter (using sanic.defaultFilter.DefaultFilter)<br>
|
||||||
|
The filter that allows only levels in `DEBUG`, `INFO`, and `NONE(0)`
|
||||||
|
|
||||||
|
|
||||||
|
- errorFilter (using sanic.defaultFilter.DefaultFilter)<br>
|
||||||
|
The filter taht allows only levels in `WARNING`, `ERROR`, and `CRITICAL`
|
||||||
|
|
||||||
|
There are two `loggers` used in sanic, and **must be defined if you want to create your own logging configuration**:
|
||||||
|
|
||||||
|
- sanic:<br>
|
||||||
|
Used to log internal messages.
|
||||||
|
|
||||||
|
|
||||||
|
- network:<br>
|
||||||
|
Used to log requests from network, and any information from those requests.
|
||||||
|
|
||||||
|
#### Log format:
|
||||||
|
|
||||||
|
In addition to default parameters provided by python (asctime, levelname, message),
|
||||||
|
Sanic provides additional parameters for network logger with accessFilter:
|
||||||
|
|
||||||
|
- 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
|
||||||
|
```
|
||||||
@@ -94,6 +94,7 @@ The following variables are accessible as properties on `Request` objects:
|
|||||||
- `host`: The host associated with the request: `localhost:8080`
|
- `host`: The host associated with the request: `localhost:8080`
|
||||||
- `path`: The path of the request: `/posts/1/`
|
- `path`: The path of the request: `/posts/1/`
|
||||||
- `query_string`: The query string of the request: `foo=bar` or a blank string `''`
|
- `query_string`: The query string of the request: `foo=bar` or a blank string `''`
|
||||||
|
- `uri_template`: Template for matching route handler: `/posts/<id>/`
|
||||||
|
|
||||||
|
|
||||||
## Accessing values using `get` and `getlist`
|
## Accessing values using `get` and `getlist`
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ async def integer_handler(request, integer_arg):
|
|||||||
async def number_handler(request, number_arg):
|
async def number_handler(request, number_arg):
|
||||||
return text('Number - {}'.format(number_arg))
|
return text('Number - {}'.format(number_arg))
|
||||||
|
|
||||||
@app.route('/person/<name:[A-z]>')
|
@app.route('/person/<name:[A-z]+>')
|
||||||
async def person_handler(request, name):
|
async def person_handler(request, name):
|
||||||
return text('Person - {}'.format(name))
|
return text('Person - {}'.format(name))
|
||||||
|
|
||||||
|
|||||||
@@ -57,17 +57,3 @@ def test_post_json_request_includes_data():
|
|||||||
More information about
|
More information about
|
||||||
the available arguments to aiohttp can be found
|
the available arguments to aiohttp can be found
|
||||||
[in the documentation for ClientSession](https://aiohttp.readthedocs.io/en/stable/client_reference.html#client-session).
|
[in the documentation for ClientSession](https://aiohttp.readthedocs.io/en/stable/client_reference.html#client-session).
|
||||||
|
|
||||||
|
|
||||||
### Deprecated: `sanic_endpoint_test`
|
|
||||||
|
|
||||||
Prior to version 0.3.2, testing was provided through the `sanic_endpoint_test` method. This method will be deprecated in the next major version after 0.4.0; please use the `test_client` instead.
|
|
||||||
|
|
||||||
```
|
|
||||||
from sanic.utils import sanic_endpoint_test
|
|
||||||
|
|
||||||
def test_index_returns_200():
|
|
||||||
request, response = sanic_endpoint_test(app)
|
|
||||||
assert response.status == 200
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
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 +0,0 @@
|
|||||||
## To use this example:
|
|
||||||
# curl -d '{"name": "John Doe"}' localhost:8000
|
|
||||||
|
|
||||||
from sanic import Sanic
|
|
||||||
from sanic.response import html
|
|
||||||
from jinja2 import Template
|
|
||||||
|
|
||||||
template = Template('Hello {{ name }}!')
|
|
||||||
|
|
||||||
app = Sanic(__name__)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
async def test(request):
|
|
||||||
data = request.json
|
|
||||||
return html(template.render(**data))
|
|
||||||
|
|
||||||
|
|
||||||
app.run(host="0.0.0.0", port=8000)
|
|
||||||
28
examples/jinja_example/jinja_example.py
Normal file
28
examples/jinja_example/jinja_example.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Render templates in a Flask like way from a "template" directory in
|
||||||
|
# the project
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic import response
|
||||||
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
# Load the template environment with async support
|
||||||
|
template_env = Environment(
|
||||||
|
loader=PackageLoader('jinja_example', 'templates'),
|
||||||
|
autoescape=select_autoescape(['html', 'xml']),
|
||||||
|
enable_async=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load the template from file
|
||||||
|
template = template_env.get_template("example_template.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def test(request):
|
||||||
|
rendered_template = await template.render_async(
|
||||||
|
knights='that say nih; asynchronously')
|
||||||
|
return response.html(rendered_template)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(host="0.0.0.0", port=8080, debug=True)
|
||||||
8
examples/jinja_example/requirements.txt
Normal file
8
examples/jinja_example/requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
aiofiles==0.3.1
|
||||||
|
httptools==0.0.9
|
||||||
|
Jinja2==2.9.6
|
||||||
|
MarkupSafe==1.0
|
||||||
|
sanic==0.5.2
|
||||||
|
ujson==1.35
|
||||||
|
uvloop==0.8.0
|
||||||
|
websockets==3.3
|
||||||
10
examples/jinja_example/templates/example_template.html
Normal file
10
examples/jinja_example/templates/example_template.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>My Webpage</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello World</h1>
|
||||||
|
<p>knights - {{ knights }}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
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)
|
||||||
61
examples/sanic_aiopeewee.py
Normal file
61
examples/sanic_aiopeewee.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from sanic import Sanic
|
||||||
|
from sanic.response import json
|
||||||
|
|
||||||
|
from aiopeewee import AioModel, AioMySQLDatabase, model_to_dict
|
||||||
|
from peewee import CharField, TextField, DateTimeField
|
||||||
|
from peewee import ForeignKeyField, PrimaryKeyField
|
||||||
|
|
||||||
|
|
||||||
|
db = AioMySQLDatabase('test', user='root', password='',
|
||||||
|
host='127.0.0.1', port=3306)
|
||||||
|
|
||||||
|
|
||||||
|
class User(AioModel):
|
||||||
|
username = CharField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
|
||||||
|
|
||||||
|
class Blog(AioModel):
|
||||||
|
user = ForeignKeyField(User)
|
||||||
|
title = CharField(max_length=25)
|
||||||
|
content = TextField(default='')
|
||||||
|
pub_date = DateTimeField(null=True)
|
||||||
|
pk = PrimaryKeyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
|
||||||
|
|
||||||
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('before_server_start')
|
||||||
|
async def setup(app, loop):
|
||||||
|
# create connection pool
|
||||||
|
await db.connect(loop)
|
||||||
|
# create table if not exists
|
||||||
|
await db.create_tables([User, Blog], safe=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('before_server_stop')
|
||||||
|
async def stop(app, loop):
|
||||||
|
# close connection pool
|
||||||
|
await db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/users')
|
||||||
|
async def add_user(request):
|
||||||
|
user = await User.create(**request.json)
|
||||||
|
return json(await model_to_dict(user))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/users/count')
|
||||||
|
async def user_count(request):
|
||||||
|
count = await User.select().count()
|
||||||
|
return json({'count': count})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
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)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
__version__ = '0.5.0'
|
__version__ = '0.5.2'
|
||||||
|
|
||||||
__all__ = ['Sanic', 'Blueprint']
|
__all__ = ['Sanic', 'Blueprint']
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
app.run(host=args.host, port=args.port,
|
app.run(host=args.host, port=args.port,
|
||||||
workers=args.workers, debug=args.debug, ssl=ssl)
|
workers=args.workers, debug=args.debug, ssl=ssl)
|
||||||
except ImportError:
|
except ImportError as e:
|
||||||
log.error("No module named {} found.\n"
|
log.error("No module named {} found.\n"
|
||||||
" Example File: project/sanic_server.py -> app\n"
|
" Example File: project/sanic_server.py -> app\n"
|
||||||
" Example Module: project.sanic_server.app"
|
" Example Module: project.sanic_server.app"
|
||||||
.format(module_name))
|
.format(e.name))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
log.error("{}".format(e))
|
log.error("{}".format(e))
|
||||||
|
|||||||
105
sanic/app.py
105
sanic/app.py
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import logging.config
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from asyncio import get_event_loop, ensure_future, CancelledError
|
from asyncio import get_event_loop, ensure_future, CancelledError
|
||||||
@@ -9,14 +10,14 @@ from traceback import format_exc
|
|||||||
from urllib.parse import urlencode, urlunparse
|
from urllib.parse import urlencode, urlunparse
|
||||||
from ssl import create_default_context, Purpose
|
from ssl import create_default_context, Purpose
|
||||||
|
|
||||||
from sanic.config import Config
|
from sanic.config import Config, LOGGING
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import ServerError, URLBuildError, SanicException
|
from sanic.exceptions import ServerError, URLBuildError, SanicException
|
||||||
from sanic.handlers import ErrorHandler
|
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
|
||||||
@@ -26,7 +27,10 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed
|
|||||||
class Sanic:
|
class Sanic:
|
||||||
|
|
||||||
def __init__(self, name=None, router=None, error_handler=None,
|
def __init__(self, name=None, router=None, error_handler=None,
|
||||||
load_env=True, request_class=None):
|
load_env=True, request_class=None,
|
||||||
|
log_config=LOGGING):
|
||||||
|
if log_config:
|
||||||
|
logging.config.dictConfig(log_config)
|
||||||
# Only set up a default log handler if the
|
# Only set up a default log handler if the
|
||||||
# end-user application didn't set anything up.
|
# end-user application didn't set anything up.
|
||||||
if not logging.root.handlers and log.level == logging.NOTSET:
|
if not logging.root.handlers and log.level == logging.NOTSET:
|
||||||
@@ -47,6 +51,7 @@ class Sanic:
|
|||||||
self.request_class = request_class
|
self.request_class = request_class
|
||||||
self.error_handler = error_handler or ErrorHandler()
|
self.error_handler = error_handler or ErrorHandler()
|
||||||
self.config = Config(load_env=load_env)
|
self.config = Config(load_env=load_env)
|
||||||
|
self.log_config = log_config
|
||||||
self.request_middleware = deque()
|
self.request_middleware = deque()
|
||||||
self.response_middleware = deque()
|
self.response_middleware = deque()
|
||||||
self.blueprints = {}
|
self.blueprints = {}
|
||||||
@@ -288,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
|
||||||
@@ -453,7 +458,8 @@ class Sanic:
|
|||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
|
||||||
# Fetch handler from router
|
# Fetch handler from router
|
||||||
handler, args, kwargs = self.router.get(request)
|
handler, args, kwargs, uri = self.router.get(request)
|
||||||
|
request.uri_template = uri
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
("'None' was returned while requesting a "
|
("'None' was returned while requesting a "
|
||||||
@@ -510,36 +516,29 @@ class Sanic:
|
|||||||
# Execution
|
# Execution
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
|
def run(self, host="127.0.0.1", port=8000, debug=False, ssl=None,
|
||||||
after_start=None, before_stop=None, after_stop=None, ssl=None,
|
sock=None, workers=1, protocol=None,
|
||||||
sock=None, workers=1, loop=None, protocol=None,
|
backlog=100, stop_event=None, register_sys_signals=True,
|
||||||
backlog=100, stop_event=None, register_sys_signals=True):
|
log_config=LOGGING):
|
||||||
"""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.
|
||||||
|
|
||||||
:param host: Address to host on
|
:param host: Address to host on
|
||||||
:param port: Port to host on
|
:param port: Port to host on
|
||||||
:param debug: Enables debug output (slows server)
|
:param debug: Enables debug output (slows server)
|
||||||
:param before_start: Functions to be executed before the server starts
|
|
||||||
accepting connections
|
|
||||||
:param after_start: Functions to be executed after the server starts
|
|
||||||
accepting connections
|
|
||||||
:param before_stop: Functions to be executed when a stop signal is
|
|
||||||
received before it is respected
|
|
||||||
:param after_stop: Functions to be executed when all requests are
|
|
||||||
complete
|
|
||||||
: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 loop:
|
|
||||||
:param backlog:
|
:param backlog:
|
||||||
:param stop_event:
|
:param stop_event:
|
||||||
:param register_sys_signals:
|
:param register_sys_signals:
|
||||||
:param protocol: Subclass of asyncio protocol class
|
:param protocol: Subclass of asyncio protocol class
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
|
if log_config:
|
||||||
|
logging.config.dictConfig(log_config)
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (WebSocketProtocol if self.websocket_enabled
|
protocol = (WebSocketProtocol if self.websocket_enabled
|
||||||
else HttpProtocol)
|
else HttpProtocol)
|
||||||
@@ -549,11 +548,10 @@ class Sanic:
|
|||||||
warnings.warn("stop_event will be removed from future versions.",
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host, port=port, debug=debug, before_start=before_start,
|
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
|
||||||
after_start=after_start, before_stop=before_stop,
|
workers=workers, protocol=protocol, backlog=backlog,
|
||||||
after_stop=after_stop, ssl=ssl, sock=sock, workers=workers,
|
register_sys_signals=register_sys_signals,
|
||||||
loop=loop, protocol=protocol, backlog=backlog,
|
has_log=log_config is not None)
|
||||||
register_sys_signals=register_sys_signals)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
@@ -564,6 +562,7 @@ class Sanic:
|
|||||||
except:
|
except:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Experienced exception while trying to serve')
|
'Experienced exception while trying to serve')
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
log.info("Server Stopped")
|
log.info("Server Stopped")
|
||||||
@@ -577,15 +576,16 @@ class Sanic:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
async def create_server(self, host="127.0.0.1", port=8000, debug=False,
|
async def create_server(self, host="127.0.0.1", port=8000, debug=False,
|
||||||
before_start=None, after_start=None,
|
ssl=None, sock=None, protocol=None,
|
||||||
before_stop=None, after_stop=None, ssl=None,
|
backlog=100, stop_event=None,
|
||||||
sock=None, loop=None, protocol=None,
|
log_config=LOGGING):
|
||||||
backlog=100, stop_event=None):
|
|
||||||
"""Asynchronous version of `run`.
|
"""Asynchronous version of `run`.
|
||||||
|
|
||||||
NOTE: This does not support multiprocessing and is not the preferred
|
NOTE: This does not support multiprocessing and is not the preferred
|
||||||
way to run a Sanic application.
|
way to run a Sanic application.
|
||||||
"""
|
"""
|
||||||
|
if log_config:
|
||||||
|
logging.config.dictConfig(log_config)
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (WebSocketProtocol if self.websocket_enabled
|
protocol = (WebSocketProtocol if self.websocket_enabled
|
||||||
else HttpProtocol)
|
else HttpProtocol)
|
||||||
@@ -595,11 +595,10 @@ class Sanic:
|
|||||||
warnings.warn("stop_event will be removed from future versions.",
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host, port=port, debug=debug, before_start=before_start,
|
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
|
||||||
after_start=after_start, before_stop=before_stop,
|
loop=get_event_loop(), protocol=protocol,
|
||||||
after_stop=after_stop, ssl=ssl, sock=sock,
|
backlog=backlog, run_async=True,
|
||||||
loop=loop or get_event_loop(), protocol=protocol,
|
has_log=log_config is not None)
|
||||||
backlog=backlog, run_async=True)
|
|
||||||
|
|
||||||
return await serve(**server_settings)
|
return await serve(**server_settings)
|
||||||
|
|
||||||
@@ -626,10 +625,9 @@ class Sanic:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def _helper(self, host="127.0.0.1", port=8000, debug=False,
|
def _helper(self, host="127.0.0.1", port=8000, debug=False,
|
||||||
before_start=None, after_start=None, before_stop=None,
|
ssl=None, sock=None, workers=1, loop=None,
|
||||||
after_stop=None, ssl=None, sock=None, workers=1, loop=None,
|
|
||||||
protocol=HttpProtocol, backlog=100, stop_event=None,
|
protocol=HttpProtocol, backlog=100, stop_event=None,
|
||||||
register_sys_signals=True, run_async=False):
|
register_sys_signals=True, run_async=False, has_log=True):
|
||||||
"""Helper function used by `run` and `create_server`."""
|
"""Helper function used by `run` and `create_server`."""
|
||||||
|
|
||||||
if isinstance(ssl, dict):
|
if isinstance(ssl, dict):
|
||||||
@@ -646,23 +644,6 @@ class Sanic:
|
|||||||
warnings.simplefilter('default')
|
warnings.simplefilter('default')
|
||||||
warnings.warn("stop_event will be removed from future versions.",
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
if loop is not None:
|
|
||||||
if debug:
|
|
||||||
warnings.simplefilter('default')
|
|
||||||
warnings.warn("Passing a loop will be deprecated in version"
|
|
||||||
" 0.4.0 https://github.com/channelcat/sanic/"
|
|
||||||
"pull/335 has more information.",
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
# Deprecate this
|
|
||||||
if any(arg is not None for arg in (after_stop, after_start,
|
|
||||||
before_start, before_stop)):
|
|
||||||
if debug:
|
|
||||||
warnings.simplefilter('default')
|
|
||||||
warnings.warn("Passing a before_start, before_stop, after_start or"
|
|
||||||
"after_stop callback will be deprecated in next "
|
|
||||||
"major version after 0.4.0",
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
self.error_handler.debug = debug
|
self.error_handler.debug = debug
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
@@ -674,32 +655,30 @@ 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,
|
||||||
|
'has_log': has_log
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
# Register start/stop events
|
# Register start/stop events
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
|
||||||
for event_name, settings_name, reverse, args in (
|
for event_name, settings_name, reverse in (
|
||||||
("before_server_start", "before_start", False, before_start),
|
("before_server_start", "before_start", False),
|
||||||
("after_server_start", "after_start", False, after_start),
|
("after_server_start", "after_start", False),
|
||||||
("before_server_stop", "before_stop", True, before_stop),
|
("before_server_stop", "before_stop", True),
|
||||||
("after_server_stop", "after_stop", True, after_stop),
|
("after_server_stop", "after_stop", True),
|
||||||
):
|
):
|
||||||
listeners = self.listeners[event_name].copy()
|
listeners = self.listeners[event_name].copy()
|
||||||
if args:
|
|
||||||
if callable(args):
|
|
||||||
listeners.append(args)
|
|
||||||
else:
|
|
||||||
listeners.extend(args)
|
|
||||||
if reverse:
|
if reverse:
|
||||||
listeners.reverse()
|
listeners.reverse()
|
||||||
# Prepend sanic to the arguments when listeners are triggered
|
# Prepend sanic to the arguments when listeners are triggered
|
||||||
|
|||||||
123
sanic/config.py
123
sanic/config.py
@@ -1,12 +1,122 @@
|
|||||||
|
from sanic.defaultFilter import DefaultFilter
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import syslog
|
||||||
|
import platform
|
||||||
import types
|
import types
|
||||||
|
|
||||||
SANIC_PREFIX = 'SANIC_'
|
SANIC_PREFIX = 'SANIC_'
|
||||||
|
|
||||||
|
_address_dict = {
|
||||||
|
'Windows': ('localhost', 514),
|
||||||
|
'Darwin': '/var/run/syslog',
|
||||||
|
'Linux': '/dev/log',
|
||||||
|
'FreeBSD': '/dev/log'
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'filters': {
|
||||||
|
'accessFilter': {
|
||||||
|
'()': DefaultFilter,
|
||||||
|
'param': [0, 10, 20]
|
||||||
|
},
|
||||||
|
'errorFilter': {
|
||||||
|
'()': DefaultFilter,
|
||||||
|
'param': [30, 40, 50]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'formatters': {
|
||||||
|
'simple': {
|
||||||
|
'format': '%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||||
|
},
|
||||||
|
'access': {
|
||||||
|
'format': '%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: ' +
|
||||||
|
'%(request)s %(message)s %(status)d %(byte)d',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'internal': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'filters': ['accessFilter'],
|
||||||
|
'formatter': 'simple',
|
||||||
|
'stream': sys.stderr
|
||||||
|
},
|
||||||
|
'accessStream': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'filters': ['accessFilter'],
|
||||||
|
'formatter': 'access',
|
||||||
|
'stream': sys.stderr
|
||||||
|
},
|
||||||
|
'errorStream': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'filters': ['errorFilter'],
|
||||||
|
'formatter': 'simple',
|
||||||
|
'stream': sys.stderr
|
||||||
|
},
|
||||||
|
# before you use accessSysLog, be sure that log levels
|
||||||
|
# 0, 10, 20 have been enabled in you syslog configuration
|
||||||
|
# otherwise you won't be able to see the output in syslog
|
||||||
|
# logging file.
|
||||||
|
'accessSysLog': {
|
||||||
|
'class': 'logging.handlers.SysLogHandler',
|
||||||
|
'address': _address_dict.get(platform.system(),
|
||||||
|
('localhost', 514)),
|
||||||
|
'facility': syslog.LOG_DAEMON,
|
||||||
|
'filters': ['accessFilter'],
|
||||||
|
'formatter': 'access'
|
||||||
|
},
|
||||||
|
'errorSysLog': {
|
||||||
|
'class': 'logging.handlers.SysLogHandler',
|
||||||
|
'address': _address_dict.get(platform.system(),
|
||||||
|
('localhost', 514)),
|
||||||
|
'facility': syslog.LOG_DAEMON,
|
||||||
|
'filters': ['errorFilter'],
|
||||||
|
'formatter': 'simple'
|
||||||
|
},
|
||||||
|
'accessTimedRotatingFile': {
|
||||||
|
'class': 'logging.handlers.TimedRotatingFileHandler',
|
||||||
|
'filters': ['accessFilter'],
|
||||||
|
'formatter': 'access',
|
||||||
|
'when': 'D',
|
||||||
|
'interval': 1,
|
||||||
|
'backupCount': 7,
|
||||||
|
'filename': 'access.log'
|
||||||
|
},
|
||||||
|
'errorTimedRotatingFile': {
|
||||||
|
'class': 'logging.handlers.TimedRotatingFileHandler',
|
||||||
|
'filters': ['errorFilter'],
|
||||||
|
'when': 'D',
|
||||||
|
'interval': 1,
|
||||||
|
'backupCount': 7,
|
||||||
|
'filename': 'error.log',
|
||||||
|
'formatter': 'simple'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'sanic': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'handlers': ['internal', 'errorStream']
|
||||||
|
},
|
||||||
|
'network': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'handlers': ['accessStream', 'errorStream']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# this happens when using container or systems without syslog
|
||||||
|
# keep things in config would cause file not exists error
|
||||||
|
_addr = LOGGING['handlers']['accessSysLog']['address']
|
||||||
|
if type(_addr) is str and not os.path.exists(_addr):
|
||||||
|
LOGGING['handlers'].pop('accessSysLog')
|
||||||
|
LOGGING['handlers'].pop('errorSysLog')
|
||||||
|
|
||||||
|
|
||||||
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 = """
|
||||||
▄▄▄▄▄
|
▄▄▄▄▄
|
||||||
@@ -31,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()
|
||||||
@@ -98,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
|
||||||
|
|||||||
13
sanic/defaultFilter.py
Normal file
13
sanic/defaultFilter.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultFilter(logging.Filter):
|
||||||
|
def __init__(self, param=None):
|
||||||
|
self.param = param
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
if self.param is None:
|
||||||
|
return True
|
||||||
|
if record.levelno in self.param:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -47,6 +47,10 @@ TRACEBACK_STYLE = '''
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tb-border {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.frame-descriptor {
|
.frame-descriptor {
|
||||||
background-color: #e2eafb;
|
background-color: #e2eafb;
|
||||||
}
|
}
|
||||||
@@ -63,12 +67,9 @@ TRACEBACK_WRAPPER_HTML = '''
|
|||||||
{style}
|
{style}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{exc_name}</h1>
|
{inner_html}
|
||||||
<h3><code>{exc_value}</code></h3>
|
<div class="summary">
|
||||||
<div class="tb-wrapper">
|
<p>
|
||||||
<p class="tb-header">Traceback (most recent call last):</p>
|
|
||||||
{frame_html}
|
|
||||||
<p class="summary">
|
|
||||||
<b>{exc_name}: {exc_value}</b>
|
<b>{exc_name}: {exc_value}</b>
|
||||||
while handling path <code>{path}</code>
|
while handling path <code>{path}</code>
|
||||||
</p>
|
</p>
|
||||||
@@ -77,6 +78,24 @@ TRACEBACK_WRAPPER_HTML = '''
|
|||||||
</html>
|
</html>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_WRAPPER_INNER_HTML = '''
|
||||||
|
<h1>{exc_name}</h1>
|
||||||
|
<h3><code>{exc_value}</code></h3>
|
||||||
|
<div class="tb-wrapper">
|
||||||
|
<p class="tb-header">Traceback (most recent call last):</p>
|
||||||
|
{frame_html}
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
|
TRACEBACK_BORDER = '''
|
||||||
|
<div class="tb-border">
|
||||||
|
<b><i>
|
||||||
|
The above exception was the direct cause of the
|
||||||
|
following exception:
|
||||||
|
</i></b>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
|
||||||
TRACEBACK_LINE_HTML = '''
|
TRACEBACK_LINE_HTML = '''
|
||||||
<div class="frame-line">
|
<div class="frame-line">
|
||||||
<p class="frame-descriptor">
|
<p class="frame-descriptor">
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ from sanic.exceptions import (
|
|||||||
SanicException,
|
SanicException,
|
||||||
TRACEBACK_LINE_HTML,
|
TRACEBACK_LINE_HTML,
|
||||||
TRACEBACK_STYLE,
|
TRACEBACK_STYLE,
|
||||||
TRACEBACK_WRAPPER_HTML)
|
TRACEBACK_WRAPPER_HTML,
|
||||||
|
TRACEBACK_WRAPPER_INNER_HTML,
|
||||||
|
TRACEBACK_BORDER)
|
||||||
from sanic.log import log
|
from sanic.log import log
|
||||||
from sanic.response import text, html
|
from sanic.response import text, html
|
||||||
|
|
||||||
@@ -24,19 +26,31 @@ class ErrorHandler:
|
|||||||
self.cached_handlers = {}
|
self.cached_handlers = {}
|
||||||
self.debug = False
|
self.debug = False
|
||||||
|
|
||||||
def _render_traceback_html(self, exception, request):
|
def _render_exception(self, exception):
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
frames = extract_tb(exception.__traceback__)
|
||||||
frames = extract_tb(tb)
|
|
||||||
|
|
||||||
frame_html = []
|
frame_html = []
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
|
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
|
||||||
|
|
||||||
|
return TRACEBACK_WRAPPER_INNER_HTML.format(
|
||||||
|
exc_name=exception.__class__.__name__,
|
||||||
|
exc_value=exception,
|
||||||
|
frame_html=''.join(frame_html))
|
||||||
|
|
||||||
|
def _render_traceback_html(self, exception, request):
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
exceptions = []
|
||||||
|
|
||||||
|
while exc_value:
|
||||||
|
exceptions.append(self._render_exception(exc_value))
|
||||||
|
exc_value = exc_value.__cause__
|
||||||
|
|
||||||
return TRACEBACK_WRAPPER_HTML.format(
|
return TRACEBACK_WRAPPER_HTML.format(
|
||||||
style=TRACEBACK_STYLE,
|
style=TRACEBACK_STYLE,
|
||||||
exc_name=exc_type.__name__,
|
exc_name=exception.__class__.__name__,
|
||||||
exc_value=exc_value,
|
exc_value=exception,
|
||||||
frame_html=''.join(frame_html),
|
inner_html=TRACEBACK_BORDER.join(reversed(exceptions)),
|
||||||
path=request.path)
|
path=request.path)
|
||||||
|
|
||||||
def add(self, exception, handler):
|
def add(self, exception, handler):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger('sanic')
|
log = logging.getLogger('sanic')
|
||||||
|
netlog = logging.getLogger('network')
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Request(dict):
|
|||||||
__slots__ = (
|
__slots__ = (
|
||||||
'app', 'headers', 'version', 'method', '_cookies', 'transport',
|
'app', 'headers', 'version', 'method', '_cookies', 'transport',
|
||||||
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
|
||||||
'_ip', '_parsed_url',
|
'_ip', '_parsed_url', 'uri_template'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, url_bytes, headers, version, method, transport):
|
def __init__(self, url_bytes, headers, version, method, transport):
|
||||||
@@ -57,6 +57,7 @@ class Request(dict):
|
|||||||
self.parsed_form = None
|
self.parsed_form = None
|
||||||
self.parsed_files = None
|
self.parsed_files = None
|
||||||
self.parsed_args = None
|
self.parsed_args = None
|
||||||
|
self.uri_template = None
|
||||||
self._cookies = None
|
self._cookies = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -78,9 +79,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'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from sanic.views import CompositionView
|
|||||||
|
|
||||||
Route = namedtuple(
|
Route = namedtuple(
|
||||||
'Route',
|
'Route',
|
||||||
['handler', 'methods', 'pattern', 'parameters', 'name'])
|
['handler', 'methods', 'pattern', 'parameters', 'name', 'uri'])
|
||||||
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
Parameter = namedtuple('Parameter', ['name', 'cast'])
|
||||||
|
|
||||||
REGEX_TYPES = {
|
REGEX_TYPES = {
|
||||||
@@ -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)
|
||||||
@@ -223,7 +225,7 @@ class Router:
|
|||||||
|
|
||||||
route = Route(
|
route = Route(
|
||||||
handler=handler, methods=methods, pattern=pattern,
|
handler=handler, methods=methods, pattern=pattern,
|
||||||
parameters=parameters, name=handler_name)
|
parameters=parameters, name=handler_name, uri=uri)
|
||||||
|
|
||||||
self.routes_all[uri] = route
|
self.routes_all[uri] = route
|
||||||
if properties['unhashable']:
|
if properties['unhashable']:
|
||||||
@@ -342,4 +344,4 @@ class Router:
|
|||||||
route_handler = route.handler
|
route_handler = route.handler
|
||||||
if hasattr(route_handler, 'handlers'):
|
if hasattr(route_handler, 'handlers'):
|
||||||
route_handler = route_handler.handlers[method]
|
route_handler = route_handler.handlers[method]
|
||||||
return route_handler, [], kwargs
|
return route_handler, [], kwargs, route.uri
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
@@ -25,7 +24,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
async_loop = asyncio
|
async_loop = asyncio
|
||||||
|
|
||||||
from sanic.log import log
|
from sanic.log import log, netlog
|
||||||
|
from sanic.response import HTTPResponse
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
RequestTimeout, PayloadTooLarge, InvalidUsage, ServerError)
|
RequestTimeout, PayloadTooLarge, InvalidUsage, ServerError)
|
||||||
@@ -65,12 +65,15 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
# request config
|
# request config
|
||||||
'request_handler', 'request_timeout', 'request_max_size',
|
'request_handler', 'request_timeout', 'request_max_size',
|
||||||
'request_class',
|
'request_class',
|
||||||
|
# enable or disable access log / error log purpose
|
||||||
|
'has_log',
|
||||||
# connection management
|
# connection management
|
||||||
'_total_request_size', '_timeout_handler', '_last_communication_time')
|
'_total_request_size', '_timeout_handler', '_last_communication_time')
|
||||||
|
|
||||||
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):
|
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
|
||||||
@@ -78,6 +81,7 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
self.url = None
|
self.url = None
|
||||||
self.headers = None
|
self.headers = None
|
||||||
self.signal = signal
|
self.signal = signal
|
||||||
|
self.has_log = has_log
|
||||||
self.connections = connections
|
self.connections = connections
|
||||||
self.request_handler = request_handler
|
self.request_handler = request_handler
|
||||||
self.error_handler = error_handler
|
self.error_handler = error_handler
|
||||||
@@ -88,6 +92,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
|
||||||
|
def keep_alive(self):
|
||||||
|
return (self._keep_alive
|
||||||
|
and not self.signal.stopped
|
||||||
|
and self.parser.should_keep_alive())
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
# Connection
|
# Connection
|
||||||
@@ -165,8 +176,7 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
self.request.body.append(body)
|
self.request.body.append(body)
|
||||||
|
|
||||||
def on_message_complete(self):
|
def on_message_complete(self):
|
||||||
if self.request.body:
|
self.request.body = b''.join(self.request.body)
|
||||||
self.request.body = b''.join(self.request.body)
|
|
||||||
|
|
||||||
self._request_handler_task = self.loop.create_task(
|
self._request_handler_task = self.loop.create_task(
|
||||||
self.request_handler(
|
self.request_handler(
|
||||||
@@ -182,13 +192,19 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
Writes response content synchronously to the transport.
|
Writes response content synchronously to the transport.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
keep_alive = (
|
keep_alive = self.keep_alive
|
||||||
self.parser.should_keep_alive() and not self.signal.stopped)
|
|
||||||
|
|
||||||
self.transport.write(
|
self.transport.write(
|
||||||
response.output(
|
response.output(
|
||||||
self.request.version, keep_alive,
|
self.request.version, keep_alive,
|
||||||
self.request_timeout))
|
self.request_timeout))
|
||||||
|
if self.has_log:
|
||||||
|
netlog.info('', extra={
|
||||||
|
'status': response.status,
|
||||||
|
'byte': len(response.body),
|
||||||
|
'host': '%s:%d' % self.request.ip,
|
||||||
|
'request': '%s %s' % (self.request.method,
|
||||||
|
self.request.url)
|
||||||
|
})
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.error(
|
log.error(
|
||||||
('Invalid response object for url {}, '
|
('Invalid response object for url {}, '
|
||||||
@@ -218,12 +234,18 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
keep_alive = (
|
keep_alive = self.keep_alive
|
||||||
self.parser.should_keep_alive() and not self.signal.stopped)
|
|
||||||
|
|
||||||
response.transport = self.transport
|
response.transport = self.transport
|
||||||
await response.stream(
|
await response.stream(
|
||||||
self.request.version, keep_alive, self.request_timeout)
|
self.request.version, keep_alive, self.request_timeout)
|
||||||
|
if self.has_log:
|
||||||
|
netlog.info('', extra={
|
||||||
|
'status': response.status,
|
||||||
|
'byte': -1,
|
||||||
|
'host': '%s:%d' % self.request.ip,
|
||||||
|
'request': '%s %s' % (self.request.method,
|
||||||
|
self.request.url)
|
||||||
|
})
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.error(
|
log.error(
|
||||||
('Invalid response object for url {}, '
|
('Invalid response object for url {}, '
|
||||||
@@ -259,6 +281,21 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
"Writing error failed, connection closed {}".format(repr(e)),
|
"Writing error failed, connection closed {}".format(repr(e)),
|
||||||
from_error=True)
|
from_error=True)
|
||||||
finally:
|
finally:
|
||||||
|
if self.has_log:
|
||||||
|
extra = {
|
||||||
|
'status': response.status,
|
||||||
|
'host': '',
|
||||||
|
'request': str(self.request) + str(self.url)
|
||||||
|
}
|
||||||
|
if response and isinstance(response, HTTPResponse):
|
||||||
|
extra['byte'] = len(response.body)
|
||||||
|
else:
|
||||||
|
extra['byte'] = -1
|
||||||
|
if self.request:
|
||||||
|
extra['host'] = '%s:%d' % self.request.ip,
|
||||||
|
extra['request'] = '%s %s' % (self.request.method,
|
||||||
|
self.url)
|
||||||
|
netlog.info('', extra=extra)
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
|
||||||
def bail_out(self, message, from_error=False):
|
def bail_out(self, message, from_error=False):
|
||||||
@@ -322,7 +359,7 @@ 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):
|
signal=Signal(), request_class=None, has_log=True, 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
|
||||||
@@ -348,6 +385,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
|||||||
:param loop: asyncio compatible event loop
|
:param loop: asyncio compatible event loop
|
||||||
:param protocol: subclass of asyncio protocol class
|
:param protocol: subclass of asyncio protocol class
|
||||||
:param request_class: Request class to use
|
:param request_class: Request class to use
|
||||||
|
:param has_log: disable/enable access log and error log
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
if not run_async:
|
if not run_async:
|
||||||
@@ -370,6 +408,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,
|
||||||
|
keep_alive=keep_alive,
|
||||||
)
|
)
|
||||||
|
|
||||||
server_coroutine = loop.create_server(
|
server_coroutine = loop.create_server(
|
||||||
@@ -440,12 +480,6 @@ def serve_multiple(server_settings, workers):
|
|||||||
:param stop_event: if provided, is used as a stop signal
|
:param stop_event: if provided, is used as a stop signal
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if server_settings.get('loop', None) is not None:
|
|
||||||
if server_settings.get('debug', False):
|
|
||||||
warnings.simplefilter('default')
|
|
||||||
warnings.warn("Passing a loop will be deprecated in version 0.4.0"
|
|
||||||
" https://github.com/channelcat/sanic/pull/335"
|
|
||||||
" has more information.", DeprecationWarning)
|
|
||||||
server_settings['reuse_port'] = True
|
server_settings['reuse_port'] = True
|
||||||
|
|
||||||
# Handling when custom socket is not provided.
|
# Handling when custom socket is not provided.
|
||||||
@@ -461,6 +495,8 @@ def serve_multiple(server_settings, workers):
|
|||||||
def sig_handler(signal, frame):
|
def sig_handler(signal, frame):
|
||||||
log.info("Received signal {}. Shutting down.".format(
|
log.info("Received signal {}. Shutting down.".format(
|
||||||
Signals(signal).name))
|
Signals(signal).name))
|
||||||
|
for process in processes:
|
||||||
|
os.kill(process.pid, SIGINT)
|
||||||
|
|
||||||
signal_func(SIGINT, lambda s, f: sig_handler(s, f))
|
signal_func(SIGINT, lambda s, f: sig_handler(s, f))
|
||||||
signal_func(SIGTERM, lambda s, f: sig_handler(s, f))
|
signal_func(SIGTERM, lambda s, f: sig_handler(s, f))
|
||||||
@@ -479,5 +515,3 @@ def serve_multiple(server_settings, workers):
|
|||||||
for process in processes:
|
for process in processes:
|
||||||
process.terminate()
|
process.terminate()
|
||||||
server_settings.get('sock').close()
|
server_settings.get('sock').close()
|
||||||
|
|
||||||
asyncio.get_event_loop().stop()
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import warnings
|
|
||||||
|
|
||||||
from sanic.testing import SanicTestClient
|
|
||||||
|
|
||||||
|
|
||||||
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
|
|
||||||
debug=False, server_kwargs={},
|
|
||||||
*request_args, **request_kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
"Use of sanic_endpoint_test will be deprecated in"
|
|
||||||
"the next major version after 0.4.0. Please use the `test_client` "
|
|
||||||
"available on the app object.", DeprecationWarning)
|
|
||||||
|
|
||||||
test_client = SanicTestClient(app)
|
|
||||||
return test_client._sanic_endpoint_test(
|
|
||||||
method, uri, gather_request, debug, server_kwargs,
|
|
||||||
*request_args, **request_kwargs)
|
|
||||||
@@ -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
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|||||||
22
setup.py
22
setup.py
@@ -9,14 +9,27 @@ from distutils.util import strtobool
|
|||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
with codecs.open(os.path.join(os.path.abspath(os.path.dirname(
|
|
||||||
__file__)), 'sanic', '__init__.py'), 'r', 'latin1') as fp:
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
with open_local(['sanic', '__init__.py'], encoding='latin1') as fp:
|
||||||
try:
|
try:
|
||||||
version = re.findall(r"^__version__ = '([^']+)'\r?$",
|
version = re.findall(r"^__version__ = '([^']+)'\r?$",
|
||||||
fp.read(), re.M)[0]
|
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:
|
||||||
|
long_description = rm.read()
|
||||||
|
|
||||||
setup_kwargs = {
|
setup_kwargs = {
|
||||||
'name': 'sanic',
|
'name': 'sanic',
|
||||||
'version': version,
|
'version': version,
|
||||||
@@ -26,6 +39,7 @@ setup_kwargs = {
|
|||||||
'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'],
|
'packages': ['sanic'],
|
||||||
'platforms': 'any',
|
'platforms': 'any',
|
||||||
'classifiers': [
|
'classifiers': [
|
||||||
@@ -64,7 +78,3 @@ except DistutilsPlatformError as exception:
|
|||||||
print("Installing without uJSON or uvLoop")
|
print("Installing without uJSON or uvLoop")
|
||||||
setup_kwargs['install_requires'] = requirements
|
setup_kwargs['install_requires'] = requirements
|
||||||
setup(**setup_kwargs)
|
setup(**setup_kwargs)
|
||||||
|
|
||||||
# Installation was successful
|
|
||||||
print(u"\n\n\U0001F680 "
|
|
||||||
"Sanic version {} installation suceeded.\n".format(version))
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import sanic
|
from sanic import Sanic
|
||||||
from sanic.utils import sanic_endpoint_test
|
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from threading import Event
|
from threading import Event
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
def test_create_task():
|
def test_create_task():
|
||||||
e = Event()
|
e = Event()
|
||||||
|
|
||||||
async def coro():
|
async def coro():
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.05)
|
||||||
e.set()
|
e.set()
|
||||||
|
|
||||||
app = sanic.Sanic()
|
app = Sanic('test_create_task')
|
||||||
app.add_task(coro)
|
app.add_task(coro)
|
||||||
|
|
||||||
@app.route('/early')
|
@app.route('/early')
|
||||||
@@ -22,9 +23,8 @@ def test_create_task():
|
|||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
return text(e.is_set())
|
return text(e.is_set())
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/early')
|
||||||
request, response = sanic_endpoint_test(app, uri='/early')
|
|
||||||
assert response.body == b'False'
|
assert response.body == b'False'
|
||||||
|
|
||||||
request, response = sanic_endpoint_test(app, uri='/late')
|
request, response = app.test_client.get('/late')
|
||||||
assert response.body == b'True'
|
assert response.body == b'True'
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ def handler_5(request):
|
|||||||
raise CustomServerError('Custom server error')
|
raise CustomServerError('Custom server error')
|
||||||
|
|
||||||
|
|
||||||
|
@exception_handler_app.route('/6/<arg:int>')
|
||||||
|
def handler_6(request, arg):
|
||||||
|
try:
|
||||||
|
foo = 1 / arg
|
||||||
|
except Exception as e:
|
||||||
|
raise e from ValueError("{}".format(arg))
|
||||||
|
return text(foo)
|
||||||
|
|
||||||
|
|
||||||
@exception_handler_app.exception(NotFound, ServerError)
|
@exception_handler_app.exception(NotFound, ServerError)
|
||||||
def handler_exception(request, exception):
|
def handler_exception(request, exception):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
@@ -84,6 +93,26 @@ def test_inherited_exception_handler():
|
|||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_chained_exception_handler():
|
||||||
|
request, response = exception_handler_app.test_client.get(
|
||||||
|
'/6/0', debug=True)
|
||||||
|
assert response.status == 500
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.body, 'html.parser')
|
||||||
|
html = str(soup)
|
||||||
|
|
||||||
|
assert 'response = handler(request, *args, **kwargs)' in html
|
||||||
|
assert 'handler_6' in html
|
||||||
|
assert 'foo = 1 / arg' in html
|
||||||
|
assert 'ValueError' in html
|
||||||
|
assert 'The above exception was the direct cause' in html
|
||||||
|
|
||||||
|
summary_text = " ".join(soup.select('.summary')[0].text.split())
|
||||||
|
assert (
|
||||||
|
"ZeroDivisionError: division by zero "
|
||||||
|
"while handling path /6/0") == summary_text
|
||||||
|
|
||||||
|
|
||||||
def test_exception_handler_lookup():
|
def test_exception_handler_lookup():
|
||||||
class CustomError(Exception):
|
class CustomError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -133,6 +133,17 @@ def test_query_string():
|
|||||||
assert request.args.get('test2') == 'false'
|
assert request.args.get('test2') == 'false'
|
||||||
|
|
||||||
|
|
||||||
|
def test_uri_template():
|
||||||
|
app = Sanic('test_uri_template')
|
||||||
|
|
||||||
|
@app.route('/foo/<id:int>/bar/<name:[A-z]+>')
|
||||||
|
async def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
request, response = app.test_client.get('/foo/123/bar/baz')
|
||||||
|
assert request.uri_template == '/foo/<id:int>/bar/<name:[A-z]+>'
|
||||||
|
|
||||||
|
|
||||||
def test_token():
|
def test_token():
|
||||||
app = Sanic('test_post_token')
|
app = Sanic('test_post_token')
|
||||||
|
|
||||||
@@ -141,6 +152,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 +172,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')
|
||||||
|
|
||||||
|
|||||||
13
tox.ini
13
tox.ini
@@ -1,11 +1,11 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py35, py36, flake8
|
envlist = py35, py36, flake8, check
|
||||||
|
|
||||||
|
|
||||||
[travis]
|
[travis]
|
||||||
python =
|
python =
|
||||||
3.5: py35, flake8
|
3.5: py35, flake8, check
|
||||||
3.6: py36, flake8
|
3.6: py36, flake8, check
|
||||||
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
@@ -24,3 +24,10 @@ deps =
|
|||||||
|
|
||||||
commands =
|
commands =
|
||||||
flake8 sanic
|
flake8 sanic
|
||||||
|
|
||||||
|
[testenv:check]
|
||||||
|
deps =
|
||||||
|
docutils
|
||||||
|
pygments
|
||||||
|
commands =
|
||||||
|
python setup.py check -r -s
|
||||||
|
|||||||
Reference in New Issue
Block a user