Compare commits

...

134 Commits

Author SHA1 Message Date
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
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
104 changed files with 52527 additions and 208 deletions
+3
View File
@@ -1 +1,4 @@
.vscode/
build
responder.egg-info/
dist/
+13
View File
@@ -0,0 +1,13 @@
language: python
python:
- "3.6"
- "3.7"
# command to install dependencies
install:
- "pip install pipenv --upgrade-strategy=only-if-needed"
- "pipenv install --dev"
# command to run the dependencies
script:
- "pytest"
+4
View File
@@ -0,0 +1,4 @@
- Kenneth Reitz (primary)
- Tom Christie
- Bruno Oliveira
- serhii73
+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 = "."}
uvicorn = "*"
[dev-packages]
pytest = "*"
"flake8" = "*"
black = "*"
twine = "*"
flask = "*"
sphinx = "*"
[requires]
python_version = "3.7"
Generated
+363 -19
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f6b7cc3cf0ac2760ea99bcb8d18c743eff418c6269da29823ccfdbdea19a8c1e"
"sha256": "76d2978ee90d2c028b13c9a5abdd2371d74d514045d50fb9b92aec44e72054b3"
},
"pipfile-spec": 6,
"requires": {
@@ -37,12 +37,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 +64,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 +84,25 @@
],
"version": "==2.7"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"parse": {
"hashes": [
"sha256:9dd6048ea212cd032a342f9f6aa2b7bc222f7407c7e37bdc2777fecd36897437"
],
"version": "==1.9.0"
},
"promise": {
"hashes": [
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
@@ -80,7 +118,6 @@
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
],
"index": "pypi",
"version": "==4.2b4"
},
"requests": {
@@ -88,16 +125,18 @@
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
],
"index": "pypi",
"version": "==2.19.1"
},
"requests-wsgi-adapter": {
"hashes": [
"sha256:7080c98ae2614b8d0b7339b611d97a535470d2fb479731f7d588d5f8108ea134"
],
"index": "pypi",
"version": "==0.4.0"
},
"responder": {
"editable": true,
"path": "."
},
"rx": {
"hashes": [
"sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23",
@@ -117,23 +156,53 @@
"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"
},
"uvicorn": {
"hashes": [
"sha256:8de03999a936d8704f07cc3b1d3a3edb6922a068b64d84b4f5e49604c8b70a11"
],
"index": "pypi",
"version": "==0.3.12"
},
"waitress": {
"hashes": [
"sha256:40b0f297a7f3af61fbfbdc67e59090c70dc150a1601c39ecc9f5f1d283fb931b",
"sha256:d33cd3d62426c0f1b3cd84ee3d65779c7003aae3fc060dee60524d10a57f05a9"
],
"index": "pypi",
"version": "==1.1.0"
},
"websockets": {
"hashes": [
"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"
],
"version": "==6.0"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"index": "pypi",
"version": "==0.14.1"
},
"whitenoise": {
@@ -141,11 +210,17 @@
"sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02",
"sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039"
],
"index": "pypi",
"version": "==4.1"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
@@ -158,7 +233,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 +242,13 @@
],
"version": "==18.2.0"
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
],
"version": "==2.6.0"
},
"black": {
"hashes": [
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
@@ -176,21 +257,119 @@
"index": "pypi",
"version": "==18.9b0"
},
"bleach": {
"hashes": [
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
],
"version": "==3.0.2"
},
"certifi": {
"hashes": [
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
],
"version": "==2018.8.24"
},
"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 +379,53 @@
"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"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@@ -215,21 +441,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 +476,12 @@
],
"version": "==2.3.1"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
],
"version": "==2.19"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
@@ -245,6 +489,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 +511,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 +546,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:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889",
"sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9"
],
"version": "==4.26.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"
}
}
}
+104 -14
View File
@@ -1,14 +1,107 @@
# 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)
![](https://github.com/kennethreitz/responder/raw/master/ext/Artboard%201%402x.png)
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.
The Python world certainly 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.
## But will it blend?
```python
import responder
api = responder.API()
@api.route("/{greeting}")
def greet_world(req, resp, *, greeting):
resp.text = f"{greeting}, world!"
if __name__ == '__main__':
api.run()
```
This gets you a WSGI app, with WhiteNoise pre-installed, jinja2 templating (without additional imports), and a production webserver (ready for slowloris attacks), serving up requests with gzip compression automatically.
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.
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. ✨🍰✨
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 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 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 familar friends.
## 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 (still working on that).
- 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.
- Waitress 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 unnecessary in production.
- GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
## Old Ideas
@@ -16,20 +109,17 @@ The primary concept here is to bring the nicities that are brought forth from bo
- 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**.
- 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.
## 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.
- Cooke-based sessions are currently an afterthought, 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.
- 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.
# When can I use it?
When it's ready. It's not. I started work on this a few days ago. It works surprisingly well, considering! :)
+33 -20
View File
@@ -1,21 +1,31 @@
import responder
import graphene
api = responder.API(static="static")
# api.mount('/subapp', other_wsgi_app)
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World from flask!"
api = responder.API(enable_hsts=False)
api.mount("/hello", app)
@api.route("/")
def hello(req, resp):
print(resp)
# resp.status = responder.status.ok
resp.media = {"hello": "world"}
resp.content = api.template("test.html")
class ThingsResource:
def on_request(self, req, resp):
resp.status = responder.status.HTTP_200
resp.media = ["ylolo"]
resp.media = ["yolo"]
class Query(graphene.ObjectType):
@@ -27,27 +37,30 @@ class Query(graphene.ObjectType):
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)
api.add_route("/graph", schema, graphiql=True)
print(
api.session()
.get(
"http://app/graph?query={ hello }",
headers={"Accept": "application/x-yaml"},
"http://app/",
# data="{ hello }",
# headers={"Accept": "application/x-yaml"},
# data="hello",
)
.text
)
# print(
# api.session()
# .get(
# "http://app/hello/",
# data="{ hello }",
# headers={"Accept": "application/x-yaml"},
# # data="hello",
# )
# .text
# )
# {hello: Hello stranger}
api.run(port=5000)
+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

