Compare commits

...

842 Commits
0.1.4 ... 0.5.0

Author SHA1 Message Date
Eli Uriegas
e5fdc7fdd0 Merge pull request #626 from seemethere/increment_050
Increment to 0.5.0
2017-04-11 16:05:26 -05:00
Eli Uriegas
015c87b5e1 Add traceback for better debugging 2017-04-11 16:02:57 -05:00
Eli Uriegas
d20a49e500 Lock chardet for now... 2017-04-11 16:02:49 -05:00
Eli Uriegas
084f0d27a3 Increment to 0.5.0 2017-04-11 15:19:00 -05:00
Eli Uriegas
522a0beec0 Merge pull request #622 from aryeh/provide_request_object
Allow a custom Request class to be passed in to Sanic
2017-04-11 15:13:05 -05:00
Raphael Deem
77a51c1e05 Merge pull request #617 from Sniedes722/master
Update Examples for 0.4.2
2017-04-10 19:07:29 -07:00
Raphael Deem
144f215705 Merge pull request #623 from qwIvan/master
Fixed #615
2017-04-10 18:58:31 -07:00
ivan
51b01b6b44 Merge branch 'master' of github.com:qwIvan/sanic 2017-04-10 18:33:43 +08:00
ivan
09885534c6 fixed #615 2017-04-10 18:31:28 +08:00
aryeh
b9dfec38c2 Break long line (> 80 chars) into 2 lines 2017-04-09 13:38:36 -04:00
aryeh
2ef8120073 Allow a custom Request class to be passed in to Sonic
Allowing a custom Request class to be defined would enable either a different Request class or a subclass of Request to be used, providing more flexibility.
2017-04-09 13:29:21 -04:00
Raphael Deem
52ff2e0e63 Merge pull request #621 from r0fls/620
fix python -m method of running
2017-04-08 13:32:17 -07:00
Raphael Deem
8cf7dce33f fix python -m method of running 2017-04-08 13:31:17 -07:00
Shawn Niederriter
9d3bb4a37a Updated examples in-line with response docs 2017-04-06 19:47:25 +00:00
Shawn Niederriter
c30437448b Updated aiohttp & run_async examples, added redirect 2017-04-06 19:42:05 +00:00
Raphael Deem
7e3496f8aa Merge pull request #614 from dkruchinin/middleware
Response middleware should be called even if server replies with an error
2017-04-06 11:49:44 -07:00
Shawn Niederriter
46ac79f4dc Update run_async demo 2017-04-06 14:39:54 -04:00
Shawn Niederriter
833b14e353 Updated aiohttp example. 2017-04-06 13:33:29 -04:00
Eli Uriegas
e9eca25792 Merge pull request #616 from r0fls/610
use socket.set_inheritable instead of os version
2017-04-04 15:17:31 -05:00
Raphael Deem
1854ad133c use socket.set_inheritable instead of os version 2017-04-04 13:13:52 -07:00
Eli Uriegas
2b5e723ea5 Merge pull request #519 from youknowone/recall-379
Add #379 again and make related test rework
2017-04-04 15:08:57 -05:00
Raphael Deem
9a18906edd Merge pull request #612 from dkruchinin/prom
Add prometheus extension
2017-04-04 11:06:43 -07:00
Raphael Deem
93cb7582c2 Merge branch 'master' into prom 2017-04-04 11:06:32 -07:00
Raphael Deem
b4529639f6 Merge pull request #611 from ashleysommer/master
Add Sanic-RestPlus to extensions.
2017-04-04 11:06:02 -07:00
Dan Kruchinin
f0a59fccf8 flake8-related fixes 2017-04-04 17:19:45 +01:00
Dan Kruchinin
46dbaf95a6 Response middleware should be called even if server replies with error 2017-04-04 15:55:43 +01:00
Dan Kruchinin
d418b03708 Add prometheus extension 2017-04-04 14:22:31 +01:00
Ashley Sommer
765e90ecfa update extensions
Add Sanic-RestPlus!
2017-04-04 13:40:59 +10:00
Ashley Sommer
ff1e88dde6 Merge pull request #2 from channelcat/master
update to latest sanic
2017-04-04 13:39:21 +10:00
Eli Uriegas
62ebcba647 Add graphql integration extension
Closes #579
2017-04-03 14:45:18 -05:00
Jeong YunWon
429e90183b Add #379 again and make related test rework
Original PR: https://github.com/channelcat/sanic/pull/379
2017-04-03 18:56:39 +09:00
Raphael Deem
875790e862 Merge pull request #606 from monobot/master
Fix URL Parse Error #599
2017-04-02 12:16:24 -07:00
monobot
25edbe6805 update docs 2017-04-02 02:28:16 +01:00
monobot
e148b50d6a Merge remote-tracking branch 'upstream/master' 2017-04-02 00:02:49 +01:00
Raphael Deem
06d46d56cd Merge pull request #602 from jkbbwr/fix/env-install
Flake8 cleanup. Setup environmental variables.
2017-03-31 10:42:21 -07:00
Jakob Bowyer
edd8770c67 Restored tests to upstream/master 2017-03-31 08:53:46 +01:00
Jakob Bowyer
daedda8547 Checked out original tests 2017-03-31 08:51:12 +01:00
Raphael Deem
df9d897e75 Merge pull request #607 from nosahama/hotfix/docs-cookie-typo-fix
Typo Fix in docs/sanic/cookies.md
2017-03-30 16:24:58 -07:00
nosaevb
fcd8e5e5ad Typo Fix in docs/sanic/cookies.md 2017-03-30 23:02:46 +01:00
monobot
6c003f71f4 Merge remote-tracking branch 'upstream/master' 2017-03-29 23:54:11 +01:00
monobot
5b704478d9 raw_args for request objects 2017-03-29 22:06:54 +01:00
Eli Uriegas
60eb528d68 Merge pull request #491 from r0fls/remove-stop-event
remove stop_event
2017-03-29 07:08:55 -05:00
Jakob Bowyer
1cf730d957 Added usage documentation for optional installs 2017-03-29 10:12:24 +01:00
Raphael Deem
171110b445 Merge pull request #604 from seemethere/add_docker_unittest_support
Fixing the unittests
2017-03-29 02:03:54 -07:00
Jakob Bowyer
22699db855 Moved skips to seperate pull request 2017-03-29 09:16:53 +01:00
Eli Uriegas
18405b3908 There was a line missing here? 2017-03-28 22:57:58 -05:00
Eli Uriegas
f0a55b5cbb Fix line length again... 2017-03-28 22:51:23 -05:00
Eli Uriegas
04a0774ee5 Fix line length 2017-03-28 22:51:23 -05:00
Eli Uriegas
3a8cfb1f45 Make these tests not so far apart 2017-03-28 22:51:23 -05:00
Eli Uriegas
dcc19d17d4 Lock to aiohttp 1.3.5 for now 2017-03-28 22:51:23 -05:00
Eli Uriegas
1ef69adc6f Simplify this as well, it replicated effort 2017-03-28 22:51:23 -05:00
Eli Uriegas
75a4df0f32 Simplify this, it had a lot of fluff 2017-03-28 22:51:23 -05:00
Eli Uriegas
8ba1b5fc35 Add docker support for local unit testing
Addresses consistency across different OS's by making it very similar to
the base Travis image.
2017-03-28 22:51:23 -05:00
Eli Uriegas
a09471ac6c Merge pull request #600 from monobot/master
added asyncorm example
2017-03-28 22:50:20 -05:00
Eli Uriegas
a916eea684 Merge pull request #601 from SakuraSound/master
Detailed example with logging, database access, environment variables, and basic middleware
2017-03-28 22:49:26 -05:00
Eli Uriegas
511998d8e1 Merge pull request #573 from r0fls/env-config
allow setting config from individual env variables
2017-03-28 22:24:37 -05:00
Joir-dan Gumbs
e3cf50f791 Changed out redis middleware for redis listeners (open/close). Fleshed out the payloads of both endpoints. Added comment about required packages. 2017-03-28 15:00:23 -07:00
Jakob Bowyer
42ba5298a7 Flake8 cleanup. Setup environmental variables.
Skipping broken tests unrelated.
2017-03-28 10:50:09 +01:00
Joir-dan Gumbs
ee79750a22 Cleaned up functions. Added extra middleware function to log endpoint being called. Added documentation to make easier to understand. 2017-03-28 01:22:36 -07:00
Raphael Deem
1787f8617f Merge pull request #592 from weargoggles/patch-1
Document synchronous response.write in streaming
2017-03-27 20:14:20 -07:00
Joir-dan Gumbs
748ca28185 Created detailed example of using sanic. Adds configurations based on various environment variables, handles database access (using aioredis), uses middleware to check for db object and attach it to request object, and logs events to a logfile (which is set using environment variables). 2017-03-27 15:42:13 -07:00
monobot
9c68d713ba added asyncorm example 2017-03-27 22:47:35 +01:00
Raphael Deem
fc69678206 Merge remote-tracking branch 'upstream/master' into remove-stop-event 2017-03-26 15:59:31 -07:00
Raphael Deem
aebd717039 fix merge conflict 2017-03-26 15:49:58 -07:00
Raphael Deem
1ddb01ac44 remove stop_event 2017-03-26 15:48:41 -07:00
Raphael Deem
3e279cd670 Merge pull request #593 from itielshwartz/master
add sanic-nginx-docker-example to extensions.md
2017-03-26 11:50:45 -07:00
Raphael Deem
724c03630a Update extensions.md 2017-03-26 11:49:15 -07:00
itiel
b00b2561e5 add sanic-nginx-docker-example to extensions.md 2017-03-26 21:16:03 +03:00
Raphael Deem
c5b50fe3cf allow setting config from individual env variables 2017-03-25 17:45:55 -07:00
Raphael Deem
df9884de3c Merge pull request #576 from matuusu/master
fix http status code not propagating in response
2017-03-24 19:23:20 -07:00
Pete Wildsmith
65ae7669f9 Document synchronous response.write in streaming
The Streaming section of the docs was updated to make clear that a synchronous write should be used in the callback, but this section was not updated.
2017-03-24 10:11:30 +00:00
Raphael Deem
179606feb1 Merge pull request #590 from r0fls/blueprint-strict-slash
add blueprint strict_slashes
2017-03-23 18:42:21 -07:00
Raphael Deem
536140340e Merge pull request #585 from subyraman/listener-docs
add docs for server lifecycle listeners and `add_task`
2017-03-23 18:38:48 -07:00
Raphael Deem
5d293df64b add blueprint strict_slashes 2017-03-23 18:37:06 -07:00
Suby Raman
6188891a53 add-listeners-docs 2017-03-23 15:49:23 -04:00
Raphael Deem
9774661cfe Merge pull request #580 from skytoup/master
Fix testing not support binary file
2017-03-23 12:22:08 -07:00
Raphael Deem
563bc34fb5 Merge pull request #578 from messense/feature/gunicorn-deploy-doc
Add documentation for Gunicorn worker
2017-03-23 12:20:00 -07:00
Raphael Deem
9c95ab3a28 Merge pull request #584 from subyraman/add-decorators-info
add decorator docs
2017-03-23 12:19:23 -07:00
Suby Raman
b776c37b36 add decorator docs 2017-03-23 15:16:46 -04:00
Eli Uriegas
5b3f92b70f Merge pull request #577 from seemethere/fix_tests_after_aiohttp_2
Hotfixes tests failing from URL object change
2017-03-23 13:56:16 -05:00
ivan
1562b81522 add arg load_body in testing 2017-03-23 20:48:57 +08:00
ivan
be1016ace6 merge upstream 2017-03-23 20:08:19 +08:00
ivan
ee27c689e1 commented aiohttp load response body in testing 2017-03-23 20:06:39 +08:00
skytoup
fdbf452ced 1. try...catch aiohttp encode response body to text in test_client
2. add tests static binary file
2017-03-23 15:22:00 +08:00
messense
3d9927dee0 Add documentation for Gunicorn worker 2017-03-23 09:12:23 +08:00
Raphael Deem
1456b128d2 Merge pull request #572 from sourcepirate/master
Removed raw string in cookies documentation
2017-03-22 14:55:20 -07:00
Eli Uriegas
166f77cb86 Merge pull request #545 from messense/feature/gunicorn-worker
Addition of a gunicorn worker
2017-03-22 16:27:06 -05:00
Eli Uriegas
5577838905 Hotfixes tests failing from URL object change
aiohttp decided to use yarl for their new URL objects so that they
aren't plain strings anymore which means that this single test fails.
Not a huge change but this should fix the testing suite.
2017-03-22 16:21:35 -05:00
matuusu
9c15982299 Update response.py
fix status code not propagating from response.stream to response.StreamingHTTPResponse
2017-03-22 12:40:40 +01:00
sourcepirate
63c24122db Removed raw string in cookies documentation 2017-03-22 06:39:23 +05:30
messense
1396ca903d Fix before_stop event 2017-03-20 14:27:02 +08:00
messense
d1fb5bdc30 Fix async before_server_start hook bug 2017-03-20 14:25:32 +08:00
messense
e27812bf3e Set signal.stopped = True on closing 2017-03-20 14:25:32 +08:00
messense
11a3cf9b99 Add signal handling 2017-03-20 14:25:32 +08:00
messense
a90d70feae Check connections is not None 2017-03-20 14:25:32 +08:00
messense
466b34735c Set app.is_running to True 2017-03-20 14:25:32 +08:00
messense
7ca9116e37 Trigger before_stop before closing server, after_stop before closing
loop
2017-03-20 14:25:31 +08:00
messense
decd3e737c Flake8 fix 2017-03-20 14:25:31 +08:00
messense
f35442ad1b Fix RuntimeError: this event loop is already running 2017-03-20 14:25:31 +08:00
messense
2b296435b3 Trigger events 2017-03-20 14:25:31 +08:00
messense
19ee1dfecc Gunicorn worker 2017-03-20 14:25:31 +08:00
Raphael Deem
7da4596ef8 Merge pull request #567 from Sniedes722/master
Added Sanic-OAuth to extensions.
2017-03-19 14:20:19 -07:00
Shawn Niederriter
a379ef6781 Added Sanic-OAuth to extensions. 2017-03-18 23:56:11 -04:00
Raphael Deem
7beb065be3 Merge pull request #562 from lixxu/master
update function name as it not halt request actually
2017-03-18 13:17:24 -07:00
Raphael Deem
38b9091513 Merge pull request #443 from sourcepirate/master
Fixed sanic peewee example.
2017-03-17 13:12:39 -07:00
Raphael Deem
96db3c9601 Merge pull request #564 from messense/feature/aioredis-example
Add an aioredis example
2017-03-17 13:11:19 -07:00
Raphael Deem
43c4fc8e33 Merge pull request #559 from r0fls/557
accept strict_slash routes
2017-03-17 13:10:26 -07:00
messense
986ff101ce Add an aioredis example 2017-03-17 14:16:13 +08:00
lixxu
94c83c445f fix broken table 2017-03-17 14:01:54 +08:00
lixxu
625865412f update function name as it not halt request actually 2017-03-17 13:12:17 +08:00
Raphael Deem
46677e69ce accept strict_slash routes 2017-03-16 11:46:07 -07:00
Eli Uriegas
5fbca5b823 Merge pull request #561 from kdelwat/master
Fix ReadTheDocs build errors
2017-03-16 09:02:39 -05:00
Eli Uriegas
879fab120f Merge pull request #560 from miguelgrinberg/cancel-websocket-tasks
cancel websocket tasks if server is stopped
2017-03-16 09:02:30 -05:00
Cadel Watson
391b24bc17 Add websockets dependency to ReadTheDocs environment 2017-03-16 17:01:49 +11:00
Cadel Watson
d713533d26 Fix docstring formatting errors 2017-03-16 16:52:18 +11:00
Cadel Watson
24f745a334 Fix formatting errors in RST files 2017-03-16 16:51:02 +11:00
Cadel Watson
86f3101861 Add autodoc extension to Sphinx configuration 2017-03-16 16:50:33 +11:00
Miguel Grinberg
fd823c63ab cancel websocket tasks if server is stopped 2017-03-15 22:45:19 -07:00
Raphael Deem
fa69892f70 Merge pull request #558 from Zheaoli/master
Add a new example by using aiomysql
2017-03-15 20:05:37 -07:00
lizheao
cfc53d0d26 Change some code in sanic aiomysql code 2017-03-15 14:42:22 +08:00
lizheao
97c2056e4a Change some code in sanic aiomysql code 2017-03-15 14:41:54 +08:00
lizheao
0ad0164171 Change some code in sanic aiomysql code 2017-03-15 14:41:38 +08:00
lizheao
df0e285b6f Add a new example 2017-03-15 14:12:37 +08:00
Raphael Deem
e92f1b8c28 Merge pull request #556 from AntonDnepr/fix-414
Small changes to the docs and tests for the #414
2017-03-14 21:07:55 -07:00
Anton Zhyrnyi
410f86c960 fix for docs&tests 2017-03-14 20:53:58 +02:00
Raphael Deem
85f27320e7 Merge pull request #553 from ashleysommer/ashleysommer-add-dispatch-extension
Add Dispatcher Extension to Extensions page in Docs
2017-03-13 17:00:46 -07:00
Raphael Deem
9a3fac90e1 Merge pull request #551 from messense/feature/documentation-links
Add hyperlinks in response documentation
2017-03-13 17:00:00 -07:00
Raphael Deem
6984f6eec4 Merge pull request #549 from ai0/websocket-scheme
add websocket scheme in request
2017-03-13 16:59:47 -07:00
Ashley Sommer
d05f502fc8 Add Dispatcher Extension
Adds a link to the extension
https://github.com/ashleysommer/sanic-dispatcher
And a very short description
2017-03-14 08:37:53 +10:00
Jing Su
ba41ab8f67 fix typo 2017-03-13 18:36:22 +08:00
Jing Su
250bb7e29d add websocket secure scheme in request @messense 2017-03-13 18:34:43 +08:00
messense
48a26fd5df Add hyperlinks in response documentation 2017-03-13 16:20:12 +08:00
Jing Su
3af26540ec add websocket scheme in request 2017-03-13 13:28:35 +08:00
Raphael Deem
7d9de068d9 Merge pull request #544 from messense/feature/document-response
Add response documentation
2017-03-11 22:49:53 -08:00
messense
d174917a07 Add response documentation 2017-03-12 14:31:51 +08:00
Eli Uriegas
af398fc4c4 Merge pull request #543 from r0fls/windows-setup
windows setup
2017-03-11 21:57:15 -08:00
Raphael Deem
878ef446a2 refactor redundant print logic 2017-03-11 21:54:07 -08:00
Raphael Deem
668f6477bb fix spacing 2017-03-11 21:46:31 -08:00
Raphael Deem
01a770cbca windows setup 2017-03-11 19:32:38 -08:00
Raphael Deem
23a1174aa2 Merge pull request #542 from nszceta/master
Faster asyncpg queries via a global connection pool
2017-03-11 16:07:16 -08:00
Raphael Deem
414020e75b Merge pull request #541 from r0fls/speedup
remove default host attribute in router
2017-03-11 16:07:05 -08:00
Adam Gradzki
ed74bccad6 Faster asyncpg queries via a global connection pool
In my benchmarks I was able to obtain a 17% performance
improvement over the current asyncpg demo code with a shared
connection pool.

