Compare commits

..

389 Commits

Author SHA1 Message Date
kennethreitz 2d935542e1 v0.0.7, immutable response object 2018-10-16 05:24:20 -07:00
kennethreitz a7ec1364f4 features 2018-10-16 04:42:44 -07:00
kennethreitz eb71ced092 graphql 2018-10-16 04:42:25 -07:00
kennethreitz 712ad0a73b mount a wsgi app 2018-10-16 04:35:30 -07:00
kennethreitz 48c0b137d5 version 2018-10-16 04:30:03 -07:00
kennethreitz dfccfcc3e5 wsgi apps 2018-10-16 04:29:44 -07:00
kennethreitz 6abe667efb Merge branch 'master' of github.com:kennethreitz/responder 2018-10-16 04:27:50 -07:00
kennethreitz c2472215ab features 2018-10-16 04:27:40 -07:00
kennethreitz ac3c1e149c Update README.md 2018-10-16 04:22:29 -07:00
kennethreitz cdf989427a mount flaks apps 2018-10-16 04:20:29 -07:00
kennethreitz ebf129edd3 /cc @tomchristie 2018-10-16 03:59:43 -07:00
kennethreitz 08c30f4baf Merge pull request #55 from sloria/upgrade-apispec
Depend on apispec>=1.0.0b1
2018-10-16 06:14:25 -04:00
kennethreitz cf6bdc20ef Merge pull request #57 from frostming/fix-docstring
Remove wsgi mentions in docstring
2018-10-16 06:14:15 -04:00
frostming 3ece644af8 Remove wsgi mentions in docstring 2018-10-16 12:54:28 +08:00
Steven Loria 3991c82c91 Depend on apispec>=1.0.0b1
The current code depends on the `apispec.yaml_utils`
module, which only exists in apispec 1.0.0b.