+202
View File
@@ -0,0 +1,202 @@
<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,
.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;
margin-left: 0.3em;
margin-right: 0.3em;
color: grey;
}
.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;
}
/* "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 type="text/javascript">
var _gauges = _gauges || [];
(function () {
var t = document.createElement('script');
t.type = 'text/javascript';
t.async = true;
t.id = 'gauges-tracker';
t.setAttribute('data-site-id', '588f8e99c88d9013e60fa373');
t.setAttribute('data-track-path', 'https://track.gaug.es/track.gif');
t.src = 'https://d36ee2fcip1434.cloudfront.net/track.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(t, s);
})();
</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>
+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
+179
View File
@@ -0,0 +1,179 @@
.. 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 familar HTTP Service Framework
================================
The Python world certainly 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.
But will it blend?
------------------
::
import responder
api = responder.API()
@api.route("/{greeting}")
def greet_world(req, resp, *, greeting):
resp.text = f"{greeting}, world!"
if __name__ == '__main__':
api.run()
This gets you a WSGI app, with WhiteNoise pre-installed, jinja2 templating (without additional imports), and a production webserver (ready for slowloris attacks), serving up requests with gzip compression automatically.
-------------
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
Render a template, with arguments::
@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.
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))
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}
Want HSTS?
::
api = responder.API(enable_hsts=True)
Boom. ✨🍰✨
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 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 familar friends.
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 (still working on that).
- 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.
- Waitress 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 have any GraphQL query exposable at any route, magically.
Old Ideas
---------
- 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.
- 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.
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.
- If frontend websites are supported, provide an official way to run webpack.
Installation
============
.. code-block:: shell
$ pipenv install responder
✨🍰✨
Only **Python 3.6+** is supported.
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
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
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"])
+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.1"
+242 -50
View File
@@ -1,23 +1,56 @@
import os
import json
from functools import partial
from pathlib import Path
import graphene
import waitress
import jinja2
from whitenoise import WhiteNoise
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
from requests import Session as RequestsSession
from werkzeug.wsgi import DispatcherMiddleware
from graphql_server import encode_execution_results, json_encode, default_format_error
from . import models
from .status import HTTP_404
from .status_codes import HTTP_404
from . import status_codes
from .routes import Route
from .formats import get_formats
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, static_dir="static", templates_dir="templates", enable_hsts=False
):
self.static_dir = Path(os.path.abspath(static_dir))
self.templates_dir = Path(os.path.abspath(templates_dir))
self.routes = {}
self.hsts_enabled = enable_hsts
self.apps = {"/": self._wsgi_app}
self.formats = get_formats()
def _wsgi_app(self, environ, start_response):
# 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)
# Mount the whitenoise application.
self.whitenoise = WhiteNoise(self.__wsgi_app, root=str(self.static_dir))
# Cached requests session.
self._session = None
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
@@ -44,40 +77,71 @@ class BaseAPI:
start the response.
"""
req = models.Request.from_environ(environ)
req = models.Request(environ, start_response)
# if not req.dispatched:
resp = self._dispatch_request(req)
return resp(environ, start_response)
def wsgi_app(self, environ, start_response):
def _wsgi_app(self, environ, start_response):
return self.whitenoise(environ, start_response)
def __call__(self, environ, start_response):
def wsgi_app(self, environ, start_response):
"""Returns the WSGI app for this application (including all mounted WSGI apps)."""
apps = self.apps.copy()
main = apps.pop("/")
return DispatcherMiddleware(main, apps)(environ, start_response)
def __call__(self, environ, start_response=None):
"""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:
def path_matches_route(self, path):
"""Given a path portion of a URL, tests that it matches against any registered 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):
# Set formats on Request object.
req.formats = self.formats
route = self.path_matches_route(req.path)
resp = models.Response(req=req)
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.path)
self.routes[route].endpoint(req, resp, **params)
# The request is using class-based views.
except TypeError:
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")
self.graphql_response(req, resp, schema=view)
except AssertionError:
# WSGI App.
try:
req.dispatched = True
return view(
environ=req._environ, start_response=req._start_response
)
except TypeError:
pass
# Run on_request first.
try:
@@ -86,7 +150,7 @@ class BaseAPI:
pass
# Then on_get.
method = req.method.lower()
method = req.method
try:
getattr(view, f"on_{method}")(req, resp)
@@ -97,50 +161,58 @@ 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.text = "Not found."
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):
if "json" in req.mimetype:
return req.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"]
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.
@@ -149,18 +221,138 @@ class API(BaseAPI):
def graphql_response(self, req, resp, schema):
query = 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 defenitions.
Usage::
@api.route("/hello")
def hello(req, resp):
req.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, wsgi_app):
"""Mounts a WSGI application at a given route.
:param route: String representation of the route to be used (shouldn't be parameterized).
:param wsgi_app: The other WSGI app (e.g. a Flask app).
"""
self.apps.update({route: wsgi_app})
def session(self, base_url="http://;"):
"""Testing HTTP client. Returns a Requests session object, able to send HTTP requests to the WSGI 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
return self._session
def url_for(self, endpoint, absolute_url=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(**params)
raise ValueError
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, **kwargs):
"""Runs the application with Waitress. 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 kwargs: Additional keyword arguments to send to ``waitress.serve()``.
"""
if "PORT" in os.environ:
if address is None:
address = "0.0.0.0"
port = os.environ["PORT"]
if address is None:
address = "127.0.0.1"
if port is None:
port = 0
bind_to = f"{address}:{port}"
waitress.serve(app=self, listen=bind_to, **kwargs)
+1
View File
@@ -0,0 +1 @@
import docopt
+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
def format_form(r, encode=False):
if not encode:
return r._wz.form
def format_yaml(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/x-yaml"})
return yaml.dump(r.media)
else:
return yaml.load(r.content)
def format_json(r, encode=False):
if encode:
r.headers.update({"Content-Type": "application/json"})
return json.dumps(r.media)
else:
return json.loads(r.content)
def get_formats():
return {"json": format_json, "yaml": format_yaml, "form": format_form}
View File
+97 -66
View File
@@ -12,90 +12,124 @@ from werkzeug.wrappers import BaseResponse as WerkzeugResponse
from urllib.parse import parse_qs
from .status import HTTP_200
from .status_codes import HTTP_200
# @staticmethod
# def funcname(parameter_list):
# pass
def flatten(d):
for key, value in d.copy().items():
if len(value) == 1:
d[key] = value[0]
return d
# TODO: add slots
class Request:
def __init__(self):
super().__init__()
self._wz = None
@classmethod
def from_environ(kls, environ):
self = kls()
def __init__(self, environ, start_response=None):
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)
return self
self.start_response = start_response
self.headers = CaseInsensitiveDict(
self._wz.headers.to_wsgi_list()
) #: A case-insensitive dictionary, containg all headers sent in the Request.
self.method = (
self._wz.method.lower()
) #: The incoming HTTP method used for the request, lower-cased.
self.full_url = (
self._wz.url
) #: The full URL of the Request, query parameters and all.
self.url = (
self._wz.base_url
) #: The URL of the Request, without query parameters.
self.full_path = (
self._wz.full_path
) #: The full path portion of the URL of the Request, query parameters and all.
self.path = (
self._wz.path
) #: The path portion of the URL of the Request, without query parameters.
self.params = flatten(
parse_qs(self._wz.query_string.decode("utf-8"))
) #: A dictionary of the parsed query paramaters used for the Request.
self.query = self._wz.query_string.decode(
"utf-8"
) #: A string containing only the query paramaters of the Request.
self.raw = self._wz.stream #: A raw file-like stream of the incoming Request.
self.content = self._wz.get_data(
cache=True, as_text=False
) #: The Request body, as bytes.
self.mimetype = self._wz.mimetype #: The mimetype of the incoming Request.
# TODO: rip that out
self.text = self._wz.get_data(
cache=False, as_text=True
) #: The Request body, as unicode.
self.formats = None
@property
def accepts_yaml(self):
return "yaml" in self.headers["accept"]
def is_secure(self):
"""Returns ``True`` if the incoming Request was securely made."""
return self._wz.is_secure
@property
def accepts_json(self):
return "json" in self.headers["accept"]
def accepts(self, content_type):
"""Returns ``True`` if the incoming Request accepts the given ``content_type``."""
return content_type in self.headers["Accept"]
def json(self):
return json.loads(self.content)
def media(self, format=None):
"""Renders incoming json/yaml/form data as Python objects.
def yaml(self):
return yaml.load(self.content)
: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"
if format in self.formats:
return self.formats[format](self)
else:
return format(self)
class Response:
def __init__(self, req):
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 = "utf-8"
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):
if self.content:
return (self.content, self.mimetype, {})
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 self.formats[format](self, encode=True), {}
# Default to JSON anyway.
else:
return (json.dumps(self.media), {"Content-Type": "application/json"})
@property
def gzipped_body(self):
body, mimetype, headers = self.body
# print(self.req.headers)
body, headers = self.body
if isinstance(body, str):
body = body.encode(self.encoding)
if "gzip" in self.req.headers["Accept-Encoding"].lower():
gzip_buffer = io.BytesIO()
gzip_file = gzip.GzipFile(mode="wb", fileobj=gzip_buffer)
@@ -108,23 +142,20 @@ 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)
body, headers = self.body
if len(self.body) > 500:
body, headers = 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,
)
r = WerkzeugResponse(body, status=self.status_code, direct_passthrough=False)
r.headers = headers
return r
+38
View File
@@ -0,0 +1,38 @@
from parse import parse, search
class Route:
def __init__(self, route, endpoint):
self.route = route
self.endpoint = endpoint
def __repr__(self):
return f"<Route {self.route!r}={self.endpoint!r}>"
def __eq__(self, other):
if hasattr(other, "route"):
# Being compared to other routes.
return self.route == other.route
else:
# Strings.
return self.does_match(other)
@property
def has_parameters(self):
return all([("{" in self.route), ("}" in self.route)])
def does_match(self, s):
if s == self.route:
return True
named = self.incoming_matches(s)
return bool(len(named))
def incoming_matches(self, s):
results = parse(self.route, s)
return results.named if results else {}
def url(self, **params):
return self.route.format(**params)
# def is_graphql, is_wsgi
View File
@@ -88,3 +88,27 @@ for number in codes:
for label in codes[number]:
locals()[label] = number
def _is_category(category, status_code):
return all([(status_code >= category), (status_code < category + 100)])
def is_100(status_code):
return _is_category(100, status_code)
def is_200(status_code):
return _is_category(200, status_code)
def is_300(status_code):
return _is_category(300, status_code)
def is_400(status_code):
return _is_category(400, status_code)
def is_500(status_code):
return _is_category(500, status_code)
-6
View File
@@ -1,6 +0,0 @@
import graphene
class GraphQLSchema:
def __init__(self, **kwargs):
self.schema = graphene.Schema(**kwargs)
View File

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