Compare commits

..

109 Commits
0.5.0 ... 0.5.3

Author SHA1 Message Date
Eli Uriegas
8d3fd75ec2 Merge pull request #695 from kszucs/aiopeewee
aiopeewee example
2017-05-05 11:31:37 -07:00
Szucs Krisztian
a42b254c33 use async version of model_to_dict 2017-05-05 10:51:12 +02:00
Szucs Krisztian
d24e1ae110 removed lines from distributed example 2017-05-05 08:25:18 +02:00
Szucs Krisztian
2e7badab4e aiopeewee example 2017-05-05 08:19:09 +02:00
Raphael Deem
7cf3d49f00 Merge pull request #690 from 38elements/sanic_endpoint_test
Remove utils.py
2017-05-03 23:56:13 -07:00
38elements
25037006bf Remove utils.py 2017-05-04 15:52:18 +09:00
Eli Uriegas
f611eb2c2b Merge pull request #686 from 38elements/loop
Remove loop argument in run() and create_server()
2017-05-03 15:27:16 -07:00
38elements
e12c10b087 Remove loop argument in run() and create_server() 2017-05-03 17:52:19 +09:00
Raphael Deem
9527e5ded8 Merge pull request #685 from r0fls/document-create-server
document create_server method
2017-05-02 22:45:51 -07:00
Raphael Deem
23b4b20b4f document create_server method 2017-05-02 22:44:42 -07:00
Eli Uriegas
7f9ecd659c Merge pull request #682 from graingert/fix-readme.rst-encoding
fix README.rst -> long_description encoding
2017-05-02 10:48:45 -07:00
Eli Uriegas
bb31d465f2 Add distribution types 2017-05-02 10:47:55 -07:00
Thomas Grainger
834468e8e7 fix README.rst -> long_description encoding 2017-05-02 17:37:17 +01:00
Eli Uriegas
4720513672 Merge pull request #679 from graingert/verify-readme-rst-pypi
verify readme for PyPI
2017-05-02 09:31:01 -07:00
Eli Uriegas
a480110d43 Merge pull request #681 from 38elements/deprecation
Remove before_start, before_stop, after_start and after_stop
2017-05-02 09:28:33 -07:00
38elements
0a2c95cc10 Remove before_start, before_stop, after_start and after_stop 2017-05-02 23:07:09 +09:00
Thomas Grainger
9d2e32902d check readme in travis 2017-05-02 10:17:24 +01:00
Thomas Grainger
77b6413526 validate readme for PyPI 2017-05-02 10:05:06 +01:00
Thomas Grainger
9e502099e0 add readme to package directly 2017-05-02 10:05:05 +01:00
Eli Uriegas
c6a7e44ae7 Merge pull request #678 from stopspazzing/patch-1
misspelling
2017-05-01 17:27:31 -07:00
Jeremy Zimmerman
1bf06312b8 misspelling
acutal -> actual
2017-05-01 17:07:38 -07:00
Eli Uriegas
c35721abbd Merge pull request #670 from ticosax/set-exit-code-on-error
In case of error when starting sanic
2017-05-01 15:16:02 -07:00
Eli Uriegas
7f3c417078 Merge pull request #677 from abuckenheimer/master
added exception chain rendering in debug #675
2017-05-01 15:15:34 -07:00
Alec Buckenheimer
69511c2783 added exception chain rendering in debug #675 2017-05-01 12:56:33 -04:00
Eli Uriegas
158a94d34c Merge pull request #674 from 38elements/test-for-uri-template
Add test for uri_template
2017-04-30 20:22:51 -07:00
Eli Uriegas
db58bd68f5 Merge pull request #673 from 38elements/routing
Fix docs/sanic/routing.md
2017-04-30 20:22:30 -07:00
38elements
e65f08a2c8 Fix docs/sanic/routing.md 2017-04-30 22:03:16 +09:00
38elements
ab8f616385 Add test for uri_template 2017-04-30 21:57:32 +09:00
Eli Uriegas
6dc6f9bbb5 Merge pull request #671 from banteg/uri-template
Expose matched request uri template
2017-04-28 14:22:59 -07:00
banteg
7754bb995b expose matched request uri template 2017-04-29 02:39:56 +07:00
Nicolas Delaby
d1fefce61c fixup! In case of error when starting sanic 2017-04-28 20:06:44 +02:00
Nicolas Delaby
c3abdab9c4 The main process that spawn sub processes doesn't run any loop.
let's not try to stop one
2017-04-28 19:57:49 +02:00
Nicolas Delaby
8b13e103fd In case of error when starting sanic
Don't exit with code 0
2017-04-28 19:08:51 +02:00
Eli Uriegas
5f94f65f4f Merge pull request #669 from 38elements/log
Add access.log and error.log to .gitignore
2017-04-28 07:40:42 -07:00
38elements
fc0d69616c Add access.log and error.log to .gitignore 2017-04-28 18:52:15 +09:00
Raphael Deem
656f5b93d6 Merge pull request #668 from seemethere/stop_workers_on_sigint
Add the killing of children
2017-04-27 22:39:48 -07:00
Eli Uriegas
436d37c079 Add the killing of children
Kills children processes when parent process receives a signal to
shutdown.