Close #52
2018-10-15 19:46:46 -04:00
kennethreitz 9b635253f0 Update index.rst 2018-10-15 12:29:50 -04:00
kennethreitz b62f41336e Merge pull request #54 from tomchristie/testimonial
Testimonial
2018-10-15 12:28:28 -04:00
Tom Christie f7b777c79e Testimonial 2018-10-15 16:47:12 +01:00
kennethreitz d18fa8e42a v0.0.6 2018-10-15 08:56:32 -04:00
kennethreitz 525c62ad26 content-type 2018-10-15 08:56:02 -04:00
kennethreitz 4000a6a48c fix 2018-10-15 08:45:22 -04:00
kennethreitz 5b173ed4c4 tour 2018-10-15 08:44:45 -04:00
kennethreitz f56ad73565 tour 2018-10-15 08:42:57 -04:00
kennethreitz 003991c8c6 fix 2018-10-15 08:41:39 -04:00
kennethreitz e2a32afb80 next version 2018-10-15 08:28:10 -04:00
kennethreitz f305a69bb3 tour 2018-10-15 08:26:13 -04:00
kennethreitz 84e8babd9e cleanups 2018-10-15 08:23:38 -04:00
kennethreitz aeb46d9b54 open_api spec 2018-10-15 08:21:40 -04:00
kennethreitz fafe0bd8e4 changelog 2018-10-15 07:19:01 -04:00
kennethreitz 9a2ab45957 safe dump 2018-10-15 07:18:19 -04:00
kennethreitz 66978a8cdc test yaml and form too 2018-10-15 07:14:14 -04:00
kennethreitz 1636012700 json uploads 2018-10-15 07:11:57 -04:00
kennethreitz 09206ae1e4 .pre 2018-10-15 07:05:59 -04:00
kennethreitz 9188475746 remove contributors file 2018-10-15 07:04:30 -04:00
kennethreitz 34d158a632 cleanup homepage 2018-10-15 07:02:53 -04:00
kennethreitz c06e6aa5ca changelog 2018-10-15 07:01:49 -04:00
kennethreitz f4f670f048 fix pipfile and pipfile.lock 2018-10-15 06:56:27 -04:00
kennethreitz 778d742b6e new lockfile 2018-10-15 06:55:31 -04:00
kennethreitz c8392b65b6 remove cli, next version 2018-10-15 06:53:41 -04:00
kennethreitz c0ace9c2e5 fix all the format things 2018-10-15 06:52:10 -04:00
kennethreitz dfcab7dcbf fix #47 2018-10-15 06:41:47 -04:00
kennethreitz eb0870deb1 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-14 20:40:45 -04:00
kennethreitz 5b7ef34523 fix #45 2018-10-14 20:40:31 -04:00
kennethreitz 6ec728e466 Merge pull request #43 from sheb/fix-typo-route
fix typo in doc string
2018-10-14 17:22:15 -04:00
kennethreitz f12a562a08 Merge pull request #44 from kennethreitz/bnm_tests
added tests for models.QueryDict
2018-10-14 17:21:55 -04:00
Luna 17c4c95593 added tests for models.QueryDict 2018-10-14 18:20:50 +01:00
kennethreitz 9b72c90944 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-14 11:57:22 -04:00
kennethreitz ec34da60a1 more tests 2018-10-14 11:57:15 -04:00
Sébastien Geffroy daa4b6368a fix typo in doc string 2018-10-14 17:51:21 +02:00
kennethreitz 931a7a1a6c Merge pull request #39 from rcatajar/patch-1
Fix typo in quickstart documentation
2018-10-14 11:47:25 -04:00
kennethreitz 69d5790078 Merge pull request #42 from kennethreitz/bnm_tests
Cleaned up redundant test function
2018-10-14 11:46:08 -04:00
kennethreitz 7571c18a55 Merge pull request #40 from tselepakis/fix-memoize
(fix) memoization functionality
2018-10-14 11:45:04 -04:00
Luna ff7ce9bdd0 linter error 2018-10-14 16:43:16 +01:00
Luna e5fc801899 added boilerplate cli 2018-10-14 16:40:57 +01:00
Konstantinos Tselepakis b362aa6813 (fix) memoization functionality
* Keep an object level cache
* Use func name as part of the dictionary key
2018-10-14 18:09:43 +03:00
Luna 652b961ac8 fixed conflict 2018-10-14 15:41:16 +01:00
Luna 652713aec4 clean up 2018-10-14 15:40:29 +01:00
kennethreitz 387b2f166b cleanup 2018-10-14 09:11:47 -04:00
kennethreitz 164b4a056a no memoize routes 2018-10-14 09:10:39 -04:00
Romain Catajar 29e514fea6 Fix typo in quickstart documentation 2018-10-14 15:10:29 +02:00
kennethreitz 310fff78c6 no benchmarks 2018-10-14 08:55:01 -04:00
kennethreitz f2efdc007c no benchmarks 2018-10-14 08:54:37 -04:00
kennethreitz b3be767923 upgrades 2018-10-14 08:46:33 -04:00
kennethreitz e86f2f3873 media() 2018-10-14 08:17:17 -04:00
kennethreitz 13d84f73d4 fix template rendering 2018-10-14 07:59:18 -04:00
kennethreitz e31342d3ba fix 2018-10-14 07:53:47 -04:00
kennethreitz daf0538bf3 fix 2018-10-14 07:52:52 -04:00
kennethreitz 451ce8b0c7 fix 2018-10-14 07:48:22 -04:00
kennethreitz b8cce14705 fix 2018-10-14 07:48:07 -04:00
kennethreitz bf1c9c650e fix 2018-10-14 07:47:45 -04:00
kennethreitz 8f6387536c fix 2018-10-14 07:46:15 -04:00
kennethreitz 56535ece11 fix 2018-10-14 07:44:13 -04:00
kennethreitz f1767719cb paragraphs 2018-10-14 07:43:43 -04:00
kennethreitz c925b06114 margin-top 2018-10-14 07:43:01 -04:00
kennethreitz 402426884d try this 2018-10-14 07:40:22 -04:00
kennethreitz df6c8a5a75 quotes 2018-10-14 07:39:51 -04:00
kennethreitz 99f5ae7125 another fix 2018-10-14 07:39:20 -04:00
kennethreitz d50a1b7d07 fix 2018-10-14 07:38:30 -04:00
kennethreitz fab3bb76f7 let's see if this works 2018-10-14 07:38:19 -04:00
kennethreitz 5025c66bb2 less testimonial 2018-10-14 07:37:22 -04:00
kennethreitz 800c153e96 Merge pull request #38 from kennethreitz/bnm_tests
Tests clean up
2018-10-14 07:29:07 -04:00
Luna 71bbda0fb7 clean up 2018-10-14 12:25:15 +01:00
kennethreitz 6e6bac429a order 2018-10-14 07:24:48 -04:00
kennethreitz 1ce091a4d9 feature tour 2018-10-14 07:24:14 -04:00
Luna a8f889be74 restructured tests 2018-10-14 12:23:33 +01:00
kennethreitz 5f33c6bfee rendering a template 2018-10-14 07:23:10 -04:00
Luna 6a290c49d8 Merge remote-tracking branch 'origin' into bnm_tests 2018-10-14 12:21:23 +01:00
kennethreitz b304d5d784 real fix 2018-10-14 07:12:45 -04:00
kennethreitz cfe83b97d9 fix 2018-10-14 07:12:27 -04:00
kennethreitz 2fec2bf560 response headers 2018-10-14 07:11:14 -04:00
kennethreitz 73dc1a7839 ! 2018-10-14 07:02:22 -04:00
kennethreitz 66fe951831 python 2018-10-14 07:01:13 -04:00
kennethreitz 7991bcbf1a note 2018-10-14 06:59:26 -04:00
kennethreitz de9516563a await 2018-10-14 06:58:56 -04:00
kennethreitz 27fefb821c quickstart 2018-10-14 06:58:14 -04:00
kennethreitz c195894db9 yaml 2018-10-14 06:57:39 -04:00
kennethreitz 6777b4d370 data 2018-10-14 06:55:54 -04:00
kennethreitz 09269c22a2 fix 2018-10-14 06:46:21 -04:00
kennethreitz 2e24a2f079 cleanup 2018-10-14 06:45:23 -04:00
kennethreitz 5d9932dd61 more quickstart 2018-10-14 06:44:18 -04:00
kennethreitz 062064213a improvements to docs 2018-10-14 06:34:04 -04:00
kennethreitz a2ae3ffb2b comments 2018-10-14 06:27:13 -04:00
kennethreitz 6cb4a0a3eb fix for encodings 2018-10-14 06:26:20 -04:00
kennethreitz f17c49091f cleanup 2018-10-14 06:23:23 -04:00
kennethreitz c16afc07df raises 2018-10-14 06:23:09 -04:00
kennethreitz 1616a96b2c fix 2018-10-14 06:21:59 -04:00
kennethreitz 261601230a better tests 2018-10-14 06:21:41 -04:00
kennethreitz 453a38df54 docstrings 2018-10-14 06:08:50 -04:00
kennethreitz 5b004a849f really fix encodings 2018-10-14 06:05:41 -04:00
kennethreitz 29d811d3fd conflict 2018-10-14 06:04:44 -04:00
kennethreitz 36c5739318 fix encoding 2018-10-14 06:03:57 -04:00
kennethreitz b3f9c67d34 cli 2018-10-14 05:47:04 -04:00
kennethreitz bc8eb802f7 remove heroku example 2018-10-14 05:46:36 -04:00
kennethreitz a138eead74 Merge pull request #35 from frostming/fix-req-content
Fix request.content
2018-10-14 05:00:13 -04:00
Frost Ming a700a0e1b1 Fix request.content 2018-10-14 10:31:03 +08:00
kennethreitz 205a33a241 Merge pull request #29 from ArtemGordinsky/ignore_missing_accept_header
Don't break when "Accept" header is missing
2018-10-13 22:08:10 -04:00
kennethreitz c88fd94c8b Merge pull request #33 from javad94/master
fixed typos
2018-10-13 22:07:40 -04:00
kennethreitz a2b4e2e87c Merge pull request #30 from gdamjan/master
fix docstring to remove mention of Waitress
2018-10-13 22:07:20 -04:00
Javad 4a8f1e95ba fixed typos 2018-10-14 01:18:14 +03:30
Javad 3a847d921e fixed typo 2018-10-14 01:15:11 +03:30
Javad 806fdb9541 fixed typos 2018-10-14 01:05:29 +03:30
Luna cf1adbdb01 Merge branch 'master' into bnm_tests 2018-10-13 21:57:29 +01:00
Luna 349d08e799 added conftest 2018-10-13 21:56:52 +01:00
Damjan Georgievski d680c7ed83 fix docstring to remove mention of Waitress 2018-10-13 19:45:31 +02:00
Artem Gordinsky d4cb7a711b Don't break when "Accept" header is missing 2018-10-13 17:30:52 +02:00
kennethreitz bb6e19e7cd requirements.txt 2018-10-13 09:54:53 -04:00
kennethreitz 1c3ea53e63 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 09:50:19 -04:00
kennethreitz 88e17029c5 fixes 2018-10-13 09:50:13 -04:00
kennethreitz 588e91b19f fix 2018-10-13 09:46:34 -04:00
kennethreitz 8cc2e7b6f1 fix 2018-10-13 09:46:29 -04:00
kennethreitz 222353b532 version 2018-10-13 09:46:14 -04:00
kennethreitz b88b266fd5 profile 2018-10-13 09:40:33 -04:00
kennethreitz 60e6fb99af fix 2018-10-13 09:27:11 -04:00
kennethreitz 65b60e57b2 Merge pull request #27 from kennethreitz/pytest
added addopts in pytest
2018-10-13 09:26:40 -04:00
kennethreitz 16a8402bf4 api.static_url 2018-10-13 09:23:05 -04:00
Luna 5896411136 added addopts in pytest 2018-10-13 14:14:59 +01:00
kennethreitz 0bb74a7885 pytest.ini 2018-10-13 09:13:33 -04:00
kennethreitz 86dfb9231f oops 2018-10-13 09:10:51 -04:00
kennethreitz 7198ce3eb0 fomatting 2018-10-13 09:09:08 -04:00
kennethreitz 08fecf1eb2 Merge branch 'bnm_tests' 2018-10-13 09:08:16 -04:00
kennethreitz 3eda26ca94 Merge branch 'master' into bnm_tests 2018-10-13 09:07:44 -04:00
kennethreitz d907914c7c Merge pull request #25 from 0xflotus/patch-1
fixed Characteristics
2018-10-13 09:02:54 -04:00
kennethreitz 266ab48fed better tests 2018-10-13 09:03:20 -04:00
Luna 3325cffa91 added entry to gitignore 2018-10-13 14:02:17 +01:00
Luna 43469ac62a Merge branch 'master' of https://github.com/kennethreitz/responder into bnm_tests 2018-10-13 14:00:59 +01:00
0xflotus a5c953fdb6 fixed Characteristics 2018-10-13 15:00:38 +02:00
Luna 627c46e458 added test cases for routes.py 2018-10-13 13:59:46 +01:00
kennethreitz 205eb34adc cleanup 2018-10-13 08:51:01 -04:00
kennethreitz 125e14d377 responder 2018-10-13 08:50:23 -04:00
kennethreitz a51c8a700b fix 2018-10-13 08:49:15 -04:00
kennethreitz 94e0400ea1 v0.0.2 2018-10-13 08:42:20 -04:00
kennethreitz 47c5b84093 form parsing working 2018-10-13 08:41:28 -04:00
kennethreitz 8b1fbfd16d better 2018-10-13 08:24:08 -04:00
kennethreitz cceb698899 installing 2018-10-13 08:23:28 -04:00
kennethreitz 01741df10d the cake is a lie 2018-10-13 08:20:38 -04:00
kennethreitz f91ebf8baa Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 08:19:21 -04:00
kennethreitz 4dde076030 move installation up 2018-10-13 08:19:12 -04:00
kennethreitz 3491001b7f Update README.md 2018-10-13 08:15:00 -04:00
kennethreitz 2acec68649 __slots__ 2018-10-13 08:13:32 -04:00
kennethreitz 51dab27374 index.rst 2018-10-13 08:08:00 -04:00
kennethreitz 145f5041bf options 2018-10-13 08:02:17 -04:00
kennethreitz 6034505380 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 08:01:18 -04:00
kennethreitz 8533d74906 5042 2018-10-13 08:01:10 -04:00
kennethreitz b2ae57b982 port 2018-10-13 07:59:16 -04:00
kennethreitz 49ffe9bec9 Merge pull request #20 from aitoehigie/master
Fleshed out the benchmarks section of the README.md file
2018-10-13 07:57:16 -04:00
kennethreitz fe5d92674e google analytics 2018-10-13 07:53:13 -04:00
kennethreitz 197d28f5c7 index 2018-10-13 07:50:29 -04:00
kennethreitz cd48bb0789 update readme 2018-10-13 07:50:09 -04:00
kennethreitz 90fc411e9a : 2018-10-13 07:46:36 -04:00
kennethreitz c22b6a84aa an 2018-10-13 07:45:42 -04:00
kennethreitz 9b65642f05 async 2018-10-13 07:43:57 -04:00
kennethreitz 83547dce9c continuation? 2018-10-13 07:43:09 -04:00
kennethreitz efeecceb54 fix? 2018-10-13 07:41:35 -04:00
kennethreitz ba9b5a40d2 simplify 2018-10-13 07:37:16 -04:00
kennethreitz 47b5bda277 fix 2018-10-13 07:36:43 -04:00
kennethreitz a343b6b1b6 fix 2018-10-13 07:36:30 -04:00
kennethreitz 0fe48d3003 index 2018-10-13 07:33:19 -04:00
kennethreitz 23e3760b08 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-13 07:31:50 -04:00
kennethreitz 3d31905562 improve formats for json 2018-10-13 07:31:45 -04:00
kennethreitz 9638c5266b index.rst 2018-10-13 07:30:56 -04:00
kennethreitz ad7ce9f55a Merge pull request #23 from aaqaishtyaq/docs-badge
add documentation badge in Readme
2018-10-13 07:26:55 -04:00
kennethreitz b0baf3b85a readme 2018-10-13 07:26:18 -04:00
kennethreitz d4d3687882 readme 2018-10-13 07:24:19 -04:00
kennethreitz faf55ca191 readme 2018-10-13 07:21:52 -04:00
kennethreitz d5096a23fb async def 2018-10-13 07:06:55 -04:00
kennethreitz ed5841d201 test async function 2018-10-13 07:06:14 -04:00
kennethreitz bbfc095a00 gitignore 2018-10-13 07:00:53 -04:00
kennethreitz 0fcb68a13d bunk 2018-10-13 07:00:42 -04:00
kennethreitz f97744c098 async defenitions work 2018-10-13 06:59:23 -04:00
kennethreitz d1cfa8d27a safe_load 2018-10-13 05:55:46 -04:00
Aaqa Ishtyaq 218dcf25c1 add documentation badge in Readme 2018-10-13 12:31:18 +05:30
aitoehigie 06e06973a4 format the readme file 2018-10-13 03:24:57 +01:00
aitoehigie 6f73cfc5f2 format the readme file 2018-10-13 03:23:47 +01:00
aitoehigie 6db5bbeaee format the readme file 2018-10-13 03:22:55 +01:00
aitoehigie 6ef5077164 format the readme file 2018-10-13 03:21:53 +01:00
aitoehigie 45e1ed7022 format the readme file 2018-10-13 03:20:36 +01:00
aitoehigie c14b4535a6 format the readme file 2018-10-13 03:19:21 +01:00
aitoehigie 411631d2f8 format the readme file 2018-10-13 03:17:42 +01:00
aitoehigie f4c3690bd8 format the readme file 2018-10-13 03:15:49 +01:00
aitoehigie 56fdea6b5d update the benchmarks section 2018-10-13 03:05:12 +01:00
aitoehigie 8a5c053d39 Add a concise benchmarks section to the README.md file 2018-10-13 02:34:58 +01:00
kennethreitz 42870cfa23 memoize template rendering 2018-10-12 16:56:21 -04:00
kennethreitz 6cf256cc05 memoize routes 2018-10-12 16:48:41 -04:00
kennethreitz 9fec915f62 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 16:42:21 -04:00
kennethreitz f1d5ab73cd readme 2018-10-12 16:42:13 -04:00
kennethreitz cd62972945 Merge pull request #14 from taoufik07/feature/query_dict
Add a QueryDict to manage query params
2018-10-12 15:26:05 -04:00
kennethreitz 998d09170c Update README.md 2018-10-12 14:36:03 -04:00
kennethreitz 4ba57181ec Update README.md 2018-10-12 14:31:27 -04:00
kennethreitz 8b9d8bdc62 Update README.md 2018-10-12 14:30:44 -04:00
taoufik07 4291d42dc0 Merge branch 'master' into feature/query_dict 2018-10-12 18:55:17 +01:00
kennethreitz 79fcc1ce40 uvloop 2018-10-12 13:28:07 -04:00
kennethreitz bfc6778dca readme 2018-10-12 13:27:02 -04:00
kennethreitz 701e57c264 fix all the things 2018-10-12 13:25:38 -04:00
kennethreitz 163d025c0d fix tests 2018-10-12 13:15:53 -04:00
kennethreitz d9befc6d8c test 2018-10-12 13:13:13 -04:00
kennethreitz 9e50a4c241 background 2018-10-12 12:28:14 -04:00
kennethreitz 9b0cae3794 background 2018-10-12 12:26:04 -04:00
taoufik07 6160dfb2f7 Add a simple test 2018-10-12 17:01:23 +01:00
taoufik07 cd013cdb06 Add QueryDict 2018-10-12 17:01:06 +01:00
kennethreitz 26cc7c90e9 Merge branch 'asgi' of https://github.com/tomchristie/responder into asgi 2018-10-12 10:29:13 -04:00
kennethreitz f28ac3cf22 Merge pull request #12 from taoufik07/patch-1
Typo : nicities -> niceties
2018-10-12 10:27:37 -04:00
kennethreitz 58fec4b082 Merge pull request #13 from taoufik07/patch-2
Fix resolve_hello indentation
2018-10-12 10:27:26 -04:00
Taoufik b91805a5df Fix graphql test 2018-10-12 15:27:04 +01:00
Tom Christie 0fa0df1bdf Add Pipfile 2018-10-12 15:18:13 +01:00
Tom Christie 3f7cacee3e Updated Pipfile.lock with latest pipenv 2018-10-12 15:12:44 +01:00
Taoufik 72637fd650 FIx resolve_hello indentation and use f-string 2018-10-12 15:12:17 +01:00
Taoufik aba1284f8e Typo : nicities -> niceties 2018-10-12 15:03:49 +01:00
Tom Christie 179e1dc9e5 Update Pipfile.lock 2018-10-12 15:02:08 +01:00
kennethreitz 75879a494e Merge pull request #7 from CianciuStyles/fix-familar-typo
Change "familar" into "familiar"
2018-10-12 09:54:46 -04:00
kennethreitz 73b1ea4713 Merge pull request #10 from OdinTech3/patch-1
Fixed a mispelt word
2018-10-12 09:54:30 -04:00
kennethreitz 55dc991c13 Merge pull request #11 from MichaelPereira/patch-1
Small typo : cooke -> cookie
2018-10-12 09:54:20 -04:00
Tom Christie c30316588a Form parsing requires Starlette 102 2018-10-12 14:53:55 +01:00
Tom Christie db5d6e7481 Lowercase method for on_{method} 2018-10-12 14:47:20 +01:00
Tom Christie f8d52f58d4 Drop form parsing support until Starlette PR #102 is in 2018-10-12 14:36:44 +01:00
Tom Christie 227ee499e4 request.params for API compatability with existing codebase/tests 2018-10-12 14:35:31 +01:00
Michael Pereira dcdaf6a674 Small typo : cooke -> cookie 2018-10-12 09:32:38 -04:00
Tom Christie d524ba3a37 Merge remote-tracking branch 'upstream/master' into asgi 2018-10-12 14:28:33 +01:00
Nick Spirit da5e288476 Fixed a mispelt word 2018-10-12 09:27:52 -04:00
kennethreitz baad7cd60d fix rst 2018-10-12 09:15:03 -04:00
kennethreitz e9d6fc33fd rst 2018-10-12 09:13:53 -04:00
kennethreitz c2fa0899e9 responder 2018-10-12 09:12:34 -04:00
kennethreitz 2dc09ec1f2 build status 2018-10-12 09:09:42 -04:00
kennethreitz fba640976f gzip 2018-10-12 09:04:33 -04:00
kennethreitz 8e7df61a73 fixes 2018-10-12 09:00:52 -04:00
Tom Christie 41776cf2df Initial pass at ASGI support 2018-10-12 13:57:22 +01:00
kennethreitz 23983f0b75 hacktoberfest 2018-10-12 08:55:27 -04:00
kennethreitz 84b457ede5 fix 2018-10-12 08:31:46 -04:00
kennethreitz a906e0bf0c Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 08:26:28 -04:00
kennethreitz 3db1aad96a hacks 2018-10-12 08:26:20 -04:00
kennethreitz 9c909e7a2c Update README.md 2018-10-12 08:19:38 -04:00
kennethreitz ad2ef7cb33 readme improvements 2018-10-12 08:17:56 -04:00
kennethreitz c851510ca9 fix travis 2018-10-12 08:13:35 -04:00
kennethreitz 71a21c2059 license 2018-10-12 08:12:54 -04:00
kennethreitz d90537eb8d README 2018-10-12 08:12:02 -04:00
kennethreitz 8266a15df5 travis 2018-10-12 08:08:05 -04:00
kennethreitz 9fce286f92 working again 2018-10-12 08:06:32 -04:00
kennethreitz fc244922cc endpoint 2018-10-12 08:03:36 -04:00
kennethreitz fce87fe20c better docs 2018-10-12 08:03:15 -04:00
kennethreitz d420358248 fix 2018-10-12 08:01:23 -04:00
kennethreitz be829ff0ae requirements 2018-10-12 07:59:31 -04:00
kennethreitz 0c9e224d45 fix 2018-10-12 07:58:14 -04:00
kennethreitz 58158c4d2b 3.6 2018-10-12 07:47:41 -04:00
kennethreitz b1e4222c93 requirements for builds 2018-10-12 07:46:12 -04:00
kennethreitz 70e5a016a6 v0.0.1 2018-10-12 07:41:56 -04:00
kennethreitz 6d7e7809a4 models 2018-10-12 07:39:30 -04:00
kennethreitz 4afc1ced93 python 3.7 2018-10-12 07:37:52 -04:00
kennethreitz 3c1807f04f docstrings 2018-10-12 07:35:36 -04:00
kennethreitz 6e9adac871 changes on the horizon 2018-10-12 07:19:29 -04:00
kennethreitz 2c3a3b2e17 docstrings 2018-10-12 07:02:35 -04:00
kennethreitz c070cc3f1a web service 2018-10-12 06:58:00 -04:00
kennethreitz d47caebc97 space for methods 2018-10-12 06:56:43 -04:00
kennethreitz 224c2bbb2b yellow 2018-10-12 06:55:35 -04:00
kennethreitz 48f1a0545e frontend 2018-10-12 06:53:38 -04:00
kennethreitz ac5146dbce fixes 2018-10-12 06:52:27 -04:00
kennethreitz 4fa82f4d7a index 2018-10-12 06:51:43 -04:00
kennethreitz 4bdd3f9138 lightgrey 2018-10-12 06:50:41 -04:00
kennethreitz 86bffbf62d italics 2018-10-12 06:49:53 -04:00
kennethreitz f7b0fb3f66 grey 2018-10-12 06:47:50 -04:00
kennethreitz 971be488d5 hacks 2018-10-12 06:47:16 -04:00
kennethreitz f415e9814c important 2018-10-12 06:44:47 -04:00
kennethreitz 01575a0b8d more colors 2018-10-12 06:43:31 -04:00
kennethreitz a77492dae1 changes 2018-10-12 06:42:39 -04:00
kennethreitz 080d6d30da color 2018-10-12 06:41:53 -04:00
kennethreitz ec9b20f87c class in yellow 2018-10-12 06:39:48 -04:00
kennethreitz 7aa405c87d lightgrey 2018-10-12 06:38:52 -04:00
kennethreitz af6257d364 cleanup 2018-10-12 06:37:06 -04:00
kennethreitz f930cbb2c9 grey 2018-10-12 06:35:52 -04:00
kennethreitz 6a5a22f035 hacks 2018-10-12 06:34:10 -04:00
kennethreitz 53f87e5def hacks 2018-10-12 06:33:28 -04:00
kennethreitz a00687cc0f hacks 2018-10-12 06:32:49 -04:00
kennethreitz d1d66c0e78 hacks 2018-10-12 06:31:53 -04:00
kennethreitz ee51f50809 fixes 2018-10-12 06:30:32 -04:00
kennethreitz b067da2a1c hacks 2018-10-12 06:27:43 -04:00
kennethreitz 3db2c00cd8 read the docs 2018-10-12 06:25:24 -04:00
kennethreitz 2f52ccbe4e responder 2018-10-12 06:22:33 -04:00
Stefano Cianciulli 25e9888438 Change "familar" into "familiar" 2018-10-12 11:00:59 +01:00
kennethreitz fabe7b9427 css 2018-10-12 05:32:22 -04:00
kennethreitz 9e464c394d Merge branch 'master' of github.com:kennethreitz/responder 2018-10-12 05:25:08 -04:00
kennethreitz dcaf9b61d4 cleanup 2018-10-12 05:24:44 -04:00
kennethreitz ecf0b2e57b Merge pull request #6 from 0x49D1/master
Update README.md
2018-10-12 05:22:20 -04:00
Dmitry Pursanov df32660754 Update README.md
Just a several small spelling fixes
2018-10-12 12:48:27 +04:00
kennethreitz 8bf795c8e4 typo 2018-10-11 22:45:10 -04:00
kennethreitz 0fc765a1fd .css 2018-10-11 22:22:41 -04:00
kennethreitz 6e7d97e5c0 hacks 2018-10-11 22:20:54 -04:00
kennethreitz a33ac8ed5f fonts 2018-10-11 22:15:08 -04:00
kennethreitz 7ca264fabd hr 2018-10-11 22:11:36 -04:00
kennethreitz e75f195f7c hacks 2018-10-11 22:10:06 -04:00
kennethreitz d3efa8b80d font haqcks 2018-10-11 22:08:56 -04:00
kennethreitz 82b78b6022 fonts 2018-10-11 22:00:12 -04:00
kennethreitz d8d1787e6f not all the time 2018-10-11 21:08:42 -04:00
kennethreitz d40cad2064 fix 2018-10-11 20:58:41 -04:00
kennethreitz cf323db503 i got it dude 2018-10-11 19:51:17 -04:00
kennethreitz 39d9b05455 cleaned up format 2018-10-11 19:43:50 -04:00
kennethreitz 475c7a9571 new media api 2018-10-11 19:40:21 -04:00
kennethreitz 7c4b6cf4f7 media 2018-10-11 19:22:54 -04:00
kennethreitz 697807c2d7 more tests 2018-10-11 19:11:54 -04:00
kennethreitz 85d900727b Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 18:54:07 -04:00
kennethreitz fc0d811740 fixes 2018-10-11 18:53:51 -04:00
kennethreitz 513867d242 Update README.md 2018-10-11 18:22:39 -04:00
kennethreitz 07ba75d9d5 changes 2018-10-11 16:44:56 -04:00
kennethreitz 6fa5f9af0c typography 2018-10-11 16:41:46 -04:00
kennethreitz 3c796b95fd models 2018-10-11 15:26:34 -04:00
kennethreitz 6de212d4bc fix sidebars 2018-10-11 14:08:47 -04:00
kennethreitz bb539c4d28 fix 2018-10-11 14:06:44 -04:00
kennethreitz f5d491667e index 2018-10-11 14:06:21 -04:00
kennethreitz 042d9ebc6c tagline 2018-10-11 14:05:46 -04:00
kennethreitz ac69ccae5e index 2018-10-11 14:04:28 -04:00
kennethreitz 940ab9d762 better 2018-10-11 13:57:55 -04:00
kennethreitz 00bfdf0e3e less is more 2018-10-11 13:23:17 -04:00
kennethreitz f3bc57a566 Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 13:21:51 -04:00
kennethreitz 50c9bc60f9 fixes 2018-10-11 13:21:42 -04:00
kennethreitz ba384bb12a Update README.md 2018-10-11 13:18:58 -04:00
kennethreitz fe9184048c Merge branch 'master' of github.com:kennethreitz/responder 2018-10-11 13:18:48 -04:00
kennethreitz 90083f945e cleanup 2018-10-11 13:18:40 -04:00
kennethreitz eee17ca20b Update README.md 2018-10-11 13:14:28 -04:00
kennethreitz b2d47abacd Update README.md 2018-10-11 13:13:56 -04:00
kennethreitz 548fb229af Update README.md 2018-10-11 13:13:22 -04:00
kennethreitz c00b259c43 Update README.md 2018-10-11 13:11:50 -04:00
kennethreitz 2dbd6ac451 small 2018-10-11 13:11:34 -04:00
kennethreitz aab82baad0 Update README.md 2018-10-11 13:11:20 -04:00
kennethreitz ea2c5c3025 docs 2018-10-11 12:59:27 -04:00
kennethreitz b391b5622f basics 2018-10-11 12:59:20 -04:00
kennethreitz ea3cb8aa7b artboards 2018-10-11 12:51:58 -04:00
kennethreitz 6e125f5f47 logos 2018-10-11 12:48:59 -04:00
kennethreitz 554500a314 assets 2018-10-11 12:26:57 -04:00
kennethreitz f1c9de7ace readme 2018-10-11 12:24:01 -04:00
kennethreitz 3bcfe89f2a updates to redirects 2018-10-11 12:22:33 -04:00
kennethreitz 151c7bd342 fix 2018-10-11 12:14:20 -04:00
kennethreitz b8d569129e more complex example 2018-10-11 12:13:53 -04:00
kennethreitz b421e2e925 cleanup 2018-10-11 12:09:12 -04:00
kennethreitz 79b09a5ae5 oh yeah 2018-10-11 12:07:35 -04:00
kennethreitz b0d3c2de09 readme 2018-10-11 11:58:11 -04:00
kennethreitz 3201a46003 switch order 2018-10-11 11:56:00 -04:00
kennethreitz 538c72f5bd dict 2018-10-11 11:55:12 -04:00
kennethreitz 6efe28bd54 fix 2018-10-11 11:54:02 -04:00
kennethreitz 7eaa95b7ee cleanup 2018-10-11 11:52:32 -04:00
kennethreitz d2f8b41e25 fix 2018-10-11 11:51:29 -04:00
kennethreitz 034aa19564 contributors 2018-10-11 11:50:47 -04:00
kennethreitz bd049d6263 Merge pull request #2 from serhii73/patch-1
Update test_responder.py
2018-10-11 11:48:07 -04:00
kennethreitz ffb7468b44 Merge pull request #3 from nicoddemus/patch-1
Fix typo in README
2018-10-11 11:47:53 -04:00
kennethreitz 6dfbde27ca fix 2018-10-11 11:47:48 -04:00
kennethreitz c6c0197d86 Merge branch 'master' into patch-1 2018-10-11 11:47:47 -04:00
kennethreitz 678596ace4 readme 2018-10-11 11:41:22 -04:00
kennethreitz 9295525b92 practice what you preach 2018-10-11 11:39:25 -04:00
kennethreitz fde2d5415f more usage 2018-10-11 11:38:26 -04:00
kennethreitz fac80383d9 grip 2018-10-11 11:37:05 -04:00
kennethreitz f4cfe5639a blend 2018-10-11 11:36:07 -04:00
kennethreitz 87749e4288 sentence 2018-10-11 11:35:21 -04:00
kennethreitz 787e056b7f python highlighting 2018-10-11 11:33:40 -04:00
kennethreitz 62bd3d905b days ago 2018-10-11 11:31:57 -04:00
kennethreitz 2122f1ef1c code example 2018-10-11 11:31:17 -04:00
Bruno Oliveira 0bed48e756 Fix typo in README 2018-10-11 12:24:58 -03:00
kennethreitz f2b2128675 fix server 2018-10-11 11:12:22 -04:00
kennethreitz c0c5770a89 tests 2018-10-11 10:52:13 -04:00
kennethreitz e7dc55edf5 more tests 2018-10-11 10:30:46 -04:00
kennethreitz 83fa6d6897 parameterized routes working 2018-10-11 10:29:15 -04:00
serhii73 5d636ee9c4 Update test_responder.py
Fix order import
2018-10-11 17:01:35 +03:00
kennethreitz 1fdda366dd tests 2018-10-11 06:37:02 -04:00
kennethreitz 671499bb43 working order 2018-10-10 11:07:11 -04:00
kennethreitz 749a7efdef templates working 2018-10-10 07:48:00 -04:00
kennethreitz 90eecdaa84 mount other wsgi apps 2018-10-10 07:14:29 -04:00
kennethreitz 39f9cbfdbb note 2018-10-10 06:42:57 -04:00
kennethreitz 5794ba890c README 2018-10-09 08:06:42 -04:00
kennethreitz 4a3bf3a1aa fixes 2018-10-09 08:06:22 -04:00
kennethreitz 1427ca0a35 pipfile cleanup 2018-10-09 07:39:05 -04:00
kennethreitz 5b7b0fcb8e cleanups 2018-10-09 07:33:38 -04:00
kennethreitz 6ac48de44c I 2018-10-09 07:30:35 -04:00
kennethreitz 674efa3052 readme 2018-10-09 07:30:19 -04:00
kennethreitz 152a7153d0 cargo culting 2018-10-09 07:28:36 -04:00
116 changed files with 53501 additions and 318 deletions
+15
View File
@@ -1 +1,16 @@
.vscode/
.cache
.idea
.coverage
.pytest_cache
.DS_Store
coverage.xml
__pycache__
tests/__pycache__
build
responder.egg-info/
dist/
app.py
app2.py
+12
View File
@@ -0,0 +1,12 @@
language: python
python:
- "3.6"
# command to install dependencies
install:
- "pip install pipenv --upgrade-strategy=only-if-needed"
- "pipenv install --dev"
# command to run the dependencies
script:
- "pytest"
+23
View File
@@ -0,0 +1,23 @@
# v0.0.7
- Immutable Request object.
# v0.0.6:
- Ability to mount WSGI apps.
- Supply content-type when serving up the schema.
# v0.0.5:
- OpenAPI Schema support.
- Safe load/dump yaml.
# v0.0.4:
- Asyncronous support for data uploads.
- Bug fixes.
# v0.0.3:
- Bug fixes.
# v0.0.2
- Switch to ASGI/Starlette.
# v0.0.1
- Conception!
+13
View File
@@ -0,0 +1,13 @@
Copyright 2018 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+5 -7
View File
@@ -4,18 +4,16 @@ verify_ssl = true
name = "pypi"
[packages]
waitress = "*"
werkzeug = "*"
pyyaml = "*"
requests = "*"
requests-wsgi-adapter = "*"
graphene = "*"
whitenoise = "*"
responder = {editable = true, path = "."}
[dev-packages]
pytest = "*"
"flake8" = "*"
black = "*"
twine = "*"
flask = "*"
sphinx = "*"
marshmallow = "*"
[requires]
python_version = "3.7"
Generated
+417 -40
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f6b7cc3cf0ac2760ea99bcb8d18c743eff418c6269da29823ccfdbdea19a8c1e"
"sha256": "9b959d9507c521f6088646507633207db03afec6ac31aeab07adf0d737dbb45b"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,13 @@
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee",
"sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d"
],
"version": "==0.4.0"
},
"aniso8601": {
"hashes": [
"sha256:7849749cf00ae0680ad2bdfe4419c7a662bef19c03691a19e008c8b9a5267802",
@@ -23,12 +30,33 @@
],
"version": "==3.0.2"
},
"apispec": {
"hashes": [
"sha256:c2e6ac6471aaf7c6ec6d12714821898910c6b3c87c189de9a2e3754786b86ada",
"sha256:fa7dfa8a292bae9b1e70c44a50bf61901805821726c5b804568c9f2501f57ebb"
],
"version": "==1.0.0b3"
},
"asgiref": {
"hashes": [
"sha256:9b05dcd41a6a89ca8c6e7f7e4089c3f3e76b5af60aebb81ae6d455ad81989c97",
"sha256:b21dc4c43d7aba5a844f4c48b8f49d56277bc34937fd9f9cb93ec97fde7e3082"
],
"version": "==2.3.2"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"version": "==3.0.1"
},
"certifi": {
"hashes": [
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.8.24"
"version": "==2018.10.15"
},
"chardet": {
"hashes": [
@@ -37,12 +65,18 @@
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"graphene": {
"hashes": [
"sha256:b8ec446d17fa68721636eaad3d6adc1a378cb6323e219814c8f98c9928fc9642",
"sha256:faa26573b598b22ffd274e2fd7a4c52efa405dcca96e01a62239482246248aa3"
],
"index": "pypi",
"version": "==2.1.3"
},
"graphql-core": {
@@ -58,6 +92,19 @@
],
"version": "==0.4.5"
},
"graphql-server-core": {
"hashes": [
"sha256:e5f82add4b3d5580aa1f1e7d9f00e944ad3abe1b65eb337e611d6a77cc20f231"
],
"version": "==1.1.1"
},
"h11": {
"hashes": [
"sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208",
"sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"
],
"version": "==0.8.1"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
@@ -65,6 +112,32 @@
],
"version": "==2.7"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"marshmallow": {
"hashes": [
"sha256:82b201ad767eb54de371c08cb1db6ca4ad2a728fa41b831e3781bf944815eb38",
"sha256:c250f37ac0e249a8287394a60d91f6240b674642ad999e66cd09463dbccd1d4f"
],
"version": "==3.0.0b18"
},
"parse": {
"hashes": [
"sha256:9dd6048ea212cd032a342f9f6aa2b7bc222f7407c7e37bdc2777fecd36897437"
],
"version": "==1.9.0"
},
"promise": {
"hashes": [
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
@@ -72,6 +145,12 @@
],
"version": "==2.2.1"
},
"python-multipart": {
"hashes": [
"sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"
],
"version": "==0.0.5"
},
"pyyaml": {
"hashes": [
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
@@ -80,7 +159,6 @@
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
],
"index": "pypi",
"version": "==4.2b4"
},
"requests": {
@@ -88,15 +166,18 @@
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"index": "pypi",
"version": "==2.19.1"
},
"requests-wsgi-adapter": {
"responder": {
"editable": true,
"path": "."
},
"rfc3986": {
"hashes": [
"sha256:7080c98ae2614b8d0b7339b611d97a535470d2fb479731f7d588d5f8108ea134"
"sha256:632b8fcd2ac37f24334316227f909be4f9d0738cbf409404cff6fa5f69a24093",
"sha256:8458571c4c57e1cf23593ad860bb601b6a604df6217f829c2bc70dc4b5af941b"
],
"index": "pypi",
"version": "==0.4.0"
"version": "==1.1.0"
},
"rx": {
"hashes": [
@@ -112,40 +193,60 @@
],
"version": "==1.11.0"
},
"starlette": {
"hashes": [
"sha256:2c7ec085440fce7146a9be2b6d53b7110c3866ce6fa03d901efdc1fbe97e0f36"
],
"version": "==0.4.2"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"markers": "python_version < '4' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6'",
"version": "==1.23"
},
"waitress": {
"uvicorn": {
"hashes": [
"sha256:40b0f297a7f3af61fbfbdc67e59090c70dc150a1601c39ecc9f5f1d283fb931b",
"sha256:d33cd3d62426c0f1b3cd84ee3d65779c7003aae3fc060dee60524d10a57f05a9"
"sha256:8de03999a936d8704f07cc3b1d3a3edb6922a068b64d84b4f5e49604c8b70a11"
],
"index": "pypi",
"version": "==1.1.0"
"version": "==0.3.12"
},
"werkzeug": {
"websockets": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
"sha256:0e2f7d6567838369af074f0ef4d0b802d19fa1fee135d864acc656ceefa33136",
"sha256:2a16dac282b2fdae75178d0ed3d5b9bc3258dabfae50196cbb30578d84b6f6a6",
"sha256:5a1fa6072405648cb5b3688e9ed3b94be683ce4a4e5723e6f5d34859dee495c1",
"sha256:5c1f55a1274df9d6a37553fef8cff2958515438c58920897675c9bc70f5a0538",
"sha256:669d1e46f165e0ad152ed8197f7edead22854a6c90419f544e0f234cc9dac6c4",
"sha256:695e34c4dbea18d09ab2c258994a8bf6a09564e762655408241f6a14592d2908",
"sha256:6b2e03d69afa8d20253455e67b64de1a82ff8612db105113cccec35d3f8429f0",
"sha256:79ca7cdda7ad4e3663ea3c43bfa8637fc5d5604c7737f19a8964781abbd1148d",
"sha256:7fd2dd9a856f72e6ed06f82facfce01d119b88457cd4b47b7ae501e8e11eba9c",
"sha256:82c0354ac39379d836719a77ee360ef865377aa6fdead87909d50248d0f05f4d",
"sha256:8f3b956d11c5b301206382726210dc1d3bee1a9ccf7aadf895aaf31f71c3716c",
"sha256:91ec98640220ae05b34b79ee88abf27f97ef7c61cf525eec57ea8fcea9f7dddb",
"sha256:952be9540d83dba815569d5cb5f31708801e0bbfc3a8c5aef1890b57ed7e58bf",
"sha256:99ac266af38ba1b1fe13975aea01ac0e14bb5f3a3200d2c69f05385768b8568e",
"sha256:9fa122e7adb24232247f8a89f2d9070bf64b7869daf93ac5e19546b409e47e96",
"sha256:a0873eadc4b8ca93e2e848d490809e0123eea154aa44ecd0109c4d0171869584",
"sha256:cb998bd4d93af46b8b49ecf5a72c0a98e5cc6d57fdca6527ba78ad89d6606484",
"sha256:e02e57346f6a68523e3c43bbdf35dde5c440318d1f827208ae455f6a2ace446d",
"sha256:e79a5a896bcee7fff24a788d72e5c69f13e61369d055f28113e71945a7eb1559",
"sha256:ee55eb6bcf23ecc975e6b47c127c201b913598f38b6a300075f84eeef2d3baff",
"sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454"
],
"index": "pypi",
"version": "==0.14.1"
},
"whitenoise": {
"hashes": [
"sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02",
"sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039"
],
"index": "pypi",
"version": "==4.1"
"version": "==6.0"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
@@ -158,7 +259,6 @@
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==1.2.1"
},
"attrs": {
@@ -168,6 +268,13 @@
],
"version": "==18.2.0"
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.6.0"
},
"black": {
"hashes": [
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
@@ -176,21 +283,119 @@
"index": "pypi",
"version": "==18.9b0"
},
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
],
"version": "==3.0.2"
},
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.10.15"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"version": "==1.11.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7'",
"version": "==7.0"
},
"cmarkgfm": {
"hashes": [
"sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061",
"sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c",
"sha256:207b7673ff4e177374c572feeae0e4ef33be620ec9171c08fd22e2b796e03e3d",
"sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de",
"sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2",
"sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63",
"sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655",
"sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889",
"sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce",
"sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190",
"sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87",
"sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957",
"sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5",
"sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7",
"sha256:a3d17ee4ae739fe16f7501a52255c2e287ac817cfd88565b9859f70520afffea",
"sha256:ba5b5488719c0f2ced0aa1986376f7baff1a1653a8eb5fdfcf3f84c7ce46ef8d",
"sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f",
"sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1",
"sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034",
"sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65",
"sha256:cf5014eb214d814a83a7a47407272d5db10b719dbeaf4d3cfe5969309d0fcf4b",
"sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697",
"sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34",
"sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9",
"sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061",
"sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a",
"sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09",
"sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c"
],
"version": "==0.4.2"
},
"colorama": {
"hashes": [
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
"sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3",
"sha256:c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c"
],
"markers": "sys_platform == 'win32'",
"version": "==0.3.9"
"version": "==0.4.0"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
],
"version": "==0.14"
},
"flake8": {
"hashes": [
@@ -200,6 +405,60 @@
"index": "pypi",
"version": "==3.5.0"
},
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"index": "pypi",
"version": "==1.0.2"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
],
"version": "==0.16.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"imagesize": {
"hashes": [
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
],
"version": "==1.1.0"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
],
"version": "==0.24"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"marshmallow": {
"hashes": [
"sha256:82b201ad767eb54de371c08cb1db6ca4ad2a728fa41b831e3781bf944815eb38",
"sha256:c250f37ac0e249a8287394a60d91f6240b674642ad999e66cd09463dbccd1d4f"
],
"version": "==3.0.0b18"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@@ -215,21 +474,33 @@
],
"version": "==4.3.0"
},
"packaging": {
"hashes": [
"sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
"sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
],
"version": "==18.0"
},
"pkginfo": {
"hashes": [
"sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474",
"sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"
],
"version": "==1.4.2"
},
"pluggy": {
"hashes": [
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
],
"markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==0.7.1"
},
"py": {
"hashes": [
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==1.6.0"
"version": "==1.7.0"
},
"pycodestyle": {
"hashes": [
@@ -238,6 +509,12 @@
],
"version": "==2.3.1"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
@@ -245,6 +522,20 @@
],
"version": "==1.6.0"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"pyparsing": {
"hashes": [
"sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a",
"sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"
],
"version": "==2.2.2"
},
"pytest": {
"hashes": [
"sha256:7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e",
@@ -253,6 +544,34 @@
"index": "pypi",
"version": "==3.8.2"
},
"pytz": {
"hashes": [
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
"sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
],
"version": "==2018.5"
},
"readme-renderer": {
"hashes": [
"sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2",
"sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d"
],
"version": "==22.0"
},
"requests": {
"hashes": [
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"version": "==2.19.1"
},
"requests-toolbelt": {
"hashes": [
"sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
"sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
],
"version": "==0.8.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
@@ -260,12 +579,70 @@
],
"version": "==1.11.0"
},
"snowballstemmer": {
"hashes": [
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
],
"version": "==1.2.1"
},
"sphinx": {
"hashes": [
"sha256:652eb8c566f18823a022bb4b6dbc868d366df332a11a0226b5bc3a798a479f17",
"sha256:d222626d8356de702431e813a05c68a35967e3d66c6cd1c2c89539bb179a7464"
],
"index": "pypi",
"version": "==1.8.1"
},
"sphinxcontrib-websupport": {
"hashes": [
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
],
"version": "==1.1.0"
},
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
],
"version": "==0.10.0"
},
"tqdm": {
"hashes": [
"sha256:a0be569511161220ff709a5b60d0890d47921f746f1c737a11d965e1b29e7b2e",
"sha256:e293e6d7a7f41a529a27f8d6624ab11544ccbfe82a205af6fad102545099fc21"
],
"version": "==4.27.0"
},
"twine": {
"hashes": [
"sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c",
"sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c"
],
"index": "pypi",
"version": "==1.12.1"
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
],
"version": "==1.23"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"version": "==0.14.1"
}
}
}
+151 -16
View File
@@ -1,35 +1,170 @@
# Responder: a Sorta Familar HTTP Framework for Python
# Responder: a familiar HTTP Service Framework for Python
![](https://farm2.staticflickr.com/1937/30196007887_604e2f10d8_k_d.jpg)
[![Build Status](https://travis-ci.org/kennethreitz/responder.svg?branch=master)](https://travis-ci.org/kennethreitz/responder)
[![Documentation Status](https://readthedocs.org/projects/mybinder/badge/?version=latest)](https://responder.readthedocs.io/en/latest/)
[![image](https://img.shields.io/pypi/v/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/pypi/l/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/pypi/pyversions/responder.svg)](https://pypi.org/project/responder/)
[![image](https://img.shields.io/github/contributors/kennethreitz/responder.svg)](https://github.com/kennethreitz/responder/graphs/contributors)
I'm adept to keep the "for humans" tagline off this project, until it comes out of the prototyping phase. I'm building this to learn, and to have fun -- while, at the same time, trying to bring something new to the table.
[![](https://github.com/kennethreitz/responder/raw/master/ext/small.jpg)](http://python-responder.org/)
The Python world certainly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd spread some [Hacktoberfest](https://hacktoberfest.digitalocean.com/) spirit around, bring some of my ideas to the table, and see what I could come up with.
```python
import responder
api = responder.API()
@api.route("/{greeting}")
async def greet_world(req, resp, *, greeting):
resp.text = f"{greeting}, world!"
if __name__ == '__main__':
api.run()
```
That `async` declaration is optional. [View documentation](http://python-responder.org).
This gets you a ASGI app, with a production static files server pre-installed, jinja2 templating (without additional imports), and a production webserver based on uvloop, serving up requests with gzip compression automatically.
## Testimonials
> "Pleasantly very taken with python-responder. [@kennethreitz](https://twitter.com/kennethreitz) at his absolute best." —Rudraksh M.K.
> "ASGI is going to enable all sorts of new high-performance web services. It's awesome to see Responder starting to take advantage of that." — Tom Christie author of [Django REST Framework](https://www.django-rest-framework.org/)
> "I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of [Two Scoops of Django]()
> "Love what I have seen while it's in progress! Many features of Responder are from my wishlist for Flask, and it's even faster and even easier than Flask!" — Luna C.
## More Examples
Class-based views (and setting some headers and stuff):
```python
@api.route("/{greeting}")
class GreetingResource:
def on_request(req, resp, *, greeting): # or on_get...
resp.text = f"{greeting}, world!"
resp.headers.update({'X-Life': '42'})
resp.status_code = api.status_codes.HTTP_416
```
Render a template, with arguments:
```python
@api.route("/{greeting}")
def greet_world(req, resp, *, greeting):
resp.content = api.template("index.html", greeting=greeting)
```
The `api` instance is available as an object during template rendering.
Here, you can spawn off a background thread to run any function, out-of-request:
```python
@api.route("/")
def hello(req, resp):
@api.background.task
def sleep(s=10):
time.sleep(s)
print("slept!")
sleep()
resp.content = "processing"
```
And even serve a GraphQL API:
```python
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
api.add_route("/graph", graphene.Schema(query=Query))
```
We can then send a query to our service:
```pycon
>>> requests = api.session()
>>> r = requests.get("http://;/graph", params={"query": "{ hello }"})
>>> r.json()
{'data': {'hello': 'Hello stranger'}}
```
Or, request YAML back:
```pycon
>>> r = requests.get("http://;/graph", params={"query": "{ hello(name:\"john\") }"}, headers={"Accept": "application/x-yaml"})
>>> print(r.text)
data: {hello: Hello john}
```
Want HSTS?
```
api = responder.API(enable_hsts=True)
```
Boom.
# Installing Responder
Install the latest release:
$ pipenv install responder
✨🍰✨
Or, install from the development branch:
$ pipenv install -e git+https://github.com/kennethreitz/responder.git#egg=responder
Only **Python 3.6+** is supported.
The Python world certianly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd bring some of my ideas to the table and see what I could come up with.
# The Basic Idea
The primary concept here is to bring the nicities that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitaves that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitives that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
## Old Ideas
- Setting `resp.text` sends back unicode, while setting `resp.content` sends back bytes.
- Setting `resp.media` sends back JSON/YAML (`.text`/`.content` override this).
- Case-insensitive `req.headers` dict (from Requests directly).
- `resp.status_code`, `req.method`, `req.url`, and other familiar friends.
- Flask-style route expression, with new capabilities -- primarily, the ability to cast a parameter to integers as well as other types that are missing from Flask, all while using Python 3.6+'s new f-string syntax.
## Ideas
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially `response.media`, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
## New Ideas
- **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses.
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests.
- WhiteNoise is built-in, for serving static files (this has yet to be built out, there's no templating or `static_url` yet)
- Waitress (will-be) built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Waitress serves well to protect against slowloris attacks, making nginx unneccessary in production.
- GraphQL support, via Graphene. The goal here is to eventually have an embedded version of GraphiQL exposable at any route.
- A production static file server is built-in.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
## Future Ideas
- I want to be able to "mount" any WSGI app into a sub-route.
- Cooke-based sessions are currently an afterthrought, as this is an API framework, but websites are APIs too.
- Potentially support ASGI instead of WSGI. Will the tradeoffs be worth it? This is a question to ask. Procedural code works well for 90% use cases.
- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too.
- If frontend websites are supported, provide an official way to run webpack.
# The Goal
The primary goal here is to learn, not to get adoption. Though, who knows how these things will pan out.
----------
[![hacktoberfest](https://hacktoberfest.digitalocean.com/assets/hacktoberfest-2018-social-card-c8d2e1489f647f2e0a26e6f598adeb760872818905b34cd437afc7ac2857ceab.png)](https://hacktoberfest.digitalocean.com/)
-53
View File
@@ -1,53 +0,0 @@
import responder
import graphene
api = responder.API(static="static")
# api.mount('/subapp', other_wsgi_app)
@api.route("/")
def hello(req, resp):
print(resp)
# resp.status = responder.status.ok
resp.media = {"hello": "world"}
class ThingsResource:
def on_request(self, req, resp):
resp.status = responder.status.HTTP_200
resp.media = ["ylolo"]
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
schema = graphene.Schema(query=Query)
class GraphQLResource(responder.GraphQLSchema):
import graphene
def on_request(self, req, resp):
resp.status = responder.status.HTTP_200
print(schema.execute("{ hello }").data)
resp.media = ["yolo"]
# Alerntatively,
api.add_route("/2", GraphQLResource)
api.add_route("/graph", schema)
print(
api.session()
.get(
"http://app/graph?query={ hello }",
headers={"Accept": "application/x-yaml"},
# data="hello",
)
.text
)
+19
View File
@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
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)
+35
View File
@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
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
+48
View File
@@ -0,0 +1,48 @@
alabaster==0.7.12
appdirs==1.4.3
atomicwrites==1.2.1
attrs==18.2.0
babel==2.6.0
black==18.9b0
bleach==3.0.2
certifi==2018.8.24
cffi==1.11.5
chardet==3.0.4
click==7.0
cmarkgfm==0.4.2
colorama==0.4.0 ; sys_platform == 'win32'
docutils==0.14
flake8==3.5.0
flask==1.0.2
future==0.16.0
idna==2.7
imagesize==1.1.0
itsdangerous==0.24
jinja2==2.10
markupsafe==1.0
mccabe==0.6.1
more-itertools==4.3.0
packaging==18.0
pkginfo==1.4.2
pluggy==0.7.1
py==1.7.0
pycodestyle==2.3.1
pycparser==2.19
pyflakes==1.6.0
pygments==2.2.0
pyparsing==2.2.2
pytest==3.8.2
pytz==2018.5
readme-renderer==22.0
requests-toolbelt==0.8.0
requests==2.19.1
six==1.11.0
snowballstemmer==1.2.1
sphinx==1.8.1
sphinxcontrib-websupport==1.1.0
toml==0.10.0
tqdm==4.26.0
twine==1.12.1
urllib3==1.23
webencodings==0.5.1
werkzeug==0.14.1
+7
View File
@@ -0,0 +1,7 @@
/* Hide module name and default value for environment variable section */
div[id$='environment-variables'] code.descclassname {
display: none;
}
div[id$='environment-variables'] em.property {
display: none;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
/*
Copyright (C) 2011-2018 Hoefler & Co.
This software is the property of Hoefler & Co. (H&Co).
Your right to access and use this software is subject to the
applicable License Agreement, or Terms of Service, that exists
between you and H&Co. If no such agreement exists, you may not
access or use this software for any purpose.
This software may only be hosted at the locations specified in
the applicable License Agreement or Terms of Service, and only
for the purposes expressly set forth therein. You may not copy,
modify, convert, create derivative works from or distribute this
software in any way, or make it accessible to any third party,
without first obtaining the written permission of H&Co.
For more information, please visit us at http://typography.com.
148887-130097-20181011
*/
<!-- sorry your browser is not supported. -->
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
/*
Copyright (C) 2011-2018 Hoefler & Co.
This software is the property of Hoefler & Co. (H&Co).
Your right to access and use this software is subject to the
applicable License Agreement, or Terms of Service, that exists
between you and H&Co. If no such agreement exists, you may not
access or use this software for any purpose.
This software may only be hosted at the locations specified in
the applicable License Agreement or Terms of Service, and only
for the purposes expressly set forth therein. You may not copy,
modify, convert, create derivative works from or distribute this
software in any way, or make it accessible to any third party,
without first obtaining the written permission of H&Co.
For more information, please visit us at http://typography.com.
148887-130097-20181011
*/
<!-- sorry your browser is not supported. -->
File diff suppressed because one or more lines are too long
+151
View File
@@ -0,0 +1,151 @@
/*
* Konami-JS ~
* :: Now with support for touch events and multiple instances for
* :: those situations that call for multiple easter eggs!
* Code: https://github.com/snaptortoise/konami-js
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
* Version: 1.6.2 (7/17/2018)
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = '';
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, 'keydown', this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
konami.removeEvent(document, 'touchend', this.touchendHandler);
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap === true) ? "TAP" : result;
return result;
}
}
}
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Konami;
} else {
if (typeof define === 'function' && define.amd) {
define([], function() {
return Konami;
});
} else {
window.Konami = Konami;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

+206
View File
@@ -0,0 +1,206 @@
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/7584432/7586812/css/fonts.css" />
<script type="text/javascript">$('#searchbox').hide(0);</script>
<!--Alabaster (krTheme++) Hacks -->
<!-- CSS Adjustments (I'm very picky.) -->
<style type="text/css">
/* Rezzy requires precise alignment. */
img.logo {
margin-left: -20px !important;
}
h1 {
font-family: "Mercury Text G1 A", "Mercury Text G1 B" !important;
font-style: normal !important;
font-weight: 600 !important;
}
.section {
font-family: "Mercury Text G1 A", "Mercury Text G1 B" !important;
font-style: normal !important;
font-weight: 400 !important;
}
pre,
.pre,
.class em,
.descname,
.method em {
font-family: "Operator Mono SSm A", "Operator Mono SSm B" !important;
font-weight: 400 !important;
}
.property {
color: lightgrey !important;
}
.method .descname {
color: #220a54;
}
.method {
margin-bottom: 2em;
}
.si,
.s2,
.s1,
.method em,
.class em {
font-style: italic !important;
color: grey;
}
.method em,
.class em {
margin-left: 0.3em;
margin-right: 0.3em;
}
.method p,
.class p {
font-family: "Mercury Text G1 A", "Mercury Text G1 B";
font-style: italic !important;
font-weight: 400 !important;
font-size: 1.15em;
}
.method p:first,
.class p:first {
background: #fffcbf;
}
.class .property {
display: none;
}
#testimonials p.attribution {
margin-top: -1em;
}
/* "Quick Search" should be not be shown for now. */
div#searchbox h3 {
display: none;
}
/* Make the document a little wider, less code is cut-off. */
div.document {
width: 1008px;
}
/* Much-improved spacing around code blocks. */
div.highlight pre {
padding: 11px 14px;
}
/* Remain Responsive! */
@media screen and (max-width: 1008px) {
div.sphinxsidebar {
display: none;
}
div.document {
width: 100% !important;
}
/* Have code blocks escape the document right-margin. */
div.highlight pre {
margin-right: -30px;
}
}
</style>
<!-- Analytics tracking for Kenneth. -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-127383416-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-127383416-1');
</script>
<!-- There are no more hacks. -->
<!-- இڿڰۣ-ڰۣ— -->
<!-- Love, Kenneth Reitz -->
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
<script>
var easter_egg = new Konami('https://www.myfortunecookie.co.uk/fortunes/' + (Math.floor(Math.random() * 152) + 1));
</script>
<style>
.injected {
display: none !important;
}
</style>
<!-- GitHub Logo -->
<a href="https://github.com/kennethreitz/responder" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
</svg>
</a>
<style>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
}
20%,
60% {
transform: rotate(-25deg)
}
40%,
80% {
transform: rotate(10deg)
}
}
@media (max-width:500px) {
.github-corner:hover .octo-arm {
animation: none
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
}
</style>
<!-- That was not a hack. That was art.
<!-- UserVoice JavaScript SDK (only needed once on a page) -->
<script>(function () { var uv = document.createElement('script'); uv.type = 'text/javascript'; uv.async = true; uv.src = '//widget.uservoice.com/f4AQraEfwInlMzkexfRLg.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(uv, s) })()</script>
<!-- A tab to launch the Classic Widget -->
<script>
UserVoice = window.UserVoice || [];
UserVoice.push(['showTab', 'classic_widget', {
mode: 'feedback',
primary_color: '#fa8c28',
link_color: '#0a8cc6',
forum_id: 913660,
tab_label: 'Got feedback?',
tab_color: '#00994f',
tab_position: 'bottom-left',
tab_inverted: true
}]);
</script>
+34
View File
@@ -0,0 +1,34 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
</ul>
+34
View File
@@ -0,0 +1,34 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/responder.png', 1) }}" title="https://kennethreitz.org/tattoos" />
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&repo=responder&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<p>
<strong>Responder</strong> is a web service framework, written for human beings.
</p>
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true" allowtransparency="true"
frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow
@kennethreitz</a>
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="http://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="http://pypi.python.org/pypi/responder">Responder @ PyPI</a></li>
<li><a href="http://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
</ul>
+35
View File
@@ -0,0 +1,35 @@
API Documentation
=================
Web Service (API) Class
-----------------------
.. module:: responder
.. autoclass:: API
:inherited-members:
Requests & Responses
--------------------
.. autoclass:: Request
:inherited-members:
.. autoclass:: Response
:inherited-members:
Utility Functions
-----------------
.. autofunction:: responder.API.status_codes.is_100
.. autofunction:: responder.API.status_codes.is_200
.. autofunction:: responder.API.status_codes.is_300
.. autofunction:: responder.API.status_codes.is_400
.. autofunction:: responder.API.status_codes.is_500
+222
View File
@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = "responder"
copyright = "2018, A Kenneth Reitz project"
author = "Kenneth Reitz"
# The short X.Y version
import os
# Path hackery to get current version number.
here = os.path.abspath(os.path.dirname(__file__))
about = {}
with open(os.path.join(here, "..", "..", "responder", "__version__.py")) as f:
exec(f.read(), about)
version = about["__version__"]
# The full version, including alpha/beta/rc tags
release = about["__version__"]
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.mathjax",
"sphinx.ext.ifconfig",
"sphinx.ext.viewcode",
"sphinx.ext.githubpages",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# 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 = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- 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 = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
"show_powered_by": False,
"github_user": "kennethreitz",
"github_repo": "responder",
"github_banner": False,
"show_related": False,
}
html_sidebars = {
"index": ["sidebarintro.html", "sourcelink.html", "searchbox.html", "hacks.html"],
"**": [
"sidebarlogo.html",
"localtoc.html",
"relations.html",
"sourcelink.html",
"searchbox.html",
"hacks.html",
],
}
# 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"]
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "responderdoc"
# -- 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, "responder.tex", "responder Documentation", "Kenneth Reitz", "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, "responder", "responder 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,
"responder",
"responder Documentation",
author,
"responder",
"One line description of project.",
"Miscellaneous",
)
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ["search.html"]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
+156
View File
@@ -0,0 +1,156 @@
.. responder documentation master file, created by
sphinx-quickstart on Thu Oct 11 12:58:34 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
A familiar HTTP Service Framework
=================================
|Build Status| |image1| |image2| |image3| |image4| |image5|
.. |Build Status| image:: https://travis-ci.org/kennethreitz/responder.svg?branch=master
:target: https://travis-ci.org/kennethreitz/responder
.. |image1| image:: https://img.shields.io/pypi/v/responder.svg
:target: https://pypi.org/project/responder/
.. |image2| image:: https://img.shields.io/pypi/l/responder.svg
:target: https://pypi.org/project/responder/
.. |image3| image:: https://img.shields.io/pypi/pyversions/responder.svg
:target: https://pypi.org/project/responder/
.. |image4| image:: https://img.shields.io/github/contributors/kennethreitz/responder.svg
:target: https://github.com/kennethreitz/responder/graphs/contributors
.. |image5| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
:target: https://saythanks.io/to/kennethreitz
The Python world certainly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd
spread some `Hacktoberfest <https://hacktoberfest.digitalocean.com/>`_ spirit around, bring some of my ideas to the table, and see what I could come up with.
.. code:: python
import responder
api = responder.API()
@api.route("/{greeting}")
async def greet_world(req, resp, *, greeting):
resp.text = f"{greeting}, world!"
if __name__ == '__main__':
api.run()
That ``async`` declaration is optional.
This gets you a ASGI app, with a production static files server
pre-installed, jinja2 templating (without additional imports), and a
production webserver based on uvloop, serving up requests with gzip
compression automatically.
Features
--------
- A pleasant API, with a single import statement.
- Class-based views without inheritence.
- ASGI framework, the future of Python web services.
- The ability to mount any ASGI / WSGI app at a subroute.
- *f-string syntax* route declration.
- Mutable response object, passed into each view. No need to return anything.
- Background tasks, spawned off in a ``ThreadPoolExecutor``.
- GraphQL support!
Testimonials
------------
“Pleasantly very taken with python-responder.
`@kennethreitz <https://twitter.com/kennethreitz>`_ at his absolute
best.”
—Rudraksh M.K.
..
"ASGI is going to enable all sorts of new high-performance web services. It's awesome to see Responder starting to take advantage of that."
—Tom Christie, author of `Django REST Framework`_
..
“I love that you are exploring new patterns. Go go go!”
— Danny Greenfield, author of `Two Scoops of Django`_
..
“The most ambitious crossover event in history.”
—Pablo Cabezas, `on Tom Christie joining the project`_
.. _APIStar: https://github.com/encode/apistar
.. _Django REST Framework: https://www.django-rest-framework.org/
.. _Two Scoops of Django:
.. _on Tom Christie joining the project: https://twitter.com/pabloteleco/status/1050841098321620992?s=20
User Guides
-----------
.. toctree::
:maxdepth: 2
quickstart
tour
api
Installing Responder
--------------------
.. code-block:: shell
$ pipenv install responder
✨🍰✨
Only **Python 3.6+** is supported.
The Basic Idea
--------------
The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitives that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
- Setting ``resp.text`` sends back unicode, while setting ``resp.content`` sends back bytes.
- Setting ``resp.media`` sends back JSON/YAML (``.text``/``.content`` override this).
- Case-insensitive ``req.headers`` dict (from Requests directly).
- ``resp.status_code``, ``req.method``, ``req.url``, and other familiar friends.
Ideas
-----
- Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially ``response.media``, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
- **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses.
- In addition to Falcon's ``on_get``, ``on_post``, etc methods, Responder features an ``on_request`` method, which gets called on every type of request, much like Requests.
- A production static files server is built-in.
- Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unneccessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
Future Ideas
------------
- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too.
- If frontend websites are supported, provide an official way to run webpack.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+126
View File
@@ -0,0 +1,126 @@
Quick Start!
============
This section of the documentation exists to provide an introduction to the Responder interface,
as well as educate the user on basic functionality.
Declare a Web Service
---------------------
The first thing you need to do is declare a web service::
import responder
api = responder.API()
Hello World!
------------
Then, you can add a view / route to it.
Here, we'll make the root URL say "hello world!"::
@api.route("/")
def hello_world(req, resp):
resp.text = "hello, world!"
Run the Server
--------------
Next, we can run our web service easily, with ``api.run()``::
api.run()
This will spin up a production web server on port ``5042``, ready for incoming HTTP requests.
Note: you can pass ``port=5000`` if you want to customize the port. The ``PORT`` environment variable for established web service providers (e.g. Heroku) will automatically be honored.
Accept Route Arguments
----------------------
If you want dynamic URLs, you can use Python's familiar *f-string syntax* to declare variables in your routes::
@api.route("/hello/{who}")
def hello_to(req, resp, *, who):
resp.text = f"hello, {who}!"
A ``GET`` request to ``/hello/brettcannon`` will result in a response of ``hello, brettcannon!``.
Returning JSON / YAML
---------------------
If you want your API to send back JSON, simply set the ``resp.media`` property to a JSON-serializable Python object::
@api.route("/hello/{who}/json")
def hello_to(req, resp, *, who):
resp.media = {"hello": who}
A ``GET`` request to ``/hello/guido/json`` will result in a response of ``{'hello': 'guido'}``.
If the client requests YAML instead (with a header of ``Accept: application/x-yaml``), YAML will be sent.
Rendering a Template
--------------------
If you want to render a template, simply use ``api.template``. No need for additional imports::
@api.route("/hello/{who}/html")
def hello_html(req, resp, *, who):
resp.content = api.template('hello.html', who=who)
The ``api`` instance is available as an object during template rendering.
Setting Response Status Code
----------------------------
If you want to set the response status code, simply set ``resp.status_code``::
@api.route("/416")
def teapot(req, resp):
resp.status_code = api.status_codes.HTTP_416 # ...or 416
Setting Response Headers
------------------------
If you want to set a response header, like ``X-Pizza: 42``, simply modify the ``resp.headers`` dictionary::
@api.route("/pizza")
def pizza_pizza(req, resp):
resp.headers['X-Pizza'] = 42
That's it!
Receiving Data & Background Tasks
---------------------------------
If you're expecting to read any request data, on the server, you need to declare your view as async and await the content.
Here, we'll process our data in the background, while responding immediately to the client::
import time
@api.route("/incoming")
async def receive_incoming(req, resp):
@api.background.task
def process_data(data):
"""Just sleeps for three seconds, as a demo."""
time.sleep(3)
# Parse the incoming data as form-encoded.
# Note: 'json' and 'yaml' formats are also automatically supported.
data = await resp.media()
# Process the data (in the background).
process_data(data)
# Immediately respond that upload was successful.
resp.media = {'success': True}
A ``POST`` request to ``/incoming`` will result in an immediate response of ``{'success': true}``.
+151
View File
@@ -0,0 +1,151 @@
Feature Tour
============
Class-Based Views
-----------------
Class-based views (and setting some headers and stuff)::
@api.route("/{greeting}")
class GreetingResource:
def on_request(req, resp, *, greeting): # or on_get...
resp.text = f"{greeting}, world!"
resp.headers.update({'X-Life': '42'})
resp.status_code = api.status_codes.HTTP_416
Background Tasks
----------------
Here, you can spawn off a background thread to run any function, out-of-request::
@api.route("/")
def hello(req, resp):
@api.background.task
def sleep(s=10):
time.sleep(s)
print("slept!")
sleep()
resp.content = "processing"
GraphQL
-------
Serve a GraphQL API::
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
api.add_route("/graph", graphene.Schema(query=Query))
Built-in Testing Client (Requests)
----------------------------------
We can then send a query to our service::
>>> requests = api.session()
>>> r = requests.get("http://;/graph", params={"query": "{ hello }"})
>>> r.json()
{'data': {'hello': 'Hello stranger'}}
Or, request YAML back::
>>> r = requests.get("http://;/graph", params={"query": "{ hello(name:\"john\") }"}, headers={"Accept": "application/x-yaml"})
>>> print(r.text)
data: {hello: Hello john}
OpenAPI Schema Support
----------------------
Responder comes with built-in support for OpenAPI / marshmallow::
import responder
from marshmallow import Schema, fields
api = responder.API(title="Web Service", version="1.0", openapi="3.0")
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
@api.route("/")
def route(req, resp):
"""A cute furry animal endpoint.
---
get:
description: Get a random pet
responses:
200:
description: A pet to be returned
schema:
$ref = "#/components/schemas/Pet"
"""
resp.media = PetSchema().dump({"name": "little orange"})
::
>>> r = api.session().get("http://;/schema.yml")
>>> print(r.text)
components:
parameters: {}
schemas:
Pet:
properties:
name: {type: string}
type: object
info: {title: Web Service, version: 1.0}
openapi: '3.0'
paths:
/:
get:
description: Get a random pet
responses:
200: {description: A pet to be returned, schema: $ref = "#/components/schemas/Pet"}
tags: []
Mount a WSGI App (e.g. Flask)
-----------------------------
Responder gives you the ability to mount another ASGI / WSGI app at a subroute::
import responder
from flask import Flask
api = responder.API()
flask = Flask(__name__)
@flask.route('/')
def hello():
return 'hello'
api.mount('/flask', flask)
That's it!
HSTS (Redirect to HTTPS)
------------------------
Want HSTS?
::
api = responder.API(enable_hsts=True)
Boom.
Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><polygon points="32.625,51 21.836,51 28.536,13 39.325,13 "></polygon><polygon points="49.107,51 38.319,51 45.019,13 55.808,13 "></polygon><rect x="9" y="18" width="12" height="12"></rect><rect x="9" y="33" width="12" height="12"></rect></svg>

After

Width:  |  Height:  |  Size: 430 B

+49431
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

-13
View File
@@ -1,13 +0,0 @@
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
return "Hello " + name
schema = graphene.Schema(query=Query)
result = schema.execute("{ hello }")
print(result.data["hello"])
+4
View File
@@ -0,0 +1,4 @@
[pytest]
; addopts= -rsxX -s -v --strict
filterwarnings =
error::UserWarning
+5
View File
@@ -0,0 +1,5 @@
build:
image: latest
python:
version: 3.6
+4
View File
@@ -0,0 +1,4 @@
from .cli import main
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
__version__ = "0.0.7"
+336 -87
View File
@@ -1,83 +1,198 @@
import os
import json
from functools import partial
from pathlib import Path
import graphene
import uvicorn
from whitenoise import WhiteNoise
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
from requests import Session as RequestsSession
import asyncio
import jinja2
from graphql_server import encode_execution_results, json_encode, default_format_error
from starlette.routing import Router
from starlette.staticfiles import StaticFiles
from starlette.testclient import TestClient
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec import yaml_utils
from asgiref.wsgi import WsgiToAsgi
from . import models
from .status import HTTP_404
from . import status_codes
from .routes import Route
from .formats import get_formats
from .background import BackgroundQueue
class BaseAPI:
__slots__ = ["routes"]
# TODO: consider moving status codes here
class API:
"""The primary web-service class.
def __init__(self):
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
"""
status_codes = status_codes
def __init__(
self,
*,
title=None,
version=None,
openapi=None,
openapi_route="/schema.yml",
static_dir="static",
templates_dir="templates",
enable_hsts=False,
):
self.title = title
self.version = version
self.openapi_version = openapi
self.static_dir = Path(os.path.abspath(static_dir))
self.static_route = f"/{static_dir}"
self.templates_dir = Path(os.path.abspath(templates_dir))
self.routes = {}
self.schemas = {}
def _wsgi_app(self, environ, start_response):
# def wsgi_app(self, request):
"""The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without
losing a reference to the app object. Instead of doing this::
self.hsts_enabled = enable_hsts
self.static_files = StaticFiles(directory=str(self.static_dir))
self.apps = {self.static_route: self.static_files}
app = MyMiddleware(app)
self.formats = get_formats()
It's a better idea to do this instead::
# Make the static/templates directory if they don't exist.
for _dir in (self.static_dir, self.templates_dir):
os.makedirs(_dir, exist_ok=True)
app.wsgi_app = MyMiddleware(app.wsgi_app)
# Cached requests session.
self._session = None
self.background = BackgroundQueue()
Then you still have the original application object around and
can continue to call methods on it.
if self.openapi_version:
self.add_route(openapi_route, self.schema_response)
.. versionchanged:: 0.7
Teardown events for the request and app contexts are called
even if an unhandled error occurs. Other events may not be
called depending on when an error occurs during dispatch.
See :ref:`callbacks-and-errors`.
@property
def _apispec(self):
spec = APISpec(
title=self.title,
version=self.version,
openapi_version=self.openapi_version,
plugins=[MarshmallowPlugin()],
)
for route in self.routes:
if self.routes[route].description:
operations = yaml_utils.load_operations_from_docstring(
self.routes[route].description
)
spec.add_path(path=route, operations=operations)
for name, schema in self.schemas.items():
spec.definition(name, schema=schema)
return spec
@property
def openapi(self):
return self._apispec.to_yaml()
def __call__(self, scope):
path = scope["path"]
root_path = scope.get("root_path", "")
# Call into a submounted app, if one exists.
for path_prefix, app in self.apps.items():
if path.startswith(path_prefix):
scope["path"] = path[len(path_prefix) :]
scope["root_path"] = root_path + path_prefix
try:
return app(scope)
except TypeError:
app = WsgiToAsgi(app)
return app(scope)
# Call the main dispatcher.
async def asgi(receive, send):
nonlocal scope, self
req = models.Request(scope, receive=receive)
resp = await self._dispatch_request(req)
await resp(receive, send)
return asgi
def add_schema(self, name, schema, check_existing=True):
"""Adds a mashmallow schema to the API specification."""
if check_existing:
assert name not in self.schemas
self.schemas[name] = schema
def schema(self, name, **options):
"""Decorator for creating new routes around function and class definitions.
Usage::
from marshmallow import Schema, fields
@api.schema("Pet")
class PetSchema(Schema):
name = fields.Str()
:param environ: A WSGI environment.
:param start_response: A callable accepting a status code,
a list of headers, and an optional exception context to
start the response.
"""
req = models.Request.from_environ(environ)
resp = self._dispatch_request(req)
def decorator(f):
self.add_schema(name=name, schema=f, **options)
return f
return resp(environ, start_response)
return decorator
def wsgi_app(self, environ, start_response):
return self.whitenoise(environ, start_response)
def path_matches_route(self, path):
"""Given a path portion of a URL, tests that it matches against any registered route.
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)
def path_matches_route(self, url):
for (route, view) in self.routes.items():
if url == route:
:param path: The path portion of a URL, to test all known routes against.
"""
for (route, route_object) in self.routes.items():
if route_object.does_match(path):
return route
def _dispatch_request(self, req):
route = self.path_matches_route(req.path)
resp = models.Response(req=req)
async def _dispatch_request(self, req):
# Set formats on Request object.
req.formats = self.formats
route = self.path_matches_route(req.url.path)
resp = models.Response(req=req, formats=self.formats)
if self.hsts_enabled:
if req.url.startswith("http://"):
url = req.url.replace("http://", "https://", 1)
self.redirect(resp, location=url)
if route:
try:
self.routes[route](req, resp)
params = self.routes[route].incoming_matches(req.url.path)
result = self.routes[route].endpoint(req, resp, **params)
if hasattr(result, "cr_running"):
await result
# The request is using class-based views.
except TypeError:
except TypeError as e:
try:
view = self.routes[route]()
# GraphQL Schema.
view = self.routes[route].endpoint(**params)
except TypeError:
view = self.routes[route]
self.graphql_response(req, resp, schema=view)
view = self.routes[route].endpoint
try:
# GraphQL Schema.
assert hasattr(view, "execute")
await self.graphql_response(req, resp, schema=view)
except AssertionError:
# WSGI App.
# try:
# return view(
# environ=req._environ, start_response=req._start_response
# )
# except TypeError:
# pass
pass
# Run on_request first.
try:
@@ -97,70 +212,204 @@ class BaseAPI:
return resp
@property
def static_dir(self):
return Path(".")
def add_route(self, route, endpoint, *, check_existing=True):
# TODO: add graphiql
"""Add a route to the API.
class API(BaseAPI):
__slots__ = ("routes", "_session", "whitenoise", "static_dir")
def __init__(self, static="static"):
super().__init__()
self._session = None
self.static_dir = Path(os.path.abspath(static))
# Make the static directory if it doesn't exist.
os.makedirs(self.static_dir, exist_ok=True)
# Mount the whitenoise application.
self.whitenoise = WhiteNoise(self._wsgi_app, root=str(self.static_dir))
def add_route(self, route, view, *, check_existing=True, graphiql=False):
:param route: A string representation of the route.
:param endpoint: The endpoint for the route -- can be a callable, a class, a WSGI application, or graphene schema (GraphQL).
:param check_existing: If ``True``, an AssertionError will be raised, if the route is already defined.
"""
if check_existing:
assert route not in self.routes
# TODO: Support grpahiql.
self.routes[route] = view
self.routes[route] = Route(route, endpoint)
def default_response(self, req, resp):
resp.status_code = HTTP_404
resp.status_code = status_codes.HTTP_404
resp.text = "Not found."
def schema_response(self, req, resp):
resp.status_code = status_codes.HTTP_200
resp.headers["Content-Type"] = "application/x-yaml"
resp.content = self.openapi
def redirect(
self, resp, location, *, set_text=True, status_code=status_codes.HTTP_301
):
"""Redirects a given response to a given location.
:param resp: The Response to mutate.
:param location: The location of the redirect.
:param set_text: If ``True``, sets the Redirect body content automatically.
:param status_code: an `API.status_codes` attribute, or an integer, representing the HTTP status code of the redirect.
"""
assert resp.status_code.is_300(status_code)
resp.status_code = status_code
if set_text:
resp.text = f"Redirecting to: {location}"
resp.headers.update({"Location": location})
@staticmethod
def _resolve_graphql_query(req):
async def _resolve_graphql_query(req):
if "json" in req.mimetype:
return (await req.media("json"))["query"]
# Support query/q in form data.
if "query" in req.data:
return req.data["query"]
if "q" in req.data:
return req.data["q"]
# Form data is awaiting https://github.com/encode/starlette/pull/102
# if "query" in req.media("form"):
# return req.media("form")["query"]
# if "q" in req.media("form"):
# return req.media("form")["q"]
# Support query/q in params.
if "query" in req.params:
return req.params["query"][0]
return req.params["query"]
if "q" in req.params:
return req.parama["q"][0]
return req.params["q"]
# Otherwise, the request text is used (typical).
# TODO: Make some assertions about content-type here.
return req.text
def graphql_response(self, req, resp, schema):
query = self._resolve_graphql_query(req)
async def graphql_response(self, req, resp, schema):
query = await self._resolve_graphql_query(req)
result = schema.execute(query)
resp.media = dict(result.data)
result, status_code = encode_execution_results(
[result],
is_batch=False,
format_error=default_format_error,
encode=partial(json_encode, pretty=False),
)
resp.media = json.loads(result)
return (query, result, status_code)
def route(self, route, **options):
"""Decorator for creating new routes around function and class definitions.
Usage::
@api.route("/hello")
def hello(req, resp):
resp.text = "hello, world!"
"""
def decorator(f):
self.add_route(route, f)
self.add_route(route, f, **options)
return f
return decorator
def session(self, base_url="http://app"):
def mount(self, route, app):
"""Mounts an WSGI / ASGI application at a given route.
:param route: String representation of the route to be used (shouldn't be parameterized).
:param app: The other WSGI / ASGI app.
"""
self.apps.update({route: app})
def session(self, base_url="http://;"):
"""Testing HTTP client. Returns a Requests session object, able to send HTTP requests to the Responder application.
:param base_url: The URL to mount the connection adaptor to.
"""
if self._session is None:
session = RequestsSession()
session.mount(base_url, RequestsWSGIAdapter(self))
self._session = session
self._session = TestClient(self)
return self._session
def url_for(self, endpoint, testing=False, **params):
# TODO: Absolute_url
"""Given an endpoint, returns a rendered URL for its route.
:param view: The route endpoint you're searching for.
:param params: Data to pass into the URL generator (for parameterized URLs).
"""
for (route, route_object) in self.routes.items():
if route_object.endpoint == endpoint:
return route_object.url(testing=testing, **params)
raise ValueError
def static_url(self, asset):
"""Given a static asset, return its URL path."""
return f"{self.static_route}/{str(asset)}"
def template(self, name, auto_escape=True, **values):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
Note: The current ``api`` instance is always passed into the view.
:param name: The filename of the jinja2 template, in ``templates_dir``.
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
:param values: Data to pass into the template.
"""
# Give reference to self.
values.update(api=self)
if auto_escape:
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(
str(self.templates_dir), followlinks=True
),
autoescape=jinja2.select_autoescape(["html", "xml"]),
)
else:
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(
str(self.templates_dir), followlinks=True
),
autoescape=jinja2.select_autoescape([]),
)
template = env.get_template(name)
return template.render(**values)
def template_string(self, s, auto_escape=True, **values):
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template string, with provided values supplied.
Note: The current ``api`` instance is always passed into the view.
:param s: The template to use.
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
:param values: Data to pass into the template.
"""
# Give reference to self.
values.update(api=self)
if auto_escape:
env = jinja2.Environment(
loader=jinja2.BaseLoader,
autoescape=jinja2.select_autoescape(["html", "xml"]),
)
else:
env = jinja2.Environment(
loader=jinja2.BaseLoader, autoescape=jinja2.select_autoescape([])
)
template = env.from_string(s)
return template.render(**values)
def run(self, address=None, port=None, **options):
"""Runs the application with uvicorn. If the ``PORT`` environment
variable is set, requests will be served on that port automatically to all
known hosts.
:param address: The address to bind to.
:param port: The port to bind to. If none is provided, one will be selected at random.
:param options: Additional keyword arguments to send to ``uvicorn.run()``.
"""
if "PORT" in os.environ:
if address is None:
address = "0.0.0.0"
port = int(os.environ["PORT"])
if address is None:
address = "127.0.0.1"
if port is None:
port = 5042
uvicorn.run(self, host=address, port=port, **options)
+27
View File
@@ -0,0 +1,27 @@
import multiprocessing
import concurrent.futures
class BackgroundQueue:
def __init__(self, n=None):
if n is None:
n = multiprocessing.cpu_count()
self.n = n
self.pool = concurrent.futures.ThreadPoolExecutor(max_workers=n)
self.results = []
def run(self, f, *args, **kwargs):
self.pool._max_workers = self.n
self.pool._adjust_thread_count()
f = self.pool.submit(f, *args, **kwargs)
self.results.append(f)
return f
def task(self, f):
def do_task(*args, **kwargs):
result = self.run(f, *args, **kwargs)
return result
return do_task
+1 -2
View File
@@ -1,3 +1,2 @@
from .api import API
from . import status
from .views import GraphQLSchema
from .models import Request, Response
+27
View File
@@ -0,0 +1,27 @@
import yaml
import json
async def format_form(r, encode=False):
if not encode:
return await r._starlette.form()
async def format_yaml(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/x-yaml"})
return yaml.safe_dump(r.media)
else:
return yaml.safe_load(await r.content)
async def format_json(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/json"})
return json.dumps(r.media)
else:
return json.loads(await r.content)
def get_formats():
return {"json": format_json, "yaml": format_yaml, "form": format_form}
View File
+236 -80
View File
@@ -2,100 +2,262 @@ import io
import json
import gzip
import chardet
import rfc3986
import graphene
import yaml
from requests.models import Request as RequestsRequest
from requests.structures import CaseInsensitiveDict
from werkzeug.wrappers import Request as WerkzeugRequest
from werkzeug.wrappers import BaseResponse as WerkzeugResponse
from starlette.datastructures import MutableHeaders
from starlette.requests import Request as StarletteRequest
from starlette.responses import Response as StarletteResponse
from urllib.parse import parse_qs
from .status import HTTP_200
# @staticmethod
# def funcname(parameter_list):
# pass
from .status_codes import HTTP_200
from .statics import DEFAULT_ENCODING
class QueryDict(dict):
def __init__(self, query_string):
self.update(parse_qs(query_string))
def __getitem__(self, key):
"""
Return the last data value for this key, or [] if it's an empty list;
raise KeyError if not found.
"""
list_ = super().__getitem__(key)
try:
return list_[-1]
except IndexError:
return []
def get(self, key, default=None):
"""
Return the last data value for the passed key. If key doesn't exist
or value is an empty list, return `default`.
"""
try:
val = self[key]
except KeyError:
return default
if val == []:
return default
return val
def _get_list(self, key, default=None, force_list=False):
"""
Return a list of values for the key.
Used internally to manipulate values list. If force_list is True,
return a new copy of values.
"""
try:
values = super().__getitem__(key)
except KeyError:
if default is None:
return []
return default
else:
if force_list:
values = list(values) if values is not None else None
return values
def get_list(self, key, default=None):
"""
Return the list of values for the key. If key doesn't exist, return a
default value.
"""
return self._get_list(key, default, force_list=True)
def items(self):
"""
Yield (key, value) pairs, where value is the last item in the list
associated with the key.
"""
for key in self:
yield key, self[key]
def items_list(self):
"""
Yield (key, value) pairs, where value is the the list.
"""
yield from super().items()
# TODO: add slots
class Request:
def __init__(self):
super().__init__()
self._wz = None
__slots__ = [
"_starlette",
"formats",
"_headers",
"_encoding",
]
@classmethod
def from_environ(kls, environ):
self = kls()
self._wz = WerkzeugRequest(environ)
self.headers = CaseInsensitiveDict(self._wz.headers.to_list())
self.method = self._wz.method
self.full_url = self._wz.url
self.url = self._wz.base_url
self.full_path = self._wz.full_path
self.path = self._wz.path
self.params = parse_qs(self._wz.query_string.decode("utf-8"))
self.raw = self._wz.stream
self.content = self._wz.get_data(cache=True, as_text=False)
self.text = self._wz.get_data(cache=True, as_text=True)
self.data = self._wz.get_data(cache=True, as_text=True, parse_form_data=True)
def __init__(self, scope, receive):
self._starlette = StarletteRequest(scope, receive)
self.formats = None
self._encoding = None
return self
headers = CaseInsensitiveDict()
for header, value in self._starlette.headers.items():
headers[header] = value
self._headers = headers
@property
def accepts_yaml(self):
return "yaml" in self.headers["accept"]
def headers(self):
"""A case-insensitive dictionary, containing all headers sent in the Request."""
return self._headers
@property
def accepts_json(self):
return "json" in self.headers["accept"]
def mimetype(self):
return self.headers.get("Content-Type", "")
def json(self):
return json.loads(self.content)
@property
def method(self):
"""The incoming HTTP method used for the request, lower-cased."""
return self._starlette.method.lower()
def yaml(self):
return yaml.load(self.content)
@property
def full_url(self):
"""The full URL of the Request, query parameters and all."""
return str(self._starlette.url)
@property
def url(self):
"""The parsed URL of the Request."""
return rfc3986.urlparse(self.full_url)
@property
def params(self):
"""A dictionary of the parsed query parameters used for the Request."""
try:
return QueryDict(self.url.query)
except AttributeError:
return QueryDict({})
@property
async def encoding(self):
"""The encoding of the Request's body. Can be set, manually. Must be awaited."""
# Use the user-set encoding first.
if self._encoding:
return self._encoding
# Then try what's defined by the Request.
elif await self.declared_encoding:
return self.declared_encoding
# Then, automatically detect the encoding.
else:
return await self.apparent_encoding
@encoding.setter
def encoding(self, value):
self._encoding = value
@property
async def content(self):
"""The Request body, as bytes. Must be awaited."""
return await self._starlette.body()
@property
async def text(self):
"""The Request body, as unicode. Must be awaited."""
return (await self._starlette.body()).decode(await self.encoding)
@property
async def declared_encoding(self):
if "Encoding" in self.headers:
return self.headers["Encoding"]
@property
async def apparent_encoding(self):
"""The apparent encoding, provided by the chardet library. Must be awaited."""
declared_encoding = await self.declared_encoding
if declared_encoding:
return declared_encoding
else:
return chardet.detect(await self.content)["encoding"]
@property
def is_secure(self):
return self.url.scheme == "https"
def accepts(self, content_type):
"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""
return content_type in self.headers.get("Accept", [])
async def media(self, format=None):
"""Renders incoming json/yaml/form data as Python objects.
:param format: The name of the format being used. Alternatively accepts a custom callable for the format type.
"""
if format is None:
format = "yaml" if "yaml" in self.mimetype or "" else "json"
format = "form" if "form" in self.mimetype or "" else format
if format in self.formats:
return await self.formats[format](self)
else:
return await format(self)
class Response:
def __init__(self, req):
__slots__ = [
"req",
"status_code",
"text",
"content",
"encoding",
"media",
"headers",
"formats",
]
def __init__(self, req, *, formats):
self.req = req
self.status_code = HTTP_200
self.text = None
self.content = None
self.encoding = None
self.media = None
self.mimetype = None
self.headers = {}
self.status_code = HTTP_200 #: The HTTP Status Code to use for the Response.
self.text = None #: A unicode representation of the response body.
self.content = None #: A bytes representation of the response body.
self.encoding = DEFAULT_ENCODING
self.media = (
None
) #: A Python object that will be content-negotiated and sent back to the client. Typically, in JSON formatting.
self.headers = (
{}
) #: A Python dictionary of {Key: value}, representing the headers of the response.
self.formats = formats
@property
def body(self):
async def body(self):
if self.content:
return (self.content, self.mimetype, {})
return (self.content, {})
if self.text:
return (
self.text.encode("utf-8"),
self.mimetype or "application/text",
{"Encoding": "utf-8"},
)
return (self.text.encode(self.encoding), {"Encoding": self.encoding})
if self.media:
if self.req.accepts_yaml:
return (
yaml.dump(self.media).encode("utf-8"),
self.mimetype or "application/x-yaml",
{},
)
if self.req.accepts_json:
return (json.dumps(self.media), self.mimetype or "application/json", {})
for format in self.formats:
if self.req.accepts(format):
return (await self.formats[format](self, encode=True)), {}
# Default to JSON anyway.
return (
await self.formats["json"](self, encode=True),
{"Content-Type": "application/json"},
)
@property
def gzipped_body(self):
async def gzipped_body(self):
body, headers = await self.body
if isinstance(body, str):
body = body.encode(self.encoding)
body, mimetype, headers = self.body
# print(self.req.headers)
if "gzip" in self.req.headers["Accept-Encoding"].lower():
gzip_buffer = io.BytesIO()
gzip_file = gzip.GzipFile(mode="wb", fileobj=gzip_buffer)
@@ -108,28 +270,22 @@ class Response:
"Content-Length": str(len(body)),
}
headers.update(new_headers)
# print(headers)
return (gzip_buffer.getvalue(), mimetype, headers)
return (gzip_buffer.getvalue(), headers)
else:
return (body, mimetype, headers)
return (body, headers)
@property
def _wz(self):
body, mimetype, headers = self.body
headers.update(self.headers)
async def __call__(self, receive, send):
body, headers = await self.body
if len(await self.body) > 500:
body, headers = await self.gzipped_body
if self.headers:
headers.update(self.headers)
r = WerkzeugResponse(
body,
status=self.status_code,
mimetype=self.mimetype or mimetype,
direct_passthrough=False,
response = StarletteResponse(
body, status_code=self.status_code, headers=headers
)
r.headers = headers
return r
def __call__(self, environ, start_response):
return self._wz(environ, start_response)
await response(receive, send)
class Schema(graphene.Schema):

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