Resolves: #540
See also: #531
2017-03-11 17:55:57 -06:00
Raphael Deem
0eedde445c remove default host attribute in router 2017-03-11 15:39:48 -08:00
Raphael Deem
88bf78213f Merge pull request #512 from subyraman/fix-url-building
Fix `request.url` and other url properties
2017-03-10 00:38:16 -08:00
Raphael Deem
d342461a51 Merge pull request #535 from ai0/master
Update blueprints example
2017-03-10 00:36:02 -08:00
Raphael Deem
dffaaf8751 Merge pull request #533 from 38elements/patch-1
Fix bail_out()
2017-03-10 00:35:54 -08:00
Raphael Deem
313edadf47 Merge pull request #528 from r0fls/523
allow running with SSL via commandline
2017-03-10 00:35:46 -08:00
Raphael Deem
c9ce33dfe6 Merge pull request #524 from r0fls/exception-list
allow exceptions to be a list
2017-03-10 00:35:38 -08:00
Raphael Deem
0f50ac7205 Merge pull request #517 from r0fls/empty-json
return valid json in request.json
2017-03-10 00:35:26 -08:00
Raphael Deem
e807c08275 Merge pull request #536 from lixxu/master
add sanic-babel extension
2017-03-09 12:28:22 -08:00
Lix Xu
893977365c Update extensions.md
add babel extension
2017-03-09 23:58:02 +08:00
Jing Su
0cac45809f add blueprints websocket example 2017-03-09 18:33:34 +08:00
Jing Su
489ca3c207 use blueprint method instead of deprecated register_blueprint 2017-03-09 18:31:19 +08:00
38elements
313535c599 Fix bail_out() 2017-03-09 13:36:01 +09:00
Raphael Deem
7f1e0557c9 Merge pull request #532 from r0fls/example-asyncpg-close
close connection in asyncpg example
2017-03-08 18:51:44 -08:00
Raphael Deem
0860f84a39 close connection in asyncpg example 2017-03-08 18:50:41 -08:00
Raphael Deem
2fe9e78b6d Merge pull request #525 from r0fls/518
rename TestClient
2017-03-08 18:24:13 -08:00
Raphael Deem
2ba30f2022 allow running with SSL via commandline 2017-03-07 19:57:10 -08:00
Raphael Deem
b3b27cab34 Merge pull request #527 from r0fls/asyncpg-example
update asyncpg example
2017-03-07 19:34:10 -08:00
Raphael Deem
694207a86d update asyncpg example 2017-03-07 19:32:26 -08:00
Raphael Deem
90138c4bae return valid json in request.json 2017-03-07 18:03:45 -08:00
Raphael Deem
58a833e987 rename TestClient 2017-03-07 17:13:23 -08:00
Raphael Deem
86c5a569d5 allow exceptions to be a list 2017-03-07 16:22:23 -08:00
Eli Uriegas
19592e8eea Merge pull request #473 from subyraman/explore-streams-v2
Add `stream` method for streaming content, add docs and examples
2017-03-05 17:51:44 -08:00
Eli Uriegas
8e6678d526 Merge pull request #469 from miguelgrinberg/websocket-support
websocket support
2017-03-05 17:42:49 -08:00
Suby Raman
e792a1e030 add host test 2017-03-03 14:51:13 -05:00
Suby Raman
f0e818a28c add host test 2017-03-03 13:32:32 -05:00
Suby Raman
69bd63b742 add docs 2017-03-03 11:59:33 -05:00
Suby Raman
b40f30f2e5 fix tests 2017-03-03 11:49:35 -05:00
Suby Raman
1fbde87ec2 initial commit 2017-03-03 11:44:50 -05:00
Eli Uriegas
f9dc34c8fa Merge pull request #510 from jamesstidard/patch-2
Sanic EnvConfig
2017-03-02 22:21:16 -06:00
James Stidard
f7186f5331 Sanic EnvConfig
Services like Heroku force your hand into using environment variables. Made this to help.
2017-03-02 21:22:07 +00:00
Eli Uriegas
6a680e4db0 Merge pull request #509 from kcy1019/master
Fix exception_monitoring example
2017-03-02 10:48:05 -06:00
고창영(Chang-young Koh)
f6b69f412f Fix exception_monitoring example 2017-03-03 00:55:08 +09:00
Eli Uriegas
5aed18862d Merge pull request #506 from zenixls2/bugfix/bind-listener
special handling when sock is provided and number of workers > 1
2017-03-02 09:08:59 -06:00
(Zenix) Han-Sheng Huang
62bf213a6e pass flake8 2017-03-02 13:42:55 +09:00
(Zenix) Han-Sheng Huang
e5c32e9b48 special handling when sock is provided and number of workers > 1 2017-03-02 13:27:52 +09:00
Raphael Deem
b87dc37fbb Merge pull request #500 from r0fls/revert-498
Revert "add reuse_port to create_server"
2017-02-28 19:29:29 -08:00
Raphael Deem
002d4cb37c Revert "add reuse_port to create_server"
This reverts commit c3386dec84.
2017-02-28 19:27:42 -08:00
Raphael Deem
ff321fc355 Merge pull request #499 from r0fls/498
add reuse_port to create_server
2017-02-28 19:24:35 -08:00
Raphael Deem
c3386dec84 add reuse_port to create_server 2017-02-28 19:21:53 -08:00
Eli Uriegas
927d2761f7 Merge pull request #496 from bohea/master
rate limiting for sanic
2017-02-28 09:03:03 -06:00
Eli Uriegas
5e5f513088 Merge pull request #497 from seemethere/increment_041
Increment version to 0.4.1
2017-02-28 08:55:34 -06:00
Eli Uriegas
9fcf725061 Increment version to 0.4.1 2017-02-28 08:55:04 -06:00
Eli Uriegas
601e015f00 Merge pull request #495 from r0fls/494
fix routing issue with slashes
2017-02-28 08:54:15 -06:00
bohea
3289e8403a Update extensions.md 2017-02-28 17:34:40 +08:00
James Stidard
104a7c7d05 Added app to websocket request (#1) 2017-02-27 22:35:28 -08:00
Miguel Grinberg
7560660ec7 handle timeouts and disconnects properly 2017-02-27 22:35:28 -08:00
Miguel Grinberg
40ccb4a0dd websocket documentation 2017-02-27 22:35:28 -08:00
Miguel Grinberg
f90288f5dc websocket routes in blueprints 2017-02-27 22:35:28 -08:00
Miguel Grinberg
3bf79898d9 websocket unit test 2017-02-27 22:35:28 -08:00
Miguel Grinberg
1d6e11ca10 addressed feedback 2017-02-27 22:35:28 -08:00
Miguel Grinberg
6e903ee7d5 websocket support, using websockets package 2017-02-27 22:35:28 -08:00
Raphael Deem
21fb1dff7e fix routing issue with slashes 2017-02-27 20:01:11 -08:00
Eli Uriegas
da924a359c Merge pull request #493 from seemethere/fix_install
Attempt to remedy install problems from 0.4.0
2017-02-27 10:19:13 -06:00
Eli Uriegas
a5066f15dc Attempt to remedy install problems from 0.4.0
Relates to 1436fb3ef4
2017-02-27 10:14:47 -06:00
Raphael Deem
2dca53a696 remove stop_event 2017-02-26 16:37:48 -08:00
Eli Uriegas
bfbcdf8b86 Merge pull request #488 from seemethere/increment_version_040
Increment version to 0.4.0
2017-02-25 13:37:09 -06:00
Eli Uriegas
42e1d18470 Increment version to 0.4.0 2017-02-25 13:36:47 -06:00
Raphael Deem
435dc72aa1 Merge pull request #484 from r0fls/483
use getattr for request url in error handler
2017-02-24 10:12:04 -08:00
Raphael Deem
66c380548b use getattr for request url in error handler 2017-02-23 23:17:31 -08:00
Raphael Deem
13f81e9a6f Merge pull request #480 from subyraman/patch-2
Add documentation about `request.app`
2017-02-23 18:52:11 -08:00
Eli Uriegas
68c8796adb Merge pull request #481 from lixxu/master
url_for has something wrong with //
2017-02-23 18:27:13 -06:00
Suby Raman
4232f5342e correct indendation 2017-02-23 11:47:39 -05:00
lixxu
28c31359bf bug: url / will be empty 2017-02-24 00:45:50 +08:00
Suby Raman
a36e815227 Add documentation about request.app 2017-02-23 11:42:59 -05:00
lixxu
ff3d33d5e0 bug: url_for in blueprint may have // at the beginning 2017-02-24 00:17:43 +08:00
Eli Uriegas
d015d6b103 Merge pull request #476 from seemethere/inject_app_into_request
Inject app into request object
2017-02-23 09:12:41 -06:00
Eli Uriegas
0d160fbeaa Merge pull request #478 from messense/fix-requests-post-test-cases
Fix exception collection typo
2017-02-22 20:43:53 -06:00
messense
fedd7920a8 Fix exception collection typo 2017-02-23 10:39:14 +08:00
Eli Uriegas
6afac06596 Merge pull request #477 from messense/fix-requests-post-test-cases
Fix test cases for requests post
2017-02-22 20:39:03 -06:00
messense
91b2b40b9a Fix test cases for requests post 2017-02-23 10:36:08 +08:00
Eli Uriegas
56ecb6a3ea Inject app into request object
Allows for injection of the app object into the request object through
the request handler.

This allows for users to setup things like database connections etc. in
listeners and then utilize them throughout the their various route
handlers.

Usage is fairly simple like so:
```python
@app.get('/')
async def handler(request):
    request.app.anything
```

Name is up in the air but I'll leave this up for a few days and I'll
change it if we get a consensus
2017-02-22 11:34:14 -06:00
Eli Uriegas
64f73f624f Merge pull request #471 from r0fls/vhost-default
allow default vhost
2017-02-22 11:08:28 -06:00
Eli Uriegas
20b78b68a6 Merge pull request #470 from r0fls/url-slash
route logic for trailing slash
2017-02-22 11:07:23 -06:00
Suby Raman
d8a6d7e02f response.write should be synchronous for performance reasons 2017-02-22 10:42:16 -05:00
Raphael Deem
d1cc14201b Merge pull request #474 from r0fls/remove-ws-extension
remove websocket extension
2017-02-21 15:31:50 -08:00
Raphael Deem
a57ec9e669 remove websocket extension 2017-02-21 15:30:42 -08:00
Suby Raman
1a8961587c more info in docs 2017-02-21 11:38:57 -05:00
Suby Raman
fa13ad8849 clean up imports 2017-02-21 11:36:45 -05:00
Suby Raman
8b23dec322 improve performance 2017-02-21 11:28:45 -05:00
Suby Raman
4e8aac4b41 rebase 2017-02-21 11:05:06 -05:00
Raphael Deem
9c72b557ec allow default vhost 2017-02-20 16:52:36 -08:00
Raphael Deem
207ec1e032 route logic for trailing slash 2017-02-20 16:11:28 -08:00
Eli Uriegas
ff5d4276bc Merge pull request #468 from hhstore/master
fix import path.
2017-02-20 07:29:11 -06:00
hhstore
26c9618e63 fix import path. 2017-02-20 17:57:15 +08:00
Eli Uriegas
9c306899ba Merge pull request #464 from tommyip/patch-1
Fix typo in routing.md
2017-02-19 11:40:48 -06:00
Tommy Ip
4b7e7aab33 Fix typo in routing.md 2017-02-19 12:39:39 +00:00
Eli Uriegas
2921e9f868 Merge pull request #463 from subyraman/patch-1
add sanic_compress extension
2017-02-18 18:56:17 -06:00
Suby Raman
70b7606cb8 add sanic_compress extension 2017-02-18 19:53:32 -05:00
Eli Uriegas
0cae91d525 Merge pull request #459 from seemethere/add_app_loop_safety
Add app loop safety
2017-02-18 16:53:49 -06:00
Eli Uriegas
2c2a28e46b Add better error message around app.loop 2017-02-18 12:29:25 -06:00
Eli Uriegas
c75d484f23 Merge branch 'master' of github.com:channelcat/sanic into add_app_loop_safety 2017-02-17 07:10:59 -06:00
Eli Uriegas
072f7fec30 Merge pull request #460 from seemethere/fix_travis_build
Fix coverage build
2017-02-17 07:10:27 -06:00
Eli Uriegas
a73379495f Fix coverage build
Moved `sanic.py` to `app.py` this change reflects that
2017-02-17 07:06:57 -06:00
Eli Uriegas
0b914866eb Merge pull request #401 from youknowone/error-logging
Enhance error logging
2017-02-17 07:01:40 -06:00
Eli Uriegas
f12186d024 Merge pull request #438 from r0fls/coverage
add coverage to tox
2017-02-17 07:01:14 -06:00
Eli Uriegas
4a2835dc84 Merge pull request #458 from lixxu/master
add user_agent to request
2017-02-17 07:00:52 -06:00
Eli Uriegas
55dc45de33 Merge pull request #455 from seemethere/add_better_response_error_detection
Adds some safety around response types being wrong
2017-02-17 06:59:52 -06:00
Eli Uriegas
39aa64ff68 Remove un-needed loop assignment 2017-02-17 06:57:41 -06:00
Eli Uriegas
94bb91f2fa Adds some safety around app loop property 2017-02-17 06:54:26 -06:00
Lix Xu
f553ca95a5 add user_agent to request 2017-02-17 15:12:07 +08:00
Jeong YunWon
b44e8167d7 Enhance error logging
- Prevent to fall into error logging recursion when it doesn't have
request info (Print 'Unknown' when the request is still `None`
- Print repr(e) rather than str(e)
2017-02-17 14:05:46 +09:00
Eli Uriegas
36c1122d20 Merge pull request #457 from seemethere/remove_double_logging_message
Removes the extra logging message for run
2017-02-16 21:53:25 -06:00
Eli Uriegas
ad13529eaa Removes the extra logging message for run
Solves for #456
2017-02-16 21:51:19 -06:00
Eli Uriegas
04c8f4a5ed Adds some safety around response types being wrong
Before we didn't check if the response object was actually a response
object. This PR catches the exception should it happen and reports it to
the user.
2017-02-16 17:49:14 -06:00
Eli Uriegas
850446195f Merge pull request #441 from robintiwari/master
added sanic motor (async driver for mongodb) example
2017-02-16 17:19:42 -06:00
Eli Uriegas
865506546f Merge pull request #440 from r0fls/isinstance
isinstance -> try/except
2017-02-16 17:19:04 -06:00
Eli Uriegas
fe72fadedd Merge pull request #432 from agoose77/cleanups
Bugfix & simplfication for host based routing
2017-02-16 17:18:24 -06:00
Eli Uriegas
9cf9d3ad5c Merge pull request #453 from seemethere/add_file_example
Adds file response object example
2017-02-16 17:17:29 -06:00
Eli Uriegas
426d0e6af1 Merge pull request #454 from seemethere/fix_empty_request_args
Default request.args to RequestParameters
2017-02-16 17:16:55 -06:00
Eli Uriegas
483b442b7d Default request.args to RequestParameters 2017-02-16 15:54:20 -06:00
Eli Uriegas
9420b0c947 Adds file response object example 2017-02-16 15:53:31 -06:00
Eli Uriegas
dcfb7345f0 Merge pull request #451 from abuckenheimer/master
#449 use stdlib json module if ujson is unavailible
2017-02-16 14:14:45 -06:00
Alec Buckenheimer
f9ab24a077 #449 use stdlib json module if ujson is unavailible 2017-02-16 15:03:52 -05:00
Raphael Deem
f932d16ba7 Merge pull request #448 from Typhon66/patch-1
Adding sanic_crud to extensions
2017-02-16 09:57:58 -08:00
Typhon
7d9acc3c36 Adding sanic_crud to extensions 2017-02-16 09:49:48 -08:00
Eli Uriegas
72f735124f Merge pull request #446 from subyraman/document-error
Fix blueprint documentation re: prefixes
2017-02-16 11:21:23 -06:00
Suby Raman
6ea5a4719a add url_for doc in blueprint 2017-02-16 11:48:31 -05:00
Suby Raman
f8c50b7f1e fix blueprints documentation 2017-02-16 11:46:19 -05:00
Eli Uriegas
feb1f1d71a Merge pull request #444 from lixxu/master
simple motor wrapper
2017-02-16 09:46:09 -06:00
Lix Xu
550afc27dc simple motor wrapper 2017-02-16 22:36:16 +08:00
sourcepirate
e6a828572a Changed method name from create to new 2017-02-16 11:07:49 +05:30
plasmashadow
6ea43d8e6d Fixed sanic_peewee error and added a simple interface to query and create 2017-02-16 10:58:52 +05:30
@robintiwari
d13af4bdc4 updated examples 2017-02-15 22:11:16 -06:00
Robin
53a365dd2b added sanic motor (async driver for mongodb) example 2017-02-15 21:42:25 -06:00
Raphael Deem
6726affa7e isinstance -> try/except 2017-02-15 19:34:45 -08:00
Eli Uriegas
6ecf2a6eb2 Merge pull request #439 from seemethere/move_sanic_to_app
Moves sanic/sanic.py to sanic/app.py
2017-02-15 21:03:41 -06:00
Eli Uriegas
a359e11f97 Merge pull request #422 from r0fls/420
move logging to method in ErrorHandler
2017-02-15 21:03:15 -06:00
Eli Uriegas
54b2d74068 Get rid of relative imports 2017-02-15 20:54:00 -06:00
Eli Uriegas
c99aa7279b Moves sanic/sanic.py to sanic/app.py
Functionality wise this won't change much for most users, unless you
were directly importing from `sanic.sanic` in which case I am sorry if
you're affected by this.

Main motivation was because jedi autocompletion didn't work with this
and we were using relative imports to compensate for the fact that
having a module inside of your module with the same name creates major
namespace problems.
2017-02-15 20:47:34 -06:00
Raphael Deem
4fa568ce8a add coverage to tox 2017-02-15 15:20:43 -08:00
Eli Uriegas
874698b93f Merge pull request #431 from subyraman/test-client-v2
Add Flask-like `test_client` to replace `sanic_endpoint_test`
2017-02-15 15:44:30 -06:00
Eli Uriegas
b286fc1e4a Merge pull request #436 from lixxu/master
simple pagination support
2017-02-15 13:31:57 -06:00
Raphael Deem
1cf1024332 Merge branch 'master' into 420 2017-02-15 09:29:52 -08:00
Lix Xu
6b391b701b simple pagination support 2017-02-15 13:27:58 +08:00
Raphael Deem
efc90f8f5a Merge pull request #434 from agoose77/fix_warn_error
Fix mistake in warning
2017-02-14 16:46:37 -08:00
Angus Hollands
6535ba7c24 Fix mistake in warning 2017-02-14 20:53:55 +00:00
Angus Hollands
5c29c3d160 Merge branch 'master' of https://github.com/channelcat/sanic into cleanups 2017-02-14 20:47:28 +00:00
Angus Hollands
742d4bff78 Change to iterable as Python3.5 doesn't support Collection.
We don't really need the getitem and len attributes anyway
2017-02-14 20:46:14 +00:00
Suby Raman
7726ffa3f7 remove documentation about passing in the loop 2017-02-14 15:44:43 -05:00
Angus Hollands
b442d78ebb Bugfix & simplfication for host based routing (if list of hosts passed after a previous vhost route was added, previously attempted to add set to set)
Add comment documenting substandard behaviour
2017-02-14 20:32:04 +00:00
Eli Uriegas
d44edb5930 Merge pull request #430 from aquacash5/master
Added raw response for bag o' bytes responses
2017-02-14 14:19:01 -06:00
Suby Raman
d5633b3705 fix deprecation message 2017-02-14 15:16:58 -05:00
Suby Raman
3b68dc72e7 rework testing 2017-02-14 14:51:20 -05:00
Angus Hollands
51611c3934 Pep8 cleanups (#429)
* PEP8 cleanups

* PEP8 cleanups (server.py)

* PEP8 cleanups (blueprints.py)

* PEP8 cleanups (config.py)

* PEP8 cleanups (cookies.py)

* PEP8 cleanups (handlers.py)

* PEP8 cleanups (request.py)

* PEP8 cleanups (response.py)

* PEP8 cleanups (router.py)

* PEP8 cleanups (sanic.py) #2

* PEP8 cleanups (server.py) #2

* PEP8 cleanups (static.py)

* PEP8 cleanups (utils.py)

* PEP8 cleanups (views.py)
Updated docstring
2017-02-14 13:10:19 -06:00
Kyle Blöm
747b7567d7 Changed docstring for raw response 2017-02-14 09:40:33 -08:00
Kyle Blöm
797891d6cf Added raw response for bag o' bytes responses 2017-02-14 09:27:39 -08:00
Eli Uriegas
286dc3c32b Merge pull request #399 from lixxu/master
improve url_for to support multi values and special options
2017-02-14 10:27:54 -06:00
Eli Uriegas
a66ba21c3d Merge pull request #421 from Superman132/master
Fixed readme
2017-02-14 10:26:41 -06:00
Eli Uriegas
b139810b6a Merge pull request #424 from growingdever/blueprint-support-view
support view instance for blueprint add_route method
2017-02-14 10:26:05 -06:00
Eli Uriegas
dddc18d77c Merge pull request #427 from agoose77/trigger_cleanup
Simplify trigger events (now guaranteeed to receive list of events)
2017-02-14 09:34:34 -06:00
Angus Hollands
56f56d008a Simplify trigger events (now guaranteeed to receive list of events)
Don't bother checking if list empty - this function is not called often
2017-02-14 15:15:15 +00:00
growingdever
81a8a99b6e wrap over width comment 2017-02-14 17:20:39 +09:00
growingdever
07aa0ee7ad - copy codes from Sonic.add_route
- modify comment by r0fls
2017-02-14 17:15:38 +09:00
growingdever
b66a6bddbc fix typo 2017-02-14 14:30:07 +09:00
growingdever
d57d90fe6b - make blueprint add_route method support view instance
- update documentation that doesn't specify url_prefix parameter
2017-02-14 14:23:22 +09:00
Raphael Deem
de6c646ee8 move logging to method in ErrorHandler 2017-02-13 19:44:54 -08:00
lixxu
4839ede64f update test for url_for and update routing.md doc 2017-02-14 10:26:30 +08:00
Superman132
84f5faf653 Update README.rst 2017-02-13 20:48:37 -05:00
Superman132
281077bc26 Update README.rst 2017-02-13 19:35:03 -05:00
Superman132
ed5fe9ae9f Merge pull request #1 from channelcat/master
Merge commits with master
2017-02-13 19:31:33 -05:00
Eli Uriegas
1866e4ef44 Merge pull request #418 from argaen/fix_cache_example
Use decorator for cache example
2017-02-13 17:23:29 -06:00
Eli Uriegas
a6a07c3b3a Merge pull request #411 from r0fls/ensure-future
Ensure future
2017-02-13 17:13:26 -06:00
argaen
b2af8e640c Use decorator 2017-02-14 00:12:39 +01:00
Raphael Deem
7a3f5d508b fix merge conflicts 2017-02-13 14:19:44 -08:00
Eli Uriegas
7e1fd03104 Merge pull request #417 from argaen/fix_cache_example
Fixed aiocache example according to new loop policy
2017-02-13 16:11:26 -06:00
argaen
758415d326 Fixed aiocache example according to new loop policy 2017-02-13 23:08:42 +01:00
Eli Uriegas
1660041470 Merge pull request #408 from agoose77/master
Use app decorator instead of run arguments for before_start
2017-02-13 12:54:46 -06:00
Eli Uriegas
1783df883e Merge pull request #416 from subyraman/more-view-tests
Add CompositionView tests, simplify checks for invalid/duplicate methods
2017-02-13 11:09:56 -06:00
Suby Raman
b2be821637 reverse router changes 2017-02-13 11:55:00 -05:00
Suby Raman
051ff2b325 remove repr stuff 2017-02-13 11:50:09 -05:00
Suby Raman
4d6f9ffd7c rebase 2017-02-13 11:45:55 -05:00
Suby Raman
d614823013 rebase 2017-02-13 11:38:28 -05:00
Eli Uriegas
48aa51b739 Merge pull request #413 from r0fls/loop-signal-handlers
use try/except when adding loop sig handlers
2017-02-13 10:13:56 -06:00
Raphael Deem
41c6125e1b use try/except when adding loop sig handlers 2017-02-12 14:43:00 -08:00
Eli Uriegas
bb3d48f98b Merge pull request #412 from seemethere/improving_performance
Header performance gains
2017-02-12 15:48:45 -06:00
Raphael Deem
b5e46e83e2 ensure_future -> add_task 2017-02-12 12:29:12 -08:00
Angus Hollands
2340910b46 Update deprecation message
Fix bug with single callbacks
2017-02-12 18:15:14 +00:00
Eli Uriegas
d8c4c1525d Merge pull request #406 from agoose77/master_pre_patches_1
Cleanup middleware decorator
2017-02-12 12:14:50 -06:00
Eli Uriegas
6713ef7726 Remove unused import 2017-02-12 12:09:06 -06:00
Eli Uriegas
ae7555b065 Performance was down so this brings it back up
Changes from #378 introduced about a 10k request/sec slowdown. This
tries to rememdy it while keeping the same functionality but it's still
not as fast as 0.3.1
2017-02-12 12:05:14 -06:00
Angus Hollands
ee6ff0cc60 Add deprecation and old API 2017-02-12 12:28:02 +00:00
Raphael Deem
94b2352c2c add ensure_future method 2017-02-11 17:40:17 -08:00
Eli Uriegas
cf3f943feb Merge pull request #409 from r0fls/loop-attribute
add loop property
2017-02-11 18:31:50 -06:00
Eli Uriegas
55c4d583b9 Merge pull request #410 from r0fls/extensions
add extension to docs
2017-02-11 18:31:16 -06:00
Raphael Deem
0e1bb6ab04 add loop property 2017-02-11 16:28:35 -08:00
Raphael Deem
7944cff7a5 add extension to docs 2017-02-11 15:31:58 -08:00
Angus Hollands
8b08a370c5 Remove todo 2017-02-11 14:39:32 +00:00
Angus Hollands
2d5fd2fe1c fix test 2017-02-11 14:35:44 +00:00
Angus Hollands
b5e50ecb75 Use app decorator instead of run arguments for before_start
Mirror listener of blueprints
2017-02-11 14:30:17 +00:00
Angus Hollands
e00c9d0ee0 Fix line length 2017-02-11 12:39:04 +00:00
Angus Hollands
be9c9f045a Cleanup middleware decorator 2017-02-11 12:27:25 +00:00
Raphael Deem
75fca1b9c7 Merge pull request #402 from agoose77/patch-1
Don't ask for uvloop on windows
2017-02-10 12:11:12 -08:00
Angus Hollands
1436fb3ef4 Don't ask for uvloop on windows
This is a tricky issue, but essentially uvloop is unavailable on windows. This means for windows users, we have to install Sanic with no requirements, and then manually specify all requirements apart from uvloop.

However, Sanic will work with standard asyncio event loop. So, I propose we remove the uvloop requirement on windows. This patch doesn't touch any demo imports.
2017-02-10 13:14:36 +00:00
Raphael Deem
2bfd127218 Merge pull request #400 from aquacash5/master
added build folder to .gitignore
2017-02-09 23:13:49 -08:00
Kyle Blöm
de5da63d5c added build folder to .gitignore 2017-02-09 18:49:11 -08:00
lixxu
fb419eaa36 fix bug: netloc always in url if SERVER_NAME defined in config even _external not true 2017-02-09 18:26:17 +08:00
lixxu
cf2a363e5e improve url_for to support multi values for one arg, add _anchor/_external/_scheme options 2017-02-09 16:44:23 +08:00
Eli Uriegas
7401facc21 Merge pull request #398 from seemethere/fix_errors_from_content_range
Fixes errors related to #378
2017-02-08 20:06:02 -06:00
Eli Uriegas
579afe012b Fixes errors related to #378 2017-02-08 19:59:34 -06:00
Eli Uriegas
4f856e8783 Merge pull request #378 from aquacash5/master
Added the tests, code formatting changes, and the Range Request feature.
2017-02-08 19:39:29 -06:00
Eli Uriegas
eb059183f7 Merge branch 'master' into master 2017-02-08 19:37:32 -06:00
Eli Uriegas
419156f7cc Merge pull request #397 from seemethere/increment_031
Increment to v0.3.1
2017-02-08 19:23:20 -06:00
Eli Uriegas
b7f7883fb7 Increment to v0.3.1 2017-02-08 19:22:51 -06:00
Eli Uriegas
a5a7490bca Merge pull request #379 from youknowone/exception
Let exception handler handle inherited exceptions
2017-02-08 19:20:42 -06:00
Eli Uriegas
6724d8131c Merge pull request #387 from subyraman/url-for-v3
Add `url_for` method for simple routes, blueprints, HTTPMethodView
2017-02-08 19:20:09 -06:00
Raphael Deem
a19bb15070 Merge pull request #389 from chenfengyuan/fix_run_async_demo
fix run_async demo
2017-02-07 14:58:48 -08:00
Eli Uriegas
2ee0147848 Merge pull request #386 from youknowone/sanic_endpoint_test
Fix sanic_endpoint_test working with redirects
2017-02-07 10:57:05 -06:00
Eli Uriegas
252f1add7e Merge pull request #394 from brian-bates/tox-cleanup
Add missing dependency to tox.ini
2017-02-07 10:55:52 -06:00
Jeong YunWon
413c92c631 Let exception handler handle inherited exceptions
Original sanic exception handler only could handle exact matching
exceptions. New `lookup` method will provide ability to looking up
their ancestors without additional cost of performance.
2017-02-07 21:08:31 +09:00
Suby Raman
36d519026f reject unnamed handlers 2017-02-06 11:11:00 -05:00
Fengyuan Chen
aa54785918 fix always warning loop is passed issue 2017-02-06 11:11:00 -05:00
Raphael Deem
d6b386083f Merge pull request #390 from chenfengyuan/pass_loop_warning
fix always warning loop is passed issue
2017-02-05 14:01:41 -08:00
Brian Bates
82f383b64f Add missing dependency
Added missing `aiofiles` to tox.ini and cleaned up requirements files.
2017-02-05 11:44:01 -08:00
Jeong YunWon
a15ee3ad06 Fix sanic_endpoint_test working with redirects
Before fix, it raises error like:

```
tests/test_utils.py F

================================= FAILURES =================================
______________________________ test_redirect _______________________________

app = <sanic.sanic.Sanic object at 0x1045fda20>, method = 'get', uri = '/1', gather_request = True, debug = False
server_kwargs = {}, request_args = (), request_kwargs = {}
_collect_request = <function sanic_endpoint_test.<locals>._collect_request at 0x1045ec950>
_collect_response = <function sanic_endpoint_test.<locals>._collect_response at 0x1045ec7b8>

    def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
                            debug=False, server_kwargs={},
                            *request_args, **request_kwargs):
        results = []
        exceptions = []

        if gather_request:
            def _collect_request(request):
                results.append(request)
            app.request_middleware.appendleft(_collect_request)

        async def _collect_response(sanic, loop):
            try:
                response = await local_request(method, uri, *request_args,
                                               **request_kwargs)
                results.append(response)
            except Exception as e:
                exceptions.append(e)
            app.stop()

        app.run(host=HOST, debug=debug, port=PORT,
                after_start=_collect_response, **server_kwargs)

        if exceptions:
            raise ValueError("Exception during request: {}".format(exceptions))

        if gather_request:
            try:
>               request, response = results
E               ValueError: too many values to unpack (expected 2)

sanic/utils.py:47: ValueError

During handling of the above exception, another exception occurred:

utils_app = <sanic.sanic.Sanic object at 0x1045fda20>

    def test_redirect(utils_app):
        """Test sanic_endpoint_test is working for redirection"""
>       request, response = sanic_endpoint_test(utils_app, uri='/1')

tests/test_utils.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

app = <sanic.sanic.Sanic object at 0x1045fda20>, method = 'get', uri = '/1', gather_request = True, debug = False
server_kwargs = {}, request_args = (), request_kwargs = {}
_collect_request = <function sanic_endpoint_test.<locals>._collect_request at 0x1045ec950>
_collect_response = <function sanic_endpoint_test.<locals>._collect_response at 0x1045ec7b8>

    def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
                            debug=False, server_kwargs={},
                            *request_args, **request_kwargs):
        results = []
        exceptions = []

        if gather_request:
            def _collect_request(request):
                results.append(request)
            app.request_middleware.appendleft(_collect_request)

        async def _collect_response(sanic, loop):
            try:
                response = await local_request(method, uri, *request_args,
                                               **request_kwargs)
                results.append(response)
            except Exception as e:
                exceptions.append(e)
            app.stop()

        app.run(host=HOST, debug=debug, port=PORT,
                after_start=_collect_response, **server_kwargs)

        if exceptions:
            raise ValueError("Exception during request: {}".format(exceptions))

        if gather_request:
            try:
                request, response = results
                return request, response
            except:
                raise ValueError(
                    "Request and response object expected, got ({})".format(
>                       results))
E               ValueError: Request and response object expected, got ([{}, {}, {}, <ClientResponse(http://127.0.0.1:42101/3) [200 OK]>
E               <CIMultiDictProxy('Content-Type': 'text/plain; charset=utf-8', 'Content-Length': '2', 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=1')>
E               ])

sanic/utils.py:52: ValueError
```
2017-02-05 13:57:04 +09:00
Fengyuan Chen
29680cb515 fix always warning loop is passed issue 2017-02-04 16:09:09 +08:00
Fengyuan Chen
884749f345 fix run_async demo 2017-02-04 15:27:46 +08:00
Suby Raman
5c63ce666c punctuation 2017-02-02 14:21:59 -05:00
Suby Raman
5632d073be update docs 2017-02-02 13:00:15 -05:00
Suby Raman
f9056099f9 all works 2017-02-02 12:52:48 -05:00
Suby Raman
7c09ec29f7 rebase 2017-02-02 12:21:14 -05:00
Eli Uriegas
6f0b09509e Merge pull request #213 from sfstpala/master
Make it possible to disable the logo by subclassing Config
2017-02-01 23:07:52 -06:00
Eli Uriegas
31c53d67e2 Merge pull request #384 from seemethere/update_static_tests
Updates static tests to test for issue #374
2017-02-01 15:20:34 -06:00
Eli Uriegas
6a322ba3f8 Updates static tests to test for issue #374
Adds a test to test for serving a static directory at the root uri '/'
to address concerns found in #374. Also rewrites the tests so that they
are parametrized and do more with less.
2017-02-01 09:00:57 -06:00
Raphael Deem
dece636d54 Merge pull request #383 from r0fls/derp
typo: async_run -> run_async
2017-02-01 00:29:12 -08:00
Raphael Deem
b29f648148 typo: async_run -> run_async 2017-01-31 12:46:02 -08:00
Eli Uriegas
c91d264ff1 Merge pull request #380 from channelcat/openapi-extension
Added sanic-openapi to extensions
2017-01-31 07:39:38 -06:00
Eli Uriegas
487e3352e4 Revert "fix async run, add tests"
This reverts commit 41da793b5a.
2017-01-31 07:30:17 -06:00
Channel Cat
34966fb182 Added sanic-openapi to extensions 2017-01-31 01:24:41 -08:00
Eli Uriegas
17a92a58b2 Merge pull request #369 from r0fls/fix-async-run
fix async run, add tests
2017-01-30 22:21:25 -06:00
Raphael Deem
60d3e5b9e0 Merge pull request #377 from r0fls/373
update route method docs
2017-01-30 17:07:26 -08:00
Kyle Blöm
d193a1eb70 Added the tests, code formatting changes, and the Range Request feature. 2017-01-30 17:04:51 -08:00
Raphael Deem
1501c56bbc update route method docs 2017-01-30 16:42:43 -08:00
Eli Uriegas
6d18fb6bae Merge pull request #363 from r0fls/run-helper
Run helper
2017-01-30 05:51:50 -06:00
Eli Uriegas
eac26a4514 Merge pull request #372 from JordanP/fix_docs_config
Fix docs/config.md: the MYAPP_SETTINGS is not exported
2017-01-30 05:43:29 -06:00
Channel Cat
1649f30808 Updated password 2017-01-30 02:22:12 -08:00
Jordan Pittier
82680bf43f Fix docs/config.md: the MYAPP_SETTINGS is not exported
If we don"t `export` the variable, it's not available in subcommand:

MYAPP_SETTINGS=/path/to/config_file; python3 -c "import os; os.environ['MYAPP_SETTINGS']"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.5/os.py", line 725, in __getitem__
    raise KeyError(key) from None
KeyError: 'MYAPP_SETTINGS'

The ';' is the culprit here.
2017-01-30 10:39:02 +01:00
Raphael Deem
41da793b5a fix async run, add tests 2017-01-29 23:47:47 -08:00
Raphael Deem
cfb5734b85 Merge pull request #371 from channelcat/blueprint-shorthand
Blueprint route shorthand
2017-01-29 23:23:47 -08:00
Channel Cat
b72d841619 . 2017-01-29 23:21:00 -08:00
Channel Cat
0ef39f35ae Added route shorthands to blueprints 2017-01-29 23:20:38 -08:00
Channel Cat
38d1ed76d2 Merge pull request #368 from channelcat/blueprint-clarity
Restructured blueprint class for clarity
2017-01-29 18:47:27 -08:00
Channel Cat
4c80cd185f Fix flake8 2017-01-29 17:44:46 -08:00
Channel Cat
629524af04 Restructured blueprint class
Blueprints currently queue functions to be called, which are simple, yet
hard to inspect.  These changes allow tools to be built that analyze
blueprints more easily.
2017-01-29 17:39:55 -08:00
Channel Cat
a245e54bd3 Merge pull request #367 from channelcat/fix-rtd-build
Fix readthedocs includes
2017-01-29 16:46:48 -08:00
Channel Cat
52e485cce9 Fix readthedocs includes 2017-01-29 16:46:16 -08:00
Channel Cat
02d374b65b Merge pull request #365 from channelcat/fix-rtd-build
Adding readthedocs file
2017-01-29 16:41:41 -08:00
Channel Cat
0de6bb0063 Adding readthedocs file 2017-01-29 16:40:36 -08:00
Channel Cat
9d6b379999 Merge pull request #364 from channelcat/fix-rtd-build
Fix readthedocs build
2017-01-29 16:38:29 -08:00
Channel Cat
c132c4e673 fix conflict part 2 2017-01-29 16:32:57 -08:00
Channel Cat
6962dcd66c fix conflict 2017-01-29 16:31:37 -08:00
Channel Cat
2a9496fcda Fix readthedocs build 2017-01-29 16:25:22 -08:00
Raphael Deem
82d1d30a41 review updates 2017-01-29 14:01:00 -08:00
Eli Uriegas
85639d0806 Revert "testing"
This reverts commit 3fd6ecaedb.
2017-01-29 15:55:47 -06:00
Channel Cat
3fd6ecaedb testing 2017-01-29 13:52:17 -08:00
Channel Cat
884d3a0316 Fix RTD build 2017-01-29 13:45:44 -08:00
Raphael Deem
10dbb9186d combine logic from create_server() and run() 2017-01-29 13:36:13 -08:00
Eli Uriegas
a547798b08 Merge pull request #360 from seemethere/fix_route_overloading_for_dynamic_routes
Fixes route overloading for dynamic routes
2017-01-29 15:35:13 -06:00
Eli Uriegas
894b434875 Merge pull request #362 from channelcat/read-the-docs
Added basic readthedocs support
2017-01-29 15:34:58 -06:00
Eli Uriegas
8db0ece459 Merge branch 'master' into read-the-docs 2017-01-29 15:31:22 -06:00
Eli Uriegas
f56c5e3a45 Merge pull request #199 from Tim-Erwin/improved_config
added methods to load config from a file
2017-01-29 15:27:34 -06:00
Eli Uriegas
0a5fa72099 Add logic to make dynamic route merging work
This is by no means the final solution but it's a start in the right
direction. Eventually what needs to happen is we need to reduce the
complexity of the routing. CompsitionView can probably be removed later
on in favor of better Route objects. Also in the next version of sanic
we need to move merge_route and add_parameter out of the add_route logic
and just have them as standalone methods.

The tests should cover everything that we need so that if any changes
are made we can identify regression.
2017-01-29 15:16:07 -06:00
Channel Cat
0eaccea38f updated project name in docs build 2017-01-29 12:49:59 -08:00
Channel Cat
de32c389d0 Added basic readthedocs support 2017-01-29 12:47:00 -08:00
Raphael Deem
753d2da6db fix async run 2017-01-28 15:47:29 -08:00
Eli Uriegas
d3344da9c5 Add a pesky newline 2017-01-27 22:15:34 -06:00
Eli Uriegas
ae0876876e Switch them to verifying headers instead 2017-01-27 22:13:16 -06:00
Eli Uriegas
dea8e16f49 Force method to lower 2017-01-27 22:07:31 -06:00
Eli Uriegas
13803bdb30 Update for HTTPMethodView compatibility 2017-01-27 22:05:46 -06:00
Eli Uriegas
7257e5794f Merge pull request #359 from r0fls/fix-warnings
fix deprecation warnings
2017-01-27 21:05:51 -06:00
Eli Uriegas
41c52487ee Fixes route overloading for dynamic routes
Addresses #353, now dynamic routes work alongside our newly minted
overloaded routes! Also fixed an unintended side effect where methods
were still being passed in as None for `Sanic.add_route`.
2017-01-27 21:00:33 -06:00
Raphael Deem
0eb779185d fix deprecation warnings 2017-01-27 18:09:19 -08:00
Eli Uriegas
3d790a71a0 Merge pull request #358 from seemethere/incrment_version_030
Increment version to 0.3.0
2017-01-27 19:37:02 -06:00
Eli Uriegas
cfc32d940a Increment version to 0.3.0 2017-01-27 19:36:03 -06:00
Eli Uriegas
59242df7d6 Move serve_multiple, fix tests (#357)
* Move serve_multiple, remove stop_events, fix tests

Moves serve_multiple out of the app, removes stop_event (adds a
deprecation warning, but it also wasn't doing anything) fixes
multiprocessing tests so that they don't freeze pytest's runner.

Other notes:

Also moves around some imports so that they are better optimized as
well.

* Re-add in stop_event, maybe it wasn't so bad!

* Get rid of unused warnings import
2017-01-27 19:34:21 -06:00
Eli Uriegas
fad9fbca6f Merge pull request #335 from r0fls/remove-loop
remove loop as argument and update examples
2017-01-27 19:25:09 -06:00
Eli Uriegas
c81c92967c Merge pull request #354 from JordanP/use_isinstance
Use ``isinstance(`` instead of ``issubclass(type(``
2017-01-27 15:05:12 -06:00
Jordan Pittier
fa36dcbe09 Use `isinstance( instead of issubclass(type(`
When we already have an `instance` it's less typing and faster to
use `isinstance`.
2017-01-27 11:11:29 +01:00
Raphael Deem
c72bcc136c add semaphore concurrency limit example 2017-01-26 17:43:54 -08:00
Raphael Deem
d52f5f0b09 remove loop as argument and update examples 2017-01-26 17:38:46 -08:00
Eli Uriegas
4f6650fce2 Merge pull request #349 from kdelwat/compositionview-docs
Add documentation for the CompositionView class
2017-01-26 19:38:32 -06:00
Raphael Deem
2774414f9e Merge pull request #350 from r0fls/deprecate-loop
add deprecate loop message
2017-01-26 15:24:43 -08:00
Eli Uriegas
c1f1dca22c Merge pull request #351 from r0fls/revert-log-changes
use string formatting in start message
2017-01-26 17:17:49 -06:00
Raphael Deem
965fbc917d use string formatting in start message 2017-01-26 15:11:53 -08:00
Raphael Deem
2a1b63c93c add deprecate loop message 2017-01-26 15:07:42 -08:00
Cadel Watson
011be51232 Add documentation for the CompositionView class 2017-01-27 09:52:41 +11:00
Eli Uriegas
d1c4f3172f Merge pull request #348 from r0fls/275
add async run
2017-01-26 10:37:52 -06:00
Raphael Deem
14697c7ea9 review changes 2017-01-26 08:36:00 -08:00
Raphael Deem
54ca8c787b add async run 2017-01-25 22:47:18 -08:00
Channel Cat
88c3bffe20 Merge pull request #342 from channelcat/fix-slow-upload
Fix slow upload
2017-01-25 22:04:48 -08:00
Channel Cat
a4f85cd02e Merge pull request #343 from channelcat/cookie-usability
Cookie deletion and default path
2017-01-25 21:59:43 -08:00
Channel Cat
fd118a41fd Only use request.body 2017-01-25 21:56:49 -08:00
Channel Cat
b0f71c5304 Fixed default path in documentation 2017-01-25 21:48:39 -08:00
Raphael Deem
a162f2ce34 Merge branch 'master' into cookie-usability 2017-01-25 21:24:30 -08:00
Eli Uriegas
0f64702d72 Merge pull request #347 from r0fls/345
false cookie attributes should not be set
2017-01-25 18:51:02 -06:00
Raphael Deem
3c355f19eb false cookie attributes should not be set 2017-01-25 16:47:14 -08:00
Raphael Deem
fb9f2171a9 Merge pull request #346 from r0fls/event-docstring
fix before/after event docstrings
2017-01-25 16:29:10 -08:00
Raphael Deem
4efcb6d5ad fix before/after event docstrings 2017-01-25 16:25:16 -08:00
Raphael Deem
5ddc166b12 Merge pull request #344 from atbentley/docs
Update blueprints docs with correct listener example
2017-01-25 10:48:17 -08:00
Andrew Bentley
ecd7e5bdd3 Update blueprints docs with correct listener example 2017-01-25 23:01:57 +11:00
Channel Cat
d0a121ad06 Added del cookie and default path 2017-01-25 01:53:39 -08:00
Channel Cat
85a28d3c30 Fix slow upload 2017-01-25 01:19:21 -08:00
Tim Mundt
5bba3388a0 Merge branch 'master' into improved_config 2017-01-25 09:36:21 +01:00
Eli Uriegas
0ad31a471b Merge pull request #341 from r0fls/340
set error handler debug from run debug arg
2017-01-24 21:07:15 -06:00
Raphael Deem
28f7abd1f8 set error handler debug from run debug arg 2017-01-24 17:24:06 -08:00
Raphael Deem
049a5a7493 Merge pull request #339 from lixxu/master
add sanic-jinja2 extension
2017-01-23 23:46:26 -08:00
Lix Xu
a9ee01c7ec add sanic-jinja2 extension 2017-01-24 15:34:32 +08:00
Manuel Miranda
1f89b15792 Caching example (#334)
* Caching example using aiocache

* Caching example using aiocache

* Added aiocache to requirements

* Fixed example with newest aiocache

* Fixed bug in cache example
2017-01-23 10:19:42 -06:00
Raphael Deem
afe390d407 Merge pull request #253 from dutradda/add_register_sys_signals_flag
add a flag to skip SIGINT and SIGTERM signals registration
2017-01-21 23:09:12 -08:00
Eli Uriegas
6f4f2758d6 Merge pull request #331 from ctlaltdefeat/json-response-args
allowed passing arguments to json response encoder
2017-01-21 15:31:57 -06:00
ctlaltdefeat
592ee5f839 fixed line length to satisfy travis 2017-01-21 23:02:02 +02:00
ctlaltdefeat
a811c84e99 allowed passing arguments to json response encoder 2017-01-21 22:40:34 +02:00
Eli Uriegas
214162adf0 Merge branch 'master' into add_register_sys_signals_flag 2017-01-21 10:25:57 -06:00
Raphael Deem
28396c8620 Merge pull request #327 from awiddersheim/logging-override
Make it easier to override logging
2017-01-20 16:23:03 -08:00
Andrew Widdersheim
72fba62e09 Make it easier to override logging
Take influence from how Werkzeug configures logging by only configuring
a handler if no root handlers were previously configured by the end
user.
2017-01-20 18:26:55 -05:00
Raphael Deem
5669cafc59 Merge pull request #328 from awiddersheim/remove-multidict
Remove multidict requirement
2017-01-20 15:14:39 -08:00
Andrew Widdersheim
0c7275da1a Remove multidict requirement
This is no longer necessary after #302.
2017-01-20 18:12:54 -05:00
Eli Uriegas
e5cbf25cbd Merge pull request #325 from seemethere/add_key_error
Simplify RequestParameters
2017-01-20 15:27:00 -06:00
Eli Uriegas
c3cf618e84 Merge pull request #326 from seemethere/fix_exception_monitoring_documentation
Fix exception_monitoring example to actually work
2017-01-20 14:39:26 -06:00
Eli Uriegas
7780a8c187 Merge pull request #320 from r0fls/method-decorators
add method shorthands
2017-01-20 14:38:34 -06:00
Eli Uriegas
40f1e14bb7 Fix exception_monitoring example to actually work
Closes #324

Super was used incorrectly in the example, also fixed some formatting
here and there.
2017-01-20 14:36:15 -06:00
Eli Uriegas
a7cd4ccd09 Simplify RequestParameters
Simplifies request parameters, it defined a bit more than it had too,
added some docstrings and made the code simpler as well. Should now
raise a KeyError on __getitem__ as @amsb had noted on commit 9dd954b
2017-01-20 14:31:24 -06:00
Raphael Deem
6fd69b6284 separate tests 2017-01-20 10:19:14 -08:00
Raphael Deem
96424b6b0a add method shorthands 2017-01-20 00:07:22 -08:00
Eli Uriegas
bef34d66f5 Merge pull request #314 from seemethere/make_closed_transport_handling_more_robust
Add exception handling for closed transports
2017-01-19 21:31:30 -06:00
Eli Uriegas
c6049be688 Merge pull request #316 from youknowone/route-get
For function decorators, ['GET'] is the default methods
2017-01-19 21:31:06 -06:00
Eli Uriegas
5ea653244e Merge pull request #319 from seemethere/fix_docs_links
Fixes doc link extensions from .html to .md
2017-01-19 21:30:40 -06:00
Eli Uriegas
596bb54ee3 Oops on 2 of the non-relative links 2017-01-19 21:26:37 -06:00
Eli Uriegas
7554e87374 Fixes doc link extensions from .html to .md
Whoops! Totally missed that all the links pointed to `.html` files
instead of `.md` files
2017-01-19 21:24:08 -06:00
Cadel Watson
6176964bdf Clarify, reformat, and add to documentation guides (#318)
* Reorder and clarify the 'Request Data' guide, adding a section on RequestParameters

* Clarify routing guide, adding introduction and HTTP types sections

* Clarify the use-cases of middleware

* Clean up formatting in the exceptions guide and add some common exceptions.

* Fix formatting of blueprints and add use-case example.

* Clarify the class-based views guide

* Clarify and fix formatting of cookies guide

* Clarify static files guide

* Clarify the custom protocols guide.

* Add more information to the deploying guide

* Fix broken list in the community extensions list.

* Add introduction and improve warning to contributing guide

* Expand getting started guide

* Reorder guides and add links between them

* Standardise heading capitalisation
2017-01-19 21:18:52 -06:00
Eli Uriegas
f77bb81def Merge pull request #229 from kdelwat/sphinx-docs
Use Sphinx for documentation
2017-01-19 16:54:02 -06:00
Eli Uriegas
ed4752bbc0 Move transport close to finally statment 2017-01-19 16:35:48 -06:00
Cadel Watson
30862c0a3e Update documentation generation instructions. 2017-01-20 09:32:08 +11:00
Cadel Watson
7c4ffa8866 Merge branch 'master' into sphinx-docs 2017-01-20 09:30:42 +11:00
Eli Uriegas
a79f073077 Merge pull request #228 from seanpar203/issue_41
Adding more docstrings
2017-01-19 16:18:28 -06:00
Raphael Deem
4682d7b4b8 Merge pull request #296 from mdaue/ssl
Add SSL context to server
2017-01-19 11:29:07 -08:00
Raphael Deem
d2217b5c5f Merge branch 'master' into ssl 2017-01-19 11:23:21 -08:00
Jeong YunWon
0a160c4a0b For function decorators, ['GET'] is the default methods 2017-01-19 23:56:51 +09:00
Eli Uriegas
aed8255a8b Merge pull request #315 from h4ck3rm1k3/patch-1
untie
2017-01-19 08:28:18 -06:00
James Michael DuPont
ba60659894 untie 2017-01-19 04:04:16 -05:00
Raphael Deem
1ad7b95437 Merge pull request #306 from zkanda/always-log-error-exception
Always log if there's an exception occurred.
2017-01-19 00:22:46 -08:00
zkanda
cc43ee3b3d Always log of there's an exception occured. 2017-01-19 16:03:12 +08:00
Eli Uriegas
5a7b70c054 Merge pull request #313 from r0fls/logging-var
remove logger from run
2017-01-18 23:50:07 -06:00
Eli Uriegas
e9bfa30c1d Add exception handling for closed transports
Adds handling for closed transports in the server for `write_response`
as well as `write_error`, letting it all flow to `bail_out` seemed to be
a little light handed in terms of telling the logs where the
error actually occured.

Handling to fix the infinite `write_error` loop is still there and those
exceptions will get reported on in the debug logs.
2017-01-18 23:46:22 -06:00
Raphael Deem
bb83a25a52 remove logger from run 2017-01-18 21:45:30 -08:00
Eli Uriegas
99c8d779dc Merge pull request #312 from r0fls/allow-vhost-lists
allow using a list of hosts on a route
2017-01-18 23:42:59 -06:00
Raphael Deem
2c1ff5bf5d allow using a list of hosts on a route 2017-01-18 19:41:32 -08:00
Eli Uriegas
e218a59911 Merge pull request #311 from youknowone/route-overload
Feature: Routing overload
2017-01-18 16:32:46 -06:00
Jeong YunWon
11f3c79a77 Feature: Routing overload
When user specifies HTTP methods to function handlers, it automatically
will be overloaded unless they duplicate.

Example:

    # This is a new route. It works as before.
    @app.route('/overload', methods=['GET'])
    async def handler1(request):
        return text('OK1')

    # This is the exiting route but a new method. They are merged and
    # work as combined. The route will serve all of GET, POST and PUT.
    @app.route('/overload', methods=['POST', 'PUT'])
    async def handler2(request):
        return text('OK2')

    # This is the existing route and PUT method is the duplicated method.
    # It raises RouteExists.
    @app.route('/overload', methods=['PUT', 'DELETE'])
    async def handler3(request):
	return text('Duplicated')
2017-01-19 07:12:45 +09:00
Cadel Watson
5cfd8b9aa8 Fix formatting of 'Final Word' in README 2017-01-19 08:58:13 +11:00
Cadel Watson
ebce7b01c7 Add new guides to documentation index 2017-01-19 08:54:20 +11:00
Cadel Watson
9d4b104d2d Merge branch 'master' into sphinx-docs 2017-01-19 08:48:54 +11:00
Raphael Deem
5a6fb679c9 Merge pull request #309 from seemethere/fix_write_error_loop
Fixes write_error loop from bail_out function
2017-01-17 18:11:22 -08:00
Raphael Deem
9102a9cd6e Merge branch 'master' into ssl 2017-01-17 18:09:33 -08:00
Eli Uriegas
573d1da0ef Fixes write_error loop from bail_out function
Fixes stack-overflow found in #307
2017-01-17 18:28:22 -06:00
Raphael Deem
2848dce968 Merge pull request #308 from r0fls/287
update logging placement
2017-01-17 15:51:38 -08:00
Raphael Deem
ba1e006585 update logging placement 2017-01-17 15:49:17 -08:00
Eli Uriegas
9108a4c69f Merge pull request #291 from subyraman/master
Add rich HTML traceback in debug mode, add HTML 500 page in prod
2017-01-17 15:47:37 -06:00
Eli Uriegas
f42ceb1baa Merge pull request #304 from gpip/missing_ip_slots
cannot use the new .ip without updating __slots__
2017-01-17 10:54:21 -06:00
Guilherme Polo
5903dd2939 cannot use the new .ip without updating __slots__ 2017-01-17 02:58:45 -02:00
Eli Uriegas
55b39a3f15 Merge pull request #301 from r0fls/cache-remote-ip
cache the remote IP property
2017-01-16 20:07:53 -06:00
Raphael Deem
9bc69f7de9 use hasattr 2017-01-16 17:35:08 -08:00
Channel Cat
2aa380c5a3 Merge pull request #302 from channelcat/request-headers-ci
Trimmed down features of CIMultiDict
2017-01-16 17:08:12 -08:00
Channel Cat
638fbcb619 Encoding needs a default 2017-01-16 17:03:55 -08:00
Channel Cat
ccbbce0036 Fix header capitalization on input
also removed redundant utf-8 in encodes/decodes
2017-01-16 16:55:55 -08:00
Channel Cat
41918eaf0a Trimmed down features of CIMultiDict 2017-01-16 16:12:42 -08:00
Eli Uriegas
5c344f7efa Remove redundant else 2017-01-16 17:51:56 -06:00
Raphael Deem
213580ea78 cache the remote IP property 2017-01-16 15:48:55 -08:00
Eli Uriegas
d5fd269fda Merge pull request #300 from channelcat/move-request-ip-retry
Moved Remote-Addr header to request.ip
2017-01-16 17:35:04 -06:00
Ubuntu
2cf4baddfb Moved Remote-Addr header to request.ip so it can be pulled on-demand 2017-01-16 23:27:50 +00:00
Eli Uriegas
48d496936a Merge pull request #294 from subyraman/redirect
Add redirect method from @pcdinh
2017-01-16 13:53:02 -06:00
Eli Uriegas
e410c06a68 Merge pull request #293 from r0fls/extensions-docs
Extensions docs
2017-01-15 19:37:30 -06:00
Eli Uriegas
f6c2d0bcaf Merge pull request #297 from seemethere/increment_020
Increment version to 0.2.0
2017-01-14 11:25:10 -06:00
Eli Uriegas
e2a16f96a8 Increment version to 0.2.0 2017-01-14 11:24:31 -06:00
Matt Daue
49fdc6563f Add SSL to server
Add ssl variable passthrough to following:
-- sanic.run
-- server.serve
Add ssl variable to loop.create_server to enable built-in async context socket wrapper
Update documentation
Tested with worker = 1, and worker = 2.

Signed-off-by: Matt Daue <mattdaue@gmail.com>
2017-01-14 07:16:59 -05:00
Suby Raman
b5bbef09c5 add redirect method 2017-01-14 00:47:28 -05:00
Suby Raman
7a1e089725 add headers none test 2017-01-14 00:45:04 -05:00
Suby Raman
7de3f7aa78 rename test app 2017-01-14 00:43:30 -05:00
Suby Raman
02b9a0a297 add redirect code from @pcdinh 2017-01-14 00:41:54 -05:00
Tim Mundt
0b9094d348 Merge branch 'master' into improved_config 2017-01-13 12:34:56 +01:00
Raphael Deem
a86cc32dff Update extensions.md
Since there have been a few extensions discussed in open issues, it makes sense to start keeping track of them.
2017-01-12 19:30:22 -08:00
Raphael Deem
974c857a97 add a list of extensions 2017-01-12 19:25:08 -08:00
Suby Raman
6e53fa03f5 add beautifulsoup4 to tox 2017-01-12 20:03:35 -05:00
Suby Raman
8c5e214131 html and tests pass 2017-01-12 19:54:34 -05:00
Eli Uriegas
cf60ebd988 Merge pull request #289 from seemethere/update_request_form_getitem
Update request.form to work with __getitem__
2017-01-11 17:02:45 -06:00
Eli Uriegas
9dd954bccd Update request.form to work with __getitem__ 2017-01-11 16:55:34 -06:00
Eli Uriegas
a02eb8e7cb Merge pull request #284 from r0fls/remove-defualt-type
remove default from host in _get method
2017-01-11 10:01:11 -06:00
Eli Uriegas
a93ca9b8f1 Merge pull request #285 from r0fls/blueprint-domains
add vhosts to blueprints
2017-01-11 10:00:35 -06:00
Raphael Deem
15e4ec7ffb add ability to override default host in blueprint 2017-01-10 22:08:15 -08:00
Raphael Deem
62df50e22b add vhosts to blueprints 2017-01-10 21:35:07 -08:00
Raphael Deem
055430d4b8 remove default from host in _get method 2017-01-10 16:01:21 -08:00
Eli Uriegas
57f27c41e0 Merge pull request #278 from r0fls/vhosts
add support for virtual hosts
2017-01-10 15:18:36 -06:00
Cadel Watson
385328ffd5 Generate API documentation in the _api folder 2017-01-10 09:41:00 +11:00
Cadel Watson
34d1e5e67e Convert README to RestructuredText and include it in the documentation index. 2017-01-10 09:28:33 +11:00
Raphael Deem
4f832ac9af add support for virtual hosts 2017-01-08 18:46:29 -08:00
Eli Uriegas
b0bf989056 Merge pull request #261 from yoloseem/parsed_json
Cache request.json even when it's empty.
2017-01-08 11:57:33 -06:00
Eli Uriegas
fd0e8624c4 Merge pull request #276 from r0fls/workers
Fix multiple worker problem
2017-01-08 11:57:10 -06:00
Eli Uriegas
5566668a5f Change the skips to actual pytest skips
By using the builtin pytest skips we can identify that the tests are still there but are being currently skipped.

Will update later to remove the skips once we figure out why they freeze with pytest (I experienced this same issue with multiprocessing when testing start/stop events).
2017-01-08 11:55:08 -06:00
Raphael Deem
f8e6becb9e skip multiprocessing tests 2017-01-07 18:58:02 -08:00
Raphael Deem
dd28d70680 fix stop event 2017-01-07 18:46:38 -08:00
Raphael Deem
ed8e3f237c this branch is broken 2017-01-07 15:28:21 -08:00
Raphael Deem
77c04c4cf9 fix multiple worker problem 2017-01-07 12:57:14 -08:00
Eli Uriegas
ebc8d7168a Merge pull request #274 from AntonDnepr/cbw_fixes
Class based views fixes
2017-01-07 10:07:14 -06:00
Anton Zhyrney
434fa74e67 removed debug from test 2017-01-07 07:14:27 +02:00
Anton Zhyrney
47a4f34cdf tests&small update 2017-01-07 07:13:49 +02:00
Anton Zhyrney
1317b1799c add docstrings&updated docs 2017-01-07 06:57:07 +02:00
Anton Zhyrney
fcae4a9f0a added as_view 2017-01-07 06:30:23 +02:00
Eli Uriegas
d733c5bb7c Merge pull request #271 from easydaniel/master
Asyncpg example
2017-01-06 09:11:42 -06:00
Eli Uriegas
baf8254907 Change Ellipsis to None for consistency 2017-01-05 15:29:57 -06:00
Eli Uriegas
802e7d4654 Merge pull request #269 from r0fls/263
add configurable backlog
2017-01-04 20:37:19 -06:00
Raphael Deem
616e20d467 move backlog to run() 2017-01-04 09:31:06 -08:00
easydaniel
5c7c2cf85e Update sanic_asyncpg_example.py
Remove unused library
2017-01-04 23:35:06 +08:00
DanielChien
1942644434 modify config to varbles 2017-01-04 23:30:29 +08:00
DanielChien
b67482de9b add example for asyncpg 2017-01-04 23:29:09 +08:00
Eli Uriegas
35bb71f952 Merge pull request #270 from seemethere/add_server_event_tests
Add server event tests
2017-01-04 00:32:13 -06:00
Eli Uriegas
9c91b09ab1 Fix this to actually reflect current behavior 2017-01-04 00:23:59 -06:00
Eli Uriegas
06911a8d2e Add tests for server start/stop event functions 2017-01-04 00:23:35 -06:00
Raphael Deem
e7922c1b54 add configurable backlog #263 2017-01-03 18:36:31 -08:00
Eli Uriegas
16959caa34 Merge pull request #259 from yoloseem/route
Correct Router documentation
2017-01-03 18:47:54 -06:00
Eli Uriegas
0675f388b9 Merge pull request #255 from seemethere/add_more_verbose_debug_error_handling
Add more verbose debug error handling
2017-01-03 15:43:26 -06:00
Eli Uriegas
b4ad9459da Merge pull request #260 from yoloseem/gitign
Update .gitignore
2017-01-03 14:42:00 -06:00
Eli Uriegas
8a9b1fee14 Merge pull request #265 from youknowone/test-middleware-schedule
Prevent flooding of meaningless traceback in `sanic_endpoint_test`
2017-01-03 14:41:23 -06:00
Jeong YunWon
e6eb697bb2 Use constant PORT rather than literal in test code (#266) 2017-01-03 14:40:13 -06:00
Eli Uriegas
4ccc782e29 Merge pull request #209 from 38elements/protocol
Customizable protocol
2017-01-03 11:52:54 -06:00
Hyunjun Kim
cfdd9f66d1 Correct sanic.router.Router documentation 2017-01-02 14:32:22 +09:00
Hyunjun Kim
035cbf84ae Cache request.json even when it's null or empty
In case of request body is set to `{}`, `[]` or `null`, even it's
already processed, parsed_json won't be used due to its boolean
evaluation.
2017-01-02 14:20:20 +09:00
Hyunjun Kim
31e92a8b4f Update .gitignore
* .python-version is generated by `pyenv local` command
* .eggs/ directory contains *.egg files
2017-01-02 13:33:24 +09:00
Jeong YunWon
87c24e5a7c Prevent flooding of meaningless traceback in sanic_endpoint_test
When Sanic has an exception in a request middleware, it fails to
save request object in `results`. In `sanic_endpoint_test`, because
it always requires `results` to have both `request` and `response` objects,
it prints traceback like attached example. It is not a user code and
it doesn't give any information to users, it is better to suppress
to print this kind of error.

To fix it, this patch insert collect hook as first request middleware
to guarantee to successfully run it always.

```
app = <sanic.sanic.Sanic object at 0x1102b5358>, method = 'get', uri = '/ping/', gather_request = True, loop = None
debug = True, request_args = (), request_kwargs = {}
_collect_request = <function sanic_endpoint_test.<locals>._collect_request at 0x11286c158>
_collect_response = <function sanic_endpoint_test.<locals>._collect_response at 0x11286c378>

    def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
                            loop=None, debug=False, *request_args,
                            **request_kwargs):
        results = []
        exceptions = []

        if gather_request:
            @app.middleware
            def _collect_request(request):
                results.append(request)

        async def _collect_response(sanic, loop):
            try:
                response = await local_request(method, uri, *request_args,
                                               **request_kwargs)
                results.append(response)
            except Exception as e:
                exceptions.append(e)
            app.stop()

        app.run(host=HOST, debug=debug, port=42101,
                after_start=_collect_response, loop=loop)

        if exceptions:
            raise ValueError("Exception during request: {}".format(exceptions))

        if gather_request:
            try:
>               request, response = results
E               ValueError: not enough values to unpack (expected 2, got 1)

../sanic/sanic/utils.py:46: ValueError
```
2017-01-02 13:18:22 +09:00
Eli Uriegas
552ff9d736 Merge pull request #235 from yoloseem/patch-1
Allow Sanic-inherited application
2016-12-31 13:21:44 -06:00
Eli Uriegas
738396c2e2 Merge pull request #236 from seanpar203/token_property
Add token property to request
2016-12-31 13:21:12 -06:00
Eli Uriegas
9f18b5a096 Merge pull request #248 from youknowone/debuggable-typecheck
Handle hooks parameters in more debuggable way
2016-12-31 13:17:06 -06:00
Eli Uriegas
15c965c08c Make exception tests test unhandled exceptions
* Adds tests for unhandled exceptions
* Adds tests for unhandled exceptions in exception handlers
* Rewrites tests to utilize pytest fixtures (No need to create the app
on import)
2016-12-30 13:50:12 -06:00
Eli Uriegas
7a8fd6b0df Add more verbose error handling
* Adds logging to error messages in debug mode as pointed out in PR #249,
while also improving the debug message.
2016-12-30 13:48:17 -06:00
Eli Uriegas
3ada6f358c Merge pull request #197 from r0fls/178
convert header values to strings
2016-12-30 12:17:29 -06:00
Eli Uriegas
f1c2854358 Merge branch 'master' into 178 2016-12-30 12:15:08 -06:00
Eli Uriegas
87559a34f8 Include more explicit loop for headers conversion
Also merges master changes into this PR for this branch
2016-12-30 12:13:16 -06:00
Eli Uriegas
9586351f37 Merge pull request #243 from dutradda/add_remove_route_method
created methods to remove a route from api/router
2016-12-30 09:39:57 -06:00
Diogo
0f6ed642da created methods to remove a route from api/router 2016-12-30 07:36:57 -02:00
Diogo Dutra
6d1d4ade19 add a flag to skip SIGINT and SIGTERM signals registration 2016-12-29 19:35:41 -02:00
Eli Uriegas
73a57e1105 Merge pull request #252 from AntonDnepr/misprints
Small docs improvements
2016-12-29 11:49:55 -06:00
Anton Zhyrney
e7314d1775 fix misprints&renaming 2016-12-29 19:22:11 +02:00
38elements
ee8f8c2930 Merge branch 'master' into protocol 2016-12-29 16:44:15 +09:00
38elements
64e0e2d19f Improve custom_protocol.md 2016-12-29 16:41:04 +09:00
38elements
6bb4dae5e0 Fix format in custom_protocol.md 2016-12-29 13:25:04 +09:00
38elements
83e9d08853 Add document for custom protocol 2016-12-29 13:11:27 +09:00
Eli Uriegas
7dfa62516c Merge pull request #238 from r0fls/73
stop multiple worker server without sleep loop; issue #73
2016-12-28 18:05:06 -06:00
Jeong YunWon
15e7d8ab2e Handle hooks parameters in more debuggable way
1. not list() -> callable()
The args of hooking parameters of Sanic have to be callables.
For wrong parameters, errors will be generated from:
```
    listeners += args
```

By checking just list type, the raised error will be associated
with `[args]` instead of `args`, which is not given by users.
With this patch, the raised error will be associated with `args`.
Then users can notice their argument was neither callable nor list
in the easier way.

2. Function -> Functions in document
Regarding the parameter as a list is harmless to the user code.
But unawareness of its type can be list can limit the potent of
the user code.
2016-12-28 18:14:57 +09:00
Raphael Deem
a4f77984b7 stop multiple worker server without sleep loop; issue #73 2016-12-26 14:51:16 -08:00
38elements
39b279f0f2 Improve examples/custom_protocol.py 2016-12-26 23:54:59 +09:00
38elements
ac44900fc4 Add test and example for custom protocol 2016-12-26 23:41:10 +09:00
Sean Parsons
548458c3e0 Added test for new token property on request object. 2016-12-26 06:48:53 -05:00
Sean Parsons
986b0aa106 Added token property to request object. 2016-12-26 06:41:41 -05:00
Hyunjun Kim
01b42fb399 Allow Sanic-inherited application 2016-12-26 20:37:16 +09:00
Eli Uriegas
f74d44152a Merge pull request #234 from seemethere/update_tests_to_use_tox
Change travis job to use tox
2016-12-25 19:10:36 -08:00
Eli Uriegas
1557854755 Update to make flake8 actually work 2016-12-25 19:05:11 -08:00
Eli Uriegas
56d6c2a929 Change travis job to use tox 2016-12-25 18:55:25 -08:00
Eli Uriegas
2d4512cd1c Merge branch 'master' into improved_config 2016-12-25 15:26:33 -08:00
Eli Uriegas
5402e6d3a6 Merge pull request #212 from r0fls/176
allow overriding logging basicConfig settings
2016-12-25 09:13:51 -08:00
Cadel Watson
52c59e7133 Fix all docstring errors.
When generating documentation with sphinx-apidoc, there are currently
many docstring errors, mostly minor. This commit fixes all errors.
2016-12-25 20:43:45 +11:00
Cadel Watson
7654c2f902 Use Sphinx for documentation.
This commit creates configuration files and an index page for
documentation using Sphinx. The recommonmark package is used to enable
Markdown support for Sphinx, using the Common Mark specification. This
means that the current documentation doesn't need to be rewritten.
2016-12-25 20:24:34 +11:00
Raphael Deem
be9eca2d63 use try/except 2016-12-24 23:05:03 -08:00
Sean Parsons
d5ad5e46da Update response docstrings to be explicit on whats returned. 2016-12-25 01:24:17 -05:00
Sean Parsons
a486fb99a9 Updated json function docstrings to be more consistent. 2016-12-25 01:06:40 -05:00
Sean Parsons
2b10860c32 Added docstrings to sanic.response.py for issue 41 2016-12-25 01:05:26 -05:00
Sean Parsons
a03f216f42 Added additional docstrings to blueprints.py 2016-12-25 00:47:51 -05:00
Raphael Deem
7d7cbaacf1 header format function 2016-12-24 20:56:13 -08:00
Raphael Deem
00b5a496dd type -> isinstance 2016-12-24 20:56:13 -08:00
Raphael Deem
7e6c92dc52 convert header values to strings 2016-12-24 20:56:13 -08:00
Eli Uriegas
6cf3754051 Merge pull request #226 from seemethere/increment_019
Increment version to 0.1.9
2016-12-24 18:54:07 -08:00
Eli Uriegas
cf7616ebe5 Increment version to 0.1.9 2016-12-24 18:51:16 -08:00
Eli Uriegas
5b7964f8b6 Merge pull request #225 from seemethere/add_response_body_not_a_string_test
Add test for PR: #215
2016-12-24 18:50:11 -08:00
Eli Uriegas
f1f38c24da Add test for PR: #215 2016-12-24 18:47:15 -08:00
Eli Uriegas
67a50becb0 Merge pull request #215 from cr0hn/patch-1
Improvement: avoid to encoding in each HTTP Response
2016-12-24 18:41:01 -08:00
Eli Uriegas
d7e94473f3 Use a try/except, it's a bit faster
Also reorder some imports and add some comments
2016-12-24 18:37:55 -08:00
Eli Uriegas
184c896f41 Merge pull request #224 from seemethere/rewrite_static_files_tests
Rewrite static files tests
2016-12-24 18:23:06 -08:00
Eli Uriegas
8be849cc40 Rewrite static files tests
Relates to PR #188

Changes include:
- Rewriting to work with pytest fixtures and an actual static directory
- Addition of a test that covers file paths that must be
  unquoted as a uri
2016-12-24 18:18:56 -08:00
Eli Uriegas
275851a755 Merge pull request #188 from webtic/master
Find URL encoded filenames on the fs by decoding them first
2016-12-24 18:14:45 -08:00
Eli Uriegas
16182472fa Remove trailing whitespace 2016-12-24 18:11:46 -08:00
Eli Uriegas
29f3c22fed Rework conditionals to not be inline 2016-12-24 18:11:12 -08:00
Eli Uriegas
a116666d55 Merge pull request #223 from r0fls/115
Raise error if response is malformed.
2016-12-24 17:12:17 -08:00
Raphael Deem
c2622511ce Raise error if response is malformed. Issue #115 2016-12-24 17:09:41 -08:00
Eli Uriegas
50243037eb Merge pull request #222 from seemethere/add_py36_testing
Adds python36 to tox.ini and .travis.yml
2016-12-24 14:11:55 -08:00
Eli Uriegas
74f305cfb7 Adds python36 to tox.ini and .travis.yml 2016-12-24 14:06:53 -08:00
Eli Uriegas
75990fbaf4 Merge pull request #220 from kgantsov/master
Make golang performance test return JSON instead of string
2016-12-24 13:40:24 -08:00
cr0hn
cc982c5a61 Update response.py
Type check by isinstance
2016-12-24 15:24:25 +01:00
38elements
2d05243c4a Refactor arguments of run function 2016-12-24 22:49:48 +09:00
Konstantin Hantsov
2f0a582aa7 Make golang performance test return JSON instead of string 2016-12-24 10:28:34 +01:00
Eli Uriegas
665881471d Merge pull request #217 from cr0hn/patch-3
Upgraded Middlewares doc: Explain how to chain two (or more) middlewares
2016-12-23 22:31:03 -08:00
38elements
39211f8fbd Refactor arguments of serve function 2016-12-24 11:40:07 +09:00
Raphael Deem
32ea45d403 allow overriding logging.basicConfig 2016-12-23 16:17:34 -08:00
Eli Uriegas
cd17a42234 Fix some verbage 2016-12-23 09:59:28 -08:00
Eli Uriegas
8e19b5938c Merge pull request #216 from cr0hn/patch-2
Apply response Middleware always
2016-12-23 09:57:38 -08:00
Eli Uriegas
9e208ab744 Merge pull request #210 from kdelwat/testing-documentation
Create documentation for testing server endpoints.
2016-12-23 09:57:22 -08:00
Eli Uriegas
94bd9702e5 Merge pull request #211 from rmno/master
Fixed import error in docs
2016-12-23 09:57:08 -08:00
cr0hn
3add40625d Explain how to chain two (or more) middlewares
A funny and useful examples about how to chain middlewares.
2016-12-23 16:07:59 +01:00
cr0hn
5afae986a0 Apply response Middleware always
Response middleware are useful to apply some post-process information, just before send to the user. For example: Add some HTTP headers (security headers, for example), remove "Server" banner (for security reasons) or cookie management. 

The change is very very simple: although an "request" middleware has produced any response, we'll even apply the response middlewares.
2016-12-23 15:59:04 +01:00
cr0hn
f091d82bad Improvement
improvement: support fo binary data as a input. This do that the response process has more performance because not encoding needed.
2016-12-23 13:12:59 +01:00
Stefano Palazzo
a73a7d1e7b Make it possible to disable the logo by subclassing Config 2016-12-23 11:42:00 +01:00
Romano Bodha
5c1ef2c1cf Fixed import error 2016-12-23 01:42:05 +01:00
Cadel Watson
8411255700 Create documentation for testing server endpoints.
Currently the sanic.utils functionality is undocumented. This provides
information on the interface as well as a complete example of testing
a server endpoint.
2016-12-23 11:08:04 +11:00
38elements
c657c531b4 Customizable protocol 2016-12-23 00:13:38 +09:00
Eli Uriegas
ef9d8710f5 Merge pull request #200 from 38elements/invalid-usage
Change HttpParserError process
2016-12-17 22:50:00 -06:00
38elements
75fc9f91b9 Change HttpParserError process 2016-12-18 09:25:39 +09:00
Tim Mundt
ef9edfd160 added documentation for configuration 2016-12-17 20:20:07 +01:00
Tim Mundt
234a7925c6 restored accidentally degraded doc string 2016-12-17 19:24:41 +01:00
Tim Mundt
a550b5c112 added tests and small fixes for config 2016-12-16 18:46:07 +01:00
Tim Mundt
04798cbf5b added methods to load config from a file 2016-12-16 17:05:09 +01:00
Eli Uriegas
545d9eb59b Merge pull request #198 from sagnew/patch-2
Fix quotes in sample code for consistency
2016-12-14 11:39:22 -06:00
Sam Agnew
a9b67c3028 Fix quotes in sample code for consistency 2016-12-14 12:36:33 -05:00
Eli Uriegas
35e79f3985 Merge pull request #171 from jpiasetz/convert_dict_to_set
Convert connections dict to set
2016-12-14 11:30:45 -06:00
Eli Uriegas
435d5585e9 Fix leftover blank line
flake8 build failed here: https://travis-ci.org/channelcat/sanic/builds/183991976
2016-12-14 11:29:09 -06:00
Eli Uriegas
ddfb7f2861 Merge branch 'master' into convert_dict_to_set 2016-12-14 11:26:31 -06:00
Eli Uriegas
6c806549ae Merge pull request #194 from sagnew/patch-1
Fix PEP8 in Hello World example
2016-12-13 11:44:53 -06:00
Sam Agnew
8957e4ec25 Fix PEP8 in Hello World example 2016-12-13 12:35:46 -05:00
Paul Jongsma
2003eceba1 remove trailing space 2016-12-13 10:41:39 +01:00
Eli Uriegas
8fc1462d11 Merge pull request #193 from r0fls/jinja-example
add jinja example
2016-12-13 00:30:07 -06:00
Raphael Deem
93b45e9598 add jinja example 2016-12-12 22:25:24 -08:00
Eli Uriegas
a3a14cdab2 Merge pull request #170 from jpiasetz/convert_lambda_to_partial
Convert server lambda to partial
2016-12-12 20:40:29 -06:00
Paul Jongsma
9ba2f99ea2 added a comment on why to decode the file_path 2016-12-13 01:10:24 +01:00
Eli Uriegas
f9db796a6e Merge pull request #189 from AntonDnepr/aiopg_examples
Aiopg examples
2016-12-12 13:22:32 -06:00
Eli Uriegas
94c7aaf7f8 Merge pull request #190 from kamyarg/master
url params docs typo fix
2016-12-12 13:21:46 -06:00
kamyar
6ef6d9a905 url params docs typo fix
add missing '>' in url params docs example
2016-12-11 16:34:22 +02:00
Anton Zhyrney
b44e9baaec aiopg with sqlalchemy example 2016-12-11 14:21:02 +02:00
Anton Zhyrney
f9176bfdea pep8&improvements 2016-12-11 14:14:03 +02:00
Anton Zhyrney
721044b378 improvements for aiopg example 2016-12-11 14:04:24 +02:00
Anton Zhyrney
154f8570f0 add sanic aiopg example with raw sql 2016-12-11 13:43:31 +02:00
Paul Jongsma
0464d31a9c Find URL encoded filenames on the fs by decoding them first 2016-12-10 12:16:37 +01:00
Eli Uriegas
e3453553e1 Merge pull request #183 from 38elements/payload-too-large
Change Payload Too Large process
2016-12-08 10:27:55 -06:00
Eli Uriegas
6abaa78f9e Merge pull request #186 from r0fls/master
return 400 on invalid json post data
2016-12-08 10:27:29 -06:00
Raphael Deem
457507d8dc return 400 on invalid json post data 2016-12-07 20:40:31 -08:00
Eli Uriegas
3ea1a80496 Merge pull request #185 from 1a23456789/master
Fix test_request_timeout.py
2016-12-06 16:04:15 -06:00
1a23456789
fac4bca4f4 Fix test_request_timeout.py
This increases sleep time, Because sometimes timeout error does not occur.
2016-12-06 10:44:08 +09:00
38elements
662e0c9965 Change Payload Too Large process
When Payload Too Large occurs, it uses error handler.
2016-12-04 10:50:32 +09:00
Eli Uriegas
80af9e6d76 Merge pull request #173 from jackfischer/master
fix for cookie header capitalization bug
2016-12-03 17:28:38 -06:00
Jack Fischer
9b466db5c9 test for http2 lowercase header cookies 2016-12-03 15:19:24 -05:00
Jack Fischer
c34427690a Merge branch 'master' of git://github.com/channelcat/sanic 2016-12-03 15:08:07 -05:00
Eli Uriegas
d8a974bb4f Merge pull request #175 from Derrreks/master
Improving comments
2016-12-02 20:07:28 -06:00
Eli Uriegas
98b08676e2 Merge pull request #177 from seemethere/increment_version
Increment version to 0.1.8
2016-11-29 15:59:48 -06:00
Eli Uriegas
39f3a63ced Increment version to 0.1.8 2016-11-29 15:59:03 -06:00
Eli Uriegas
89e2084489 Merge pull request #172 from 38elements/timeout
Change request timeout process
2016-11-29 15:56:07 -06:00
Derek Schuster
70c56b7db3 fixing line length 2016-11-28 14:22:07 -05:00
Derek Schuster
209b763302 fix typo 2016-11-28 14:05:47 -05:00
Derek Schuster
190b7a6076 improving comments and examples 2016-11-28 14:00:39 -05:00
Eli Uriegas
cce47a633a Merge pull request #167 from AntonDnepr/class-based-views
Class based views
2016-11-27 21:33:46 -06:00
Eli Uriegas
ec2330c42b Merge pull request #169 from jpiasetz/simplify_imports
Use explicit import for httptools
2016-11-27 20:02:57 -06:00
Jack Fischer
0c215685f2 refactoring cookies 2016-11-27 08:30:46 -05:00
Jack Fischer
d86ac5e3e0 fix for cookie header capitalization bug 2016-11-26 11:20:29 -05:00
38elements
ee89b6ad03 before process 2016-11-26 16:47:16 +09:00
38elements
a5e6d6d2e8 Use default error process 2016-11-26 16:02:44 +09:00
Anton Zhyrney
1eea1f5485 rename&remove redundant code 2016-11-26 08:45:08 +02:00
Anton Zhyrney
da4567eea5 changes in doc 2016-11-26 08:44:46 +02:00
38elements
9010a6573f Add status code 2016-11-26 15:21:57 +09:00
38elements
d8e480ab48 Change sleep time 2016-11-26 14:47:42 +09:00
38elements
0bd61f6a57 Use write_response 2016-11-26 14:14:30 +09:00
38elements
c01cbb3a8c Change Request timeout process
This add a request timeout exception.
It cancels task, when request is timeout.
2016-11-26 13:55:45 +09:00
John Piasetzki
0ca5c4eeff Use explicit import for httptools
Explicit importing the parser and the exception to save a name lookup.
2016-11-25 15:14:44 -05:00
John Piasetzki
47927608b2 Convert connections dict to set
Connections don't need to be a dict since the value is never used
2016-11-25 15:14:19 -05:00
John Piasetzki
13808bf282 Convert server lambda to partial
Partials are faster then lambdas for repeated calls.
2016-11-25 15:13:58 -05:00
Anton Zhyrney
c3c7964e2e pep8 fixes 2016-11-25 09:29:25 +02:00
Anton Zhyrney
fca0221d91 update readme 2016-11-25 09:14:37 +02:00
Anton Zhyrney
9f2d73e2f1 class based views implementation for sanic 2016-11-25 09:10:25 +02:00
Eli Uriegas
fc19f2ea34 Merge pull request #163 from channelcat/request-data-vars
Access Request like a dictionary

Closes #129 #132
2016-11-23 13:51:17 -06:00
Eli Uriegas
aa0f15fbb2 Adding a new line 2016-11-23 11:03:00 -06:00
Eli Uriegas
93f50b8ef7 Merge pull request #160 from jiajunhuang/log
fix the way using logging.exception
2016-11-21 10:37:10 -06:00
Eli Uriegas
7b85843363 Merge pull request #165 from abhishek7/master
Revised error message in server.py (Issue #157)
2016-11-21 10:17:02 -06:00
abhishek7
f7f578ed44 Fixed Exception error log on line 157 of server.py 2016-11-20 21:37:01 -05:00
Abhishek
de92603ccf Merge pull request #3 from channelcat/master
Updating base fork
2016-11-20 21:28:33 -05:00
Channel Cat
d02fffb6b8 Fixing import of CIMultiDict 2016-11-19 18:41:40 -08:00
Channel Cat
922c96e3c1 Updated test terminology 2016-11-19 18:26:03 -08:00
Channel Cat
993627ec44 Merged with master 2016-11-19 18:21:44 -08:00
Channel Cat
01681599ff Fixed new test error with aiohttp 2016-11-19 18:13:02 -08:00
Channel Cat
3ce6434532 Fix flake8 2016-11-19 18:04:35 -08:00
Channel Cat
a97e554f8f Added shared request data 2016-11-19 17:48:28 -08:00
Eli Uriegas
fd5a79a685 Merge pull request #162 from r0fls/ciheaders
Update headers to use CIMultiDict instead of dict.
2016-11-19 18:32:26 -06:00
Raphael Deem
635921adc7 Update headers to use CIMultiDict instead of dict 2016-11-19 16:09:38 -08:00
jiajunhuang
9eb4cecbc1 fix the way using logging.exception 2016-11-19 15:19:38 +08:00
Eli Uriegas
879b9a4a15 Merge pull request #159 from r0fls/namedefault
provide default app name
2016-11-18 20:47:50 -06:00
Raphael Deem
8be4dc8fb5 update readme example to use default 2016-11-18 17:22:24 -08:00
Raphael Deem
f16ea20de5 provide default app name 2016-11-18 17:16:48 -08:00
Eli Uriegas
c51b14856e Merge pull request #154 from jackfischer/master
Example for using error_handler
2016-11-16 13:03:20 -06:00
Eli Uriegas
88ee71c425 Merge pull request #155 from seemethere/fix_flake8_errors
Fix the flake8 error caused by new flake8 version
2016-11-16 12:58:27 -06:00
Eli Uriegas
edb12da154 Fix the flake8 error caused by new flake8 version 2016-11-16 12:55:13 -06:00
Jack Fischer
d9f6846c76 improved default handling 2016-11-16 07:55:54 -05:00
Jack Fischer
9e0747db15 Example for using error_handler 2016-11-15 19:37:40 -05:00
Eli Uriegas
ae3d33ad58 Merge pull request #149 from asvetlov/patch-1
aiohttp is slightly faster actually
2016-11-14 10:24:15 -06:00
Manuel Miranda
edb25f799d Caching example (#150)
* Caching example using aiocache

* Caching example using aiocache

* Added aiocache to requirements

* Fixed example with newest aiocache
2016-11-13 17:11:31 -06:00
Andrew Svetlov
0822674f70 aiohttp is slightly faster actually
Disabling access log increases RPS a lot
2016-11-11 22:36:49 +02:00
Eli Uriegas
49d004736a Merge pull request #148 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2016-11-10 22:12:09 -06:00
The Gitter Badger
695f8733bb Add Gitter badge 2016-11-11 04:11:07 +00:00
Eli Uriegas
b51af7f4bf Merge pull request #147 from webtic/master
Add the client address to the request header
2016-11-10 15:30:04 -06:00
Eli Uriegas
28ce2447ef Update variable name
Give `ra` a more explicit name
2016-11-10 15:28:16 -06:00
Eli Uriegas
42e3a50274 Merge pull request #145 from pahaz/fix-request-parse-multipart-form
Fix request parse multipart form
2016-11-10 09:00:06 -06:00
Paul Jongsma
8ebc92c236 pass flake8 tests 2016-11-10 13:09:37 +01:00
Paul Jongsma
b92e46df40 fix whitespace 2016-11-10 13:06:27 +01:00
Paul Jongsma
be5588d5d8 Add the client address to the request header 2016-11-10 12:53:00 +01:00
Pahaz Blinov
0d9fb2f927 docs(request): return value docstring 2016-11-09 18:04:15 +05:00
Pahaz Blinov
0e9819fba1 fix(request): parse_multipart_form should return RequestParameters
I have this code:

```
form = FileForm(request.files)
```

and it raise error because the `request.files` is `dict` but `RequestParameters` is expected =/
2016-11-09 00:36:37 +05:00
Pahaz Blinov
aaee40aabd Merge pull request #6 from channelcat/master
fix(request.py): problem in case of request without content-type head…
2016-11-09 00:30:39 +05:00
Pahaz Blinov
5efe51b661 fix(request.py): problem in case of request without content-type header (#142)
* fix(request.py): exception if access request.form on GET request

* fix(request): just make a unification (parsed_form and parsed_files) + RFC fixes

parsed_form and parsed_files must be a RequestParameters type in all cases!
2016-11-07 15:27:50 -06:00
Pahaz Blinov
50f63142db Merge pull request #4 from channelcat/master
pull from master
2016-11-06 21:35:20 +05:00
Pahaz Blinov
1b65b2e0c6 fix(blueprints): @middleware IndexError (#139) 2016-11-06 10:08:55 -06:00
Manuel Miranda
ce8742c605 Caching example using aiocache (#140)
* Keep-alive requests stay open if communicating

* time.time faster than loop.time?

* Fix flake8

* Add aiofiles to requirements.txt

* Caching example using aiocache

* Caching example using aiocache

* Added aiocache to requirements
2016-11-06 09:26:15 -06:00
Eli Uriegas
01a013b48a Merge pull request #141 from seemethere/fix_query_tests
Fix value error for query string test
2016-11-05 12:16:13 -06:00
Eli Uriegas
3a2eeb9709 Fix value error for query string test 2016-11-05 13:12:55 -05:00
Eli Uriegas
1271c7d958 Merge pull request #128 from channelcat/keep-alive-timeout-fix
Keep alive timeout fix
2016-11-05 12:11:41 -06:00
Eli Uriegas
0032f525ce Merge pull request #138 from seemethere/add_aiofiles_requirements
Add aiofiles to requirements.txt
2016-11-05 12:11:08 -06:00
Eli Uriegas
df2f91b82f Add aiofiles to requirements.txt 2016-11-03 09:35:14 -05:00
Eli Uriegas
3a1ef6bef2 Merge pull request #125 from clenimar/fix-comments
Fix comments over-indentation
2016-11-03 08:27:39 -06:00
Eli Uriegas
28488075b9 Merge pull request #137 from imbolc/upload-without-content-type
Fix upload without content-type
2016-11-03 08:26:05 -06:00
imbolc
3cd3b2d9b7 Fix upload without content-type 2016-11-03 12:34:55 +07:00
Channel Cat
3d88818841 Merge pull request #135 from Sharpek/master
Add loop kwargs to sanic_endpoint_test
2016-11-02 21:37:52 -07:00
Channel Cat
b74cf65eca Merge pull request #134 from RyanKung/patch-1
Update README.md
2016-11-02 21:28:36 -07:00
Marcin Baran
80fcacaf8b Add loop kwargs to sanic_endpoint_test 2016-11-02 12:27:58 +01:00
Ryan Kung
96fcd8443f Update README.md
via flake8
2016-11-01 14:35:06 +08:00
Channel Cat
707c55fbe7 Fix flake8 2016-10-28 03:35:30 -07:00
Channel Cat
c44b5551bc time.time faster than loop.time? 2016-10-28 03:13:03 -07:00
Channel Cat
bd28da0abc Keep-alive requests stay open if communicating 2016-10-28 02:56:32 -07:00
Abhishek
410299f5a1 Merge pull request #2 from channelcat/master
Updates from Oct 17th
2016-10-27 20:06:56 -04:00
Clenimar Filemon
f3fc958a0c Fix comments over-indentation 2016-10-27 11:09:36 -03:00
Channel Cat
47b417db28 Merge pull request #119 from jackfischer/master
Add example with async http requests (Issue #96)
2016-10-26 22:43:01 -07:00
Jack Fischer
5171cdd305 add example with async http requests 2016-10-26 16:53:34 -04:00
Channel Cat
65950250d9 Merge pull request #111 from channelcat/reverse-static
Reverse static arguments
2016-10-25 02:52:21 -07:00
Channel Cat
74ae0007d3 Reverse static arguments 2016-10-25 02:45:28 -07:00
Channel Cat
977081f4af Merge pull request #110 from channelcat/cookies-lazy-creation
Lazy cookie creation
2016-10-25 01:50:33 -07:00
Channel Cat
ee70f1e55e Upped to version 0.1.6 2016-10-25 01:49:43 -07:00
Channel Cat
9c16f6dbea Fix flake8 issues 2016-10-25 01:36:12 -07:00
Channel Cat
c50aa34dd9 Lazy cookie creation 2016-10-25 01:27:54 -07:00
Channel Cat
0e479d53da Merge pull request #104 from channelcat/pr/101
Static file support
2016-10-24 22:42:01 -07:00
Channel Cat
984c086296 Merge pull request #105 from channelcat/blueprint-ordering
Added blueprint order test and used deques to add blueprints
2016-10-24 02:45:31 -07:00
Channel Cat
53e00b2b4c Added blueprint order test and used deques to add blueprints 2016-10-24 02:09:07 -07:00
Channel Cat
bb1cb29edd Merge pull request #103 from pcdinh/master
Made Pylint happy: clean up some unused variables
2016-10-24 01:22:06 -07:00
Channel Cat
bf6879e46f Made static file serving part of Sanic
Added sanic.static, blueprint.static, documentation, and testing
2016-10-24 01:21:06 -07:00
Channel Cat
12e900e8f9 Merge pull request #100 from chhsiao90/test-router
Add test for method not allow situation
2016-10-23 21:15:31 -07:00
imbolc
d7fff12b71 Static middleware 2016-10-24 02:17:03 +07:00
chhsiao90
9051e985a0 Add test for method not allow situation 2016-10-23 21:58:57 +08:00
pcdinh
5361c6f243 e is an unused variable. Safe to remove 2016-10-23 19:38:28 +07:00
pcdinh
963aef19e0 w is unused variable to it is safe to suppress Pylint warning using _
(underscore)
2016-10-23 19:36:08 +07:00
Channel Cat
201e232a0d Releasing 0.1.5 2016-10-23 03:43:01 -07:00
Channel Cat
6a71ea50bd Merge pull request #99 from channelcat/fix-incomplete-body
Fix incomplete request body being read
2016-10-23 03:35:23 -07:00
Channel Cat
47ec026536 Fix incomplete request body being read 2016-10-23 03:30:13 -07:00
Channel Cat
e70263d012 Merge pull request #87 from channelcat/blueprint-extras
Blueprint start/stop listeners + ordering
2016-10-23 02:04:55 -07:00
Channel Cat
658ced9188 Merge pull request #98 from channelcat/cookies
Adding cookie capabilities for issue #74
2016-10-23 02:04:30 -07:00
Channel Cat
23290b8627 Merge pull request #95 from narzeja/example_peewee_async
Provide example of using peewee_async with Sanic
2016-10-23 02:04:02 -07:00
Channel Cat
41ea40fc35 increased server event handler type flexibility 2016-10-23 01:51:46 -07:00
Channel Cat
3802141007 Adding cookie capabilities for issue #74 2016-10-23 01:32:16 -07:00
Channel Cat
50ae2048cc Merge pull request #93 from rogererens/patch-1
Fix typos
2016-10-22 03:07:14 -07:00
Channel Cat
b21ab3db12 Merge pull request #91 from pcdinh/master
Document `request.body` as a way to get raw POST body
2016-10-22 03:04:34 -07:00
Channel Cat
c80abb8cad Merge pull request #94 from narzeja/bugfix_missing_req_bp_doc
Simple blueprint was missing the 'request' parameter
2016-10-22 02:54:21 -07:00
pcdinh
a3bd1eaeab Merge branch 'master' of https://github.com/pcdinh/sanic 2016-10-22 14:29:20 +07:00
narzeja
be0739614d better get example 2016-10-22 08:52:37 +02:00
narzeja
b048f1bad3 better POST example 2016-10-22 08:50:56 +02:00
narzeja
c3628407eb post method doc 2016-10-22 08:48:19 +02:00
narzeja
96c13fe23c post method requires 'GET' 2016-10-22 08:47:51 +02:00
narzeja
ac9770dd89 a bit more informative return value when posting 2016-10-22 08:46:26 +02:00
narzeja
0e2c092ce3 fix method naming conflict 2016-10-22 08:40:24 +02:00
narzeja
22876b31b1 Provide example of using peewee_async with Sanic 2016-10-22 08:36:46 +02:00
narzeja
113047d450 Simple blueprint was missing the 'request' parameter 2016-10-22 07:13:14 +02:00
Roger Erens
268a87e3b4 Fix typos
I guess renaming was forgotten in a copy-n-paste frenzy?!
2016-10-21 23:47:13 +02:00
pcdinh
452764a8eb Document request.body as a way to get raw POST body 2016-10-22 01:35:38 +07:00
Channel Cat
f540f1e7c4 reverting reverted change 2016-10-21 04:32:05 -07:00
Channel Cat
9b561e83e3 Revert "."
This reverts commit 77c69e3810.
2016-10-21 04:14:50 -07:00
Channel Cat
77c69e3810 . 2016-10-21 04:11:40 -07:00
Channel Cat
a5614f6880 Added server start/stop listeners and reverse ordering on response middleware to blueprints 2016-10-21 04:11:18 -07:00
Channel Cat
b74d312c57 Merge pull request #84 from channelcat/update-changelog
Moved changelog and posted new benchmarks in readme
2016-10-21 04:01:27 -07:00
pcdinh
2312a176fe Document request.body as a way to get raw POST body 2016-10-21 17:55:30 +07:00
Channel Cat
e060dbfec8 Moved changelog and posted new benchmarks in readme 2016-10-21 00:11:52 -07:00
Channel Cat
8f6e5a1263 Merge pull request #82 from htkm/81
Content Type of JSON response should not have a charset
2016-10-20 21:45:43 -07:00
Hyungtae Kim
c256825de6 Content Type of JSON response should not have a charset 2016-10-20 13:38:03 -07:00
Channel Cat
cab43503d0 Merge branch 'jpiasetz-fast_router' 2016-10-20 11:34:28 +00:00
Channel Cat
d4e2d94816 Added support for routes with / in custom regexes and updated lru to use url and method 2016-10-20 11:33:28 +00:00
John Piasetzki
f510550888 Fix flake8 2016-10-20 01:37:12 -04:00
John Piasetzki
fc4c192237 Add simple uri hash to lookup 2016-10-20 01:29:22 -04:00
John Piasetzki
f4b45deb7f Convert dict to set 2016-10-20 00:28:05 -04:00
John Piasetzki
d1beabfc8f Add lru_cache to get 2016-10-20 00:28:05 -04:00
John Piasetzki
baf1ce95b1 Refactor get 2016-10-20 00:28:05 -04:00
John Piasetzki
e25e1c0e4b Convert string formats 2016-10-20 00:28:05 -04:00
John Piasetzki
04a6cc9416 Refactor add parameter 2016-10-20 00:28:05 -04:00
John Piasetzki
50e4dd167e Extract constant 2016-10-19 23:43:31 -04:00
John Piasetzki
f2cc404d7f Remove simple router 2016-10-19 23:41:22 -04:00
Channel Cat
f6a8dbf486 Merge pull request #79 from Eyepea/master
Enable after_start and before_stop callbacks for multiprocess
2016-10-19 17:17:04 -07:00
Ludovic Gasc (GMLudo)
7dcdc6208d Enable after_start and before_stop callbacks for multiprocess 2016-10-20 01:01:51 +02:00
Channel Cat
f5569f1723 Merge pull request #71 from channelcat/tornado-results
Added tornado benchmarks
2016-10-19 01:47:25 -07:00
Channel Cat
0327e6efba Added tornado benchmarks 2016-10-19 01:47:12 -07:00
Ubuntu
138b947b95 Merge branch 'mikoim-feature/statuscode' 2016-10-19 08:37:56 +00:00
Ubuntu
3d00ca09b9 Added fast lookup dict for common response codes 2016-10-19 08:37:35 +00:00
Ubuntu
69345272cd Merge branch 'feature/statuscode' of https://github.com/mikoim/sanic into mikoim-feature/statuscode 2016-10-19 08:26:11 +00:00
Channel Cat
b6a06afdc0 Merge pull request #63 from blakev/feature/performance-tornado
Adds `tornado` test server for speed comparison (#13)
2016-10-19 01:21:56 -07:00
Channel Cat
2903e7ee7c Merge pull request #65 from blakev/feature/expose-loop
Exposes `loop`in sanic `serve` and `run` functions (#64)
2016-10-19 01:21:15 -07:00
Channel Cat
d5e4355a1c Merge pull request #70 from mikoim/feature/tests
Added tests for Request.form
2016-10-19 01:20:35 -07:00
Eshin Kunishima
6d2d9d3afc Added tests for Request.form 2016-10-19 16:29:40 +09:00
Channel Cat
71a783e7e1 Merge pull request #59 from yishibashi/comment-fix
comment fixed
2016-10-18 21:14:10 -07:00
Channel Cat
a6fa496c30 Merge pull request #60 from kylefrost/master
Fix routing doc typo
2016-10-18 21:13:26 -07:00
Channel Cat
f34fa40ed2 Merge pull request #68 from channelcat/server-start-exception
Changed start failure to print exception
2016-10-18 16:51:58 -07:00
Channel Cat
c58741fe7a Changed start failure to print exception 2016-10-18 16:50:14 -07:00
Eshin Kunishima
7b0f524fb3 Added HTTP status codes
Based on http.HTTPStatus
2016-10-19 01:53:11 +09:00
Blake VandeMerwe
5e459cb69d Exposes loopin sanic serve and run functions (#64) 2016-10-18 10:05:29 -06:00
Blake VandeMerwe
cbb1f99ccb Adds tornado test server for speed comparison (#13) 2016-10-18 09:41:45 -06:00
Kyle Frost
3c05382e07 Fix routing doc typo 2016-10-18 08:13:37 -04:00
yishibashi
7c3faea0dd comment fixed 2016-10-18 19:32:47 +09:00
Channel Cat
452438dc07 Delete test.py, not needed 2016-10-18 02:52:35 -07:00
Abhishek
f95fe4192b Merge pull request #1 from channelcat/master
Updating the fork
2016-10-17 19:36:13 -04:00
132 changed files with 9200 additions and 1329 deletions

View File

@@ -1,7 +1,7 @@
[run]
branch = True
source = sanic, tests
omit = site-packages
source = sanic
omit = site-packages, sanic/utils.py
[html]
directory = coverage
directory = coverage

7
.gitignore vendored
View File

@@ -1,11 +1,16 @@
*~
*.egg-info
*.egg
*.eggs
*.pyc
.coverage
.coverage.*
coverage
.tox
settings.py
*.pyc
.idea/*
.cache/*
.python-version
docs/_build/
docs/_api/
build/*

View File

@@ -1,18 +1,14 @@
sudo: false
language: python
python:
- '3.5'
install:
- pip install -r requirements.txt
- pip install -r requirements-dev.txt
- python setup.py install
- pip install flake8
- pip install pytest
before_script: flake8 sanic
script: py.test -v tests
- '3.5'
- '3.6'
install: pip install tox-travis
script: tox
deploy:
provider: pypi
user: channelcat
password:
secure: jH4+Di2/qcBwWVhI5/3NYd/JuDDgf5/cF85h+oQnAjgwP6me3th9RS0PHL2gjKJrmyRgwrW7a3eSAityo5sQSlBloQCNrtCE30rkDiwtgoIxDW72NR/nE8nUkS9Utgy87eS+3B4NrO7ag4GTqO5ET8SQ4/MCiQwyUQATLXj2s2eTpQvqJeZG6YgoeFAOYvlR580yznXoOwldWlkiymJiWSdR/01lthtWCi40sYC/QoU7psODJ/tPcsqgQtQKyUVsci7mKvp3Y8ImkoO/POM01jYNsS9qLh5pKTNCEYxtyzC77whenCNHn7WReVidd56g1ADosbNo4yY/1D3VAvwjUnkQ0SzdBQfT7IIzccEuC0j1NXKPN97OX0a6XzyUMYJ1XiU3juTJOPxdYBPbsDM3imQiwrOh1faIf0HCgNTN+Lxe5l8obCH7kffNcVUhs2zI0+2t4MS5tjb/OVuYD/TFn+bM33DqzLctTOK/pGn6xefzZcdzb191LPo99Lof+4fo6jNUpb0UmcBu5ZJzxh0lGe8FPIK3UAG/hrYDDgjx8s8RtUJjcEUQz0659XffYx7DLlgHO7cWyfjrHD3yrLzDbYr5mAS4FR+4D917V7UL+on4SsKHN00UuMGPguqSYo/xYyPLnJU5XK0du4MIpsNMB8TtrJOIewOOfD32+AisPQ8=
secure: OgADRQH3+dTL5swGzXkeRJDNbLpFzwqYnXB4iLD0Npvzj9QnKyQVvkbaeq6VmV9dpEFb5ULaAKYQq19CrXYDm28yanUSn6jdJ4SukaHusi7xt07U6H7pmoX/uZ2WZYqCSLM8cSp8TXY/3oV3rY5Jfj/AibE5XTbim5/lrhsvW6NR+ALzxc0URRPAHDZEPpojTCjSTjpY0aDsaKWg4mXVRMFfY3O68j6KaIoukIZLuoHfePLKrbZxaPG5VxNhMHEaICdxVxE/dO+7pQmQxXuIsEOHK1QiVJ9YrSGcNqgEqhN36kYP8dqMeVB07sv8Xa6o/Uax2/wXS2HEJvuwP1YD6WkoZuo9ZB85bcMdg7BV9jJDbVFVPJwc75BnTLHrMa3Q1KrRlKRDBUXBUsQivPuWhFNwUgvEayq2qSI3aRQR4Z0O+DfboEhXYojSoD64/EWBTZ7vhgbvOTGEdukUQSYrKj9P8jc1s8exomTsAiqdFxTUpzfiammUSL+M93lP4urtahl1jjXFX7gd3DzdEEb0NsGkx5lm/qdsty8/TeAvKUmC+RVU6T856W6MqN0P+yGbpWUARcSE7fwztC3SPxwAuxvIN3BHmRhOUHoORPNG2VpfbnscIzBKJR4v0JKzbpi0IDa66K+tCGsCEvQuL4cxVOtoUySPWNSUAyUWWUrGM2k=
on:
tags: true

22
CHANGELOG.md Normal file
View File

@@ -0,0 +1,22 @@
Version 0.1
-----------
- 0.1.7
- Reversed static url and directory arguments to meet spec
- 0.1.6
- Static files
- Lazy Cookie Loading
- 0.1.5
- Cookies
- Blueprint listeners and ordering
- Faster Router
- Fix: Incomplete file reads on medium+ sized post requests
- Breaking: after_start and before_stop now pass sanic as their first argument
- 0.1.4
- Multiprocessing
- 0.1.3
- Blueprint support
- Faster Response processing
- 0.1.1 - 0.1.2
- Struggling to update pypi via CI
- 0.1.0
- Released to public

View File

@@ -1,7 +0,0 @@
Version 0.1
-----------
- 0.1.4 - Multiprocessing
- 0.1.3 - Blueprint support
- 0.1.1 - 0.1.2 - Struggling to update pypi via CI
Released to public.

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM python:3.6
ADD . /app
WORKDIR /app
RUN pip install tox

4
Makefile Normal file
View File

@@ -0,0 +1,4 @@
test:
find . -name "*.pyc" -delete
docker build -t sanic/test-image .
docker run -t sanic/test-image tox

View File

@@ -1,85 +0,0 @@
# Sanic
[![Build Status](https://travis-ci.org/channelcat/sanic.svg?branch=master)](https://travis-ci.org/channelcat/sanic)
[![PyPI](https://img.shields.io/pypi/v/sanic.svg)](https://pypi.python.org/pypi/sanic/)
[![PyPI](https://img.shields.io/pypi/pyversions/sanic.svg)](https://pypi.python.org/pypi/sanic/)
Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by this article: https://magic.io/blog/uvloop-blazing-fast-python-networking/.
On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.
## Benchmarks
All tests were run on an AWS medium instance running ubuntu, using 1 process. Each script delivered a small JSON response and was tested with wrk using 100 connections. Pypy was tested for Falcon and Flask but did not speed up requests.
| Server | Implementation | Requests/sec | Avg Latency |
| ------- | ------------------- | ------------:| -----------:|
| Sanic | Python 3.5 + uvloop | 30,601 | 3.23ms |
| Wheezy | gunicorn + meinheld | 20,244 | 4.94ms |
| Falcon | gunicorn + meinheld | 18,972 | 5.27ms |
| Bottle | gunicorn + meinheld | 13,596 | 7.36ms |
| Flask | gunicorn + meinheld | 4,988 | 20.08ms |
| Kyoukai | Python 3.5 + uvloop | 3,889 | 27.44ms |
| Aiohttp | Python 3.5 + uvloop | 2,979 | 33.42ms |
## Hello World
```python
from sanic import Sanic
from sanic.response import json
app = Sanic(__name__)
@app.route("/")
async def test(request):
return json({ "hello": "world" })
app.run(host="0.0.0.0", port=8000)
```
## Installation
* `python -m pip install sanic`
## Documentation
* [Getting started](docs/getting_started.md)
* [Request Data](docs/request_data.md)
* [Routing](docs/routing.md)
* [Middleware](docs/middleware.md)
* [Exceptions](docs/exceptions.md)
* [Blueprints](docs/blueprints.md)
* [Deploying](docs/deploying.md)
* [Contributing](docs/contributing.md)
* [License](LICENSE)
## TODO:
* Streamed file processing
* File output
* Examples of integrations with 3rd-party modules
* RESTful router
## Limitations:
* No wheels for uvloop and httptools on Windows :(
## Final Thoughts:
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \
▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! |
▀▀█████▄▄ ▀██████▄██ | _________________/
▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
▀▀▀▄ ▀▀███ ▀ ▄▄
▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
██▀▄▄▄██▀▄███▀ ▀▀████ ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀
▌ ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
▀▀█████████▀
▄▄██▀██████▀█
▄██▀ ▀▀▀ █
▄█ ▐▌
▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀

116
README.rst Normal file
View File

@@ -0,0 +1,116 @@
Sanic
=================================
|Join the chat at https://gitter.im/sanic-python/Lobby| |Build Status| |PyPI| |PyPI version|
Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by `this article <https://magic.io/blog/uvloop-blazing-fast-python-networking/>`_.
On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.
Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contributions are welcome!
Benchmarks
----------
All tests were run on an AWS medium instance running ubuntu, using 1
process. Each script delivered a small JSON response and was tested with
wrk using 100 connections. Pypy was tested for Falcon and Flask but did
not speed up requests.
+-----------+-----------------------+----------------+---------------+
| Server | Implementation | Requests/sec | Avg Latency |
+===========+=======================+================+===============+
| Sanic | Python 3.5 + uvloop | 33,342 | 2.96ms |
+-----------+-----------------------+----------------+---------------+
| Wheezy | gunicorn + meinheld | 20,244 | 4.94ms |
+-----------+-----------------------+----------------+---------------+
| Falcon | gunicorn + meinheld | 18,972 | 5.27ms |
+-----------+-----------------------+----------------+---------------+
| Bottle | gunicorn + meinheld | 13,596 | 7.36ms |
+-----------+-----------------------+----------------+---------------+
| Flask | gunicorn + meinheld | 4,988 | 20.08ms |
+-----------+-----------------------+----------------+---------------+
| Kyoukai | Python 3.5 + uvloop | 3,889 | 27.44ms |
+-----------+-----------------------+----------------+---------------+
| Aiohttp | Python 3.5 + uvloop | 2,979 | 33.42ms |
+-----------+-----------------------+----------------+---------------+
| Tornado | Python 3.5 | 2,138 | 46.66ms |
+-----------+-----------------------+----------------+---------------+
Hello World Example
-------------------
.. code:: python
from sanic import Sanic
from sanic.response import json
app = Sanic()
@app.route("/")
async def test(request):
return json({"hello": "world"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Installation
------------
- ``python -m pip install sanic``
To install sanic without uvloop or json using bash, you can provide either or both of these environmental variables
using any truthy string like `'y', 'yes', 't', 'true', 'on', '1'` and setting the NO_X to true will stop that features
installation.
- ``SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true python -m pip install sanic``
Documentation
-------------
`Documentation on Readthedocs <http://sanic.readthedocs.io/>`_.
.. |Join the chat at https://gitter.im/sanic-python/Lobby| image:: https://badges.gitter.im/sanic-python/Lobby.svg
:target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |Build Status| image:: https://travis-ci.org/channelcat/sanic.svg?branch=master
:target: https://travis-ci.org/channelcat/sanic
.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest
:target: http://sanic.readthedocs.io/en/latest/?badge=latest
.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg
:target: https://pypi.python.org/pypi/sanic/
.. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg
:target: https://pypi.python.org/pypi/sanic/
TODO
----
* Streamed file processing
* http2
Limitations
-----------
* No wheels for uvloop and httptools on Windows :(
Final Thoughts
--------------
::
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \
▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Gotta go fast! |
▀▀█████▄▄ ▀██████▄██ | _________________/
▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
▀▀▀▄ ▀▀███ ▀ ▄▄
▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
██▀▄▄▄██▀▄███▀ ▀▀████ ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀
▌ ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
▀▀█████████▀
▄▄██▀██████▀█
▄██▀ ▀▀▀ █
▄█ ▐▌
▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀

20
docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Sanic
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -1,82 +0,0 @@
# Blueprints
Blueprints are objects that can be used for sub-routing within an application.
Instead of adding routes to the application object, blueprints define similar
methods for adding routes, which are then registered with the application in a
flexible and pluggable manner.
## Why?
Blueprints are especially useful for larger applications, where your application
logic can be broken down into several groups or areas of responsibility.
It is also useful for API versioning, where one blueprint may point at
`/v1/<routes>`, and another pointing at `/v2/<routes>`.
## My First Blueprint
The following shows a very simple blueprint that registers a handler-function at
the root `/` of your application.
Suppose you save this file as `my_blueprint.py`, this can be imported in your
main application later.
```python
from sanic.response import json
from sanic import Blueprint
bp = Blueprint('my_blueprint')
@bp.route('/')
async def bp_root():
return json({'my': 'blueprint'})
```
## Registering Blueprints
Blueprints must be registered with the application.
```python
from sanic import Sanic
from my_blueprint import bp
app = Sanic(__name__)
app.register_blueprint(bp)
app.run(host='0.0.0.0', port=8000, debug=True)
```
This will add the blueprint to the application and register any routes defined
by that blueprint.
In this example, the registered routes in the `app.router` will look like:
```python
[Route(handler=<function bp_root at 0x7f908382f9d8>, methods=None, pattern=re.compile('^/$'), parameters=[])]
```
## Middleware
Using blueprints allows you to also register middleware globally.
```python
@bp.middleware
async def halt_request(request):
print("I am a spy")
@bp.middleware('request')
async def halt_request(request):
return text('I halted the request')
@bp.middleware('response')
async def halt_response(request, response):
return text('I halted the response')
```
## Exceptions
Exceptions can also be applied exclusively to blueprints globally.
```python
@bp.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```

142
docs/conf.py Normal file
View File

@@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Sanic documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 25 18:07:21 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
import os
import sys
# Add support for Markdown documentation using Recommonmark
from recommonmark.parser import CommonMarkParser
# Ensure that sanic is present in the path, to allow sphinx-apidoc to
# autogenerate documentation from docstrings
root_directory = os.path.dirname(os.getcwd())
sys.path.insert(0, root_directory)
import sanic
# -- General configuration ------------------------------------------------
extensions = ['sphinx.ext.autodoc']
templates_path = ['_templates']
# Enable support for both Restructured Text and Markdown
source_parsers = {'.md': CommonMarkParser}
source_suffix = ['.rst', '.md']
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Sanic'
copyright = '2016, Sanic contributors'
author = 'Sanic contributors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = sanic.__version__
# The full version, including alpha/beta/rc tags.
release = sanic.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
#
# modules.rst is generated by sphinx-apidoc but is unused. This suppresses
# a warning about it.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'modules.rst']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Sanicdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [(master_doc, 'Sanic.tex', 'Sanic Documentation',
'Sanic contributors', 'manual'), ]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, 'sanic', 'Sanic Documentation', [author], 1)]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Sanic', 'Sanic Documentation', author, 'Sanic',
'One line description of project.', 'Miscellaneous'),
]
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Custom Settings -------------------------------------------------------
suppress_warnings = ['image.nonlocal_uri']

View File

@@ -1,10 +0,0 @@
# How to contribute to Sanic
Thank you for your interest!
## Running tests
* `python -m pip install pytest`
* `python -m pytest tests`
## 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.

View File

@@ -1,35 +0,0 @@
# Deploying
When it comes to deploying Sanic, there's not much to it, but there are
a few things to take note of.
## Workers
By default, Sanic listens in the main process using only 1 CPU core.
To crank up the juice, just specify the number of workers in the run
arguments like so:
```python
app.run(host='0.0.0.0', port=1337, workers=4)
```
Sanic will automatically spin up multiple processes and route
traffic between them. We recommend as many workers as you have
available cores.
## Running via Command
If you like using command line arguments, you can launch a sanic server
by executing the module. For example, if you initialized sanic as
app in a file named server.py, you could run the server like so:
`python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4`
With this way of running sanic, it is not necessary to run app.run in
your python file. If you do, just make sure you wrap it in name == main
like so:
```python
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, workers=4)
```

View File

@@ -1,28 +0,0 @@
# Exceptions
Exceptions can be thrown from within request handlers and will automatically be handled by Sanic. Exceptions take a message as their first argument, and can also take a status_code to be passed back in the HTTP response. Check sanic.exceptions for the full list of exceptions to throw.
## Throwing an exception
```python
from sanic import Sanic
from sanic.exceptions import ServerError
@app.route('/killme')
def i_am_ready_to_die(request):
raise ServerError("Something bad happened")
```
## Handling Exceptions
Just use the @exception decorator. The decorator expects a list of exceptions to handle as arguments. You can pass SanicException to catch them all! The exception handler must expect a request and exception object as arguments.
```python
from sanic import Sanic
from sanic.response import text
from sanic.exceptions import NotFound
@app.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```

View File

@@ -1,25 +0,0 @@
# Getting Started
Make sure you have pip and python 3.5 before starting
## Benchmarks
* Install Sanic
* `python3 -m pip install sanic`
* Edit main.py to include:
```python
from sanic import Sanic
from sanic.response import json
app = Sanic(__name__)
@app.route("/")
async def test(request):
return json({ "hello": "world" })
app.run(host="0.0.0.0", port=8000, debug=True)
```
* Run `python3 main.py`
You now have a working Sanic server! To continue on, check out:
* [Request Data](request_data.md)
* [Routing](routing.md)

35
docs/index.rst Normal file
View File

@@ -0,0 +1,35 @@
.. include:: sanic/index.rst
Guides
======
.. toctree::
:maxdepth: 2
sanic/getting_started
sanic/routing
sanic/request_data
sanic/response
sanic/static_files
sanic/exceptions
sanic/middleware
sanic/blueprints
sanic/config
sanic/cookies
sanic/streaming
sanic/class_based_views
sanic/custom_protocol
sanic/ssl
sanic/testing
sanic/deploying
sanic/extensions
sanic/contributing
Module Documentation
====================
.. toctree::
* :ref:`genindex`
* :ref:`search`

36
docs/make.bat Normal file
View File

@@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=Sanic
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

View File

@@ -1,29 +0,0 @@
# Middleware
Middleware can be executed before or after requests. It is executed in the order it was registered. If middleware returns a response object, the request will stop processing and a response will be returned.
Middleware is registered via the middleware decorator, and can either be added as 'request' or 'response' middleware, based on the argument provided in the decorator. Response middleware receives both the request and the response as arguments.
## Examples
```python
app = Sanic(__name__)
@app.middleware
async def halt_request(request):
print("I am a spy")
@app.middleware('request')
async def halt_request(request):
return text('I halted the request')
@app.middleware('response')
async def halt_response(request, response):
return text('I halted the response')
@app.route('/')
async def handler(request):
return text('I would like to speak now please')
app.run(host="0.0.0.0", port=8000)
```

View File

@@ -1,43 +0,0 @@
# Request Data
## Properties
The following request variables are accessible as properties:
`request.files` (dictionary of File objects) - List of files that have a name, body, and type
`request.json` (any) - JSON body
`request.args` (dict) - Query String variables. Use getlist to get multiple of the same name
`request.form` (dict) - Posted form variables. Use getlist to get multiple of the same name
See request.py for more information
## Examples
```python
from sanic import Sanic
from sanic.response import json
@app.route("/json")
def post_json(request):
return 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') })
@app.route("/files")
def post_json(request):
test_file = request.files.get('test')
file_parameters = {
'body': test_file.body,
'name': test_file.name,
'type': test_file.type,
}
return json({ "received": True, "file_names": request.files.keys(), "test_file_parameters": file_parameters })
@app.route("/query_string")
def query_string(request):
return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
```

View File

@@ -1,32 +0,0 @@
# Routing
Sanic comes with a basic router that supports request parameters. To specify a parameter, surround it with carrots like so: `<PARAM>`. Request parameters will be passed to the request handler functions as keyword arguments. To specify a type, add a :type after the parameter name, in the carrots. If the parameter does not match the type supplied, Sanic will throw a NotFound exception, resulting in a 404 page not found error.
## Examples
```python
from sanic import Sanic
from sanic.response import text
@app.route('/tag/<tag>')
async def person_handler(request, tag):
return text('Tag - {}'.format(tag))
@app.route('/number/<integer_arg:int>')
async def person_handler(request, integer_arg):
return text('Integer - {}'.format(integer_arg))
@app.route('/number/<number_arg:number>')
async def person_handler(request, number_arg):
return text('Number - {}'.format(number))
@app.route('/person/<name:[A-z]>')
async def person_handler(request, name):
return text('Person - {}'.format(name))
@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
async def folder_handler(request, folder_id):
return text('Folder - {}'.format(folder_id))
```

184
docs/sanic/blueprints.md Normal file
View File

@@ -0,0 +1,184 @@
# Blueprints
Blueprints are objects that can be used for sub-routing within an application.
Instead of adding routes to the application instance, blueprints define similar
methods for adding routes, which are then registered with the application in a
flexible and pluggable manner.
Blueprints are especially useful for larger applications, where your
application logic can be broken down into several groups or areas of
responsibility.
## My First Blueprint
The following shows a very simple blueprint that registers a handler-function at
the root `/` of your application.
Suppose you save this file as `my_blueprint.py`, which can be imported into your
main application later.
```python
from sanic.response import json
from sanic import Blueprint
bp = Blueprint('my_blueprint')
@bp.route('/')
async def bp_root(request):
return json({'my': 'blueprint'})
```
## Registering blueprints
Blueprints must be registered with the application.
```python
from sanic import Sanic
from my_blueprint import bp
app = Sanic(__name__)
app.blueprint(bp)
app.run(host='0.0.0.0', port=8000, debug=True)
```
This will add the blueprint to the application and register any routes defined
by that blueprint. In this example, the registered routes in the `app.router`
will look like:
```python
[Route(handler=<function bp_root at 0x7f908382f9d8>, methods=None, pattern=re.compile('^/$'), parameters=[])]
```
## Using blueprints
Blueprints have much the same functionality as an application instance.
### WebSocket routes
WebSocket handlers can be registered on a blueprint using the `@bp.websocket`
decorator or `bp.add_websocket_route` method.
### Middleware
Using blueprints allows you to also register middleware globally.
```python
@bp.middleware
async def print_on_request(request):
print("I am a spy")
@bp.middleware('request')
async def halt_request(request):
return text('I halted the request')
@bp.middleware('response')
async def halt_response(request, response):
return text('I halted the response')
```
### Exceptions
Exceptions can be applied exclusively to blueprints globally.
```python
@bp.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```
### Static files
Static files can be served globally, under the blueprint prefix.
```python
bp.static('/folder/to/serve', '/web/path')
```
## Start and stop
Blueprints can run functions during the start and stop process of the server.
If running in multiprocessor mode (more than 1 worker), these are triggered
after the workers fork.
Available events are:
- `before_server_start`: Executed before the server begins to accept connections
- `after_server_start`: Executed after the server begins to accept connections
- `before_server_stop`: Executed before the server stops accepting connections
- `after_server_stop`: Executed after the server is stopped and all requests are complete
```python
bp = Blueprint('my_blueprint')
@bp.listener('before_server_start')
async def setup_connection(app, loop):
global database
database = mysql.connect(host='127.0.0.1'...)
@bp.listener('after_server_stop')
async def close_connection(app, loop):
await database.close()
```
## Use-case: API versioning
Blueprints can be very useful for API versioning, where one blueprint may point
at `/v1/<routes>`, and another pointing at `/v2/<routes>`.
When a blueprint is initialised, it can take an optional `url_prefix` argument,
which will be prepended to all routes defined on the blueprint. This feature
can be used to implement our API versioning scheme.
```python
# blueprints.py
from sanic.response import text
from sanic import Blueprint
blueprint_v1 = Blueprint('v1', url_prefix='/v1')
blueprint_v2 = Blueprint('v2', url_prefix='/v2')
@blueprint_v1.route('/')
async def api_v1_root(request):
return text('Welcome to version 1 of our documentation')
@blueprint_v2.route('/')
async def api_v2_root(request):
return text('Welcome to version 2 of our documentation')
```
When we register our blueprints on the app, the routes `/v1` and `/v2` will now
point to the individual blueprints, which allows the creation of *sub-sites*
for each API version.
```python
# main.py
from sanic import Sanic
from blueprints import blueprint_v1, blueprint_v2
app = Sanic(__name__)
app.blueprint(blueprint_v1, url_prefix='/v1')
app.blueprint(blueprint_v2, url_prefix='/v2')
app.run(host='0.0.0.0', port=8000, debug=True)
```
## URL Building with `url_for`
If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name
takes the format `<blueprint_name>.<handler_name>`. For example:
```
@blueprint_v1.route('/')
async def root(request):
url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
return redirect(url)
@blueprint_v1.route('/post/<post_id>')
async def post_handler(request, post_id):
return text('Post {} in Blueprint V1'.format(post_id))
```

View File

@@ -0,0 +1,149 @@
# Class-Based Views
Class-based views are simply classes which implement response behaviour to
requests. They provide a way to compartmentalise handling of different HTTP
request types at the same endpoint. Rather than defining and decorating three
different handler functions, one for each of an endpoint's supported request
type, the endpoint can be assigned a class-based view.
## Defining views
A class-based view should subclass `HTTPMethodView`. You can then implement
class methods for every HTTP request type you want to support. If a request is
received that has no defined method, a `405: Method not allowed` response will
be generated.
To register a class-based view on an endpoint, the `app.add_route` method is
used. The first argument should be the defined class with the method `as_view`
invoked, and the second should be the URL endpoint.
The available methods are `get`, `post`, `put`, `patch`, and `delete`. A class
using all these methods would look like the following.
```python
from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text
app = Sanic('some_name')
class SimpleView(HTTPMethodView):
def get(self, request):
return text('I am get method')
def post(self, request):
return text('I am post method')
def put(self, request):
return text('I am put method')
def patch(self, request):
return text('I am patch method')
def delete(self, request):
return text('I am delete method')
app.add_route(SimpleView.as_view(), '/')
```
You can also use `async` syntax.
```python
from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text
app = Sanic('some_name')
class SimpleAsyncView(HTTPMethodView):
async def get(self, request):
return text('I am async get method')
app.add_route(SimpleAsyncView.as_view(), '/')
```
## URL parameters
If you need any URL parameters, as discussed in the routing guide, include them
in the method definition.
```python
class NameView(HTTPMethodView):
def get(self, request, name):
return text('Hello {}'.format(name))
app.add_route(NameView.as_view(), '/<name>')
```
## Decorators
If you want to add any decorators to the class, you can set the `decorators`
class variable. These will be applied to the class when `as_view` is called.
```python
class ViewWithDecorator(HTTPMethodView):
decorators = [some_decorator_here]
def get(self, request, name):
return text('Hello I have a decorator')
app.add_route(ViewWithDecorator.as_view(), '/url')
```
#### URL Building
If you wish to build a URL for an HTTPMethodView, remember that the class name will be the endpoint
that you will pass into `url_for`. For example:
```python
@app.route('/')
def index(request):
url = app.url_for('SpecialClassView')
return redirect(url)
class SpecialClassView(HTTPMethodView):
def get(self, request):
return text('Hello from the Special Class View!')
app.add_route(SpecialClassView.as_view(), '/special_class_view')
```
## Using CompositionView
As an alternative to the `HTTPMethodView`, you can use `CompositionView` to
move handler functions outside of the view class.
Handler functions for each supported HTTP method are defined elsewhere in the
source, and then added to the view using the `CompositionView.add` method. The
first parameter is a list of HTTP methods to handle (e.g. `['GET', 'POST']`),
and the second is the handler function. The following example shows
`CompositionView` usage with both an external handler function and an inline
lambda:
```python
from sanic import Sanic
from sanic.views import CompositionView
from sanic.response import text
app = Sanic(__name__)
def get_handler(request):
return text('I am a get method')
view = CompositionView()
view.add(['GET'], get_handler)
view.add(['POST', 'PUT'], lambda request: text('I am a post/put method'))
# Use the new view to handle requests to the base URL
app.add_route(view, '/')
```
Note: currently you cannot build a URL for a CompositionView using `url_for`.

85
docs/sanic/config.md Normal file
View File

@@ -0,0 +1,85 @@
# 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.
## Basics
Sanic holds the configuration in the `config` attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary:
```
app = Sanic('myapp')
app.config.DB_NAME = 'appdb'
app.config.DB_USER = 'appuser'
```
Since the config object actually is a dictionary, you can use its `update` method in order to set several values at once:
```
db_settings = {
'DB_HOST': 'localhost',
'DB_NAME': 'appdb',
'DB_USER': 'appuser'
}
app.config.update(db_settings)
```
In general the convention is to only have UPPERCASE configuration parameters. The methods described below for loading configuration only look for such uppercase parameters.
## Loading Configuration
There are several ways how to load configuration.
### From environment variables.
Any variables defined with the `SANIC_` prefix will be applied to the sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically. You can pass the `load_vars` boolean to the Sanic constructor to override that:
```python
app = Sanic(load_vars=False)
```
### From an Object
If there are a lot of configuration values and they have sensible defaults it might be helpful to put them into a module:
```
import myapp.default_settings
app = Sanic('myapp')
app.config.from_object(myapp.default_settings)
```
You could use a class or any other object as well.
### From a File
Usually you will want to load configuration from a file that is not part of the distributed application. You can load configuration from a file using `from_file(/path/to/config_file)`. However, that requires the program to know the path to the config file. So instead you can specify the location of the config file in an environment variable and tell Sanic to use that to find the config file:
```
app = Sanic('myapp')
app.config.from_envvar('MYAPP_SETTINGS')
```
Then you can run your application with the `MYAPP_SETTINGS` environment variable set:
```
$ MYAPP_SETTINGS=/path/to/config_file python3 myapp.py
INFO: Goin' Fast @ http://0.0.0.0:8000
```
The config files are regular Python files which are executed in order to load them. This allows you to use arbitrary logic for constructing the right configuration. Only uppercase varibales are added to the configuration. Most commonly the configuration consists of simple key value pairs:
```
# config_file
DB_HOST = 'localhost'
DB_NAME = 'appdb'
DB_USER = 'appuser'
```
## Builtin Configuration Values
Out of the box there are just a few predefined values which can be overwritten when creating the application.
| Variable | Default | Description |
| ----------------- | --------- | --------------------------------- |
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
| REQUEST_TIMEOUT | 60 | How long a request can take (sec) |

View File

@@ -0,0 +1,33 @@
# 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.
## Running tests
* `python -m pip install pytest`
* `python -m pytest tests`
## 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.

75
docs/sanic/cookies.md Normal file
View File

@@ -0,0 +1,75 @@
# Cookies
Cookies are pieces of data which persist inside a user's browser. Sanic can
both read and write cookies, which are stored as key-value pairs.
## Reading cookies
A user's cookies can be accessed via the `Request` object's `cookies` dictionary.
```python
from sanic.response import text
@app.route("/cookie")
async def test(request):
test_cookie = request.cookies.get('test')
return text("Test cookie set to: {}".format(test_cookie))
```
## Writing cookies
When returning a response, cookies can be set on the `Response` object.
```python
from sanic.response import text
@app.route("/cookie")
async def test(request):
response = text("There's a cookie up in this response")
response.cookies['test'] = 'It worked!'
response.cookies['test']['domain'] = '.gotta-go-fast.com'
response.cookies['test']['httponly'] = True
return response
```
## Deleting cookies
Cookies can be removed semantically or explicitly.
```python
from sanic.response import text
@app.route("/cookie")
async def test(request):
response = text("Time to eat some cookies muahaha")
# This cookie will be set to expire in 0 seconds
del response.cookies['kill_me']
# This cookie will self destruct in 5 seconds
response.cookies['short_life'] = 'Glad to be here'
response.cookies['short_life']['max-age'] = 5
del response.cookies['favorite_color']
# This cookie will remain unchanged
response.cookies['favorite_color'] = 'blue'
response.cookies['favorite_color'] = 'pink'
del response.cookies['favorite_color']
return response
```
Response cookies can be set like dictionary values and have the following
parameters available:
- `expires` (datetime): The time for the cookie to expire on the
client's browser.
- `path` (string): The subset of URLs to which this cookie applies. Defaults to /.
- `comment` (string): A comment (metadata).
- `domain` (string): Specifies the domain for which the cookie is valid. An
explicitly specified domain must always start with a dot.
- `max-age` (number): Number of seconds the cookie should live for.
- `secure` (boolean): Specifies whether the cookie will only be sent via
HTTPS.
- `httponly` (boolean): Specifies whether the cookie cannot be read by
Javascript.

View File

@@ -0,0 +1,72 @@
# Custom Protocols
*Note: this is advanced usage, and most readers will not need such functionality.*
You can change the behavior of Sanic's protocol by specifying a custom
protocol, which should be a subclass
of
[asyncio.protocol](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes).
This protocol can then be passed as the keyword argument `protocol` to the `sanic.run` method.
The constructor of the custom protocol class receives the following keyword
arguments from Sanic.
- `loop`: an `asyncio`-compatible event loop.
- `connections`: a `set` to store protocol objects. When Sanic receives
`SIGINT` or `SIGTERM`, it executes `protocol.close_if_idle` for all protocol
objects stored in this set.
- `signal`: a `sanic.server.Signal` object with the `stopped` attribute. When
Sanic receives `SIGINT` or `SIGTERM`, `signal.stopped` is assigned `True`.
- `request_handler`: a coroutine that takes a `sanic.request.Request` object
and a `response` callback as arguments.
- `error_handler`: a `sanic.exceptions.Handler` which is called when exceptions
are raised.
- `request_timeout`: the number of seconds before a request times out.
- `request_max_size`: an integer specifying the maximum size of a request, in bytes.
## Example
An error occurs in the default protocol if a handler function does not return
an `HTTPResponse` object.
By overriding the `write_response` protocol method, if a handler returns a
string it will be converted to an `HTTPResponse object`.
```python
from sanic import Sanic
from sanic.server import HttpProtocol
from sanic.response import text
app = Sanic(__name__)
class CustomHttpProtocol(HttpProtocol):
def __init__(self, *, loop, request_handler, error_handler,
signal, connections, request_timeout, request_max_size):
super().__init__(
loop=loop, request_handler=request_handler,
error_handler=error_handler, signal=signal,
connections=connections, request_timeout=request_timeout,
request_max_size=request_max_size)
def write_response(self, response):
if isinstance(response, str):
response = text(response)
self.transport.write(
response.output(self.request.version)
)
self.transport.close()
@app.route('/')
async def string(request):
return 'string'
@app.route('/1')
async def response(request):
return text('response')
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
```

39
docs/sanic/decorators.md Normal file
View File

@@ -0,0 +1,39 @@
# Handler Decorators
Since Sanic handlers are simple Python functions, you can apply decorators to them in a similar manner to Flask. A typical use case is when you want some code to run before a handler's code is executed.
## Authorization Decorator
Let's say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response.
```python
from functools import wraps
from sanic.response import json
def authorized():
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
# run some method that checks the request
# for the client's authorization status
is_authorized = check_request_for_authorization_status(request)
if is_authorized:
# the user is authorized.
# run the handler method and return the response
response = await f(request, *args, **kwargs)
return response
else:
# the user is not authorized.
return json({'status': 'not_authorized'}, 403)
return decorated_function
return decorator
@app.route("/")
@authorized()
async def test(request):
return json({status: 'authorized'})
```

58
docs/sanic/deploying.md Normal file
View File

@@ -0,0 +1,58 @@
# Deploying
Deploying Sanic is made simple by the inbuilt webserver. After defining an
instance of `sanic.Sanic`, we can call the `run` method with the following
keyword arguments:
- `host` *(default `"127.0.0.1"`)*: Address to host the server on.
- `port` *(default `8000`)*: Port to host the server on.
- `debug` *(default `False`)*: Enables debug output (slows server).
- `ssl` *(default `None`)*: `SSLContext` for SSL encryption of worker(s).
- `sock` *(default `None`)*: Socket for the server to accept connections from.
- `workers` *(default `1`)*: Number of worker processes to spawn.
- `loop` *(default `None`)*: An `asyncio`-compatible event loop. If none is
specified, Sanic creates its own event loop.
- `protocol` *(default `HttpProtocol`)*: Subclass
of
[asyncio.protocol](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes).
## Workers
By default, Sanic listens in the main process using only one CPU core. To crank
up the juice, just specify the number of workers in the `run` arguments.
```python
app.run(host='0.0.0.0', port=1337, workers=4)
```
Sanic will automatically spin up multiple processes and route traffic between
them. We recommend as many workers as you have available cores.
## Running via command
If you like using command line arguments, you can launch a Sanic server by
executing the module. For example, if you initialized Sanic as `app` in a file
named `server.py`, you could run the server like so:
`python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4`
With this way of running sanic, it is not necessary to invoke `app.run` in your
Python file. If you do, make sure you wrap it so that it only executes when
directly run by the interpreter.
```python
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337, workers=4)
```
## Running via Gunicorn
[Gunicorn](http://gunicorn.org/) Green Unicorn is a WSGI HTTP Server for UNIX.
Its a pre-fork worker model ported from Rubys Unicorn project.
In order to run Sanic application with Gunicorn, you need to use the special `sanic.worker.GunicornWorker`
for Gunicorn `worker-class` argument:
```
gunicorn --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker
```

45
docs/sanic/exceptions.md Normal file
View File

@@ -0,0 +1,45 @@
# Exceptions
Exceptions can be thrown from within request handlers and will automatically be
handled by Sanic. Exceptions take a message as their first argument, and can
also take a status code to be passed back in the HTTP response.
## Throwing an exception
To throw an exception, simply `raise` the relevant exception from the
`sanic.exceptions` module.
```python
from sanic.exceptions import ServerError
@app.route('/killme')
def i_am_ready_to_die(request):
raise ServerError("Something bad happened", status_code=500)
```
## Handling exceptions
To override Sanic's default handling of an exception, the `@app.exception`
decorator is used. The decorator expects a list of exceptions to handle as
arguments. You can pass `SanicException` to catch them all! The decorated
exception handler function must take a `Request` and `Exception` object as
arguments.
```python
from sanic.response import text
from sanic.exceptions import NotFound
@app.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```
## Useful exceptions
Some of the most useful exceptions are presented below:
- `NotFound`: called when a suitable route for the request isn't found.
- `ServerError`: called when something goes wrong inside the server. This
usually occurs if there is an exception raised in user code.
See the `sanic.exceptions` module for the full list of exceptions to throw.

24
docs/sanic/extensions.md Normal file
View File

@@ -0,0 +1,24 @@
# Extensions
A list of Sanic extensions created by the community.
- [Sessions](https://github.com/subyraman/sanic_session): Support for sessions.
Allows using redis, memcache or an in memory store.
- [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors.
- [Compress](https://github.com/subyraman/sanic_compress): Allows you to easily gzip Sanic responses. A port of Flask-Compress.
- [Jinja2](https://github.com/lixxu/sanic-jinja2): Support for Jinja2 template.
- [OpenAPI/Swagger](https://github.com/channelcat/sanic-openapi): OpenAPI support, plus a Swagger UI.
- [Pagination](https://github.com/lixxu/python-paginate): Simple pagination support.
- [Motor](https://github.com/lixxu/sanic-motor): Simple motor wrapper.
- [Sanic CRUD](https://github.com/Typhon66/sanic_crud): CRUD REST API generation with peewee models.
- [UserAgent](https://github.com/lixxu/sanic-useragent): Add `user_agent` to request
- [Limiter](https://github.com/bohea/sanic-limiter): Rate limiting for sanic.
- [Sanic EnvConfig](https://github.com/jamesstidard/sanic-envconfig): Pull environment variables into your sanic config.
- [Babel](https://github.com/lixxu/sanic-babel): Adds i18n/l10n support to Sanic applications with the help of the
`Babel` library
- [Dispatch](https://github.com/ashleysommer/sanic-dispatcher): A dispatcher inspired by `DispatcherMiddleware` in werkzeug. Can act as a Sanic-to-WSGI adapter.
- [Sanic-OAuth](https://github.com/Sniedes722/Sanic-OAuth): OAuth Library for connecting to & creating your own token providers.
- [Sanic-nginx-docker-example](https://github.com/itielshwartz/sanic-nginx-docker-example): Simple and easy to use example of Sanic behined nginx using docker-compose.
- [sanic-graphql](https://github.com/graphql-python/sanic-graphql): GraphQL integration with Sanic
- [sanic-prometheus](https://github.com/dkruchinin/sanic-prometheus): Prometheus metrics for Sanic
- [Sanic-RestPlus](https://github.com/ashleysommer/sanic-restplus): A port of Flask-RestPlus for Sanic. Full-featured REST API with SwaggerUI generation.

View File

@@ -0,0 +1,27 @@
# Getting Started
Make sure you have both [pip](https://pip.pypa.io/en/stable/installing/) and at
least version 3.5 of Python before starting. Sanic uses the new `async`/`await`
syntax, so earlier versions of python won't work.
1. Install Sanic: `python3 -m pip install sanic`
2. Create a file called `main.py` with the following code:
```python
from sanic import Sanic
from sanic.response import text
app = Sanic(__name__)
@app.route("/")
async def test(request):
return text('Hello world!')
app.run(host="0.0.0.0", port=8000, debug=True)
```
3. Run the server: `python3 main.py`
4. Open the address `http://0.0.0.0:8000` in your web browser. You should see
the message *Hello world!*.
You now have a working Sanic server!

25
docs/sanic/index.rst Normal file
View File

@@ -0,0 +1,25 @@
Sanic
=================================
Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by `this article <https://magic.io/blog/uvloop-blazing-fast-python-networking/>`_.
On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.
Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contributions are welcome!
Sanic aspires to be simple
---------------------------
.. code:: python
from sanic import Sanic
from sanic.response import json
app = Sanic()
@app.route("/")
async def test(request):
return json({"hello": "world"})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

112
docs/sanic/middleware.md Normal file
View File

@@ -0,0 +1,112 @@
# Middleware And Listeners
Middleware are functions which are executed before or after requests to the
server. They can be used to modify the *request to* or *response from*
user-defined handler functions.
Additionally, Sanic providers listeners which allow you to run code at various points of your application's lifecycle.
## Middleware
There are two types of middleware: request and response. Both are declared
using the `@app.middleware` decorator, with the decorator's parameter being a
string representing its type: `'request'` or `'response'`. Response middleware
receives both the request and the response as arguments.
The simplest middleware doesn't modify the request or response at all:
```python
@app.middleware('request')
async def print_on_request(request):
print("I print when a request is received by the server")
@app.middleware('response')
async def print_on_response(request, response):
print("I print when a response is returned by the server")
```
## Modifying the request or response
Middleware can modify the request or response parameter it is given, *as long
as it does not return it*. The following example shows a practical use-case for
this.
```python
app = Sanic(__name__)
@app.middleware('response')
async def custom_banner(request, response):
response.headers["Server"] = "Fake-Server"
@app.middleware('response')
async def prevent_xss(request, response):
response.headers["x-xss-protection"] = "1; mode=block"
app.run(host="0.0.0.0", port=8000)
```
The above code will apply the two middleware in order. First, the middleware
**custom_banner** will change the HTTP response header *Server* to
*Fake-Server*, and the second middleware **prevent_xss** will add the HTTP
header for preventing Cross-Site-Scripting (XSS) attacks. These two functions
are invoked *after* a user function returns a response.
## Responding early
If middleware returns a `HTTPResponse` object, the request will stop processing
and the response will be returned. If this occurs to a request before the
relevant user route handler is reached, the handler will never be called.
Returning a response will also prevent any further middleware from running.
```python
@app.middleware('request')
async def halt_request(request):
return text('I halted the request')
@app.middleware('response')
async def halt_response(request, response):
return text('I halted the response')
```
## Listeners
If you want to execute startup/teardown code as your server starts or closes, you can use the following listeners:
- `before_server_start`
- `after_server_start`
- `before_server_stop`
- `after_server_stop`
These listeners are implemented as decorators on functions which accept the app object as well as the asyncio loop.
For example:
```python
@app.listener('before_server_start')
async def setup_db(app, loop):
app.db = await db_setup()
@app.listener('after_server_start')
async def notify_server_started(app, loop):
print('Server successfully started!')
@app.listener('before_server_stop')
async def notify_server_stopping(app, loop):
print('Server shutting down!')
@app.listener('after_server_stop')
async def close_db(app, loop):
await app.db.close()
```
If you want to schedule a background task to run after the loop has started,
Sanic provides the `add_task` method to easily do so.
```python
async def notify_server_started_after_five_seconds():
await asyncio.sleep(5)
print('Server successfully started!')
app.add_task(notify_server_started_after_five_seconds())
```

118
docs/sanic/request_data.md Normal file
View File

@@ -0,0 +1,118 @@
# Request Data
When an endpoint receives a HTTP request, the route function is passed a
`Request` object.
The following variables are accessible as properties on `Request` objects:
- `json` (any) - JSON body
```python
from sanic.response import json
@app.route("/json")
def post_json(request):
return json({ "received": True, "message": request.json })
```
- `args` (dict) - Query string variables. A query string is the section of a
URL that resembles `?key1=value1&key2=value2`. If that URL were to be parsed,
the `args` dictionary would look like `{'key1': ['value1'], 'key2': ['value2']}`.
The request's `query_string` variable holds the unparsed string value.
```python
from sanic.response import json
@app.route("/query_string")
def query_string(request):
return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
```
- `raw_args` (dict) - On many cases you would need to access the url arguments in
a less packed dictionary. For same previous URL `?key1=value1&key2=value2`, the
`raw_args` dictionary would look like `{'key1': 'value1', 'key2': 'value2'}`.
- `files` (dictionary of `File` objects) - List of files that have a name, body, and type
```python
from sanic.response import json
@app.route("/files")
def post_json(request):
test_file = request.files.get('test')
file_parameters = {
'body': test_file.body,
'name': test_file.name,
'type': test_file.type,
}
return json({ "received": True, "file_names": request.files.keys(), "test_file_parameters": file_parameters })
```
- `form` (dict) - Posted form variables.
```python
from sanic.response import json
@app.route("/form")
def post_json(request):
return json({ "received": True, "form_data": request.form, "test": request.form.get('test') })
```
- `body` (bytes) - Posted raw body. This property allows retrieval of the
request's raw data, regardless of content type.
```python
from sanic.response import text
@app.route("/users", methods=["POST",])
def create_user(request):
return text("You are trying to create a user with the following POST: %s" % request.body)
```
- `ip` (str) - IP address of the requester.
- `app` - a reference to the Sanic application object that is handling this request. This is useful when inside blueprints or other handlers in modules that do not have access to the global `app` object.
```python
from sanic.response import json
from sanic import Blueprint
bp = Blueprint('my_blueprint')
@bp.route('/')
async def bp_root(request):
if request.app.config['DEBUG']:
return json({'status': 'debug'})
else:
return json({'status': 'production'})
```
- `url`: The full URL of the request, ie: `http://localhost:8000/posts/1/?foo=bar`
- `scheme`: The URL scheme associated with the request: `http` or `https`
- `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 `''`
## Accessing values using `get` and `getlist`
The request properties which return a dictionary actually return a subclass of
`dict` called `RequestParameters`. The key difference when using this object is
the distinction between the `get` and `getlist` methods.
- `get(key, default=None)` operates as normal, except that when the value of
the given key is a list, *only the first item is returned*.
- `getlist(key, default=None)` operates as normal, *returning the entire list*.
```python
from sanic.request import RequestParameters
args = RequestParameters()
args['titles'] = ['Post 1', 'Post 2']
args.get('titles') # => 'Post 1'
args.getlist('titles') # => ['Post 1', 'Post 2']
```

102
docs/sanic/response.md Normal file
View File

@@ -0,0 +1,102 @@
# Response
Use functions in `sanic.response` module to create responses.
## Plain Text
```python
from sanic import response
@app.route('/text')
def handle_request(request):
return response.text('Hello world!')
```
## HTML
```python
from sanic import response
@app.route('/html')
def handle_request(request):
return response.html('<p>Hello world!</p>')
```
## JSON
```python
from sanic import response
@app.route('/json')
def handle_request(request):
return response.json({'message': 'Hello world!'})
```
## File
```python
from sanic import response
@app.route('/file')
async def handle_request(request):
return await response.file('/srv/www/whatever.png')
```
## Streaming
```python
from sanic import response
@app.route("/streaming")
async def index(request):
async def streaming_fn(response):
response.write('foo')
response.write('bar')
return response.stream(streaming_fn, content_type='text/plain')
```
## Redirect
```python
from sanic import response
@app.route('/redirect')
def handle_request(request):
return response.redirect('/json')
```
## Raw
Response without encoding the body
```python
from sanic import response
@app.route('/raw')
def handle_request(request):
return response.raw('raw data')
```
## Modify headers or status
To modify headers or status code, pass the `headers` or `status` argument to those functions:
```python
from sanic import response
@app.route('/json')
def handle_request(request):
return response.json(
{'message': 'Hello world!'},
headers={'X-Served-By': 'sanic'},
status=200
)
```

217
docs/sanic/routing.md Normal file
View File

@@ -0,0 +1,217 @@
# Routing
Routing allows the user to specify handler functions for different URL endpoints.
A basic route looks like the following, where `app` is an instance of the
`Sanic` class:
```python
from sanic.response import json
@app.route("/")
async def test(request):
return json({ "hello": "world" })
```
When the url `http://server.url/` is accessed (the base url of the server), the
final `/` is matched by the router to the handler function, `test`, which then
returns a JSON object.
Sanic handler functions must be defined using the `async def` syntax, as they
are asynchronous functions.
## Request parameters
Sanic comes with a basic router that supports request parameters.
To specify a parameter, surround it with angle quotes like so: `<PARAM>`.
Request parameters will be passed to the route handler functions as keyword
arguments.
```python
from sanic.response import text
@app.route('/tag/<tag>')
async def tag_handler(request, tag):
return text('Tag - {}'.format(tag))
```
To specify a type for the parameter, add a `:type` after the parameter name,
inside the quotes. If the parameter does not match the specified type, Sanic
will throw a `NotFound` exception, resulting in a `404: Page not found` error
on the URL.
```python
from sanic.response import text
@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
return text('Integer - {}'.format(integer_arg))
@app.route('/number/<number_arg:number>')
async def number_handler(request, number_arg):
return text('Number - {}'.format(number_arg))
@app.route('/person/<name:[A-z]>')
async def person_handler(request, name):
return text('Person - {}'.format(name))
@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
async def folder_handler(request, folder_id):
return text('Folder - {}'.format(folder_id))
```
## HTTP request types
By default, a route defined on a URL will be available for only GET requests to that URL.
However, the `@app.route` decorator accepts an optional parameter, `methods`,
which allows the handler function to work with any of the HTTP methods in the list.
```python
from sanic.response import text
@app.route('/post', methods=['POST'])
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.route('/get', methods=['GET'])
async def get_handler(request):
return text('GET request - {}'.format(request.args))
```
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default.
```python
@app.route('/get', methods=['GET'], host='example.com')
async def get_handler(request):
return text('GET request - {}'.format(request.args))
# if the host header doesn't match example.com, this route will be used
@app.route('/get', methods=['GET'])
async def get_handler(request):
return text('GET request in default - {}'.format(request.args))
```
There are also shorthand method decorators:
```python
from sanic.response import text
@app.post('/post')
async def post_handler(request):
return text('POST request - {}'.format(request.json))
@app.get('/get')
async def get_handler(request):
return text('GET request - {}'.format(request.args))
```
## The `add_route` method
As we have seen, routes are often specified using the `@app.route` decorator.
However, this decorator is really just a wrapper for the `app.add_route`
method, which is used as follows:
```python
from sanic.response import text
# Define the handler functions
async def handler1(request):
return text('OK')
async def handler2(request, name):
return text('Folder - {}'.format(name))
async def person_handler2(request, name):
return text('Person - {}'.format(name))
# Add each handler function as a route
app.add_route(handler1, '/test')
app.add_route(handler2, '/folder/<name>')
app.add_route(person_handler2, '/person/<name:[A-z]>', methods=['GET'])
```
## URL building with `url_for`
Sanic provides a `url_for` method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example:
```python
@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 redirect(url)
@app.route('/posts/<post_id>')
async def post_handler(request, post_id):
return text('Post - {}'.format(post_id))
```
Other things to keep in mind when using `url_for`:
- Keyword arguments passed to `url_for` that are not request parameters will be included in the URL's query string. For example:
```python
url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
# /posts/5?arg_one=one&arg_two=two
```
- Multivalue argument can be passed to `url_for`. For example:
```python
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two
```
- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example:
```python
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
# /posts/5?arg_one=one#anchor
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
# //server/posts/5?arg_one=one
# _external requires passed argument _server or SERVER_NAME in app.config or url will be same as no _external
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
# http://server/posts/5?arg_one=one
# when specifying _scheme, _external must be True
# you can pass all special arguments one time
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
```
- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown.
## WebSocket routes
Routes for the WebSocket protocol can be defined with the `@app.websocket`
decorator:
```python
@app.websocket('/feed')
async def feed(request, ws):
while True:
data = 'hello!'
print('Sending: ' + data)
await ws.send(data)
data = await ws.recv()
print('Received: ' + data)
```
Alternatively, the `app.add_websocket_route` method can be used instead of the
decorator:
```python
async def feed(request, ws):
pass
app.add_websocket_route(my_websocket_handler, '/feed')
```
Handlers for a WebSocket route are passed the request as first argument, and a
WebSocket protocol object as second argument. The protocol object has `send`
and `recv` methods to send and receive data respectively.
WebSocket support requires the [websockets](https://github.com/aaugustin/websockets)
package by Aymeric Augustin.

20
docs/sanic/ssl.rst Normal file
View File

@@ -0,0 +1,20 @@
SSL Example
-----------
Optionally pass in an SSLContext:
.. code:: python
import ssl
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain("/path/to/cert", keyfile="/path/to/keyfile")
app.run(host="0.0.0.0", port=8443, ssl=context)
You can also pass in the locations of a certificate and key as a dictionary:
.. code:: python
ssl = {'cert': "/path/to/cert", 'key': "/path/to/keyfile"}
app.run(host="0.0.0.0", port=8443, ssl=ssl)

View File

@@ -0,0 +1,21 @@
# Static Files
Static files and directories, such as an image file, are served by Sanic when
registered with the `app.static` method. The method takes an endpoint URL and a
filename. The file specified will then be accessible via the given endpoint.
```python
from sanic import Sanic
app = Sanic(__name__)
# Serves files from the static folder to the URL /static
app.static('/static', './static')
# Serves the file /home/ubuntu/test.png when the URL /the_best.png
# is requested
app.static('/the_best.png', '/home/ubuntu/test.png')
app.run(host="0.0.0.0", port=8000)
```
Note: currently you cannot build a URL for a static file using `url_for`.

32
docs/sanic/streaming.md Normal file
View File

@@ -0,0 +1,32 @@
# Streaming
Sanic allows you to stream content to the client with the `stream` method. This method accepts a coroutine callback which is passed a `StreamingHTTPResponse` object that is written to. A simple example is like follows:
```python
from sanic import Sanic
from sanic.response import stream
app = Sanic(__name__)
@app.route("/")
async def test(request):
async def sample_streaming_fn(response):
response.write('foo,')
response.write('bar')
return stream(sample_streaming_fn, content_type='text/csv')
```
This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides:
```python
@app.route("/")
async def index(request):
async def stream_from_db(response):
conn = await asyncpg.connect(database='test')
async with conn.transaction():
async for record in conn.cursor('SELECT generate_series(0, 10)'):
response.write(record[0])
return stream(stream_from_db)
```

73
docs/sanic/testing.md Normal file
View File

@@ -0,0 +1,73 @@
# Testing
Sanic endpoints can be tested locally using the `test_client` object, which
depends on the additional [aiohttp](https://aiohttp.readthedocs.io/en/stable/)
library.
The `test_client` exposes `get`, `post`, `put`, `delete`, `patch`, `head` and `options` methods
for you to run against your application. A simple example (using pytest) is like follows:
```python
# Import the Sanic app, usually created with Sanic(__name__)
from external_server import app
def test_index_returns_200():
request, response = app.test_client.get('/')
assert response.status == 200
def test_index_put_not_allowed():
request, response = app.test_client.put('/')
assert response.status == 405
```
Internally, each time you call one of the `test_client` methods, the Sanic app is run at `127.0.01:42101` and
your test request is executed against your application, using `aiohttp`.
The `test_client` methods accept the following arguments and keyword arguments:
- `uri` *(default `'/'`)* A string representing the URI to test.
- `gather_request` *(default `True`)* A boolean which determines whether the
original request will be returned by the function. If set to `True`, the
return value is a tuple of `(request, response)`, if `False` only the
response is returned.
- `server_kwargs` *(default `{}`) a dict of additional arguments to pass into `app.run` before the test request is run.
- `debug` *(default `False`)* A boolean which determines whether to run the server in debug mode.
The function further takes the `*request_args` and `**request_kwargs`, which are passed directly to the aiohttp ClientSession request.
For example, to supply data to a GET request, you would do the following:
```python
def test_get_request_includes_data():
params = {'key1': 'value1', 'key2': 'value2'}
request, response = app.test_client.get('/', params=params)
assert request.args.get('key1') == 'value1'
```
And to supply data to a JSON POST request:
```python
def test_post_json_request_includes_data():
data = {'key1': 'value1', 'key2': 'value2'}
request, response = app.test_client.post('/', data=json.dumps(data))
assert request.json.get('key1') == 'value1'
```
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
```

19
environment.yml Normal file
View File

@@ -0,0 +1,19 @@
name: py35
dependencies:
- openssl=1.0.2g=0
- pip=8.1.1=py35_0
- python=3.5.1=0
- readline=6.2=2
- setuptools=20.3=py35_0
- sqlite=3.9.2=0
- tk=8.5.18=0
- wheel=0.29.0=py35_0
- xz=5.0.5=1
- zlib=1.2.8=0
- pip:
- uvloop>=0.5.3
- httptools>=0.0.9
- ujson>=1.35
- aiofiles>=0.3.0
- websockets>=3.2
- https://github.com/channelcat/docutils-fork/zipball/master

View File

@@ -0,0 +1,26 @@
from sanic import Sanic
from sanic import response
import aiohttp
app = Sanic(__name__)
async def fetch(session, url):
"""
Use session object to perform 'get' request on url
"""
async with session.get(url) as result:
return await result.json()
@app.route('/')
async def handle_request(request):
url = "https://api.github.com/repos/channelcat/sanic"
async with aiohttp.ClientSession() as session:
result = await fetch(session, url)
return response.json(result)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000, workers=2)

View File

View File

@@ -0,0 +1,140 @@
from sanic import Sanic
from sanic.exceptions import NotFound
from sanic.response import json
from sanic.views import HTTPMethodView
from asyncorm import configure_orm
from asyncorm.exceptions import QuerysetError
from library.models import Book
from library.serializer import BookSerializer
app = Sanic(name=__name__)
@app.listener('before_server_start')
def orm_configure(sanic, loop):
db_config = {'database': 'sanic_example',
'host': 'localhost',
'user': 'sanicdbuser',
'password': 'sanicDbPass',
}
# configure_orm needs a dictionary with:
# * the database configuration
# * the application/s where the models are defined
orm_app = configure_orm({'loop': loop, # always use the sanic loop!
'db_config': db_config,
'modules': ['library', ], # list of apps
})
# orm_app is the object that orchestrates the whole ORM
# sync_db should be run only once, better do that as external command
# it creates the tables in the database!!!!
# orm_app.sync_db()
# for all the 404 lets handle the exceptions
@app.exception(NotFound)
def ignore_404s(request, exception):
return json({'method': request.method,
'status': exception.status_code,
'error': exception.args[0],
'results': None,
})
# now the propper sanic workflow
class BooksView(HTTPMethodView):
def arg_parser(self, request):
parsed_args = {}
for k, v in request.args.items():
parsed_args[k] = v[0]
return parsed_args
async def get(self, request):
filtered_by = self.arg_parser(request)
if filtered_by:
q_books = await Book.objects.filter(**filtered_by)
else:
q_books = await Book.objects.all()
books = [BookSerializer.serialize(book) for book in q_books]
return json({'method': request.method,
'status': 200,
'results': books or None,
'count': len(books),
})
async def post(self, request):
# populate the book with the data in the request
book = Book(**request.json)
# and await on save
await book.save()
return json({'method': request.method,
'status': 201,
'results': BookSerializer.serialize(book),
})
class BookView(HTTPMethodView):
async def get_object(self, request, book_id):
try:
# await on database consults
book = await Book.objects.get(**{'id': book_id})
except QuerysetError as e:
raise NotFound(e.args[0])
return book
async def get(self, request, book_id):
# await on database consults
book = await self.get_object(request, book_id)
return json({'method': request.method,
'status': 200,
'results': BookSerializer.serialize(book),
})
async def put(self, request, book_id):
# await on database consults
book = await self.get_object(request, book_id)
# await on save
await book.save(**request.json)
return json({'method': request.method,
'status': 200,
'results': BookSerializer.serialize(book),
})
async def patch(self, request, book_id):
# await on database consults
book = await self.get_object(request, book_id)
# await on save
await book.save(**request.json)
return json({'method': request.method,
'status': 200,
'results': BookSerializer.serialize(book),
})
async def delete(self, request, book_id):
# await on database consults
book = await self.get_object(request, book_id)
# await on its deletion
await book.delete()
return json({'method': request.method,
'status': 200,
'results': None
})
app.add_route(BooksView.as_view(), '/books/')
app.add_route(BookView.as_view(), '/books/<book_id:int>/')
if __name__ == '__main__':
app.run()

View File

View File

@@ -0,0 +1,21 @@
from asyncorm.model import Model
from asyncorm.fields import CharField, IntegerField, DateField
BOOK_CHOICES = (
('hard cover', 'hard cover book'),
('paperback', 'paperback book')
)
# This is a simple model definition
class Book(Model):
name = CharField(max_length=50)
synopsis = CharField(max_length=255)
book_type = CharField(max_length=15, null=True, choices=BOOK_CHOICES)
pages = IntegerField(null=True)
date_created = DateField(auto_now=True)
class Meta():
ordering = ['name', ]
unique_together = ['name', 'synopsis']

View File

@@ -0,0 +1,15 @@
from asyncorm.model import ModelSerializer, SerializerMethod
from library.models import Book
class BookSerializer(ModelSerializer):
book_type = SerializerMethod()
def get_book_type(self, instance):
return instance.book_type_display()
class Meta():
model = Book
fields = [
'id', 'name', 'synopsis', 'book_type', 'pages', 'date_created'
]

View File

@@ -0,0 +1,2 @@
asyncorm==0.0.7
sanic==0.4.1

View File

@@ -6,6 +6,7 @@ from sanic.response import json, text
app = Sanic(__name__)
blueprint = Blueprint('name', url_prefix='/my_blueprint')
blueprint2 = Blueprint('name', url_prefix='/my_blueprint2')
blueprint3 = Blueprint('name', url_prefix='/my_blueprint3')
@blueprint.route('/foo')
@@ -17,8 +18,17 @@ async def foo(request):
async def foo2(request):
return json({'msg': 'hi from blueprint2'})
@blueprint3.websocket('/foo')
async def foo3(request, ws):
while True:
data = 'hello!'
print('Sending: ' + data)
await ws.send(data)
data = await ws.recv()
print('Received: ' + data)
app.register_blueprint(blueprint)
app.register_blueprint(blueprint2)
app.blueprint(blueprint)
app.blueprint(blueprint2)
app.blueprint(blueprint3)
app.run(host="0.0.0.0", port=8000, debug=True)

46
examples/cache_example.py Normal file
View File

@@ -0,0 +1,46 @@
"""
Example of caching using aiocache package. To run it you will need a Redis
instance running in localhost:6379. You can also try with SimpleMemoryCache.
Running this example you will see that the first call lasts 3 seconds and
the rest are instant because the value is retrieved from the Redis.
If you want more info about the package check
https://github.com/argaen/aiocache
"""
import asyncio
import aiocache
from sanic import Sanic
from sanic.response import json
from sanic.log import log
from aiocache import cached
from aiocache.serializers import JsonSerializer
app = Sanic(__name__)
@app.listener('before_server_start')
def init_cache(sanic, loop):
aiocache.settings.set_defaults(
class_="aiocache.RedisCache",
# class_="aiocache.SimpleMemoryCache",
loop=loop
)
@cached(key="my_custom_key", serializer=JsonSerializer())
async def expensive_call():
log.info("Expensive has been called")
await asyncio.sleep(3)
return {"test": True}
@app.route("/")
async def test(request):
log.info("Received GET /")
return json(await expensive_call())
app.run(host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,136 @@
# This demo requires aioredis and environmental variables established in ENV_VARS
import json
import logging
import os
from datetime import datetime
import aioredis
import sanic
from sanic import Sanic
ENV_VARS = ["REDIS_HOST", "REDIS_PORT",
"REDIS_MINPOOL", "REDIS_MAXPOOL",
"REDIS_PASS", "APP_LOGFILE"]
app = Sanic(name=__name__)
logger = None
@app.middleware("request")
async def log_uri(request):
# Simple middleware to log the URI endpoint that was called
logger.info("URI called: {0}".format(request.url))
@app.listener('before_server_start')
async def before_server_start(app, loop):
logger.info("Starting redis pool")
app.redis_pool = await aioredis.create_pool(
(app.config.REDIS_HOST, int(app.config.REDIS_PORT)),
minsize=int(app.config.REDIS_MINPOOL),
maxsize=int(app.config.REDIS_MAXPOOL),
password=app.config.REDIS_PASS)
@app.listener('after_server_stop')
async def after_server_stop(app, loop):
logger.info("Closing redis pool")
app.redis_pool.close()
await app.redis_pool.wait_closed()
@app.middleware("request")
async def attach_db_connectors(request):
# Just put the db objects in the request for easier access
logger.info("Passing redis pool to request object")
request["redis"] = request.app.redis_pool
@app.route("/state/<user_id>", methods=["GET"])
async def access_state(request, user_id):
try:
# Check to see if the value is in cache, if so lets return that
with await request["redis"] as redis_conn:
state = await redis_conn.get(user_id, encoding="utf-8")
if state:
return sanic.response.json({"msg": "Success",
"status": 200,
"success": True,
"data": json.loads(state),
"finished_at": datetime.now().isoformat()})
# Then state object is not in redis
logger.critical("Unable to find user_data in cache.")
return sanic.response.HTTPResponse({"msg": "User state not found",
"success": False,
"status": 404,
"finished_at": datetime.now().isoformat()}, status=404)
except aioredis.ProtocolError:
logger.critical("Unable to connect to state cache")
return sanic.response.HTTPResponse({"msg": "Internal Server Error",
"status": 500,
"success": False,
"finished_at": datetime.now().isoformat()}, status=500)
@app.route("/state/<user_id>/push", methods=["POST"])
async def set_state(request, user_id):
try:
# Pull a connection from the pool
with await request["redis"] as redis_conn:
# Set the value in cache to your new value
await redis_conn.set(user_id, json.dumps(request.json), expire=1800)
logger.info("Successfully pushed state to cache")
return sanic.response.HTTPResponse({"msg": "Successfully pushed state to cache",
"success": True,
"status": 200,
"finished_at": datetime.now().isoformat()})
except aioredis.ProtocolError:
logger.critical("Unable to connect to state cache")
return sanic.response.HTTPResponse({"msg": "Internal Server Error",
"status": 500,
"success": False,
"finished_at": datetime.now().isoformat()}, status=500)
def configure():
# Setup environment variables
env_vars = [os.environ.get(v, None) for v in ENV_VARS]
if not all(env_vars):
# Send back environment variables that were not set
return False, ", ".join([ENV_VARS[i] for i, flag in env_vars if not flag])
else:
# Add all the env vars to our app config
app.config.update({k: v for k, v in zip(ENV_VARS, env_vars)})
setup_logging()
return True, None
def setup_logging():
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
logging_format += "%(module)s::%(funcName)s():l%(lineno)d: "
logging_format += "%(message)s"
logging.basicConfig(
filename=app.config.APP_LOGFILE,
format=logging_format,
level=logging.DEBUG)
def main(result, missing):
if result:
try:
app.run(host="0.0.0.0", port=8080, debug=True)
except:
logging.critical("User killed server. Closing")
else:
logging.critical("Unable to start. Missing environment variables [{0}]".format(missing))
if __name__ == "__main__":
result, missing = configure()
logger = logging.getLogger()
main(result, missing)

View File

@@ -0,0 +1,58 @@
"""
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
an external service.
"""
from sanic.handlers import ErrorHandler
from sanic.exceptions import SanicException
"""
Imports and code relevant for our CustomHandler class
(Ordinarily this would be in a separate file)
"""
class CustomHandler(ErrorHandler):
def default(self, request, exception):
# Here, we have access to the exception object
# and can do anything with it (log, send to external service, etc)
# Some exceptions are trivial and built into Sanic (404s, etc)
if not isinstance(exception, SanicException):
print(exception)
# Then, we must finish handling the exception by returning
# our response to the client
# For this we can just call the super class' default handler
return super().default(request, exception)
"""
This is an ordinary Sanic server, with the exception that we set the
server's error_handler to an instance of our CustomHandler
"""
from sanic import Sanic
from sanic.response import json
app = Sanic(__name__)
handler = CustomHandler()
app.error_handler = handler
@app.route("/")
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})
app.run(host="0.0.0.0", port=8000, debug=True)

18
examples/jinja_example.py Normal file
View File

@@ -0,0 +1,18 @@
## 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,37 @@
from sanic import Sanic
from sanic.response import json
import asyncio
import aiohttp
app = Sanic(__name__)
sem = None
@app.listener('before_server_start')
def init(sanic, loop):
global sem
CONCURRENCY_PER_WORKER = 4
sem = asyncio.Semaphore(CONCURRENCY_PER_WORKER, loop=loop)
async def bounded_fetch(session, url):
"""
Use session object to perform 'get' request on url
"""
async with sem, session.get(url) as response:
return await response.json()
@app.route("/")
async def test(request):
"""
Download and serve example JSON
"""
url = "https://api.github.com/repos/channelcat/sanic"
async with aiohttp.ClientSession() as session:
response = await bounded_fetch(session, url)
return json(response)
app.run(host="0.0.0.0", port=8000, workers=2)

View File

@@ -0,0 +1,23 @@
from sanic import Sanic
from sanic.response import text
import json
import logging
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
logging_format += "%(module)s::%(funcName)s():l%(lineno)d: "
logging_format += "%(message)s"
logging.basicConfig(
format=logging_format,
level=logging.DEBUG
)
log = logging.getLogger()
# Set logger to override default basicConfig
sanic = Sanic()
@sanic.route("/")
def test(request):
log.info("received request; responding with 'hey'")
return text("hey")
sanic.run(host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,17 @@
from sanic import Sanic
from sanic import response
app = Sanic(__name__)
@app.route('/')
def handle_request(request):
return response.redirect('/redirect')
@app.route('/redirect')
async def test(request):
return response.json({"Redirected": True})
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,21 @@
from sanic import Sanic
import asyncio
from sanic.response import text
from sanic.config import Config
from sanic.exceptions import RequestTimeout
Config.REQUEST_TIMEOUT = 1
app = Sanic(__name__)
@app.route('/')
async def test(request):
await asyncio.sleep(3)
return text('Hello, world!')
@app.exception(RequestTimeout)
def timeout(request, exception):
return text('RequestTimeout from error_handler.', 408)
app.run(host='0.0.0.0', port=8000)

22
examples/run_async.py Normal file
View File

@@ -0,0 +1,22 @@
from sanic import Sanic
from sanic import response
from multiprocessing import Event
from signal import signal, SIGINT
import asyncio
import uvloop
app = Sanic(__name__)
@app.route("/")
async def test(request):
return response.json({"answer": "42"})
asyncio.set_event_loop(uvloop.new_event_loop())
server = app.create_server(host="0.0.0.0", port=8000)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server)
signal(SIGINT, lambda s, f: loop.stop())
try:
loop.run_forever()
except:
loop.stop()

View File

@@ -0,0 +1,62 @@
# encoding: utf-8
"""
You need the aiomysql
"""
import os
import aiomysql
from sanic import Sanic
from sanic.response import json
database_name = os.environ['DATABASE_NAME']
database_host = os.environ['DATABASE_HOST']
database_user = os.environ['DATABASE_USER']
database_password = os.environ['DATABASE_PASSWORD']
app = Sanic()
@app.listener("before_server_start")
async def get_pool(app, loop):
"""
the first param is the global instance ,
so we can store our connection pool in it .
and it can be used by different request
:param args:
:param kwargs:
:return:
"""
app.pool = {
"aiomysql": await aiomysql.create_pool(host=database_host, user=database_user, password=database_password,
db=database_name,
maxsize=5)}
async with app.pool['aiomysql'].acquire() as conn:
async with conn.cursor() as cur:
await cur.execute('DROP TABLE IF EXISTS sanic_polls')
await cur.execute("""CREATE TABLE sanic_polls (
id serial primary key,
question varchar(50),
pub_date timestamp
);""")
for i in range(0, 100):
await cur.execute("""INSERT INTO sanic_polls
(id, question, pub_date) VALUES ({}, {}, now())
""".format(i, i))
@app.route("/")
async def test():
result = []
data = {}
async with app.pool['aiomysql'].acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT question, pub_date FROM sanic_polls")
async for row in cur:
result.append({"question": row[0], "pub_date": row[1]})
if result or len(result) > 0:
data['data'] = res
return json(data)
if __name__ == '__main__':
app.run(host="127.0.0.1", workers=4, port=12000)

View File

@@ -0,0 +1,65 @@
""" To run this example you need additional aiopg package
"""
import os
import asyncio
import uvloop
import aiopg
from sanic import Sanic
from sanic.response import json
database_name = os.environ['DATABASE_NAME']
database_host = os.environ['DATABASE_HOST']
database_user = os.environ['DATABASE_USER']
database_password = os.environ['DATABASE_PASSWORD']
connection = 'postgres://{0}:{1}@{2}/{3}'.format(database_user,
database_password,
database_host,
database_name)
async def get_pool():
return await aiopg.create_pool(connection)
app = Sanic(name=__name__)
@app.listener('before_server_start')
async def prepare_db(app, loop):
"""
Let's create some table and add some data
"""
async with aiopg.create_pool(connection) as pool:
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute('DROP TABLE IF EXISTS sanic_polls')
await cur.execute("""CREATE TABLE sanic_polls (
id serial primary key,
question varchar(50),
pub_date timestamp
);""")
for i in range(0, 100):
await cur.execute("""INSERT INTO sanic_polls
(id, question, pub_date) VALUES ({}, {}, now())
""".format(i, i))
@app.route("/")
async def handle(request):
result = []
async def test_select():
async with aiopg.create_pool(connection) as pool:
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT question, pub_date FROM sanic_polls")
async for row in cur:
result.append({"question": row[0], "pub_date": row[1]})
res = await test_select()
return json({'polls': result})
if __name__ == '__main__':
app.run(host='0.0.0.0',
port=8000,
debug=True)

View File

@@ -0,0 +1,67 @@
""" To run this example you need additional aiopg package
"""
import os
import asyncio
import datetime
import uvloop
from aiopg.sa import create_engine
import sqlalchemy as sa
from sanic import Sanic
from sanic.response import json
database_name = os.environ['DATABASE_NAME']
database_host = os.environ['DATABASE_HOST']
database_user = os.environ['DATABASE_USER']
database_password = os.environ['DATABASE_PASSWORD']
connection = 'postgres://{0}:{1}@{2}/{3}'.format(database_user,
database_password,
database_host,
database_name)
metadata = sa.MetaData()
polls = sa.Table('sanic_polls', metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('question', sa.String(50)),
sa.Column("pub_date", sa.DateTime))
app = Sanic(name=__name__)
@app.listener('before_server_start')
async def prepare_db(app, loop):
""" Let's add some data
"""
async with create_engine(connection) as engine:
async with engine.acquire() as conn:
await conn.execute('DROP TABLE IF EXISTS sanic_polls')
await conn.execute("""CREATE TABLE sanic_polls (
id serial primary key,
question varchar(50),
pub_date timestamp
);""")
for i in range(0, 100):
await conn.execute(
polls.insert().values(question=i,
pub_date=datetime.datetime.now())
)
@app.route("/")
async def handle(request):
async with create_engine(connection) as engine:
async with engine.acquire() as conn:
result = []
async for row in conn.execute(polls.select()):
result.append({"question": row.question,
"pub_date": row.pub_date})
return json({"polls": result})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

View File

@@ -0,0 +1,34 @@
""" To run this example you need additional aioredis package
"""
from sanic import Sanic, response
import aioredis
app = Sanic(__name__)
@app.route("/")
async def handle(request):
async with request.app.redis_pool.get() as redis:
await redis.set('test-my-key', 'value')
val = await redis.get('test-my-key')
return response.text(val.decode('utf-8'))
@app.listener('before_server_start')
async def before_server_start(app, loop):
app.redis_pool = await aioredis.create_pool(
('localhost', 6379),
minsize=5,
maxsize=10,
loop=loop
)
@app.listener('after_server_stop')
async def after_server_stop(app, loop):
app.redis_pool.close()
await app.redis_pool.wait_closed()
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,51 @@
import os
import asyncio
import uvloop
from asyncpg import connect, create_pool
from sanic import Sanic
from sanic.response import json
DB_CONFIG = {
'host': '<host>',
'user': '<user>',
'password': '<password>',
'port': '<port>',
'database': '<database>'
}
def jsonify(records):
"""
Parse asyncpg record response into JSON format
"""
return [dict(r.items()) for r in records]
app = Sanic(__name__)
@app.listener('before_server_start')
async def register_db(app, loop):
app.pool = await create_pool(**DB_CONFIG, loop=loop, max_size=100)
async with app.pool.acquire() as connection:
await connection.execute('DROP TABLE IF EXISTS sanic_post')
await connection.execute("""CREATE TABLE sanic_post (
id serial primary key,
content varchar(50),
post_date timestamp
);""")
for i in range(0, 1000):
await connection.execute(f"""INSERT INTO sanic_post
(id, content, post_date) VALUES ({i}, {i}, now())""")
@app.get('/')
async def root_get(request):
async with app.pool.acquire() as connection:
results = await connection.fetch('SELECT * FROM sanic_post')
return json({'posts': jsonify(results)})
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080)

41
examples/sanic_motor.py Normal file
View File

@@ -0,0 +1,41 @@
""" sanic motor (async driver for mongodb) example
Required packages:
pymongo==3.4.0
motor==1.1
sanic==0.2.0
"""
from sanic import Sanic
from sanic.response import json
app = Sanic('motor_mongodb')
def get_db():
from motor.motor_asyncio import AsyncIOMotorClient
mongo_uri = "mongodb://127.0.0.1:27017/test"
client = AsyncIOMotorClient(mongo_uri)
return client['test']
@app.route('/objects', methods=['GET'])
async def get(request):
db = get_db()
docs = await db.test_col.find().to_list(length=100)
for doc in docs:
doc['id'] = str(doc['_id'])
del doc['_id']
return json(docs)
@app.route('/post', methods=['POST'])
async def new(request):
doc = request.json
print(doc)
db = get_db()
object_id = await db.test_col.save(doc)
return json({'object_id': str(object_id)})
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8000)

116
examples/sanic_peewee.py Normal file
View File

@@ -0,0 +1,116 @@
## You need the following additional packages for this example
# aiopg
# peewee_async
# peewee
## sanic imports
from sanic import Sanic
from sanic.response import json
## peewee_async related imports
import peewee
from peewee import Model, BaseModel
from peewee_async import Manager, PostgresqlDatabase, execute
from functools import partial
# we instantiate a custom loop so we can pass it to our db manager
## from peewee_async docs:
# Also theres no need to connect and re-connect before executing async queries
# with manager! Its all automatic. But you can run Manager.connect() or
# Manager.close() when you need it.
class AsyncManager(Manager):
"""Inherit the peewee_async manager with our own object
configuration
database.allow_sync = False
"""
def __init__(self, _model_class, *args, **kwargs):
super(AsyncManager, self).__init__(*args, **kwargs)
self._model_class = _model_class
self.database.allow_sync = False
def _do_fill(self, method, *args, **kwargs):
_class_method = getattr(super(AsyncManager, self), method)
pf = partial(_class_method, self._model_class)
return pf(*args, **kwargs)
def new(self, *args, **kwargs):
return self._do_fill('create', *args, **kwargs)
def get(self, *args, **kwargs):
return self._do_fill('get', *args, **kwargs)
def execute(self, query):
return execute(query)
def _get_meta_db_class(db):
"""creating a declartive class model for db"""
class _BlockedMeta(BaseModel):
def __new__(cls, name, bases, attrs):
_instance = super(_BlockedMeta, cls).__new__(cls, name, bases, attrs)
_instance.objects = AsyncManager(_instance, db)
return _instance
class _Base(Model, metaclass=_BlockedMeta):
def to_dict(self):
return self._data
class Meta:
database=db
return _Base
def declarative_base(*args, **kwargs):
"""Returns a new Modeled Class after inheriting meta and Model classes"""
db = PostgresqlDatabase(*args, **kwargs)
return _get_meta_db_class(db)
AsyncBaseModel = declarative_base(database='test',
host='127.0.0.1',
user='postgres',
password='mysecretpassword')
# let's create a simple key value store:
class KeyValue(AsyncBaseModel):
key = peewee.CharField(max_length=40, unique=True)
text = peewee.TextField(default='')
app = Sanic('peewee_example')
@app.route('/post/<key>/<value>')
async def post(request, key, value):
"""
Save get parameters to database
"""
obj = await KeyValue.objects.new(key=key, text=value)
return json({'object_id': obj.id})
@app.route('/get')
async def get(request):
"""
Load all objects from database
"""
all_objects = await KeyValue.objects.execute(KeyValue.select())
serialized_obj = []
for obj in all_objects:
serialized_obj.append({
'id': obj.id,
'key': obj.key,
'value': obj.text}
)
return json({'objects': serialized_obj})
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000)

View File

@@ -9,4 +9,5 @@ async def test(request):
return json({"test": True})
app.run(host="0.0.0.0", port=8000)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

View File

@@ -1,6 +1,8 @@
import os
from sanic import Sanic
from sanic.log import log
from sanic.response import json, text
from sanic.response import json, text, file
from sanic.exceptions import ServerError
app = Sanic(__name__)
@@ -31,6 +33,10 @@ async def test_await(request):
await asyncio.sleep(5)
return text("I'm feeling sleepy")
@app.route("/file")
async def test_file(request):
return await file(os.path.abspath("setup.py"))
# ----------------------------------------------- #
# Exceptions
@@ -64,12 +70,25 @@ def query_string(request):
# Run Server
# ----------------------------------------------- #
def after_start(loop):
@app.listener('before_server_start')
def before_start(app, loop):
log.info("SERVER STARTING")
@app.listener('after_server_start')
def after_start(app, loop):
log.info("OH OH OH OH OHHHHHHHH")
def before_stop(loop):
@app.listener('before_server_stop')
def before_stop(app, loop):
log.info("SERVER STOPPING")
@app.listener('after_server_stop')
def after_stop(app, loop):
log.info("TRIED EVERYTHING")
app.run(host="0.0.0.0", port=8000, debug=True, after_start=after_start, before_stop=before_stop)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000, debug=True)

39
examples/vhosts.py Normal file
View File

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

29
examples/websocket.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>WebSocket demo</title>
</head>
<body>
<script>
var ws = new WebSocket('ws://' + document.domain + ':' + location.port + '/feed'),
messages = document.createElement('ul');
ws.onmessage = function (event) {
var messages = document.getElementsByTagName('ul')[0],
message = document.createElement('li'),
content = document.createTextNode('Received: ' + event.data);
message.appendChild(content);
messages.appendChild(message);
};
document.body.appendChild(messages);
window.setInterval(function() {
data = 'bye!'
ws.send(data);
var messages = document.getElementsByTagName('ul')[0],
message = document.createElement('li'),
content = document.createTextNode('Sent: ' + data);
message.appendChild(content);
messages.appendChild(message);
}, 1000);
</script>
</body>
</html>

23
examples/websocket.py Normal file
View File

@@ -0,0 +1,23 @@
from sanic import Sanic
from sanic.response import file
app = Sanic(__name__)
@app.route('/')
async def index(request):
return await file('websocket.html')
@app.websocket('/feed')
async def feed(request, ws):
while True:
data = 'hello!'
print('Sending: ' + data)
await ws.send(data)
data = await ws.recv()
print('Received: ' + data)
if __name__ == '__main__':
app.run()

2
readthedocs.yml Normal file
View File

@@ -0,0 +1,2 @@
conda:
file: environment.yml

View File

@@ -1,11 +1,11 @@
aiofiles
aiohttp==1.3.5
chardet<=2.3.0
beautifulsoup4
coverage
httptools
flake8
pytest
tox
ujson
uvloop
aiohttp
pytest
coverage
tox
gunicorn
bottle
kyoukai
falcon

View File

@@ -1,3 +1,5 @@
aiofiles
httptools
ujson
uvloop
uvloop
websockets

View File

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

View File

@@ -1,13 +1,17 @@
from argparse import ArgumentParser
from importlib import import_module
from .log import log
from .sanic import Sanic
from sanic.log import log
from sanic.app import Sanic
if __name__ == "__main__":
parser = ArgumentParser(prog='sanic')
parser.add_argument('--host', dest='host', type=str, default='127.0.0.1')
parser.add_argument('--port', dest='port', type=int, default=8000)
parser.add_argument('--cert', dest='cert', type=str,
help='location of certificate for SSL')
parser.add_argument('--key', dest='key', type=str,
help='location of keyfile for SSL.')
parser.add_argument('--workers', dest='workers', type=int, default=1, )
parser.add_argument('--debug', dest='debug', action="store_true")
parser.add_argument('module')
@@ -20,13 +24,17 @@ if __name__ == "__main__":
module = import_module(module_name)
app = getattr(module, app_name, None)
if type(app) is not Sanic:
if not isinstance(app, Sanic):
raise ValueError("Module is not a Sanic app, it is a {}. "
"Perhaps you meant {}.app?"
.format(type(app).__name__, args.module))
if args.cert is not None or args.key is not None:
ssl = {'cert': args.cert, 'key': args.key}
else:
ssl = None
app.run(host=args.host, port=args.port,
workers=args.workers, debug=args.debug)
workers=args.workers, debug=args.debug, ssl=ssl)
except ImportError:
log.error("No module named {} found.\n"
" Example File: project/sanic_server.py -> app\n"

724
sanic/app.py Normal file
View File

@@ -0,0 +1,724 @@
import logging
import re
import warnings
from asyncio import get_event_loop, ensure_future, CancelledError
from collections import deque, defaultdict
from functools import partial
from inspect import isawaitable, stack, getmodulename
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.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.static import register as static_register
from sanic.testing import SanicTestClient
from sanic.views import CompositionView
from sanic.websocket import WebSocketProtocol, ConnectionClosed
class Sanic:
def __init__(self, name=None, router=None, error_handler=None,
load_env=True, request_class=None):
# 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:
formatter = logging.Formatter(
"%(asctime)s: %(levelname)s: %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
log.addHandler(handler)
log.setLevel(logging.INFO)
# Get name from previous stack frame
if name is None:
frame_records = stack()[1]
name = getmodulename(frame_records[1])
self.name = name
self.router = router or Router()
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env)
self.request_middleware = deque()
self.response_middleware = deque()
self.blueprints = {}
self._blueprint_order = []
self.debug = None
self.sock = None
self.listeners = defaultdict(list)
self.is_running = False
self.websocket_enabled = False
self.websocket_tasks = []
# Register alternative method names
self.go_fast = self.run
@property
def loop(self):
"""Synonymous with asyncio.get_event_loop().
Only supported when using the `app.run` method.
"""
if not self.is_running:
raise SanicException(
'Loop can only be retrieved after the app has started '
'running. Not supported with `create_server` function')
return get_event_loop()
# -------------------------------------------------------------------- #
# Registration
# -------------------------------------------------------------------- #
def add_task(self, task):
"""Schedule a task to run later, after the loop has started.
Different from asyncio.ensure_future in that it does not
also return a future, and the actual ensure_future call
is delayed until before server start.
:param task: future, couroutine or awaitable
"""
@self.listener('before_server_start')
def run(app, loop):
if callable(task):
loop.create_task(task())
else:
loop.create_task(task)
# Decorator
def listener(self, event):
"""Create a listener from a decorated function.
:param event: event to listen to
"""
def decorator(listener):
self.listeners[event].append(listener)
return listener
return decorator
# Decorator
def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False):
"""Decorate a function to be registered as a route
:param uri: path of the URL
:param methods: list or tuple of methods allowed
:param host:
:return: decorated function
"""
# Fix case where the user did not prefix the URL with a /
# and will probably get confused as to why it's not working
if not uri.startswith('/'):
uri = '/' + uri
def response(handler):
self.router.add(uri=uri, methods=methods, handler=handler,
host=host, strict_slashes=strict_slashes)
return handler
return response
# Shorthand method decorators
def get(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"GET"}), host=host,
strict_slashes=strict_slashes)
def post(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"POST"}), host=host,
strict_slashes=strict_slashes)
def put(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"PUT"}), host=host,
strict_slashes=strict_slashes)
def head(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
strict_slashes=strict_slashes)
def options(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
strict_slashes=strict_slashes)
def patch(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
strict_slashes=strict_slashes)
def delete(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
strict_slashes=strict_slashes)
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False):
"""A helper method to register class instance or
functions as a handler to the application url
routes.
:param handler: function or class instance
:param uri: path of the URL
:param methods: list or tuple of methods allowed, these are overridden
if using a HTTPMethodView
:param host:
:return: function or class instance
"""
# Handle HTTPMethodView differently
if hasattr(handler, 'view_class'):
methods = set()
for method in HTTP_METHODS:
if getattr(handler.view_class, method.lower(), None):
methods.add(method)
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
self.route(uri=uri, methods=methods, host=host,
strict_slashes=strict_slashes)(handler)
return handler
# Decorator
def websocket(self, uri, host=None, strict_slashes=False):
"""Decorate a function to be registered as a websocket route
:param uri: path of the URL
:param host:
:return: decorated function
"""
self.enable_websocket()
# Fix case where the user did not prefix the URL with a /
# and will probably get confused as to why it's not working
if not uri.startswith('/'):
uri = '/' + uri
def response(handler):
async def websocket_handler(request, *args, **kwargs):
request.app = self
protocol = request.transport.get_protocol()
ws = await protocol.websocket_handshake(request)
# schedule the application handler
# its future is kept in self.websocket_tasks in case it
# needs to be cancelled due to the server being stopped
fut = ensure_future(handler(request, ws, *args, **kwargs))
self.websocket_tasks.append(fut)
try:
await fut
except (CancelledError, ConnectionClosed):
pass
self.websocket_tasks.remove(fut)
await ws.close()
self.router.add(uri=uri, handler=websocket_handler,
methods=frozenset({'GET'}), host=host,
strict_slashes=strict_slashes)
return handler
return response
def add_websocket_route(self, handler, uri, host=None,
strict_slashes=False):
"""A helper method to register a function as a websocket route."""
return self.websocket(uri, host=host,
strict_slashes=strict_slashes)(handler)
def enable_websocket(self, enable=True):
"""Enable or disable the support for websocket.
Websocket is enabled automatically if websocket routes are
added to the application.
"""
if not self.websocket_enabled:
# if the server is stopped, we want to cancel any ongoing
# websocket tasks, to allow the server to exit promptly
@self.listener('before_server_stop')
def cancel_websocket_tasks(app, loop):
for task in self.websocket_tasks:
task.cancel()
self.websocket_enabled = enable
def remove_route(self, uri, clean_cache=True, host=None):
self.router.remove(uri, clean_cache, host)
# Decorator
def exception(self, *exceptions):
"""Decorate a function to be registered as a handler for exceptions
:param exceptions: exceptions
:return: decorated function
"""
def response(handler):
for exception in exceptions:
if isinstance(exception, (tuple, list)):
for e in exception:
self.error_handler.add(e, handler)
else:
self.error_handler.add(exception, handler)
return handler
return response
# Decorator
def middleware(self, middleware_or_request):
"""Decorate and register middleware to be called before a request.
Can either be called as @app.middleware or @app.middleware('request')
"""
def register_middleware(middleware, attach_to='request'):
if attach_to == 'request':
self.request_middleware.append(middleware)
if attach_to == 'response':
self.response_middleware.appendleft(middleware)
return middleware
# Detect which way this was called, @middleware or @middleware('AT')
if callable(middleware_or_request):
return register_middleware(middleware_or_request)
else:
return partial(register_middleware,
attach_to=middleware_or_request)
# Static Files
def static(self, uri, file_or_directory, pattern='.+',
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
"""
static_register(self, uri, file_or_directory, pattern,
use_modified_since, use_content_range)
def blueprint(self, blueprint, **options):
"""Register a blueprint on the application.
:param blueprint: Blueprint object
:param options: option dictionary with blueprint defaults
:return: Nothing
"""
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint with the name "%s" is already registered. ' \
'Blueprint names must be unique.' % \
(blueprint.name,)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
blueprint.register(self, options)
def register_blueprint(self, *args, **kwargs):
# TODO: deprecate 1.0
if self.debug:
warnings.simplefilter('default')
warnings.warn("Use of register_blueprint will be deprecated in "
"version 1.0. Please use the blueprint method"
" instead",
DeprecationWarning)
return self.blueprint(*args, **kwargs)
def url_for(self, view_name: str, **kwargs):
"""Build a URL based on a view name and the values provided.
In order to build a URL, all request parameters must be supplied as
keyword arguments, and each parameter must pass the test for the
specified parameter type. If these conditions are not met, a
`URLBuildError` will be thrown.
Keyword arguments that are not request parameters will be included in
the output URL's query string.
:param view_name: string referencing the view name
:param \*\*kwargs: keys and values that are used to build request
parameters and query string arguments.
:return: the built URL
Raises:
URLBuildError
"""
# find the route by the supplied view name
uri, route = self.router.find_route_by_view_name(view_name)
if not uri or not route:
raise URLBuildError(
'Endpoint with name `{}` was not found'.format(
view_name))
if uri != '/' and uri.endswith('/'):
uri = uri[:-1]
out = uri
# find all the parameters we will need to build in the URL
matched_params = re.findall(
self.router.parameter_pattern, uri)
# _method is only a placeholder now, don't know how to support it
kwargs.pop('_method', None)
anchor = kwargs.pop('_anchor', '')
# _external need SERVER_NAME in config or pass _server arg
external = kwargs.pop('_external', False)
scheme = kwargs.pop('_scheme', '')
if scheme and not external:
raise ValueError('When specifying _scheme, _external must be True')
netloc = kwargs.pop('_server', None)
if netloc is None and external:
netloc = self.config.get('SERVER_NAME', '')
for match in matched_params:
name, _type, pattern = self.router.parse_parameter_string(
match)
# we only want to match against each individual parameter
specific_pattern = '^{}$'.format(pattern)
supplied_param = None
if kwargs.get(name):
supplied_param = kwargs.get(name)
del kwargs[name]
else:
raise URLBuildError(
'Required parameter `{}` was not passed to url_for'.format(
name))
supplied_param = str(supplied_param)
# determine if the parameter supplied by the caller passes the test
# in the URL
passes_pattern = re.match(specific_pattern, supplied_param)
if not passes_pattern:
if _type != str:
msg = (
'Value "{}" for parameter `{}` does not '
'match pattern for type `{}`: {}'.format(
supplied_param, name, _type.__name__, pattern))
else:
msg = (
'Value "{}" for parameter `{}` '
'does not satisfy pattern {}'.format(
supplied_param, name, pattern))
raise URLBuildError(msg)
# replace the parameter in the URL with the supplied value
replacement_regex = '(<{}.*?>)'.format(name)
out = re.sub(
replacement_regex, supplied_param, out)
# parse the remainder of the keyword arguments into a querystring
query_string = urlencode(kwargs, doseq=True) if kwargs else ''
# scheme://netloc/path;parameters?query#fragment
out = urlunparse((scheme, netloc, out, '', query_string, anchor))
return out
# -------------------------------------------------------------------- #
# Request Handling
# -------------------------------------------------------------------- #
def converted_response_type(self, response):
pass
async def handle_request(self, request, write_callback, stream_callback):
"""Take a request from the HTTP Server and return a response object
to be sent back The HTTP Server only expects a response object, so
exception handling must be done here
:param request: HTTP Request object
:param write_callback: Synchronous response function to be
called with the response as the only argument
:param stream_callback: Coroutine that handles streaming a
StreamingHTTPResponse if produced by the handler.
:return: Nothing
"""
try:
# -------------------------------------------- #
# Request Middleware
# -------------------------------------------- #
request.app = self
response = await self._run_request_middleware(request)
# No middleware results
if not response:
# -------------------------------------------- #
# Execute Handler
# -------------------------------------------- #
# Fetch handler from router
handler, args, kwargs = self.router.get(request)
if handler is None:
raise ServerError(
("'None' was returned while requesting a "
"handler from the router"))
# Run response handler
response = handler(request, *args, **kwargs)
if isawaitable(response):
response = await response
except Exception as e:
# -------------------------------------------- #
# Response Generation Failed
# -------------------------------------------- #
try:
response = self.error_handler.response(request, e)
if isawaitable(response):
response = await response
except Exception as e:
if self.debug:
response = HTTPResponse(
"Error while handling error: {}\nStack: {}".format(
e, format_exc()))
else:
response = HTTPResponse(
"An error occurred while handling an error")
finally:
# -------------------------------------------- #
# Response Middleware
# -------------------------------------------- #
try:
response = await self._run_response_middleware(request,
response)
except:
log.exception(
'Exception occured in one of response middleware handlers'
)
# pass the response to the correct callback
if isinstance(response, StreamingHTTPResponse):
await stream_callback(response)
else:
write_callback(response)
# -------------------------------------------------------------------- #
# Testing
# -------------------------------------------------------------------- #
@property
def test_client(self):
return SanicTestClient(self)
# -------------------------------------------------------------------- #
# 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):
"""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 protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
if stop_event is not None:
if debug:
warnings.simplefilter('default')
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)
try:
self.is_running = True
if workers == 1:
serve(**server_settings)
else:
serve_multiple(server_settings, workers)
except:
log.exception(
'Experienced exception while trying to serve')
finally:
self.is_running = False
log.info("Server Stopped")
def stop(self):
"""This kills the Sanic"""
get_event_loop().stop()
def __call__(self):
"""gunicorn compatibility"""
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):
"""Asynchronous version of `run`.
NOTE: This does not support multiprocessing and is not the preferred
way to run a Sanic application.
"""
if protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol)
if stop_event is not None:
if debug:
warnings.simplefilter('default')
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)
return await serve(**server_settings)
async def _run_request_middleware(self, request):
# The if improves speed. I don't know why
if self.request_middleware:
for middleware in self.request_middleware:
response = middleware(request)
if isawaitable(response):
response = await response
if response:
return response
return None
async def _run_response_middleware(self, request, response):
if self.response_middleware:
for middleware in self.response_middleware:
_response = middleware(request, response)
if isawaitable(_response):
_response = await _response
if _response:
response = _response
break
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,
protocol=HttpProtocol, backlog=100, stop_event=None,
register_sys_signals=True, run_async=False):
"""Helper function used by `run` and `create_server`."""
if isinstance(ssl, dict):
# try common aliaseses
cert = ssl.get('cert') or ssl.get('certificate')
key = ssl.get('key') or ssl.get('keyfile')
if cert is None or key is None:
raise ValueError("SSLContext or certificate and key required.")
context = create_default_context(purpose=Purpose.CLIENT_AUTH)
context.load_cert_chain(cert, keyfile=key)
ssl = context
if stop_event is not None:
if debug:
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
server_settings = {
'protocol': protocol,
'request_class': self.request_class,
'host': host,
'port': port,
'sock': sock,
'ssl': ssl,
'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,
'loop': loop,
'register_sys_signals': register_sys_signals,
'backlog': backlog
}
# -------------------------------------------- #
# 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),
):
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
listeners = [partial(listener, self) for listener in listeners]
server_settings[settings_name] = listeners
if debug:
log.setLevel(logging.DEBUG)
if self.config.LOGO is not None:
log.debug(self.config.LOGO)
if run_async:
server_settings['run_async'] = True
# Serve
if host and port:
proto = "http"
if ssl is not None:
proto = "https"
log.info('Goin\' Fast @ {}://{}:{}'.format(proto, host, port))
return server_settings

View File

@@ -1,97 +1,219 @@
class BlueprintSetup:
"""
"""
from collections import defaultdict, namedtuple
def __init__(self, blueprint, app, options):
self.app = app
self.blueprint = blueprint
self.options = options
from sanic.constants import HTTP_METHODS
from sanic.views import CompositionView
url_prefix = self.options.get('url_prefix')
if url_prefix is None:
url_prefix = self.blueprint.url_prefix
#: The prefix that should be used for all URLs defined on the
#: blueprint.
self.url_prefix = url_prefix
def add_route(self, handler, uri, methods):
"""
A helper method to register a handler to the application url routes.
"""
if self.url_prefix:
uri = self.url_prefix + uri
self.app.router.add(uri, methods, handler)
def add_exception(self, handler, *args, **kwargs):
"""
Registers exceptions to sanic
"""
self.app.exception(*args, **kwargs)(handler)
def add_middleware(self, middleware, *args, **kwargs):
"""
Registers middleware to sanic
"""
if args or kwargs:
self.app.middleware(*args, **kwargs)(middleware)
else:
self.app.middleware(middleware)
FutureRoute = namedtuple('Route',
['handler', 'uri', 'methods',
'host', 'strict_slashes'])
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
FutureStatic = namedtuple('Route',
['uri', 'file_or_directory', 'args', 'kwargs'])
class Blueprint:
def __init__(self, name, url_prefix=None):
def __init__(self, name, url_prefix=None, host=None):
"""Create a new blueprint
:param name: unique name of the blueprint
:param url_prefix: URL to be prefixed before all route URLs
"""
self.name = name
self.url_prefix = url_prefix
self.deferred_functions = []
self.host = host
def record(self, func):
"""
Registers a callback function that is invoked when the blueprint is
registered on the application.
"""
self.deferred_functions.append(func)
def make_setup_state(self, app, options):
"""
"""
return BlueprintSetup(self, app, options)
self.routes = []
self.websocket_routes = []
self.exceptions = []
self.listeners = defaultdict(list)
self.middlewares = []
self.statics = []
def register(self, app, options):
"""
"""
state = self.make_setup_state(app, options)
for deferred in self.deferred_functions:
deferred(state)
"""Register the blueprint to the sanic app."""
def route(self, uri, methods=None):
"""
url_prefix = options.get('url_prefix', self.url_prefix)
# Routes
for future in self.routes:
# attach the blueprint name to the handler so that it can be
# prefixed properly in the router
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
app.route(
uri=uri[1:] if uri.startswith('//') else uri,
methods=future.methods,
host=future.host or self.host,
strict_slashes=future.strict_slashes
)(future.handler)
for future in self.websocket_routes:
# attach the blueprint name to the handler so that it can be
# prefixed properly in the router
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
app.websocket(
uri=uri,
host=future.host or self.host,
strict_slashes=future.strict_slashes
)(future.handler)
# Middleware
for future in self.middlewares:
if future.args or future.kwargs:
app.middleware(*future.args,
**future.kwargs)(future.middleware)
else:
app.middleware(future.middleware)
# Exceptions
for future in self.exceptions:
app.exception(*future.args, **future.kwargs)(future.handler)
# Static Files
for future in self.statics:
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
app.static(uri, future.file_or_directory,
*future.args, **future.kwargs)
# Event listeners
for event, listeners in self.listeners.items():
for listener in listeners:
app.listener(event)(listener)
def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False):
"""Create a blueprint route from a decorated function.
:param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods.
"""
def decorator(handler):
self.record(lambda s: s.add_route(handler, uri, methods))
route = FutureRoute(handler, uri, methods, host, strict_slashes)
self.routes.append(route)
return handler
return decorator
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=False):
"""Create a blueprint route from a function.
:param handler: function for handling uri requests. Accepts function,
or class instance with a view_class method.
:param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods.
:return: function or class instance
"""
# Handle HTTPMethodView differently
if hasattr(handler, 'view_class'):
methods = set()
for method in HTTP_METHODS:
if getattr(handler.view_class, method.lower(), None):
methods.add(method)
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
self.route(uri=uri, methods=methods, host=host,
strict_slashes=strict_slashes)(handler)
return handler
def websocket(self, uri, host=None, strict_slashes=False):
"""Create a blueprint websocket route from a decorated function.
:param uri: endpoint at which the route will be accessible.
"""
def decorator(handler):
route = FutureRoute(handler, uri, [], host, strict_slashes)
self.websocket_routes.append(route)
return handler
return decorator
def add_websocket_route(self, handler, uri, host=None):
"""Create a blueprint websocket route from a function.
:param handler: function for handling uri requests. Accepts function,
or class instance with a view_class method.
:param uri: endpoint at which the route will be accessible.
:return: function or class instance
"""
self.websocket(uri=uri, host=host)(handler)
return handler
def listener(self, event):
"""Create a listener from a decorated function.
:param event: Event to listen to.
"""
def decorator(listener):
self.listeners[event].append(listener)
return listener
return decorator
def middleware(self, *args, **kwargs):
"""
"""
def register_middleware(middleware):
self.record(
lambda s: s.add_middleware(middleware, *args, **kwargs))
return middleware
"""Create a blueprint middleware from a decorated function."""
def register_middleware(_middleware):
future_middleware = FutureMiddleware(_middleware, args, kwargs)
self.middlewares.append(future_middleware)
return _middleware
# Detect which way this was called, @middleware or @middleware('AT')
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
middleware = args[0]
args = []
return register_middleware(args[0])
return register_middleware(middleware)
else:
return register_middleware
def exception(self, *args, **kwargs):
"""
"""
"""Create a blueprint exception from a decorated function."""
def decorator(handler):
self.record(lambda s: s.add_exception(handler, *args, **kwargs))
exception = FutureException(handler, args, kwargs)
self.exceptions.append(exception)
return handler
return decorator
def static(self, uri, file_or_directory, *args, **kwargs):
"""Create a blueprint static route from a decorated function.
:param uri: endpoint at which the route will be accessible.
:param file_or_directory: Static asset.
"""
static = FutureStatic(uri, file_or_directory, args, kwargs)
self.statics.append(static)
# Shorthand method decorators
def get(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["GET"], host=host,
strict_slashes=strict_slashes)
def post(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["POST"], host=host,
strict_slashes=strict_slashes)
def put(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["PUT"], host=host,
strict_slashes=strict_slashes)
def head(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["HEAD"], host=host,
strict_slashes=strict_slashes)
def options(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["OPTIONS"], host=host,
strict_slashes=strict_slashes)
def patch(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["PATCH"], host=host,
strict_slashes=strict_slashes)
def delete(self, uri, host=None, strict_slashes=False):
return self.route(uri, methods=["DELETE"], host=host,
strict_slashes=strict_slashes)

View File

@@ -1,5 +1,14 @@
class Config:
LOGO = """
import os
import types
SANIC_PREFIX = 'SANIC_'
class Config(dict):
def __init__(self, defaults=None, load_env=True):
super().__init__(defaults or {})
self.LOGO = """
▄▄▄▄▄
▀▀▀██████▄▄▄ _______________
▄▄▄▄▄ █████████▄ / \\
@@ -20,5 +29,80 @@ class Config:
▌ ▐ ▀▀▄▄▄▀
▀▀▄▄▀
"""
REQUEST_MAX_SIZE = 100000000 # 100 megababies
REQUEST_TIMEOUT = 60 # 60 seconds
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
self.REQUEST_TIMEOUT = 60 # 60 seconds
if load_env:
self.load_environment_vars()
def __getattr__(self, attr):
try:
return self[attr]
except KeyError as ke:
raise AttributeError("Config has no '{}'".format(ke.args[0]))
def __setattr__(self, attr, value):
self[attr] = value
def from_envvar(self, variable_name):
"""Load a configuration from an environment variable pointing to
a configuration file.
:param variable_name: name of the environment variable
:return: bool. ``True`` if able to load config, ``False`` otherwise.
"""
config_file = os.environ.get(variable_name)
if not config_file:
raise RuntimeError('The environment variable %r is not set and '
'thus configuration could not be loaded.' %
variable_name)
return self.from_pyfile(config_file)
def from_pyfile(self, filename):
"""Update the values in the config from a Python file.
Only the uppercase variables in that module are stored in the config.
:param filename: an absolute path to the config file
"""
module = types.ModuleType('config')
module.__file__ = filename
try:
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'),
module.__dict__)
except IOError as e:
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
raise
self.from_object(module)
return True
def from_object(self, obj):
"""Update the values from the given object.
Objects are usually either modules or classes.
Just the uppercase variables in that object are stored in the config.
Example usage::
from yourapplication import default_config
app.config.from_object(default_config)
You should not use this function to load the actual configuration but
rather configuration defaults. The actual config should be loaded
with :meth:`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide.
:param obj: an object holding the configuration
"""
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
def load_environment_vars(self):
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

1
sanic/constants.py Normal file
View File

@@ -0,0 +1 @@
HTTP_METHODS = ('GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'PATCH', 'DELETE')

140
sanic/cookies.py Normal file
View File

@@ -0,0 +1,140 @@
import re
import string
# ------------------------------------------------------------ #
# SimpleCookie
# ------------------------------------------------------------ #
# Straight up copied this section of dark magic from SimpleCookie
_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:"
_UnescapedChars = _LegalChars + ' ()/<=>?@[]{}'
_Translator = {n: '\\%03o' % n
for n in set(range(256)) - set(map(ord, _UnescapedChars))}
_Translator.update({
ord('"'): '\\"',
ord('\\'): '\\\\',
})
def _quote(str):
"""Quote a string for use in a cookie header.
If the string does not need to be double-quoted, then just return the
string. Otherwise, surround the string in doublequotes and quote
(with a \) special characters.
"""
if str is None or _is_legal_key(str):
return str
else:
return '"' + str.translate(_Translator) + '"'
_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
# ------------------------------------------------------------ #
# Custom SimpleCookie
# ------------------------------------------------------------ #
class CookieJar(dict):
"""CookieJar dynamically writes headers as cookies are added and removed
It gets around the limitation of one header per name by using the
MultiHeader class to provide a unique key that encodes to Set-Cookie.
"""
def __init__(self, headers):
super().__init__()
self.headers = headers
self.cookie_headers = {}
def __setitem__(self, key, value):
# If this cookie doesn't exist, add it to the header keys
cookie_header = self.cookie_headers.get(key)
if not cookie_header:
cookie = Cookie(key, value)
cookie['path'] = '/'
cookie_header = MultiHeader("Set-Cookie")
self.cookie_headers[key] = cookie_header
self.headers[cookie_header] = cookie
return super().__setitem__(key, cookie)
else:
self[key].value = value
def __delitem__(self, key):
if key not in self.cookie_headers:
self[key] = ''
self[key]['max-age'] = 0
else:
cookie_header = self.cookie_headers[key]
del self.headers[cookie_header]
del self.cookie_headers[key]
return super().__delitem__(key)
class Cookie(dict):
"""A stripped down version of Morsel from SimpleCookie #gottagofast"""
_keys = {
"expires": "expires",
"path": "Path",
"comment": "Comment",
"domain": "Domain",
"max-age": "Max-Age",
"secure": "Secure",
"httponly": "HttpOnly",
"version": "Version",
}
_flags = {'secure', 'httponly'}
def __init__(self, key, value):
if key in self._keys:
raise KeyError("Cookie name is a reserved word")
if not _is_legal_key(key):
raise KeyError("Cookie key contains illegal characters")
self.key = key
self.value = value
super().__init__()
def __setitem__(self, key, value):
if key not in self._keys:
raise KeyError("Unknown cookie property")
return super().__setitem__(key, value)
def encode(self, encoding):
output = ['%s=%s' % (self.key, _quote(self.value))]
for key, value in self.items():
if key == 'max-age':
try:
output.append('%s=%d' % (self._keys[key], value))
except TypeError:
output.append('%s=%s' % (self._keys[key], value))
elif key == 'expires':
try:
output.append('%s=%s' % (
self._keys[key],
value.strftime("%a, %d-%b-%Y %T GMT")
))
except AttributeError:
output.append('%s=%s' % (self._keys[key], value))
elif key in self._flags:
if self[key]:
output.append(self._keys[key])
else:
output.append('%s=%s' % (self._keys[key], value))
return "; ".join(output).encode(encoding)
# ------------------------------------------------------------ #
# Header Trickery
# ------------------------------------------------------------ #
class MultiHeader:
"""String-holding object which allow us to set a header within response
that has a unique key, but may contain duplicate header names
"""
def __init__(self, name):
self.name = name
def encode(self):
return self.name.encode()

View File

@@ -1,10 +1,106 @@
from .response import text
from traceback import format_exc
TRACEBACK_STYLE = '''
<style>
body {
padding: 20px;
font-family: Arial, sans-serif;
}
p {
margin: 0;
}
.summary {
padding: 10px;
}
h1 {
margin-bottom: 0;
}
h3 {
margin-top: 10px;
}
h3 code {
font-size: 24px;
}
.frame-line > * {
padding: 5px 10px;
}
.frame-line {
margin-bottom: 5px;
}
.frame-code {
font-size: 16px;
padding-left: 30px;
}
.tb-wrapper {
border: 1px solid #f3f3f3;
}
.tb-header {
background-color: #f3f3f3;
padding: 5px 10px;
}
.frame-descriptor {
background-color: #e2eafb;
}
.frame-descriptor {
font-size: 14px;
}
</style>
'''
TRACEBACK_WRAPPER_HTML = '''
<html>
<head>
{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">
<b>{exc_name}: {exc_value}</b>
while handling path <code>{path}</code>
</p>
</div>
</body>
</html>
'''
TRACEBACK_LINE_HTML = '''
<div class="frame-line">
<p class="frame-descriptor">
File {0.filename}, line <i>{0.lineno}</i>,
in <code><b>{0.name}</b></code>
</p>
<p class="frame-code"><code>{0.line}</code></p>
</div>
'''
INTERNAL_SERVER_ERROR_HTML = '''
<h1>Internal Server Error</h1>
<p>
The server encountered an internal error and cannot complete
your request.
</p>
'''
class SanicException(Exception):
def __init__(self, message, status_code=None):
super().__init__(message)
if status_code is not None:
self.status_code = status_code
@@ -21,36 +117,41 @@ class ServerError(SanicException):
status_code = 500
class Handler:
handlers = None
class URLBuildError(SanicException):
status_code = 500
def __init__(self, sanic):
self.handlers = {}
self.sanic = sanic
def add(self, exception, handler):
self.handlers[exception] = handler
class FileNotFound(NotFound):
status_code = 404
def response(self, request, exception):
"""
Fetches and executes an exception handler and returns a response object
:param request: Request
:param exception: Exception to handle
:return: Response object
"""
handler = self.handlers.get(type(exception), self.default)
response = handler(request=request, exception=exception)
return response
def __init__(self, message, path, relative_url):
super().__init__(message)
self.path = path
self.relative_url = relative_url
def default(self, request, exception):
if issubclass(type(exception), SanicException):
return text(
"Error: {}".format(exception),
status=getattr(exception, 'status_code', 500))
elif self.sanic.debug:
return text(
"Error: {}\nException: {}".format(
exception, format_exc()), status=500)
else:
return text(
"An error occurred while generating the request", status=500)
class RequestTimeout(SanicException):
status_code = 408
class PayloadTooLarge(SanicException):
status_code = 413
class HeaderNotFound(SanicException):
status_code = 400
class ContentRangeError(SanicException):
status_code = 416
def __init__(self, message, content_range):
super().__init__(message)
self.headers = {
'Content-Type': 'text/plain',
"Content-Range": "bytes */%s" % (content_range.total,)
}
class InvalidRangeType(ContentRangeError):
pass

157
sanic/handlers.py Normal file
View File

@@ -0,0 +1,157 @@
import sys
from traceback import format_exc, extract_tb
from sanic.exceptions import (
ContentRangeError,
HeaderNotFound,
INTERNAL_SERVER_ERROR_HTML,
InvalidRangeType,
SanicException,
TRACEBACK_LINE_HTML,
TRACEBACK_STYLE,
TRACEBACK_WRAPPER_HTML)
from sanic.log import log
from sanic.response import text, html
class ErrorHandler:
handlers = None
cached_handlers = None
_missing = object()
def __init__(self):
self.handlers = []
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)
frame_html = []
for frame in frames:
frame_html.append(TRACEBACK_LINE_HTML.format(frame))
return TRACEBACK_WRAPPER_HTML.format(
style=TRACEBACK_STYLE,
exc_name=exc_type.__name__,
exc_value=exc_value,
frame_html=''.join(frame_html),
path=request.path)
def add(self, exception, handler):
self.handlers.append((exception, handler))
def lookup(self, exception):
handler = self.cached_handlers.get(exception, self._missing)
if handler is self._missing:
for exception_class, handler in self.handlers:
if isinstance(exception, exception_class):
self.cached_handlers[type(exception)] = handler
return handler
self.cached_handlers[type(exception)] = None
handler = None
return handler
def response(self, request, exception):
"""Fetches and executes an exception handler and returns a response
object
:param request: Request
:param exception: Exception to handle
:return: Response object
"""
handler = self.lookup(exception)
response = None
try:
if handler:
response = handler(request=request, exception=exception)
if response is None:
response = self.default(request=request, exception=exception)
except Exception:
self.log(format_exc())
if self.debug:
url = getattr(request, 'url', 'unknown')
response_message = (
'Exception raised in exception handler "{}" '
'for uri: "{}"\n{}').format(
handler.__name__, url, format_exc())
log.error(response_message)
return text(response_message, 500)
else:
return text('An error occurred while handling an error', 500)
return response
def log(self, message, level='error'):
"""
Override this method in an ErrorHandler subclass to prevent
logging exceptions.
"""
getattr(log, level)(message)
def default(self, request, exception):
self.log(format_exc())
if issubclass(type(exception), SanicException):
return text(
'Error: {}'.format(exception),
status=getattr(exception, 'status_code', 500),
headers=getattr(exception, 'headers', dict())
)
elif self.debug:
html_output = self._render_traceback_html(exception, request)
response_message = (
'Exception occurred while handling uri: "{}"\n{}'.format(
request.url, format_exc()))
log.error(response_message)
return html(html_output, status=500)
else:
return html(INTERNAL_SERVER_ERROR_HTML, status=500)
class ContentRangeHandler:
"""Class responsible for parsing request header"""
__slots__ = ('start', 'end', 'size', 'total', 'headers')
def __init__(self, request, stats):
self.total = stats.st_size
_range = request.headers.get('Range')
if _range is None:
raise HeaderNotFound('Range Header Not Found')
unit, _, value = tuple(map(str.strip, _range.partition('=')))
if unit != 'bytes':
raise InvalidRangeType(
'%s is not a valid Range Type' % (unit,), self)
start_b, _, end_b = tuple(map(str.strip, value.partition('-')))
try:
self.start = int(start_b) if start_b else None
except ValueError:
raise ContentRangeError(
'\'%s\' is invalid for Content Range' % (start_b,), self)
try:
self.end = int(end_b) if end_b else None
except ValueError:
raise ContentRangeError(
'\'%s\' is invalid for Content Range' % (end_b,), self)
if self.end is None:
if self.start is None:
raise ContentRangeError(
'Invalid for Content Range parameters', self)
else:
# this case represents `Content-Range: bytes 5-`
self.end = self.total
else:
if self.start is None:
# this case represents `Content-Range: bytes -5`
self.start = self.total - self.end
self.end = self.total
if self.start >= self.end:
raise ContentRangeError(
'Invalid for Content Range parameters', self)
self.size = self.end - self.start
self.headers = {
'Content-Range': "bytes %s-%s/%s" % (
self.start, self.end, self.total)}
def __bool__(self):
return self.size > 0

View File

@@ -1,5 +1,3 @@
import logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s: %(levelname)s: %(message)s")
log = logging.getLogger(__name__)
log = logging.getLogger('sanic')

View File

@@ -1,79 +1,97 @@
from cgi import parse_header
from collections import namedtuple
from http.cookies import SimpleCookie
from httptools import parse_url
from urllib.parse import parse_qs
from ujson import loads as json_loads
from urllib.parse import parse_qs, urlunparse
from .log import log
try:
from ujson import loads as json_loads
except ImportError:
from json import loads as json_loads
from sanic.exceptions import InvalidUsage
from sanic.log import log
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
# HTTP/1.1: https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
# > If the media type remains unknown, the recipient SHOULD treat it
# > as type "application/octet-stream"
class RequestParameters(dict):
"""
Hosts a dict with lists as values where get returns the first
"""Hosts a dict with lists as values where get returns the first
value of the list and getlist returns the whole shebang
"""
def __init__(self, *args, **kwargs):
self.super = super()
self.super.__init__(*args, **kwargs)
def get(self, name, default=None):
values = self.super.get(name)
return values[0] if values else default
"""Return the first value, either the default or actual"""
return super().get(name, [default])[0]
def getlist(self, name, default=None):
return self.super.get(name, default)
"""Return the entire list"""
return super().get(name, default)
class Request:
"""
Properties of an HTTP request such as URL, headers, etc.
"""
class Request(dict):
"""Properties of an HTTP request such as URL, headers, etc."""
__slots__ = (
'url', 'headers', 'version', 'method',
'query_string', 'body',
'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
'app', 'headers', 'version', 'method', '_cookies', 'transport',
'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files',
'_ip', '_parsed_url',
)
def __init__(self, url_bytes, headers, version, method):
def __init__(self, url_bytes, headers, version, method, transport):
# TODO: Content-Encoding detection
url_parsed = parse_url(url_bytes)
self.url = url_parsed.path.decode('utf-8')
self._parsed_url = parse_url(url_bytes)
self.app = None
self.headers = headers
self.version = version
self.method = method
self.query_string = None
if url_parsed.query:
self.query_string = url_parsed.query.decode('utf-8')
self.transport = transport
# Init but do not inhale
self.body = None
self.body = []
self.parsed_json = None
self.parsed_form = None
self.parsed_files = None
self.parsed_args = None
self._cookies = None
@property
def json(self):
if not self.parsed_json:
if self.parsed_json is None:
try:
self.parsed_json = json_loads(self.body)
except Exception:
pass
if not self.body:
return None
raise InvalidUsage("Failed when parsing body as json")
return self.parsed_json
@property
def token(self):
"""Attempt to return the auth header token.
: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
@property
def form(self):
if self.parsed_form is None:
self.parsed_form = {}
self.parsed_files = {}
content_type, parameters = parse_header(
self.headers.get('Content-Type'))
self.parsed_form = RequestParameters()
self.parsed_files = RequestParameters()
content_type = self.headers.get(
'Content-Type', DEFAULT_HTTP_CONTENT_TYPE)
content_type, parameters = parse_header(content_type)
try:
is_url_encoded = (
content_type == 'application/x-www-form-urlencoded')
if content_type is None or is_url_encoded:
if content_type == 'application/x-www-form-urlencoded':
self.parsed_form = RequestParameters(
parse_qs(self.body.decode('utf-8')))
elif content_type == 'multipart/form-data':
@@ -81,9 +99,8 @@ class Request:
boundary = parameters['boundary'].encode('utf-8')
self.parsed_form, self.parsed_files = (
parse_multipart_form(self.body, boundary))
except Exception as e:
log.exception(e)
pass
except Exception:
log.exception("Failed when parsing form")
return self.parsed_form
@@ -101,23 +118,85 @@ class Request:
self.parsed_args = RequestParameters(
parse_qs(self.query_string))
else:
self.parsed_args = {}
self.parsed_args = RequestParameters()
return self.parsed_args
@property
def raw_args(self):
return {k: v[0] for k, v in self.args.items()}
@property
def cookies(self):
if self._cookies is None:
cookie = self.headers.get('Cookie') or self.headers.get('cookie')
if cookie is not None:
cookies = SimpleCookie()
cookies.load(cookie)
self._cookies = {name: cookie.value
for name, cookie in cookies.items()}
else:
self._cookies = {}
return self._cookies
@property
def ip(self):
if not hasattr(self, '_ip'):
self._ip = self.transport.get_extra_info('peername')
return self._ip
@property
def scheme(self):
if self.app.websocket_enabled \
and self.headers.get('upgrade') == 'websocket':
scheme = 'ws'
else:
scheme = 'http'
if self.transport.get_extra_info('sslcontext'):
scheme += 's'
return scheme
@property
def host(self):
# it appears that httptools doesn't return the host
# so pull it from the headers
return self.headers.get('Host', '')
@property
def path(self):
return self._parsed_url.path.decode('utf-8')
@property
def query_string(self):
if self._parsed_url.query:
return self._parsed_url.query.decode('utf-8')
else:
return ''
@property
def url(self):
return urlunparse((
self.scheme,
self.host,
self.path,
None,
self.query_string,
None))
File = namedtuple('File', ['type', 'body', 'name'])
def parse_multipart_form(body, boundary):
"""Parse a request body and returns fields and files
:param body: bytes request body
:param boundary: bytes multipart boundary
:return: fields (RequestParameters), files (RequestParameters)
"""
Parses a request body and returns fields and files
:param body: Bytes request body
:param boundary: Bytes multipart boundary
:return: fields (dict), files (dict)
"""
files = {}
fields = {}
files = RequestParameters()
fields = RequestParameters()
form_parts = body.split(boundary)
for form_part in form_parts[1:-1]:
@@ -148,9 +227,16 @@ def parse_multipart_form(body, boundary):
post_data = form_part[line_index:-4]
if file_name or file_type:
files[field_name] = File(
type=file_type, name=file_name, body=post_data)
file = File(type=file_type, name=file_name, body=post_data)
if field_name in files:
files[field_name].append(file)
else:
files[field_name] = [file]
else:
fields[field_name] = post_data.decode('utf-8')
value = post_data.decode('utf-8')
if field_name in fields:
fields[field_name].append(value)
else:
fields[field_name] = [value]
return fields, files

View File

@@ -1,77 +1,360 @@
import ujson
from mimetypes import guess_type
from os import path
STATUS_CODES = {
try:
from ujson import dumps as json_dumps
except:
from json import dumps as json_dumps
from aiofiles import open as open_async
from sanic.cookies import CookieJar
COMMON_STATUS_CODES = {
200: b'OK',
400: b'Bad Request',
404: b'Not Found',
500: b'Internal Server Error',
}
ALL_STATUS_CODES = {
100: b'Continue',
101: b'Switching Protocols',
102: b'Processing',
200: b'OK',
201: b'Created',
202: b'Accepted',
203: b'Non-Authoritative Information',
204: b'No Content',
205: b'Reset Content',
206: b'Partial Content',
207: b'Multi-Status',
208: b'Already Reported',
226: b'IM Used',
300: b'Multiple Choices',
301: b'Moved Permanently',
302: b'Found',
303: b'See Other',
304: b'Not Modified',
305: b'Use Proxy',
307: b'Temporary Redirect',
308: b'Permanent Redirect',
400: b'Bad Request',
401: b'Unauthorized',
402: b'Payment Required',
403: b'Forbidden',
404: b'Not Found',
405: b'Method Not Allowed',
406: b'Not Acceptable',
407: b'Proxy Authentication Required',
408: b'Request Timeout',
409: b'Conflict',
410: b'Gone',
411: b'Length Required',
412: b'Precondition Failed',
413: b'Request Entity Too Large',
414: b'Request-URI Too Long',
415: b'Unsupported Media Type',
416: b'Requested Range Not Satisfiable',
417: b'Expectation Failed',
422: b'Unprocessable Entity',
423: b'Locked',
424: b'Failed Dependency',
426: b'Upgrade Required',
428: b'Precondition Required',
429: b'Too Many Requests',
431: b'Request Header Fields Too Large',
500: b'Internal Server Error',
501: b'Not Implemented',
502: b'Bad Gateway',
503: b'Service Unavailable',
504: b'Gateway Timeout',
505: b'HTTP Version Not Supported',
506: b'Variant Also Negotiates',
507: b'Insufficient Storage',
508: b'Loop Detected',
510: b'Not Extended',
511: b'Network Authentication Required'
}
class HTTPResponse:
__slots__ = ('body', 'status', 'content_type', 'headers')
class BaseHTTPResponse:
def _encode_body(self, data):
try:
# Try to encode it regularly
return data.encode()
except AttributeError:
# Convert it to a str if you can't
return str(data).encode()
def _parse_headers(self):
headers = b''
for name, value in self.headers.items():
try:
headers += (
b'%b: %b\r\n' % (
name.encode(), value.encode('utf-8')))
except AttributeError:
headers += (
b'%b: %b\r\n' % (
str(name).encode(), str(value).encode('utf-8')))
return headers
@property
def cookies(self):
if self._cookies is None:
self._cookies = CookieJar(self.headers)
return self._cookies
class StreamingHTTPResponse(BaseHTTPResponse):
__slots__ = (
'transport', 'streaming_fn',
'status', 'content_type', 'headers', '_cookies')
def __init__(self, streaming_fn, status=200, headers=None,
content_type='text/plain'):
self.content_type = content_type
self.streaming_fn = streaming_fn
self.status = status
self.headers = headers or {}
self._cookies = None
def write(self, data):
"""Writes a chunk of data to the streaming response.
:param data: bytes-ish data to be written.
"""
if type(data) != bytes:
data = self._encode_body(data)
self.transport.write(
b"%x\r\n%b\r\n" % (len(data), data))
async def stream(
self, version="1.1", keep_alive=False, keep_alive_timeout=None):
"""Streams headers, runs the `streaming_fn` callback that writes
content to the response body, then finalizes the response body.
"""
headers = self.get_headers(
version, keep_alive=keep_alive,
keep_alive_timeout=keep_alive_timeout)
self.transport.write(headers)
await self.streaming_fn(self)
self.transport.write(b'0\r\n\r\n')
def get_headers(
self, version="1.1", keep_alive=False, keep_alive_timeout=None):
# This is all returned in a kind-of funky way
# We tried to make this as fast as possible in pure python
timeout_header = b''
if keep_alive and keep_alive_timeout is not None:
timeout_header = b'Keep-Alive: %d\r\n' % keep_alive_timeout
self.headers['Transfer-Encoding'] = 'chunked'
self.headers.pop('Content-Length', None)
self.headers['Content-Type'] = self.headers.get(
'Content-Type', self.content_type)
headers = self._parse_headers()
# Try to pull from the common codes first
# 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)
return (b'HTTP/%b %d %b\r\n'
b'%b'
b'%b\r\n') % (
version.encode(),
self.status,
status,
timeout_header,
headers
)
class HTTPResponse(BaseHTTPResponse):
__slots__ = ('body', 'status', 'content_type', 'headers', '_cookies')
def __init__(self, body=None, status=200, headers=None,
content_type='text/plain', body_bytes=b''):
self.content_type = content_type
if body is not None:
self.body = body.encode('utf-8')
self.body = self._encode_body(body)
else:
self.body = body_bytes
self.status = status
self.headers = headers or {}
self._cookies = None
def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None):
def output(
self, version="1.1", keep_alive=False, keep_alive_timeout=None):
# This is all returned in a kind-of funky way
# We tried to make this as fast as possible in pure python
timeout_header = b''
if keep_alive and keep_alive_timeout:
timeout_header = b'Keep-Alive: timeout=%d\r\n' % keep_alive_timeout
if keep_alive and keep_alive_timeout is not None:
timeout_header = b'Keep-Alive: %d\r\n' % keep_alive_timeout
self.headers['Content-Length'] = self.headers.get(
'Content-Length', len(self.body))
self.headers['Content-Type'] = self.headers.get(
'Content-Type', self.content_type)
headers = self._parse_headers()
# Try to pull from the common codes first
# 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)
headers = b''
if self.headers:
headers = b''.join(
b'%b: %b\r\n' % (name.encode(), value.encode('utf-8'))
for name, value in self.headers.items()
)
return (b'HTTP/%b %d %b\r\n'
b'Content-Type: %b\r\n'
b'Content-Length: %d\r\n'
b'Connection: %b\r\n'
b'%b%b\r\n'
b'%b'
b'%b\r\n'
b'%b') % (
version.encode(),
self.status,
STATUS_CODES.get(self.status, b'FAIL'),
self.content_type.encode(),
len(self.body),
b'keep-alive' if keep_alive else b'close',
timeout_header,
headers,
self.body
)
version.encode(),
self.status,
status,
b'keep-alive' if keep_alive else b'close',
timeout_header,
headers,
self.body
)
@property
def cookies(self):
if self._cookies is None:
self._cookies = CookieJar(self.headers)
return self._cookies
def json(body, status=200, headers=None):
return HTTPResponse(ujson.dumps(body), headers=headers, status=status,
content_type="application/json; charset=utf-8")
def json(body, status=200, headers=None, **kwargs):
"""
Returns response object with body in json format.
:param body: Response data to be serialized.
:param status: Response code.
:param headers: Custom Headers.
:param kwargs: Remaining arguments that are passed to the json encoder.
"""
return HTTPResponse(json_dumps(body, **kwargs), headers=headers,
status=status, content_type="application/json")
def text(body, status=200, headers=None):
return HTTPResponse(body, status=status, headers=headers,
content_type="text/plain; charset=utf-8")
def text(body, status=200, headers=None,
content_type="text/plain; charset=utf-8"):
"""
Returns response object with body in text format.
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
:param content_type: the content type (string) of the response
"""
return HTTPResponse(
body, status=status, headers=headers,
content_type=content_type)
def raw(body, status=200, headers=None,
content_type="application/octet-stream"):
"""
Returns response object without encoding the body.
:param body: Response data.
:param status: Response code.
:param headers: Custom Headers.
:param content_type: the content type (string) of the response.
"""
return HTTPResponse(body_bytes=body, status=status, headers=headers,
content_type=content_type)
def html(body, status=200, headers=None):
"""
Returns response object with body in html format.
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
"""
return HTTPResponse(body, status=status, headers=headers,
content_type="text/html; charset=utf-8")
async def file(location, mime_type=None, headers=None, _range=None):
"""Return a response object with file data.
:param location: Location of file on system.
:param mime_type: Specific mime_type.
:param headers: Custom Headers.
:param _range:
"""
filename = path.split(location)[-1]
async with open_async(location, mode='rb') as _file:
if _range:
await _file.seek(_range.start)
out_stream = await _file.read(_range.size)
headers['Content-Range'] = 'bytes %s-%s/%s' % (
_range.start, _range.end, _range.total)
else:
out_stream = await _file.read()
mime_type = mime_type or guess_type(filename)[0] or 'text/plain'
return HTTPResponse(status=200,
headers=headers,
content_type=mime_type,
body_bytes=out_stream)
def stream(
streaming_fn, status=200, headers=None,
content_type="text/plain; charset=utf-8"):
"""Accepts an coroutine `streaming_fn` which can be used to
write chunks to a streaming response. Returns a `StreamingHTTPResponse`.
Example usage::
@app.route("/")
async def index(request):
async def streaming_fn(response):
await response.write('foo')
await response.write('bar')
return stream(streaming_fn, content_type='text/plain')
:param streaming_fn: A coroutine accepts a response and
writes content to that response.
:param mime_type: Specific mime_type.
:param headers: Custom Headers.
"""
return StreamingHTTPResponse(
streaming_fn,
headers=headers,
content_type=content_type,
status=status
)
def redirect(to, headers=None, status=302,
content_type="text/html; charset=utf-8"):
"""Abort execution and cause a 302 redirect (by default).
:param to: path or fully qualified URL to redirect to
:param headers: optional dict of headers to include in the new request
:param status: status code (int) of the new request, defaults to 302
:param content_type: the content type (string) of the response
:returns: the redirecting Response
"""
headers = headers or {}
# According to RFC 7231, a relative URI is now permitted.
headers['Location'] = to
return HTTPResponse(
status=status,
headers=headers,
content_type=content_type)

View File

@@ -1,146 +1,345 @@
import re
from collections import namedtuple
from .exceptions import NotFound, InvalidUsage
from collections import defaultdict, namedtuple
from collections.abc import Iterable
from functools import lru_cache
Route = namedtuple("Route", ['handler', 'methods', 'pattern', 'parameters'])
Parameter = namedtuple("Parameter", ['name', 'cast'])
from sanic.exceptions import NotFound, InvalidUsage
from sanic.views import CompositionView
Route = namedtuple(
'Route',
['handler', 'methods', 'pattern', 'parameters', 'name'])
Parameter = namedtuple('Parameter', ['name', 'cast'])
REGEX_TYPES = {
'string': (str, r'[^/]+'),
'int': (int, r'\d+'),
'number': (float, r'[0-9\\.]+'),
'alpha': (str, r'[A-Za-z]+'),
}
ROUTER_CACHE_SIZE = 1024
def url_hash(url):
return url.count('/')
class RouteExists(Exception):
pass
class RouteDoesNotExist(Exception):
pass
class Router:
"""
Router supports basic routing with parameters and method checks
"""Router supports basic routing with parameters and method checks
Usage:
@sanic.route('/my/url/<my_parameter>', methods=['GET', 'POST', ...])
def my_route(request, my_parameter):
.. code-block:: python
@sanic.route('/my/url/<my_param>', methods=['GET', 'POST', ...])
def my_route(request, my_param):
do stuff...
or
.. code-block:: python
@sanic.route('/my/url/<my_param:my_type>', methods['GET', 'POST', ...])
def my_route_with_type(request, my_param: my_type):
do stuff...
Parameters will be passed as keyword arguments to the request handling
function provided Parameters can also have a type by appending :type to
the <parameter>. If no type is provided, a string is expected. A regular
expression can also be passed in as the type
TODO:
This probably needs optimization for larger sets of routes,
since it checks every route until it finds a match which is bad and
I should feel bad
function. Provided parameters can also have a type by appending :type to
the <parameter>. Given parameter must be able to be type-casted to this.
If no type is provided, a string is expected. A regular expression can
also be passed in as the type. The argument given to the function will
always be a string, independent of the type.
"""
routes = None
regex_types = {
"string": (None, "[^/]+"),
"int": (int, "\d+"),
"number": (float, "[0-9\\.]+"),
"alpha": (None, "[A-Za-z]+"),
}
routes_static = None
routes_dynamic = None
routes_always_check = None
parameter_pattern = re.compile(r'<(.+?)>')
def __init__(self):
self.routes = []
self.routes_all = {}
self.routes_static = {}
self.routes_dynamic = defaultdict(list)
self.routes_always_check = []
self.hosts = set()
def add(self, uri, methods, handler):
def parse_parameter_string(self, parameter_string):
"""Parse a parameter string into its constituent name, type, and
pattern
For example::
parse_parameter_string('<param_one:[A-z]>')` ->
('param_one', str, '[A-z]')
:param parameter_string: String to parse
:return: tuple containing
(parameter_name, parameter_type, parameter_pattern)
"""
Adds a handler to the route list
:param uri: Path to match
:param methods: Array of accepted method names.
If none are provided, any method is allowed
:param handler: Request handler function.
When executed, it should provide a response object.
# We could receive NAME or NAME:PATTERN
name = parameter_string
pattern = 'string'
if ':' in parameter_string:
name, pattern = parameter_string.split(':', 1)
default = (str, pattern)
# Pull from pre-configured types
_type, pattern = REGEX_TYPES.get(pattern, default)
return name, _type, pattern
def add(self, uri, methods, handler, host=None, strict_slashes=False):
# add regular version
self._add(uri, methods, handler, host)
if strict_slashes:
return
# Add versions with and without trailing /
slash_is_missing = (
not uri[-1] == '/'
and not self.routes_all.get(uri + '/', False)
)
without_slash_is_missing = (
uri[-1] == '/'
and not self.routes_all.get(uri[:-1], False)
and not uri == '/'
)
# add version with trailing slash
if slash_is_missing:
self._add(uri + '/', methods, handler, host)
# add version without trailing slash
elif without_slash_is_missing:
self._add(uri[:-1], methods, handler, host)
def _add(self, uri, methods, handler, host=None):
"""Add a handler to the route list
:param uri: path to match
:param methods: sequence of accepted method names. If none are
provided, any method is allowed
:param handler: request handler function.
When executed, it should provide a response object.
:return: Nothing
"""
if host is not None:
if isinstance(host, str):
uri = host + uri
self.hosts.add(host)
else:
if not isinstance(host, Iterable):
raise ValueError("Expected either string or Iterable of "
"host strings, not {!r}".format(host))
for host_ in host:
self.add(uri, methods, handler, host_)
return
# Dict for faster lookups of if method allowed
methods_dict = None
if methods:
methods_dict = {method: True for method in methods}
methods = frozenset(methods)
parameters = []
properties = {"unhashable": None}
def add_parameter(match):
# We could receive NAME or NAME:PATTERN
parts = match.group(1).split(':')
if len(parts) == 2:
parameter_name, parameter_pattern = parts
else:
parameter_name = parts[0]
parameter_pattern = 'string'
name = match.group(1)
name, _type, pattern = self.parse_parameter_string(name)
# Pull from pre-configured types
parameter_regex = self.regex_types.get(parameter_pattern)
if parameter_regex:
parameter_type, parameter_pattern = parameter_regex
else:
parameter_type = None
parameter = Parameter(name=parameter_name, cast=parameter_type)
parameter = Parameter(
name=name, cast=_type)
parameters.append(parameter)
return "({})".format(parameter_pattern)
# Mark the whole route as unhashable if it has the hash key in it
if re.search('(^|[^^]){1}/', pattern):
properties['unhashable'] = True
# Mark the route as unhashable if it matches the hash key
elif re.search(pattern, '/'):
properties['unhashable'] = True
pattern_string = re.sub("<(.+?)>", add_parameter, uri)
pattern = re.compile("^{}$".format(pattern_string))
return '({})'.format(pattern)
route = Route(
handler=handler, methods=methods_dict, pattern=pattern,
parameters=parameters)
self.routes.append(route)
pattern_string = re.sub(self.parameter_pattern, add_parameter, uri)
pattern = re.compile(r'^{}$'.format(pattern_string))
def merge_route(route, methods, handler):
# merge to the existing route when possible.
if not route.methods or not methods:
# method-unspecified routes are not mergeable.
raise RouteExists(
"Route already registered: {}".format(uri))
elif route.methods.intersection(methods):
# already existing method is not overloadable.
duplicated = methods.intersection(route.methods)
raise RouteExists(
"Route already registered: {} [{}]".format(
uri, ','.join(list(duplicated))))
if isinstance(route.handler, CompositionView):
view = route.handler
else:
view = CompositionView()
view.add(route.methods, route.handler)
view.add(methods, handler)
route = route._replace(
handler=view, methods=methods.union(route.methods))
return route
if parameters:
# TODO: This is too complex, we need to reduce the complexity
if properties['unhashable']:
routes_to_check = self.routes_always_check
ndx, route = self.check_dynamic_route_exists(
pattern, routes_to_check)
else:
routes_to_check = self.routes_dynamic[url_hash(uri)]
ndx, route = self.check_dynamic_route_exists(
pattern, routes_to_check)
if ndx != -1:
# Pop the ndx of the route, no dups of the same route
routes_to_check.pop(ndx)
else:
route = self.routes_all.get(uri)
if route:
route = merge_route(route, methods, handler)
else:
# prefix the handler name with the blueprint name
# if available
if hasattr(handler, '__blueprintname__'):
handler_name = '{}.{}'.format(
handler.__blueprintname__, handler.__name__)
else:
handler_name = getattr(handler, '__name__', None)
route = Route(
handler=handler, methods=methods, pattern=pattern,
parameters=parameters, name=handler_name)
self.routes_all[uri] = route
if properties['unhashable']:
self.routes_always_check.append(route)
elif parameters:
self.routes_dynamic[url_hash(uri)].append(route)
else:
self.routes_static[uri] = route
@staticmethod
def check_dynamic_route_exists(pattern, routes_to_check):
for ndx, route in enumerate(routes_to_check):
if route.pattern == pattern:
return ndx, route
else:
return -1, None
def remove(self, uri, clean_cache=True, host=None):
if host is not None:
uri = host + uri
try:
route = self.routes_all.pop(uri)
except KeyError:
raise RouteDoesNotExist("Route was not registered: {}".format(uri))
if route in self.routes_always_check:
self.routes_always_check.remove(route)
elif url_hash(uri) in self.routes_dynamic \
and route in self.routes_dynamic[url_hash(uri)]:
self.routes_dynamic[url_hash(uri)].remove(route)
else:
self.routes_static.pop(uri)
if clean_cache:
self._get.cache_clear()
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def find_route_by_view_name(self, view_name):
"""Find a route in the router based on the specified view name.
:param view_name: string of view name to search by
:return: tuple containing (uri, Route)
"""
if not view_name:
return (None, None)
for uri, route in self.routes_all.items():
if route.name == view_name:
return uri, route
return (None, None)
def get(self, request):
"""
Gets a request handler based on the URL of the request, or raises an
"""Get a request handler based on the URL of the request, or raises an
error
:param request: Request object
:return: handler, arguments, keyword arguments
"""
# No virtual hosts specified; default behavior
if not self.hosts:
return self._get(request.path, request.method, '')
# virtual hosts specified; try to match route to the host header
try:
return self._get(request.path, request.method,
request.headers.get("Host", ''))
# try default hosts
except NotFound:
return self._get(request.path, request.method, '')
route = None
args = []
kwargs = {}
for _route in self.routes:
match = _route.pattern.match(request.url)
if match:
for index, parameter in enumerate(_route.parameters, start=1):
value = match.group(index)
if parameter.cast:
kwargs[parameter.name] = parameter.cast(value)
else:
kwargs[parameter.name] = value
route = _route
break
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def _get(self, url, method, host):
"""Get a request handler based on the URL of the request, or raises an
error. Internal method for caching.
:param url: request URL
:param method: request method
:return: handler, arguments, keyword arguments
"""
url = host + url
# Check against known static routes
route = self.routes_static.get(url)
method_not_supported = InvalidUsage(
'Method {} not allowed for URL {}'.format(
method, url), status_code=405)
if route:
if route.methods and request.method not in route.methods:
raise InvalidUsage(
"Method {} not allowed for URL {}".format(
request.method, request.url), status_code=405)
return route.handler, args, kwargs
if route.methods and method not in route.methods:
raise method_not_supported
match = route.pattern.match(url)
else:
raise NotFound("Requested URL {} not found".format(request.url))
route_found = False
# Move on to testing all regex routes
for route in self.routes_dynamic[url_hash(url)]:
match = route.pattern.match(url)
route_found |= match is not None
# Do early method checking
if match and method in route.methods:
break
else:
# Lastly, check against all regex routes that cannot be hashed
for route in self.routes_always_check:
match = route.pattern.match(url)
route_found |= match is not None
# Do early method checking
if match and method in route.methods:
break
else:
# Route was found but the methods didn't match
if route_found:
raise method_not_supported
raise NotFound('Requested URL {} not found'.format(url))
class SimpleRouter:
"""
Simple router records and reads all routes from a dictionary
It does not support parameters in routes, but is very fast
"""
routes = None
def __init__(self):
self.routes = {}
def add(self, uri, methods, handler):
# Dict for faster lookups of method allowed
methods_dict = None
if methods:
methods_dict = {method: True for method in methods}
self.routes[uri] = Route(
handler=handler, methods=methods_dict, pattern=uri,
parameters=None)
def get(self, request):
route = self.routes.get(request.url)
if route:
if route.methods and request.method not in route.methods:
raise InvalidUsage(
"Method {} not allowed for URL {}".format(
request.method, request.url), status_code=405)
return route.handler, [], {}
else:
raise NotFound("Requested URL {} not found".format(request.url))
kwargs = {p.name: p.cast(value)
for value, p
in zip(match.groups(1), route.parameters)}
route_handler = route.handler
if hasattr(route_handler, 'handlers'):
route_handler = route_handler.handlers[method]
return route_handler, [], kwargs

View File

@@ -1,267 +0,0 @@
from asyncio import get_event_loop
from inspect import isawaitable
from multiprocessing import Process, Event
from signal import signal, SIGTERM, SIGINT
from time import sleep
from traceback import format_exc
from .config import Config
from .exceptions import Handler
from .log import log, logging
from .response import HTTPResponse
from .router import Router
from .server import serve
from .exceptions import ServerError
class Sanic:
def __init__(self, name, router=None, error_handler=None):
self.name = name
self.router = router or Router()
self.error_handler = error_handler or Handler(self)
self.config = Config()
self.request_middleware = []
self.response_middleware = []
self.blueprints = {}
self._blueprint_order = []
# -------------------------------------------------------------------- #
# Registration
# -------------------------------------------------------------------- #
# Decorator
def route(self, uri, methods=None):
"""
Decorates a function to be registered as a route
:param uri: path of the URL
:param methods: list or tuple of methods allowed
:return: decorated function
"""
def response(handler):
self.router.add(uri=uri, methods=methods, handler=handler)
return handler
return response
# Decorator
def exception(self, *exceptions):
"""
Decorates a function to be registered as a route
:param uri: path of the URL
:param methods: list or tuple of methods allowed
:return: decorated function
"""
def response(handler):
for exception in exceptions:
self.error_handler.add(exception, handler)
return handler
return response
# Decorator
def middleware(self, *args, **kwargs):
"""
Decorates and registers middleware to be called before a request
can either be called as @app.middleware or @app.middleware('request')
"""
attach_to = 'request'
def register_middleware(middleware):
if attach_to == 'request':
self.request_middleware.append(middleware)
if attach_to == 'response':
self.response_middleware.append(middleware)
return middleware
# Detect which way this was called, @middleware or @middleware('AT')
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
return register_middleware(args[0])
else:
attach_to = args[0]
return register_middleware
def register_blueprint(self, blueprint, **options):
"""
Registers a blueprint on the application.
:param blueprint: Blueprint object
:param options: option dictionary with blueprint defaults
:return: Nothing
"""
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint with the name "%s" is already registered. ' \
'Blueprint names must be unique.' % \
(blueprint.name,)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
blueprint.register(self, options)
# -------------------------------------------------------------------- #
# Request Handling
# -------------------------------------------------------------------- #
async def handle_request(self, request, response_callback):
"""
Takes a request from the HTTP Server and returns a response object to
be sent back The HTTP Server only expects a response object, so
exception handling must be done here
:param request: HTTP Request object
:param response_callback: Response function to be called with the
response as the only argument
:return: Nothing
"""
try:
# Middleware process_request
response = False
# The if improves speed. I don't know why
if self.request_middleware:
for middleware in self.request_middleware:
response = middleware(request)
if isawaitable(response):
response = await response
if response:
break
# No middleware results
if not response:
# Fetch handler from router
handler, args, kwargs = self.router.get(request)
if handler is None:
raise ServerError(
("'None' was returned while requesting a "
"handler from the router"))
# Run response handler
response = handler(request, *args, **kwargs)
if isawaitable(response):
response = await response
# Middleware process_response
if self.response_middleware:
for middleware in self.response_middleware:
_response = middleware(request, response)
if isawaitable(_response):
_response = await _response
if _response:
response = _response
break
except Exception as e:
try:
response = self.error_handler.response(request, e)
if isawaitable(response):
response = await response
except Exception as e:
if self.debug:
response = HTTPResponse(
"Error while handling error: {}\nStack: {}".format(
e, format_exc()))
else:
response = HTTPResponse(
"An error occured while handling an error")
response_callback(response)
# -------------------------------------------------------------------- #
# Execution
# -------------------------------------------------------------------- #
def run(self, host="127.0.0.1", port=8000, debug=False, after_start=None,
before_stop=None, sock=None, workers=1):
"""
Runs the HTTP Server and listens until keyboard interrupt or term
signal. On termination, drains connections before closing.
:param host: Address to host on
:param port: Port to host on
:param debug: Enables debug output (slows server)
:param after_start: Function to be executed after the server starts
listening
:param before_stop: Function to be executed when a stop signal is
received before it is respected
:param sock: Socket for the server to accept connections from
:param workers: Number of processes
received before it is respected
:return: Nothing
"""
self.error_handler.debug = True
self.debug = debug
server_settings = {
'host': host,
'port': port,
'sock': sock,
'debug': debug,
'request_handler': self.handle_request,
'request_timeout': self.config.REQUEST_TIMEOUT,
'request_max_size': self.config.REQUEST_MAX_SIZE,
}
if debug:
log.setLevel(logging.DEBUG)
log.debug(self.config.LOGO)
# Serve
log.info('Goin\' Fast @ http://{}:{}'.format(host, port))
try:
if workers == 1:
server_settings['after_start'] = after_start
server_settings['before_stop'] = before_stop
serve(**server_settings)
else:
log.info('Spinning up {} workers...'.format(workers))
self.serve_multiple(server_settings, workers)
except Exception as e:
log.exception(
'Experienced exception while trying to serve: {}'.format(e))
pass
log.info("Server Stopped")
def stop(self):
"""
This kills the Sanic
"""
get_event_loop().stop()
@staticmethod
def serve_multiple(server_settings, workers, stop_event=None):
"""
Starts multiple server processes simultaneously. Stops on interrupt
and terminate signals, and drains connections when complete.
:param server_settings: kw arguments to be passed to the serve function
:param workers: number of workers to launch
:param stop_event: if provided, is used as a stop signal
:return:
"""
server_settings['reuse_port'] = True
# Create a stop event to be triggered by a signal
if not stop_event:
stop_event = Event()
signal(SIGINT, lambda s, f: stop_event.set())
signal(SIGTERM, lambda s, f: stop_event.set())
processes = []
for w in range(workers):
process = Process(target=serve, kwargs=server_settings)
process.start()
processes.append(process)
# Infinitely wait for the stop event
try:
while not stop_event.is_set():
sleep(0.3)
except:
pass
log.info('Spinning down workers...')
for process in processes:
process.terminate()
for process in processes:
process.join()

View File

@@ -1,22 +1,61 @@
import asyncio
import os
import traceback
import warnings
from functools import partial
from inspect import isawaitable
from signal import SIGINT, SIGTERM
from multiprocessing import Process
from signal import (
SIGTERM, SIGINT,
signal as signal_func,
Signals
)
from socket import (
socket,
SOL_SOCKET,
SO_REUSEADDR,
)
from time import time
import httptools
from httptools import HttpRequestParser
from httptools.parser.errors import HttpParserError
try:
import uvloop as async_loop
except ImportError:
async_loop = asyncio
from .log import log
from .request import Request
from sanic.log import log
from sanic.request import Request
from sanic.exceptions import (
RequestTimeout, PayloadTooLarge, InvalidUsage, ServerError)
current_time = None
class Signal:
stopped = False
class CIDict(dict):
"""Case Insensitive dict where all keys are converted to lowercase
This does not maintain the inputted case when calling items() or keys()
in favor of speed, since headers are case insensitive
"""
def get(self, key, default=None):
return super().get(key.casefold(), default)
def __getitem__(self, key):
return super().__getitem__(key.casefold())
def __setitem__(self, key, value):
return super().__setitem__(key.casefold(), value)
def __contains__(self, key):
return super().__contains__(key.casefold())
class HttpProtocol(asyncio.Protocol):
__slots__ = (
# event loop, connection
@@ -25,12 +64,13 @@ class HttpProtocol(asyncio.Protocol):
'parser', 'request', 'url', 'headers',
# request config
'request_handler', 'request_timeout', 'request_max_size',
'request_class',
# connection management
'_total_request_size', '_timeout_handler')
'_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, signal=Signal(),
connections={}, request_timeout=60,
request_max_size=None):
def __init__(self, *, loop, request_handler, error_handler,
signal=Signal(), connections=set(), request_timeout=60,
request_max_size=None, request_class=None):
self.loop = loop
self.transport = None
self.request = None
@@ -40,32 +80,44 @@ class HttpProtocol(asyncio.Protocol):
self.signal = signal
self.connections = connections
self.request_handler = request_handler
self.error_handler = error_handler
self.request_timeout = request_timeout
self.request_max_size = request_max_size
self.request_class = request_class or Request
self._total_request_size = 0
self._timeout_handler = None
self._last_request_time = None
self._request_handler_task = None
# -------------------------------------------- #
# -------------------------------------------- #
# Connection
# -------------------------------------------- #
def connection_made(self, transport):
self.connections[self] = True
self.connections.add(self)
self._timeout_handler = self.loop.call_later(
self.request_timeout, self.connection_timeout)
self.transport = transport
self._last_request_time = current_time
def connection_lost(self, exc):
del self.connections[self]
self.connections.discard(self)
self._timeout_handler.cancel()
self.cleanup()
def connection_timeout(self):
self.bail_out("Request timed out, connection closed")
# -------------------------------------------- #
# Check if
time_elapsed = current_time - self._last_request_time
if time_elapsed < self.request_timeout:
time_left = self.request_timeout - time_elapsed
self._timeout_handler = (
self.loop.call_later(time_left, self.connection_timeout))
else:
if self._request_handler_task:
self._request_handler_task.cancel()
exception = RequestTimeout('Request Timeout')
self.write_error(exception)
# -------------------------------------------- #
# Parsing
# -------------------------------------------- #
@@ -74,81 +126,165 @@ class HttpProtocol(asyncio.Protocol):
# memory limits
self._total_request_size += len(data)
if self._total_request_size > self.request_max_size:
return self.bail_out(
"Request too large ({}), connection closed".format(
self._total_request_size))
exception = PayloadTooLarge('Payload Too Large')
self.write_error(exception)
# Create parser if this is the first time we're receiving data
if self.parser is None:
assert self.request is None
self.headers = []
self.parser = httptools.HttpRequestParser(self)
self.parser = HttpRequestParser(self)
# Parse request chunk or close connection
try:
self.parser.feed_data(data)
except httptools.parser.errors.HttpParserError as e:
self.bail_out(
"Invalid request data, connection closed ({})".format(e))
except HttpParserError:
exception = InvalidUsage('Bad Request')
self.write_error(exception)
def on_url(self, url):
self.url = url
def on_header(self, name, value):
if name == b'Content-Length' and int(value) > self.request_max_size:
return self.bail_out(
"Request body too large ({}), connection closed".format(value))
exception = PayloadTooLarge('Payload Too Large')
self.write_error(exception)
self.headers.append((name.decode(), value.decode('utf-8')))
self.headers.append((name.decode().casefold(), value.decode()))
def on_headers_complete(self):
self.request = Request(
self.request = self.request_class(
url_bytes=self.url,
headers=dict(self.headers),
headers=CIDict(self.headers),
version=self.parser.get_http_version(),
method=self.parser.get_method().decode()
method=self.parser.get_method().decode(),
transport=self.transport
)
def on_body(self, body):
self.request.body = body
self.request.body.append(body)
def on_message_complete(self):
self.loop.create_task(
self.request_handler(self.request, self.write_response))
if self.request.body:
self.request.body = b''.join(self.request.body)
self._request_handler_task = self.loop.create_task(
self.request_handler(
self.request,
self.write_response,
self.stream_response))
# -------------------------------------------- #
# Responding
# -------------------------------------------- #
def write_response(self, response):
"""
Writes response content synchronously to the transport.
"""
try:
keep_alive = self.parser.should_keep_alive() \
and not self.signal.stopped
keep_alive = (
self.parser.should_keep_alive() and not self.signal.stopped)
self.transport.write(
response.output(
self.request.version, keep_alive, self.request_timeout))
self.request.version, keep_alive,
self.request_timeout))
except AttributeError:
log.error(
('Invalid response object for url {}, '
'Expected Type: HTTPResponse, Actual Type: {}').format(
self.url, type(response)))
self.write_error(ServerError('Invalid response type'))
except RuntimeError:
log.error(
'Connection lost before response written @ {}'.format(
self.request.ip))
except Exception as e:
self.bail_out(
"Writing response failed, connection closed {}".format(
repr(e)))
finally:
if not keep_alive:
self.transport.close()
else:
self._last_request_time = current_time
self.cleanup()
async def stream_response(self, response):
"""
Streams a response to the client asynchronously. Attaches
the transport to the response so the response consumer can
write to the response as needed.
"""
try:
keep_alive = (
self.parser.should_keep_alive() and not self.signal.stopped)
response.transport = self.transport
await response.stream(
self.request.version, keep_alive, self.request_timeout)
except AttributeError:
log.error(
('Invalid response object for url {}, '
'Expected Type: HTTPResponse, Actual Type: {}').format(
self.url, type(response)))
self.write_error(ServerError('Invalid response type'))
except RuntimeError:
log.error(
'Connection lost before response written @ {}'.format(
self.request.ip))
except Exception as e:
self.bail_out(
"Writing request failed, connection closed {}".format(e))
"Writing response failed, connection closed {}".format(
repr(e)))
finally:
if not keep_alive:
self.transport.close()
else:
self._last_request_time = current_time
self.cleanup()
def bail_out(self, message):
log.error(message)
self.transport.close()
def write_error(self, exception):
try:
response = self.error_handler.response(self.request, exception)
version = self.request.version if self.request else '1.1'
self.transport.write(response.output(version))
except RuntimeError:
log.error(
'Connection lost before error written @ {}'.format(
self.request.ip if self.request else 'Unknown'))
except Exception as e:
self.bail_out(
"Writing error failed, connection closed {}".format(repr(e)),
from_error=True)
finally:
self.transport.close()
def bail_out(self, message, from_error=False):
if from_error or self.transport.is_closing():
log.error(
("Transport closed @ {} and exception "
"experienced during error handling").format(
self.transport.get_extra_info('peername')))
log.debug(
'Exception:\n{}'.format(traceback.format_exc()))
else:
exception = ServerError(message)
self.write_error(exception)
log.error(message)
def cleanup(self):
self.parser = None
self.request = None
self.url = None
self.headers = None
self._request_handler_task = None
self._total_request_size = 0
def close_if_idle(self):
"""
Close the connection if a request is not being sent or received
"""Close the connection if a request is not being sent or received
:return: boolean - True if closed, false if staying open
"""
if not self.parser:
@@ -157,52 +293,126 @@ class HttpProtocol(asyncio.Protocol):
return False
def serve(host, port, request_handler, after_start=None, before_stop=None,
debug=False, request_timeout=60, sock=None,
request_max_size=None, reuse_port=False):
# Create Event Loop
loop = async_loop.new_event_loop()
asyncio.set_event_loop(loop)
# I don't think we take advantage of this
# And it slows everything waaayyy down
# loop.set_debug(debug)
def update_current_time(loop):
"""Cache the current time, since it is needed at the end of every
keep-alive request to update the request timeout time
connections = {}
signal = Signal()
server_coroutine = loop.create_server(lambda: HttpProtocol(
:param loop:
:return:
"""
global current_time
current_time = time()
loop.call_later(1, partial(update_current_time, loop))
def trigger_events(events, loop):
"""Trigger event callbacks (functions or async)
:param events: one or more sync or async functions to execute
:param loop: event loop
"""
for event in events:
result = event(loop)
if isawaitable(result):
loop.run_until_complete(result)
def serve(host, port, request_handler, error_handler, before_start=None,
after_start=None, before_stop=None, after_stop=None, debug=False,
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):
"""Start asynchronous HTTP Server on an individual process.
:param host: Address to host on
:param port: Port to host on
:param request_handler: Sanic request handler with middleware
:param error_handler: Sanic error handler with middleware
:param before_start: function to be executed before the server starts
listening. Takes arguments `app` instance and `loop`
:param after_start: function to be executed after the server starts
listening. Takes arguments `app` instance and `loop`
:param before_stop: function to be executed when a stop signal is
received before it is respected. Takes arguments
`app` instance and `loop`
:param after_stop: function to be executed when a stop signal is
received after it is respected. Takes arguments
`app` instance and `loop`
:param debug: enables debug output (slows server)
:param request_timeout: time in seconds
:param ssl: SSLContext
:param sock: Socket for the server to accept connections from
:param request_max_size: size in bytes, `None` for no limit
:param reuse_port: `True` for multiple workers
:param loop: asyncio compatible event loop
:param protocol: subclass of asyncio protocol class
:param request_class: Request class to use
:return: Nothing
"""
if not run_async:
loop = async_loop.new_event_loop()
asyncio.set_event_loop(loop)
if debug:
loop.set_debug(debug)
trigger_events(before_start, loop)
connections = connections if connections is not None else set()
server = partial(
protocol,
loop=loop,
connections=connections,
signal=signal,
request_handler=request_handler,
error_handler=error_handler,
request_timeout=request_timeout,
request_max_size=request_max_size,
), host, port, reuse_port=reuse_port, sock=sock)
request_class=request_class,
)
server_coroutine = loop.create_server(
server,
host,
port,
ssl=ssl,
reuse_port=reuse_port,
sock=sock,
backlog=backlog
)
# Instead of pulling time at the end of every request,
# pull it once per minute
loop.call_soon(partial(update_current_time, loop))
if run_async:
return server_coroutine
try:
http_server = loop.run_until_complete(server_coroutine)
except Exception as e:
log.error("Unable to start server: {}".format(e))
except:
log.exception("Unable to start server")
return
# Run the on_start function if provided
if after_start:
result = after_start(loop)
if isawaitable(result):
loop.run_until_complete(result)
trigger_events(after_start, loop)
# Register signals for graceful termination
for _signal in (SIGINT, SIGTERM):
loop.add_signal_handler(_signal, loop.stop)
if register_sys_signals:
for _signal in (SIGINT, SIGTERM):
try:
loop.add_signal_handler(_signal, loop.stop)
except NotImplementedError:
log.warn('Sanic tried to use loop.add_signal_handler but it is'
' not implemented on this platform.')
pid = os.getpid()
try:
log.info('Starting worker [{}]'.format(pid))
loop.run_forever()
finally:
log.info("Stop requested, draining connections...")
log.info("Stopping worker [{}]".format(pid))
# Run the on_stop function if provided
if before_stop:
result = before_stop(loop)
if isawaitable(result):
loop.run_until_complete(result)
trigger_events(before_stop, loop)
# Wait for event loop to finish and all connections to drain
http_server.close()
@@ -210,10 +420,64 @@ def serve(host, port, request_handler, after_start=None, before_stop=None,
# Complete all tasks on the loop
signal.stopped = True
for connection in connections.keys():
for connection in connections:
connection.close_if_idle()
while connections:
loop.run_until_complete(asyncio.sleep(0.1))
trigger_events(after_stop, loop)
loop.close()
def serve_multiple(server_settings, workers):
"""Start multiple server processes simultaneously. Stop on interrupt
and terminate signals, and drain connections when complete.
:param server_settings: kw arguments to be passed to the serve function
:param workers: number of workers to launch
: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.
if server_settings.get('sock') is None:
sock = socket()
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sock.bind((server_settings['host'], server_settings['port']))
sock.set_inheritable(True)
server_settings['sock'] = sock
server_settings['host'] = None
server_settings['port'] = None
def sig_handler(signal, frame):
log.info("Received signal {}. Shutting down.".format(
Signals(signal).name))
signal_func(SIGINT, lambda s, f: sig_handler(s, f))
signal_func(SIGTERM, lambda s, f: sig_handler(s, f))
processes = []
for _ in range(workers):
process = Process(target=serve, kwargs=server_settings)
process.daemon = True
process.start()
processes.append(process)
for process in processes:
process.join()
# the above processes will block this until they're stopped
for process in processes:
process.terminate()
server_settings.get('sock').close()
asyncio.get_event_loop().stop()

100
sanic/static.py Normal file
View File

@@ -0,0 +1,100 @@
from mimetypes import guess_type
from os import path
from re import sub
from time import strftime, gmtime
from urllib.parse import unquote
from aiofiles.os import stat
from sanic.exceptions import (
ContentRangeError,
FileNotFound,
HeaderNotFound,
InvalidUsage,
)
from sanic.handlers import ContentRangeHandler
from sanic.response import file, HTTPResponse
def register(app, uri, file_or_directory, pattern,
use_modified_since, use_content_range):
# TODO: Though sanic is not a file server, I feel like we should at least
# make a good effort here. Modified-since is nice, but we could
# also look into etags, expires, and caching
"""
Register a static directory handler with Sanic by adding a route to the
router and registering a handler.
:param app: Sanic
:param file_or_directory: File or directory path to serve from
:param uri: URL to serve from
:param pattern: regular expression used to match files in the URL
:param use_modified_since: If true, send file modified time, and return
not modified if the browser's matches the
server's
:param use_content_range: If true, process header for range requests
and sends the file part that is requested
"""
# If we're not trying to match a file directly,
# serve from the folder
if not path.isfile(file_or_directory):
uri += '<file_uri:' + pattern + '>'
async def _handler(request, file_uri=None):
# Using this to determine if the URL is trying to break out of the path
# served. os.path.realpath seems to be very slow
if file_uri and '../' in file_uri:
raise InvalidUsage("Invalid URL")
# 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
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)
try:
headers = {}
# Check if the client has been sent this file before
# and it has not been modified since
stats = None
if use_modified_since:
stats = await stat(file_path)
modified_since = strftime(
'%a, %d %b %Y %H:%M:%S GMT', gmtime(stats.st_mtime))
if request.headers.get('If-Modified-Since') == modified_since:
return HTTPResponse(status=304)
headers['Last-Modified'] = modified_since
_range = None
if use_content_range:
_range = None
if not stats:
stats = await stat(file_path)
headers['Accept-Ranges'] = 'bytes'
headers['Content-Length'] = str(stats.st_size)
if request.method != 'HEAD':
try:
_range = ContentRangeHandler(request, stats)
except HeaderNotFound:
pass
else:
del headers['Content-Length']
for key, value in _range.headers.items():
headers[key] = value
if request.method == 'HEAD':
return HTTPResponse(
headers=headers,
content_type=guess_type(file_path)[0] or 'text/plain')
else:
return await file(file_path, headers=headers, _range=_range)
except ContentRangeError:
raise
except Exception:
raise FileNotFound('File not found',
path=file_or_directory,
relative_url=file_uri)
app.route(uri, methods=['GET', 'HEAD'])(_handler)

100
sanic/testing.py Normal file
View File

@@ -0,0 +1,100 @@
import traceback
from sanic.log import log
HOST = '127.0.0.1'
PORT = 42101
class SanicTestClient:
def __init__(self, app):
self.app = app
async def _local_request(self, method, uri, cookies=None, *args, **kwargs):
import aiohttp
if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')):
url = uri
else:
url = 'http://{host}:{port}{uri}'.format(
host=HOST, port=PORT, uri=uri)
log.info(url)
conn = aiohttp.TCPConnector(verify_ssl=False)
async with aiohttp.ClientSession(
cookies=cookies, connector=conn) as session:
async with getattr(
session, method.lower())(url, *args, **kwargs) as response:
try:
response.text = await response.text()
except UnicodeDecodeError as e:
response.text = None
response.body = await response.read()
return response
def _sanic_endpoint_test(
self, method='get', uri='/', gather_request=True,
debug=False, server_kwargs={},
*request_args, **request_kwargs):
results = [None, None]
exceptions = []
if gather_request:
def _collect_request(request):
if results[0] is None:
results[0] = request
self.app.request_middleware.appendleft(_collect_request)
@self.app.listener('after_server_start')
async def _collect_response(sanic, loop):
try:
response = await self._local_request(
method, uri, *request_args,
**request_kwargs)
results[-1] = response
except Exception as e:
log.error(
'Exception:\n{}'.format(traceback.format_exc()))
exceptions.append(e)
self.app.stop()
self.app.run(host=HOST, debug=debug, port=PORT, **server_kwargs)
self.app.listeners['after_server_start'].pop()
if exceptions:
raise ValueError("Exception during request: {}".format(exceptions))
if gather_request:
try:
request, response = results
return request, response
except:
raise ValueError(
"Request and response object expected, got ({})".format(
results))
else:
try:
return results[-1]
except:
raise ValueError(
"Request object expected, got ({})".format(results))
def get(self, *args, **kwargs):
return self._sanic_endpoint_test('get', *args, **kwargs)
def post(self, *args, **kwargs):
return self._sanic_endpoint_test('post', *args, **kwargs)
def put(self, *args, **kwargs):
return self._sanic_endpoint_test('put', *args, **kwargs)
def delete(self, *args, **kwargs):
return self._sanic_endpoint_test('delete', *args, **kwargs)
def patch(self, *args, **kwargs):
return self._sanic_endpoint_test('patch', *args, **kwargs)
def options(self, *args, **kwargs):
return self._sanic_endpoint_test('options', *args, **kwargs)
def head(self, *args, **kwargs):
return self._sanic_endpoint_test('head', *args, **kwargs)

View File

@@ -1,54 +1,17 @@
import aiohttp
from sanic.log import log
import warnings
HOST = '127.0.0.1'
PORT = 42101
async def local_request(method, uri, *args, **kwargs):
url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri)
log.info(url)
async with aiohttp.ClientSession() as session:
async with getattr(session, method)(url, *args, **kwargs) as response:
response.text = await response.text()
return response
from sanic.testing import SanicTestClient
def sanic_endpoint_test(app, method='get', uri='/', gather_request=True,
debug=False, server_kwargs={},
*request_args, **request_kwargs):
results = []
exceptions = []
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)
if gather_request:
@app.middleware
def _collect_request(request):
results.append(request)
async def _collect_response(loop):
try:
response = await local_request(method, uri, *request_args,
**request_kwargs)
results.append(response)
except Exception as e:
exceptions.append(e)
app.stop()
app.run(host=HOST, port=42101, after_start=_collect_response)
if exceptions:
raise ValueError("Exception during request: {}".format(exceptions))
if gather_request:
try:
request, response = results
return request, response
except:
raise ValueError(
"request and response object expected, got ({})".format(
results))
else:
try:
return results[0]
except:
raise ValueError(
"request object expected, got ({})".format(results))
test_client = SanicTestClient(app)
return test_client._sanic_endpoint_test(
method, uri, gather_request, debug, server_kwargs,
*request_args, **request_kwargs)

99
sanic/views.py Normal file
View File

@@ -0,0 +1,99 @@
from sanic.exceptions import InvalidUsage
from sanic.constants import HTTP_METHODS
class HTTPMethodView:
"""Simple class based implementation of view for the sanic.
You should implement methods (get, post, put, patch, delete) for the class
to every HTTP method you want to support.
For example:
.. code-block:: python
class DummyView(HTTPMethodView):
def get(self, request, *args, **kwargs):
return text('I am get method')
def put(self, request, *args, **kwargs):
return text('I am put method')
etc.
If someone tries to use a non-implemented method, there will be a
405 response.
If you need any url params just mention them in method definition:
.. code-block:: python
class DummyView(HTTPMethodView):
def get(self, request, my_param_here, *args, **kwargs):
return text('I am get method with %s' % my_param_here)
To add the view into the routing you could use
1) app.add_route(DummyView.as_view(), '/')
2) app.route('/')(DummyView.as_view())
To add any decorator you could set it into decorators variable
"""
decorators = []
def dispatch_request(self, request, *args, **kwargs):
handler = getattr(self, request.method.lower(), None)
return handler(request, *args, **kwargs)
@classmethod
def as_view(cls, *class_args, **class_kwargs):
"""Return view function for use with the routing system, that
dispatches request to appropriate handler method.
"""
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)
if cls.decorators:
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
view.view_class = cls
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__name__ = cls.__name__
return view
class CompositionView:
"""Simple method-function mapped view for the sanic.
You can add handler functions to methods (get, post, put, patch, delete)
for every HTTP method you want to support.
For example:
view = CompositionView()
view.add(['GET'], lambda request: text('I am get method'))
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
etc.
If someone tries to use a non-implemented method, there will be a
405 response.
"""
def __init__(self):
self.handlers = {}
def add(self, methods, handler):
for method in methods:
if method not in HTTP_METHODS:
raise InvalidUsage(
'{} is not a valid HTTP method.'.format(method))
if method in self.handlers:
raise InvalidUsage(
'Method {} is already registered.'.format(method))
self.handlers[method] = handler
def __call__(self, request, *args, **kwargs):
handler = self.handlers[request.method.upper()]
return handler(request, *args, **kwargs)

67
sanic/websocket.py Normal file
View File

@@ -0,0 +1,67 @@
from sanic.exceptions import InvalidUsage
from sanic.server import HttpProtocol
from httptools import HttpParserUpgrade
from websockets import handshake, WebSocketCommonProtocol, InvalidHandshake
from websockets import ConnectionClosed # noqa
class WebSocketProtocol(HttpProtocol):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.websocket = None
def connection_timeout(self):
# timeouts make no sense for websocket routes
if self.websocket is None:
super().connection_timeout()
def connection_lost(self, exc):
if self.websocket is not None:
self.websocket.connection_lost(exc)
super().connection_lost(exc)
def data_received(self, data):
if self.websocket is not None:
# pass the data to the websocket protocol
self.websocket.data_received(data)
else:
try:
super().data_received(data)
except HttpParserUpgrade:
# this is okay, it just indicates we've got an upgrade request
pass
def write_response(self, response):
if self.websocket is not None:
# websocket requests do not write a response
self.transport.close()
else:
super().write_response(response)
async def websocket_handshake(self, request):
# let the websockets package do the handshake with the client
headers = []
def get_header(k):
return request.headers.get(k, '')
def set_header(k, v):
headers.append((k, v))
try:
key = handshake.check_request(get_header)
handshake.build_response(set_header, key)
except InvalidHandshake:
raise InvalidUsage('Invalid websocket request')
# write the 101 response back to the client
rv = b'HTTP/1.1 101 Switching Protocols\r\n'
for k, v in headers:
rv += k.encode('utf-8') + b': ' + v.encode('utf-8') + b'\r\n'
rv += b'\r\n'
request.transport.write(rv)
# hook up the websocket protocol
self.websocket = WebSocketCommonProtocol()
self.websocket.connection_made(request.transport)
return self.websocket

166
sanic/worker.py Normal file
View File

@@ -0,0 +1,166 @@
import os
import sys
import signal
import asyncio
import logging
try:
import ssl
except ImportError:
ssl = None
import uvloop
import gunicorn.workers.base as base
from sanic.server import trigger_events, serve, HttpProtocol, Signal
from sanic.websocket import WebSocketProtocol
class GunicornWorker(base.Worker):
def __init__(self, *args, **kw): # pragma: no cover
super().__init__(*args, **kw)
cfg = self.cfg
if cfg.is_ssl:
self.ssl_context = self._create_ssl_context(cfg)
else:
self.ssl_context = None
self.servers = []
self.connections = set()
self.exit_code = 0
self.signal = Signal()
def init_process(self):
# create new event_loop after fork
asyncio.get_event_loop().close()
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
super().init_process()
def run(self):
is_debug = self.log.loglevel == logging.DEBUG
protocol = (WebSocketProtocol if self.app.callable.websocket_enabled
else HttpProtocol)
self._server_settings = self.app.callable._helper(
host=None,
port=None,
loop=self.loop,
debug=is_debug,
protocol=protocol,
ssl=self.ssl_context,
run_async=True
)
self._server_settings.pop('sock')
trigger_events(self._server_settings.get('before_start', []),
self.loop)
self._server_settings['before_start'] = ()
self._runner = asyncio.ensure_future(self._run(), loop=self.loop)
try:
self.loop.run_until_complete(self._runner)
self.app.callable.is_running = True
trigger_events(self._server_settings.get('after_start', []),
self.loop)
self.loop.run_until_complete(self._check_alive())
trigger_events(self._server_settings.get('before_stop', []),
self.loop)
self.loop.run_until_complete(self.close())
finally:
trigger_events(self._server_settings.get('after_stop', []),
self.loop)
self.loop.close()
sys.exit(self.exit_code)
async def close(self):
if self.servers:
# stop accepting connections
self.log.info("Stopping server: %s, connections: %s",
self.pid, len(self.connections))
for server in self.servers:
server.close()
await server.wait_closed()
self.servers.clear()
# prepare connections for closing
self.signal.stopped = True
for conn in self.connections:
conn.close_if_idle()
while self.connections:
await asyncio.sleep(0.1)
async def _run(self):
for sock in self.sockets:
self.servers.append(await serve(
sock=sock,
connections=self.connections,
signal=self.signal,
**self._server_settings
))
async def _check_alive(self):
# If our parent changed then we shut down.
pid = os.getpid()
try:
while self.alive:
self.notify()
if pid == os.getpid() and self.ppid != os.getppid():
self.alive = False
self.log.info("Parent changed, shutting down: %s", self)
else:
await asyncio.sleep(1.0, loop=self.loop)
except (Exception, BaseException, GeneratorExit, KeyboardInterrupt):
pass
@staticmethod
def _create_ssl_context(cfg):
""" Creates SSLContext instance for usage in asyncio.create_server.
See ssl.SSLSocket.__init__ for more details.
"""
ctx = ssl.SSLContext(cfg.ssl_version)
ctx.load_cert_chain(cfg.certfile, cfg.keyfile)
ctx.verify_mode = cfg.cert_reqs
if cfg.ca_certs:
ctx.load_verify_locations(cfg.ca_certs)
if cfg.ciphers:
ctx.set_ciphers(cfg.ciphers)
return ctx
def init_signals(self):
# Set up signals through the event loop API.
self.loop.add_signal_handler(signal.SIGQUIT, self.handle_quit,
signal.SIGQUIT, None)
self.loop.add_signal_handler(signal.SIGTERM, self.handle_exit,
signal.SIGTERM, None)
self.loop.add_signal_handler(signal.SIGINT, self.handle_quit,
signal.SIGINT, None)
self.loop.add_signal_handler(signal.SIGWINCH, self.handle_winch,
signal.SIGWINCH, None)
self.loop.add_signal_handler(signal.SIGUSR1, self.handle_usr1,
signal.SIGUSR1, None)
self.loop.add_signal_handler(signal.SIGABRT, self.handle_abort,
signal.SIGABRT, None)
# Don't let SIGTERM and SIGUSR1 disturb active requests
# by interrupting system calls
signal.siginterrupt(signal.SIGTERM, False)
signal.siginterrupt(signal.SIGUSR1, False)
def handle_quit(self, sig, frame):
self.alive = False
self.cfg.worker_int(self)
def handle_abort(self, sig, frame):
self.alive = False
self.exit_code = 1
self.cfg.worker_abort(self)

View File

@@ -1,28 +1,70 @@
"""
Sanic
"""
import codecs
import os
import re
from distutils.errors import DistutilsPlatformError
from distutils.util import strtobool
from setuptools import setup
setup(
name='Sanic',
version="0.1.4",
url='http://github.com/channelcat/sanic/',
license='MIT',
author='Channel Cat',
author_email='channelcat@gmail.com',
description='A microframework based on uvloop, httptools, and learnings of flask',
packages=['sanic'],
platforms='any',
install_requires=[
'uvloop>=0.5.3',
'httptools>=0.0.9',
'ujson>=1.35',
],
classifiers=[
with codecs.open(os.path.join(os.path.abspath(os.path.dirname(
__file__)), 'sanic', '__init__.py'), 'r', 'latin1') as fp:
try:
version = re.findall(r"^__version__ = '([^']+)'\r?$",
fp.read(), re.M)[0]
except IndexError:
raise RuntimeError('Unable to determine version.')
setup_kwargs = {
'name': 'sanic',
'version': version,
'url': 'http://github.com/channelcat/sanic/',
'license': 'MIT',
'author': 'Channel Cat',
'author_email': 'channelcat@gmail.com',
'description': (
'A microframework based on uvloop, httptools, and learnings of flask'),
'packages': ['sanic'],
'platforms': 'any',
'classifiers': [
'Development Status :: 2 - Pre-Alpha',
'Environment :: Web Environment',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
)
}
ujson = 'ujson>=1.35'
uvloop = 'uvloop>=0.5.3'
requirements = [
'httptools>=0.0.9',
uvloop,
ujson,
'aiofiles>=0.3.0',
'websockets>=3.2',
]
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
print("Installing without uJSON")
requirements.remove(ujson)
if strtobool(os.environ.get("SANIC_NO_UVLOOP", "no")):
print("Installing without uvLoop")
requirements.remove(uvloop)
try:
setup_kwargs['install_requires'] = requirements
setup(**setup_kwargs)
except DistutilsPlatformError as exception:
requirements.remove(ujson)
requirements.remove(uvloop)
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))

52
test.py
View File

@@ -1,52 +0,0 @@
from multiprocessing import Array, Event, Process
from time import sleep
from ujson import loads as json_loads
from sanic import Sanic
from sanic.response import json
from sanic.utils import local_request, HOST, PORT
# ------------------------------------------------------------ #
# GET
# ------------------------------------------------------------ #
def test_json():
app = Sanic('test_json')
response = Array('c', 50)
@app.route('/')
async def handler(request):
return json({"test": True})
stop_event = Event()
async def after_start(*args, **kwargs):
http_response = await local_request('get', '/')
response.value = http_response.text.encode()
stop_event.set()
def rescue_crew():
sleep(5)
stop_event.set()
rescue_process = Process(target=rescue_crew)
rescue_process.start()
app.serve_multiple({
'host': HOST,
'port': PORT,
'after_start': after_start,
'request_handler': app.handle_request,
'request_max_size': 100000,
}, workers=2, stop_event=stop_event)
rescue_process.terminate()
try:
results = json_loads(response.value)
except:
raise ValueError("Expected JSON response but got '{}'".format(response))
assert results.get('test') == True
test_json()

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIJAO6wb0FSc/rNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTcwMzAzMTUyODAzWhcNMTkxMTI4MTUyODAzWjBF
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAsy7Zb3p4yCEnUtPLwqeJrwj9u/ZmcFCrMAktFBx9hG6rY2r7mdB6Bflh
V5cUJXxnsNiDpYcxGhA8kry7pEork1vZ05DyZC9ulVlvxBouVShBcLLwdpaoTGqE
vYtejv6x7ogwMXOjkWWb1WpOv4CVhpeXJ7O/d1uAiYgcUpTpPp4ONG49IAouBHq3
h+o4nVvNfB0J8gaCtTsTZqi1Wt8WYs3XjxGJaKh//ealfRe1kuv40CWQ8gjaC8/1
w9pHdom3Wi/RwfDM3+dVGV6M5lAbPXMB4RK17Hk9P3hlJxJOpKBdgcBJPXtNrTwf
qEWWxk2mB/YVyB84AxjkkNoYyi2ggQIDAQABo4GnMIGkMB0GA1UdDgQWBBRa46Ix
9s9tmMqu+Zz1mocHghm4NTB1BgNVHSMEbjBsgBRa46Ix9s9tmMqu+Zz1mocHghm4
NaFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAO6wb0FSc/rNMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBACdrnM8zb7abxAJsU5WLn1IR0f2+EFA7
ezBEJBM4bn0IZrXuP5ThZ2wieJlshG0C16XN9+zifavHci+AtQwWsB0f/ppHdvWQ
7wt7JN88w+j0DNIYEadRCjWxR3gRAXPgKu3sdyScKFq8MvB49A2EdXRmQSTIM6Fj
teRbE+poxewFT0mhurf3xrtGiSALmv7uAzhRDqpYUzcUlbOGgkyFLYAOOdvZvei+
mfXDi4HKYxgyv53JxBARMdajnCHXM7zQ6Tjc8j1HRtmDQ3XapUB559KfxfODGQq5
zmeoZWU4duxcNXJM0Eiz1CJ39JoWwi8sqaGi/oskuyAh7YKyVTn8xa8=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsy7Zb3p4yCEnUtPLwqeJrwj9u/ZmcFCrMAktFBx9hG6rY2r7
mdB6BflhV5cUJXxnsNiDpYcxGhA8kry7pEork1vZ05DyZC9ulVlvxBouVShBcLLw
dpaoTGqEvYtejv6x7ogwMXOjkWWb1WpOv4CVhpeXJ7O/d1uAiYgcUpTpPp4ONG49
IAouBHq3h+o4nVvNfB0J8gaCtTsTZqi1Wt8WYs3XjxGJaKh//ealfRe1kuv40CWQ
8gjaC8/1w9pHdom3Wi/RwfDM3+dVGV6M5lAbPXMB4RK17Hk9P3hlJxJOpKBdgcBJ
PXtNrTwfqEWWxk2mB/YVyB84AxjkkNoYyi2ggQIDAQABAoIBAFgVasxTf3aaXbNo
7JzXMWb7W4iAG2GRNmZZzHA7hTSKFvS7jc3SX3n6WvDtEvlOi8ay2RyRNgEjBDP6
VZ/w2jUJjS5k7dN0Qb9nhPr5B9fS/0CAppcVfsx5/KEVFzniWOPyzQYyW7FJKu8h
4G5hrp/Ie4UH5tKtB6YUZB/wliyyQUkAZdBcoy1hfkOZLAXb1oofArKsiQUHIRA5
th1yyS4cZP8Upngd1EE+d95dFHM2F6iI2lj6DHuu+JxUZ+wKXoNimdG7JniRtIf4
56GoDov83Ey+XbIS6FSQc9nY0ijBDcubl/yP3roCQpE+MZ9BNEo5uj7YmCtAMYLW
TXTNBGUCgYEA4wdkH1NLdub2NcpqwmSA0AtbRvDkt0XTDWWwmuMr/+xPVa4sUKHs
80THQEX/WAZroP6IPbMP6BJhzb53vECukgC65qPxu6M9D1lBGtglxgen4AMu1bKK
gnM8onwARGIo/2ay6qRRZZCxg0TvBky3hbTcIM2zVrnKU6VVyGKHSV8CgYEAygxs
WQYrACv3XN6ZEzyxy08JgjbcnkPWK/m3VPcyHgdEkDu8+nDdUVdbF/js2JWMMx5g
vrPhZ7jVLOXGcLr5mVU4dG5tW5lU0bMy+YYxpEQDiBKlpXgfOsQnakHj7cCZ6bay
mKjJck2oEAQS9bqOJN/Ts5vhOmc8rmhkO7hnAh8CgYEArhVDy9Vl/1WYo6SD+m1w
bJbYtewPpQzwicxZAFuDqKk+KDf3GRkhBWTO2FUUOB4sN3YVaCI+5zf5MPeE/qAm
fCP9LM+3k6bXMkbBamEljdTfACHQruJJ3T+Z1gn5dnZCc5z/QncfRx8NTtfz5MO8
0dTeGnVAuBacs0kLHy2WCUcCgYALNBkl7pOf1NBIlAdE686oCV/rmoMtO3G6yoQB
8BsVUy3YGZfnAy8ifYeNkr3/XHuDsiGHMY5EJBmd/be9NID2oaUZv63MsHnljtw6
vdgu1Z6kgvQwcrK4nXvaBoFPA6kFLp5EnMde0TOKf89VVNzg6pBgmzon9OWGfj9g
mF8N3QKBgQCeoLwxUxpzEA0CPHm7DWF0LefVGllgZ23Eqncdy0QRku5zwwibszbL
sWaR3uDCc3oYcbSGCDVx3cSkvMAJNalc5ZHPfoV9W0+v392/rrExo5iwD8CSoCb2
gFWkeR7PBrD3NzFzFAWyiudzhBKHfRsB0MpCXbJV/WLqTlGIbEypjg==
-----END RSA PRIVATE KEY-----

View File

@@ -15,4 +15,4 @@ async def handle(request):
app = web.Application(loop=loop)
app.router.add_route('GET', '/', handle)
web.run_app(app, port=sys.argv[1])
web.run_app(app, port=sys.argv[1], access_log=None)

Some files were not shown because too many files have changed in this diff Show More