Solves for #594
2017-04-27 17:47:08 -07:00
Eli Uriegas
ed0081fcf7 Merge pull request #625 from zenixls2/master
based on issue #608, create access log
2017-04-27 14:31:53 -07:00
Eli Uriegas
140062f8a3 Merge pull request #662 from rsrdesarrollo/master
invariant: body after request is processed must be binary
2017-04-27 14:31:28 -07:00
Raphael Deem
75f5fa7c06 Merge pull request #666 from pyx/issue-665
Fix #665 - ImportError not preserved in __main__.py
2017-04-27 00:32:09 -07:00
Philip Xu
9152a1a266 Improved on wording 2017-04-27 03:16:38 -04:00
Philip Xu
ade89ab795 Fix #665 - ImportError not preserved in __main__.py 2017-04-26 22:57:19 -04:00
zenix
95cfdee8b8 Merge branch 'master' of https://github.com/channelcat/sanic 2017-04-26 14:50:42 +09:00
zenix
63a27cc5e2 add document on logging 2017-04-26 14:50:21 +09:00
Eli Uriegas
472face796 Add link to issue tracking sanic projects! 2017-04-25 21:50:49 -07:00
Raphael Deem
8d537a6d0b Merge pull request #663 from mmaybeno/fix_jinja_example_typo
Fix typo for jinja example and converted to dir
2017-04-25 20:29:12 -07:00
Matt Maybeno
b3101d339e Fix typo for jinja example and converted to dir 2017-04-25 20:10:46 -07:00
Raúl Sampedro
85acddddba invariant: body after request is processed must be binary 2017-04-25 12:00:27 +02:00
zenix
c9d747d97f fix merge error 2017-04-25 11:46:13 +09:00
zenix
0bba267808 Merge branch 'master' of https://github.com/channelcat/sanic 2017-04-25 11:07:40 +09:00
Eli Uriegas
1036242064 Merge pull request #661 from seemethere/increment_052
Increment to 0.5.2
2017-04-24 13:39:39 -05:00
Eli Uriegas
5fd62098bd Increment to 0.5.2 2017-04-24 11:37:04 -07:00
Raphael Deem
74cc7be922 Merge branch 'master' into master 2017-04-24 00:47:01 -07:00
Raphael Deem
b3814ca89a Merge pull request #646 from r0fls/637
allow disabling keep alive
2017-04-24 00:43:16 -07:00
Raphael Deem
b75a321e4a Merge pull request #644 from kszucs/master
Example to use dask distibuted
2017-04-22 01:14:34 -07:00
Raphael Deem
9caa4fec4a Merge pull request #656 from r0fls/token-rework
update token attribute
2017-04-21 22:44:42 -07:00
Raphael Deem
a0cba1aee1 accept token directly in auth header 2017-04-21 22:36:45 -07:00
Raphael Deem
97018ad62f Merge pull request #655 from seemethere/fix_gunicorn_worker
Fix duplicate signal settings for gunicorn worker
2017-04-21 22:16:43 -07:00
Eli Uriegas
a7d17fae44 Fix duplicate signal settings for gunicorn worker 2017-04-21 17:06:52 -05:00
Raphael Deem
6ce0050979 Merge pull request #652 from 38elements/signal-stopped
Fix `this.signal.stopped` is `True` #639
2017-04-20 13:06:28 -07:00
38elements
bc035fca78 Remove unnecessary variables 2017-04-20 18:27:28 +09:00
38elements
df914a92e4 Fix this.signal.stopped is True #639 2017-04-19 11:19:01 +09:00
Szucs Krisztian
1b939a6823 work with distributed 1.16.1 2017-04-17 11:05:19 +02:00
Raphael Deem
81b6d988ec NO_KEEP_ALIVE -> KEEP_ALIVE 2017-04-16 22:43:49 -07:00
Raphael Deem
7e9b65feca Merge pull request #647 from r0fls/645
use absolute path in static root
2017-04-16 22:06:40 -07:00
Raphael Deem
6f098b3d21 add no_keep_alive setting to docs 2017-04-16 22:05:34 -07:00
Raphael Deem
5ddb0488f2 allow disabling keep alive 2017-04-16 22:03:20 -07:00
Raphael Deem
3e87314adf use absolute path in static root 2017-04-16 21:58:10 -07:00
Raphael Deem
f6d4a06661 Merge pull request #643 from aryeh/allow_unknown_status_codes
Allow unknown status codes
2017-04-16 18:42:59 -07:00
Raphael Deem
ff17fc95e6 Merge pull request #632 from messense/feature/path-route
Add path type for router
2017-04-16 18:39:10 -07:00
Eli Uriegas
c5a46f1cea Merge pull request #638 from seemethere/add_contributing_rules
Add new contributing rules
2017-04-16 13:15:01 -05:00
Eli Uriegas
0b072189c4 Merge pull request #641 from TomIsPrettyCool/master
Use render_async and a template env with the Jinja2 example.
2017-04-16 13:14:35 -05:00
Szucs Krisztian
5b22d1486a fix syntax error in comment 2017-04-16 18:13:00 +02:00
Szucs Krisztian
9eb48c2b0d dask distributed example 2017-04-16 18:11:24 +02:00
aryeh
ff0632001c prevent crash for unknown response codes
set text for unknown status code, otherwise when None is used exception occurs.
2017-04-16 10:07:29 -04:00
aryeh
28bd09a2ea Merge pull request #1 from channelcat/master
pull master
2017-04-16 09:48:59 -04:00
Tom Haines
c6aaa9b09c Use render_async and a template env with jinja2 2017-04-16 13:50:07 +01:00
Eli Uriegas
20d9ec1fd2 Fix the numbered list 2017-04-14 14:38:45 -05:00
Eli Uriegas
2c45c2d3c0 Add new contributing rules 2017-04-14 14:35:28 -05:00
Eli Uriegas
18829e648a Merge pull request #635 from yeahx/master
fix directory traversal flaw
2017-04-14 14:01:29 -05:00
Eli Uriegas
a64c636a33 Merge pull request #627 from Sniedes722/master
Updating examples for 0.5.0
2017-04-14 13:21:43 -05:00
Shawn Niederriter
5796f211c1 Added detailed plotly example project 2017-04-14 17:17:23 +00:00
lazydog
ae09dec05e fixed UnboundLocalError 2017-04-14 03:38:55 +08:00
lazydog
afd51e0823 fix directory traversal flaw 2017-04-14 02:55:39 +08:00
zenix
0bbf826b21 fix typo 2017-04-13 16:52:40 +09:00
zenix
02d1900e2f try to fix container error 2017-04-13 16:49:36 +09:00
zenix
73da11b04c switch to use streaming for access log and error log 2017-04-13 16:26:13 +09:00
zenix
4af07e3731 change naming of default log config 2017-04-13 13:49:45 +09:00
zenix
7f60f85cd4 Merge branch 'master' of https://github.com/channelcat/sanic 2017-04-13 13:35:37 +09:00
messense
4c66cb1854 Fix static files router 2017-04-13 12:11:38 +08:00
messense
35b92e1511 Add path type for router 2017-04-13 11:34:35 +08:00
Raphael Deem
e5d3fe52c5 Merge pull request #630 from 38elements/keep_alive
Refactor keep_alive
2017-04-12 19:46:33 -07:00
Raphael Deem
63fe7c0a86 Merge pull request #631 from adamserafini/fix-installation
Fix installation on Ubuntu 16.10 #629
2017-04-12 18:53:23 -07:00
zenix
c5f137c715 fix original code logic 2017-04-12 18:52:01 +09:00
zenix
66923bc0e3 remove unused param 2017-04-12 18:48:16 +09:00
zenix
8bf7b5a323 remove unused dependency 2017-04-12 18:45:38 +09:00
zenix
36d4d85849 change to use default python config code 2017-04-12 18:44:47 +09:00
zenix
5f0e05f3bf fix flake8 2017-04-12 18:08:06 +09:00
adam.serafini
235e5511eb Bump version 2017-04-12 11:02:13 +02:00
zenix
6fb60ae0b1 Merge branch 'master' of https://github.com/channelcat/sanic 2017-04-12 18:01:12 +09:00
adam.serafini
6b2883074b Fix installation on Ubuntu 16.10
Fixes issue #629. Printing a unicode string at the end of the
setup.py script is asking for trouble. It's also redundant:
the pip tool itself tells the user whether the installation was
successful or not.
2017-04-12 10:59:03 +02:00
38elements
7fe418d1b7 Refactor keep_alive 2017-04-12 17:55:22 +09:00
zenix
f872ceb0d9 fix bug in access logging when error happens 2017-04-12 17:39:17 +09:00
Shawn Niederriter
0f10a36b40 Added url_for example 2017-04-12 06:20:35 +00:00
Shawn Niederriter
3c45c9170f Fixed to merge with #626 2017-04-11 21:55:45 +00:00
Shawn Niederriter
a0730aeb44 Merge https://github.com/channelcat/sanic
Version 0.0.5
2017-04-11 21:47:08 +00:00
Shawn Niederriter
adb7331670 Updated examples for 0.5.0 2017-04-11 20:34:55 +00:00
zenix
bf46bcf376 Merge branch 'logging' 2017-04-11 19:03:35 +09:00
zenix
f330c3f8c5 add logging based on issue #608, add default config 2017-04-11 18:59:07 +09:00
52 changed files with 986 additions and 228 deletions

2
.gitignore vendored
View File

@@ -14,3 +14,5 @@ settings.py
docs/_build/
docs/_api/
build/*
access.log
error.log

View File

@@ -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=
on:
tags: true
distributions: "sdist bdist_wheel"

62
CONTRIBUTING.md Normal file
View 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
View File

@@ -0,0 +1,4 @@
include README.rst
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

View File

@@ -1,5 +1,5 @@
Sanic
=================================
=====
|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!
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
----------
@@ -84,8 +86,9 @@ Documentation
TODO
----
* Streamed file processing
* http2
* Streamed file processing
* http2
Limitations
-----------
* No wheels for uvloop and httptools on Windows :(

View File

@@ -20,6 +20,7 @@ Guides
sanic/class_based_views
sanic/custom_protocol
sanic/ssl
sanic/logging
sanic/testing
sanic/deploying
sanic/extensions

View File

@@ -1,6 +1,6 @@
# 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
@@ -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_TIMEOUT | 60 | How long a request can take (sec) |
| KEEP_ALIVE | True | Disables keep-alive when False |

View File

@@ -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
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
* `python -m pip install pytest`
* `python -m pytest 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
* 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

View File

@@ -56,3 +56,17 @@ for Gunicorn `worker-class` argument:
```
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
View 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
```

View File

@@ -94,6 +94,7 @@ The following variables are accessible as properties on `Request` objects:
- `host`: The host associated with the request: `localhost:8080`
- `path`: The path of the request: `/posts/1/`
- `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`

View File

@@ -52,7 +52,7 @@ async def integer_handler(request, integer_arg):
async def number_handler(request, 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):
return text('Person - {}'.format(name))

View File

@@ -57,17 +57,3 @@ def test_post_json_request_includes_data():
More information about
the available arguments to aiohttp can be found
[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
```

View 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)

View File

@@ -1,9 +1,7 @@
"""
Example intercepting uncaught exceptions using Sanic's error handler framework.
This may be useful for developers wishing to use Sentry, Airbrake, etc.
or a custom system to log and monitor unexpected errors in production.
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
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.response import json
from sanic import response
app = Sanic(__name__)
@@ -52,7 +50,7 @@ async def test(request):
# Here, something occurs which causes an unexpected exception
# This exception will flow to our custom handler.
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)

View File

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

View 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)

View 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

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

View 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)

View File

@@ -1,6 +1,5 @@
from sanic import Sanic
from sanic.response import text
import json
from sanic import response
import logging
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
@@ -18,6 +17,6 @@ sanic = Sanic()
@sanic.route("/")
def test(request):
log.info("received request; responding with 'hey'")
return text("hey")
return response.text("hey")
sanic.run(host="0.0.0.0", port=8000)

View 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)

View 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

View File

@@ -1,6 +1,6 @@
from sanic import Sanic
import asyncio
from sanic.response import text
from sanic import Sanic
from sanic import response
from sanic.config import Config
from sanic.exceptions import RequestTimeout
@@ -11,11 +11,11 @@ app = Sanic(__name__)
@app.route('/')
async def test(request):
await asyncio.sleep(3)
return text('Hello, world!')
return response.text('Hello, world!')
@app.exception(RequestTimeout)
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)

View 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)

View File

@@ -5,7 +5,7 @@ motor==1.1
sanic==0.2.0
"""
from sanic import Sanic
from sanic.response import json
from sanic import response
app = Sanic('motor_mongodb')
@@ -25,7 +25,7 @@ async def get(request):
for doc in docs:
doc['id'] = str(doc['_id'])
del doc['_id']
return json(docs)
return response.json(docs)
@app.route('/post', methods=['POST'])
@@ -34,8 +34,8 @@ async def new(request):
print(doc)
db = get_db()
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__":
app.run(host='127.0.0.1', port=8000)
app.run(host='0.0.0.0', port=8000, debug=True)

View File

@@ -1,12 +1,12 @@
from sanic import Sanic
from sanic.response import json
from sanic import response
app = Sanic(__name__)
@app.route("/")
async def test(request):
return json({"test": True})
return response.json({"test": True})
if __name__ == '__main__':

View File

@@ -2,7 +2,7 @@ import os
from sanic import Sanic
from sanic.log import log
from sanic.response import json, text, file
from sanic import response
from sanic.exceptions import ServerError
app = Sanic(__name__)
@@ -10,17 +10,17 @@ app = Sanic(__name__)
@app.route("/")
async def test_async(request):
return json({"test": True})
return response.json({"test": True})
@app.route("/sync", methods=['GET', 'POST'])
def test_sync(request):
return json({"test": True})
return response.json({"test": True})
@app.route("/dynamic/<name>/<id:int>")
def test_params(request, name, id):
return text("yeehaww {} {}".format(name, id))
return response.text("yeehaww {} {}".format(name, id))
@app.route("/exception")
@@ -31,11 +31,11 @@ def exception(request):
async def test_await(request):
import asyncio
await asyncio.sleep(5)
return text("I'm feeling sleepy")
return response.text("I'm feeling sleepy")
@app.route("/file")
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)
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")
def post_json(request):
return json({"received": True, "message": request.json})
return response.json({"received": True, "message": request.json})
@app.route("/form")
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")
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})
# ----------------------------------------------- #

View 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)

View File

@@ -1,4 +1,4 @@
from sanic.response import text
from sanic import response
from sanic import Sanic
from sanic.blueprints import Blueprint
@@ -15,25 +15,25 @@ bp = Blueprint("bp", host="bp.example.com")
"somethingelse.com",
"therestofyourdomains.com"])
async def hello(request):
return text("Some defaults")
return response.text("Some defaults")
@app.route('/', host="example.com")
async def hello(request):
return text("Answer")
return response.text("Answer")
@app.route('/', host="sub.example.com")
async def hello(request):
return text("42")
return response.text("42")
@bp.route("/question")
async def hello(request):
return text("What is the meaning of life?")
return response.text("What is the meaning of life?")
@bp.route("/answer")
async def hello(request):
return text("42")
return response.text("42")
app.register_blueprint(bp)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)
app.run(host="0.0.0.0", port=8000)

View File

@@ -1,6 +1,6 @@
from sanic.app import Sanic
from sanic.blueprints import Blueprint
__version__ = '0.5.0'
__version__ = '0.5.2'
__all__ = ['Sanic', 'Blueprint']

View File

@@ -35,10 +35,10 @@ if __name__ == "__main__":
app.run(host=args.host, port=args.port,
workers=args.workers, debug=args.debug, ssl=ssl)
except ImportError:
except ImportError as e:
log.error("No module named {} found.\n"
" Example File: project/sanic_server.py -> app\n"
" Example Module: project.sanic_server.app"
.format(module_name))
.format(e.name))
except ValueError as e:
log.error("{}".format(e))

View File

@@ -1,4 +1,5 @@
import logging
import logging.config
import re
import warnings
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 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.exceptions import ServerError, URLBuildError, SanicException
from sanic.handlers import ErrorHandler
from sanic.log import log
from sanic.response import HTTPResponse, StreamingHTTPResponse
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.testing import SanicTestClient
from sanic.views import CompositionView
@@ -26,7 +27,10 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed
class Sanic:
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
# end-user application didn't set anything up.
if not logging.root.handlers and log.level == logging.NOTSET:
@@ -47,6 +51,7 @@ class Sanic:
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env)
self.log_config = log_config
self.request_middleware = deque()
self.response_middleware = deque()
self.blueprints = {}
@@ -288,7 +293,7 @@ class Sanic:
attach_to=middleware_or_request)
# 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):
"""Register a root to serve files from. The input can either be a
file or a directory. See
@@ -453,7 +458,8 @@ class Sanic:
# -------------------------------------------- #
# 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:
raise ServerError(
("'None' was returned while requesting a "
@@ -510,36 +516,29 @@ class Sanic:
# Execution
# -------------------------------------------------------------------- #
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
after_start=None, before_stop=None, after_stop=None, ssl=None,
sock=None, workers=1, loop=None, protocol=None,
backlog=100, stop_event=None, register_sys_signals=True):
def run(self, host="127.0.0.1", port=8000, debug=False, ssl=None,
sock=None, workers=1, protocol=None,
backlog=100, stop_event=None, register_sys_signals=True,
log_config=LOGGING):
"""Run the HTTP Server and listen until keyboard interrupt or term
signal. On termination, drain connections before closing.
:param host: Address to host on
:param port: Port to host on
: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
for SSL encryption of worker(s)
:param sock: Socket for the server to accept connections from
:param workers: Number of processes
received before it is respected
:param loop:
:param backlog:
:param stop_event:
:param register_sys_signals:
:param protocol: Subclass of asyncio protocol class
:return: Nothing
"""
if log_config:
logging.config.dictConfig(log_config)
if protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
@@ -549,11 +548,10 @@ class Sanic:
warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning)
server_settings = self._helper(
host=host, port=port, debug=debug, before_start=before_start,
after_start=after_start, before_stop=before_stop,
after_stop=after_stop, ssl=ssl, sock=sock, workers=workers,
loop=loop, protocol=protocol, backlog=backlog,
register_sys_signals=register_sys_signals)
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
workers=workers, protocol=protocol, backlog=backlog,
register_sys_signals=register_sys_signals,
has_log=log_config is not None)
try:
self.is_running = True
@@ -564,6 +562,7 @@ class Sanic:
except:
log.exception(
'Experienced exception while trying to serve')
raise
finally:
self.is_running = False
log.info("Server Stopped")
@@ -577,15 +576,16 @@ class Sanic:
return self
async def create_server(self, host="127.0.0.1", port=8000, debug=False,
before_start=None, after_start=None,
before_stop=None, after_stop=None, ssl=None,
sock=None, loop=None, protocol=None,
backlog=100, stop_event=None):
ssl=None, sock=None, protocol=None,
backlog=100, stop_event=None,
log_config=LOGGING):
"""Asynchronous version of `run`.
NOTE: This does not support multiprocessing and is not the preferred
way to run a Sanic application.
"""
if log_config:
logging.config.dictConfig(log_config)
if protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
@@ -595,11 +595,10 @@ class Sanic:
warnings.warn("stop_event will be removed from future versions.",
DeprecationWarning)
server_settings = self._helper(
host=host, port=port, debug=debug, before_start=before_start,
after_start=after_start, before_stop=before_stop,
after_stop=after_stop, ssl=ssl, sock=sock,
loop=loop or get_event_loop(), protocol=protocol,
backlog=backlog, run_async=True)
host=host, port=port, debug=debug, ssl=ssl, sock=sock,
loop=get_event_loop(), protocol=protocol,
backlog=backlog, run_async=True,
has_log=log_config is not None)
return await serve(**server_settings)
@@ -626,10 +625,9 @@ class Sanic:
return response
def _helper(self, host="127.0.0.1", port=8000, debug=False,
before_start=None, after_start=None, before_stop=None,
after_stop=None, ssl=None, sock=None, workers=1, loop=None,
ssl=None, sock=None, workers=1, loop=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`."""
if isinstance(ssl, dict):
@@ -646,23 +644,6 @@ class Sanic:
warnings.simplefilter('default')
warnings.warn("stop_event will be removed from future versions.",
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.debug = debug
@@ -674,32 +655,30 @@ class Sanic:
'port': port,
'sock': sock,
'ssl': ssl,
'signal': Signal(),
'debug': debug,
'request_handler': self.handle_request,
'error_handler': self.error_handler,
'request_timeout': self.config.REQUEST_TIMEOUT,
'request_max_size': self.config.REQUEST_MAX_SIZE,
'keep_alive': self.config.KEEP_ALIVE,
'loop': loop,
'register_sys_signals': register_sys_signals,
'backlog': backlog
'backlog': backlog,
'has_log': has_log
}
# -------------------------------------------- #
# Register start/stop events
# -------------------------------------------- #
for event_name, settings_name, reverse, args in (
("before_server_start", "before_start", False, before_start),
("after_server_start", "after_start", False, after_start),
("before_server_stop", "before_stop", True, before_stop),
("after_server_stop", "after_stop", True, after_stop),
for event_name, settings_name, reverse in (
("before_server_start", "before_start", False),
("after_server_start", "after_start", False),
("before_server_stop", "before_stop", True),
("after_server_stop", "after_stop", True),
):
listeners = self.listeners[event_name].copy()
if args:
if callable(args):
listeners.append(args)
else:
listeners.extend(args)
if reverse:
listeners.reverse()
# Prepend sanic to the arguments when listeners are triggered

View File

@@ -1,12 +1,122 @@
from sanic.defaultFilter import DefaultFilter
import os
import sys
import syslog
import platform
import types
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):
def __init__(self, defaults=None, load_env=True):
def __init__(self, defaults=None, load_env=True, keep_alive=True):
super().__init__(defaults or {})
self.LOGO = """
▄▄▄▄▄
@@ -31,6 +141,7 @@ class Config(dict):
"""
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
self.REQUEST_TIMEOUT = 60 # 60 seconds
self.KEEP_ALIVE = keep_alive
if load_env:
self.load_environment_vars()
@@ -98,11 +209,11 @@ class Config(dict):
self[key] = getattr(obj, key)
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():
"""
Looks for any SANIC_ prefixed environment variables and applies
them to the configuration if present.
"""
if k.startswith(SANIC_PREFIX):
_, config_key = k.split(SANIC_PREFIX, 1)
self[config_key] = v

13
sanic/defaultFilter.py Normal file
View 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

View File

@@ -47,6 +47,10 @@ TRACEBACK_STYLE = '''
padding: 5px 10px;
}
.tb-border {
padding-top: 20px;
}
.frame-descriptor {
background-color: #e2eafb;
}
@@ -63,12 +67,9 @@ TRACEBACK_WRAPPER_HTML = '''
{style}
</head>
<body>
<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}
<p class="summary">
{inner_html}
<div class="summary">
<p>
<b>{exc_name}: {exc_value}</b>
while handling path <code>{path}</code>
</p>
@@ -77,6 +78,24 @@ TRACEBACK_WRAPPER_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 = '''
<div class="frame-line">
<p class="frame-descriptor">

View File

@@ -9,7 +9,9 @@ from sanic.exceptions import (
SanicException,
TRACEBACK_LINE_HTML,
TRACEBACK_STYLE,
TRACEBACK_WRAPPER_HTML)
TRACEBACK_WRAPPER_HTML,
TRACEBACK_WRAPPER_INNER_HTML,
TRACEBACK_BORDER)
from sanic.log import log
from sanic.response import text, html
@@ -24,19 +26,31 @@ class ErrorHandler:
self.cached_handlers = {}
self.debug = False
def _render_traceback_html(self, exception, request):
exc_type, exc_value, tb = sys.exc_info()
frames = extract_tb(tb)
def _render_exception(self, exception):
frames = extract_tb(exception.__traceback__)
frame_html = []
for frame in frames:
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(
style=TRACEBACK_STYLE,
exc_name=exc_type.__name__,
exc_value=exc_value,
frame_html=''.join(frame_html),
exc_name=exception.__class__.__name__,
exc_value=exception,
inner_html=TRACEBACK_BORDER.join(reversed(exceptions)),
path=request.path)
def add(self, exception, handler):

View File

@@ -1,3 +1,4 @@
import logging
log = logging.getLogger('sanic')
netlog = logging.getLogger('network')

View File

@@ -38,7 +38,7 @@ class Request(dict):
__slots__ = (
'app', 'headers', 'version', 'method', '_cookies', 'transport',
'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):
@@ -57,6 +57,7 @@ class Request(dict):
self.parsed_form = None
self.parsed_files = None
self.parsed_args = None
self.uri_template = None
self._cookies = None
@property
@@ -78,9 +79,10 @@ class Request(dict):
:return: token related to request
"""
auth_header = self.headers.get('Authorization')
if auth_header is not None:
return auth_header.split()[1]
return auth_header
if 'Token ' in auth_header:
return auth_header.partition('Token ')[-1]
else:
return auth_header
@property
def form(self):

View File

@@ -210,7 +210,7 @@ class HTTPResponse(BaseHTTPResponse):
# Speeds up response rate 6% over pulling from all
status = COMMON_STATUS_CODES.get(self.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'
b'Connection: %b\r\n'

View File

@@ -8,7 +8,7 @@ from sanic.views import CompositionView
Route = namedtuple(
'Route',
['handler', 'methods', 'pattern', 'parameters', 'name'])
['handler', 'methods', 'pattern', 'parameters', 'name', 'uri'])
Parameter = namedtuple('Parameter', ['name', 'cast'])
REGEX_TYPES = {
@@ -16,6 +16,7 @@ REGEX_TYPES = {
'int': (int, r'\d+'),
'number': (float, r'[0-9\\.]+'),
'alpha': (str, r'[A-Za-z]+'),
'path': (str, r'[^/].*?'),
}
ROUTER_CACHE_SIZE = 1024
@@ -71,7 +72,8 @@ class Router:
self.routes_always_check = []
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
pattern
@@ -161,10 +163,10 @@ class Router:
parameters.append(parameter)
# 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
# Mark the route as unhashable if it matches the hash key
elif re.search(pattern, '/'):
elif re.search(r'/', pattern):
properties['unhashable'] = True
return '({})'.format(pattern)
@@ -223,7 +225,7 @@ class Router:
route = Route(
handler=handler, methods=methods, pattern=pattern,
parameters=parameters, name=handler_name)
parameters=parameters, name=handler_name, uri=uri)
self.routes_all[uri] = route
if properties['unhashable']:
@@ -342,4 +344,4 @@ class Router:
route_handler = route.handler
if hasattr(route_handler, 'handlers'):
route_handler = route_handler.handlers[method]
return route_handler, [], kwargs
return route_handler, [], kwargs, route.uri

View File

@@ -1,7 +1,6 @@
import asyncio
import os
import traceback
import warnings
from functools import partial
from inspect import isawaitable
from multiprocessing import Process
@@ -25,7 +24,8 @@ try:
except ImportError:
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.exceptions import (
RequestTimeout, PayloadTooLarge, InvalidUsage, ServerError)
@@ -65,12 +65,15 @@ class HttpProtocol(asyncio.Protocol):
# request config
'request_handler', 'request_timeout', 'request_max_size',
'request_class',
# enable or disable access log / error log purpose
'has_log',
# connection management
'_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, error_handler,
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.transport = None
self.request = None
@@ -78,6 +81,7 @@ class HttpProtocol(asyncio.Protocol):
self.url = None
self.headers = None
self.signal = signal
self.has_log = has_log
self.connections = connections
self.request_handler = request_handler
self.error_handler = error_handler
@@ -88,6 +92,13 @@ class HttpProtocol(asyncio.Protocol):
self._timeout_handler = None
self._last_request_time = 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
@@ -165,8 +176,7 @@ class HttpProtocol(asyncio.Protocol):
self.request.body.append(body)
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(
@@ -182,13 +192,19 @@ class HttpProtocol(asyncio.Protocol):
Writes response content synchronously to the transport.
"""
try:
keep_alive = (
self.parser.should_keep_alive() and not self.signal.stopped)
keep_alive = self.keep_alive
self.transport.write(
response.output(
self.request.version, keep_alive,
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:
log.error(
('Invalid response object for url {}, '
@@ -218,12 +234,18 @@ class HttpProtocol(asyncio.Protocol):
"""
try:
keep_alive = (
self.parser.should_keep_alive() and not self.signal.stopped)
keep_alive = self.keep_alive
response.transport = self.transport
await response.stream(
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:
log.error(
('Invalid response object for url {}, '
@@ -259,6 +281,21 @@ class HttpProtocol(asyncio.Protocol):
"Writing error failed, connection closed {}".format(repr(e)),
from_error=True)
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()
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,
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100,
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.
: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 protocol: subclass of asyncio protocol class
:param request_class: Request class to use
:param has_log: disable/enable access log and error log
:return: Nothing
"""
if not run_async:
@@ -370,6 +408,8 @@ def serve(host, port, request_handler, error_handler, before_start=None,
request_timeout=request_timeout,
request_max_size=request_max_size,
request_class=request_class,
has_log=has_log,
keep_alive=keep_alive,
)
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
: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
# Handling when custom socket is not provided.
@@ -461,6 +495,8 @@ def serve_multiple(server_settings, workers):
def sig_handler(signal, frame):
log.info("Received signal {}. Shutting down.".format(
Signals(signal).name))
for process in processes:
os.kill(process.pid, SIGINT)
signal_func(SIGINT, 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:
process.terminate()
server_settings.get('sock').close()
asyncio.get_event_loop().stop()

View File

@@ -48,14 +48,18 @@ def register(app, uri, file_or_directory, pattern,
# Merge served directory and requested file if provided
# 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
file_path = file_or_directory
root_path = file_path = file_or_directory
if file_uri:
file_path = path.join(
file_or_directory, sub('^[/]*', '', file_uri))
# URL decode the path sent by the browser otherwise we won't be able to
# 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:
headers = {}
# Check if the client has been sent this file before

View File

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

View File

@@ -3,6 +3,7 @@ import sys
import signal
import asyncio
import logging
try:
import ssl
except ImportError:
@@ -50,8 +51,8 @@ class GunicornWorker(base.Worker):
debug=is_debug,
protocol=protocol,
ssl=self.ssl_context,
run_async=True
)
run_async=True)
self._server_settings['signal'] = self.signal
self._server_settings.pop('sock')
trigger_events(self._server_settings.get('before_start', []),
self.loop)
@@ -97,7 +98,6 @@ class GunicornWorker(base.Worker):
self.servers.append(await serve(
sock=sock,
connections=self.connections,
signal=self.signal,
**self._server_settings
))

View File

@@ -9,14 +9,27 @@ from distutils.util import strtobool
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:
version = re.findall(r"^__version__ = '([^']+)'\r?$",
fp.read(), re.M)[0]
except IndexError:
raise RuntimeError('Unable to determine version.')
with open_local(['README.rst']) as rm:
long_description = rm.read()
setup_kwargs = {
'name': 'sanic',
'version': version,
@@ -26,6 +39,7 @@ setup_kwargs = {
'author_email': 'channelcat@gmail.com',
'description': (
'A microframework based on uvloop, httptools, and learnings of flask'),
'long_description': long_description,
'packages': ['sanic'],
'platforms': 'any',
'classifiers': [
@@ -64,7 +78,3 @@ except DistutilsPlatformError as exception:
print("Installing without uJSON or uvLoop")
setup_kwargs['install_requires'] = requirements
setup(**setup_kwargs)
# Installation was successful
print(u"\n\n\U0001F680 "
"Sanic version {} installation suceeded.\n".format(version))

View File

@@ -1,16 +1,17 @@
import sanic
from sanic.utils import sanic_endpoint_test
from sanic import Sanic
from sanic.response import text
from threading import Event
import asyncio
def test_create_task():
e = Event()
async def coro():
await asyncio.sleep(0.05)
e.set()
app = sanic.Sanic()
app = Sanic('test_create_task')
app.add_task(coro)
@app.route('/early')
@@ -22,9 +23,8 @@ def test_create_task():
await asyncio.sleep(0.1)
return text(e.is_set())
request, response = sanic_endpoint_test(app, uri='/early')
request, response = app.test_client.get('/early')
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'

View File

@@ -35,6 +35,15 @@ def handler_5(request):
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)
def handler_exception(request, exception):
return text("OK")
@@ -84,6 +93,26 @@ def test_inherited_exception_handler():
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():
class CustomError(Exception):
pass

View File

@@ -133,6 +133,17 @@ def test_query_string():
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():
app = Sanic('test_post_token')
@@ -141,6 +152,16 @@ def test_token():
return text('OK')
# 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'
headers = {
'content-type': 'application/json',
@@ -151,6 +172,18 @@ def test_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
# ------------------------------------------------------------ #

View File

@@ -238,6 +238,30 @@ def test_dynamic_route_regex():
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():
app = Sanic('test_dynamic_route_unhashable')

13
tox.ini
View File

@@ -1,11 +1,11 @@
[tox]
envlist = py35, py36, flake8
envlist = py35, py36, flake8, check
[travis]
python =
3.5: py35, flake8
3.6: py36, flake8
3.5: py35, flake8, check
3.6: py36, flake8, check
[testenv]
@@ -24,3 +24,10 @@ deps =
commands =
flake8 sanic
[testenv:check]
deps =
docutils
pygments
commands =
python setup.py check -r -s