mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
Compare commits
1098 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30801557a3 | |||
| 73d46e9b03 | |||
| 3d65d88ea9 | |||
| 8f979719a0 | |||
| 0cbcaf9c4f | |||
| 3fa6f11ffa | |||
| 8b88b148bf | |||
| 1aecafa82a | |||
| 8c763aa97e | |||
| 91aa242a5a | |||
| 084d057a99 | |||
| d3acf2c1c1 | |||
| 80715a12ac | |||
| 66fc7afbe4 | |||
| e7776eb9e8 | |||
| 944d47da45 | |||
| a3a12cff77 | |||
| 7b2839086d | |||
| 351ff8d95e | |||
| 2278beba18 | |||
| 3cfc7ec2b6 | |||
| 0de22eeed2 | |||
| b0cc37861b | |||
| 7d4532acc9 | |||
| 1b63d2943a | |||
| b5723303c8 | |||
| 5730be4b31 | |||
| 6f9c11645a | |||
| 827cc64988 | |||
| 7b5db5bc33 | |||
| b9a03c7088 | |||
| 4cbf55508e | |||
| 83d0fcf1ae | |||
| a698eaaab3 | |||
| 3aa21eed08 | |||
| 2741c74b90 | |||
| aba96525ad | |||
| a5b6d36991 | |||
| e4cff76fa6 | |||
| f11ad7136d | |||
| c32e8c7468 | |||
| d93e3cd12c | |||
| 040f1a57e4 | |||
| 307313744f | |||
| 98ca45003b | |||
| ab76594297 | |||
| 7fba0f6362 | |||
| 4ff73e9d0c | |||
| 68bbea0a55 | |||
| 106e5e9073 | |||
| 3426aa71da | |||
| 413028b636 | |||
| 3edf979a8c | |||
| cd75deeb4e | |||
| b71bb5ddb9 | |||
| 27a9459f22 | |||
| b39c539d57 | |||
| 718b53cce2 | |||
| 2e0b4975f7 | |||
| a118a5dc4b | |||
| 69c1d7f185 | |||
| fba2f135a3 | |||
| 4006de72cd | |||
| b3c7252197 | |||
| 398ac3343e | |||
| 8b197ba361 | |||
| e700aa2937 | |||
| 3894550642 | |||
| 43fd041138 | |||
| 363af5338d | |||
| 55430a4366 | |||
| f7c6a3ae97 | |||
| dcadba1425 | |||
| de08b15ae8 | |||
| 0cfca6d906 | |||
| a73e413a66 | |||
| 87931a25d0 | |||
| 1fd9a682dd | |||
| 5d3e650901 | |||
| 48d082e6a5 | |||
| 87e22481e8 | |||
| e48ce6c301 | |||
| e9613500da | |||
| c2943accd0 | |||
| 649a255657 | |||
| 7eaaaaafe1 | |||
| ae09b88978 | |||
| e3e307fd68 | |||
| 89f0724029 | |||
| bebe62adaf | |||
| eb9cddc8c2 | |||
| 7c19eca78a | |||
| ed28b11d21 | |||
| 46cdd4a245 | |||
| ac91b172e6 | |||
| ed0da6d462 | |||
| 555e9bff65 | |||
| bf43d9f202 | |||
| e239cc304d | |||
| 3285bd57c7 | |||
| 3090fb9e68 | |||
| e90bd24ebe | |||
| a0acc03a97 | |||
| 8a668e6efe | |||
| 4c75742e4d | |||
| 796fdc2ddf | |||
| a8caa3054b | |||
| 2ef9e133ad | |||
| 2ec570ad61 | |||
| 02aa338970 | |||
| 882250bd86 | |||
| 3809eda2f2 | |||
| b32eda70d2 | |||
| f1b2f46a10 | |||
| cf82dac4ad | |||
| a0913e3f63 | |||
| f90955a9b9 | |||
| 3736c9229d | |||
| a802853367 | |||
| 96ca88fe88 | |||
| a57570210a | |||
| 7682e94b35 | |||
| 8bbebe113c | |||
| 7c921f827b | |||
| 4cc055f93a | |||
| e596a8b457 | |||
| fd2da55880 | |||
| 975e9b5643 | |||
| c0036e0474 | |||
| 103816e27a | |||
| b7c1684ab4 | |||
| 16bd6ca266 | |||
| 20bae4712b | |||
| a7aa80c690 | |||
| df89d1d58b | |||
| 477cddd29c | |||
| 9b8cf3a1b1 | |||
| 2871a3c07f | |||
| 13763296dd | |||
| 783b22ab1c | |||
| 109937adf4 | |||
| 63ea9cc4e0 | |||
| ec40a0c4c3 | |||
| 0855d1a378 | |||
| 77fe17d350 | |||
| 0b8a031ccb | |||
| 0678daa880 | |||
| 6761e3bdd8 | |||
| ead213a506 | |||
| 75b5782eee | |||
| a80df809e4 | |||
| 7f3177f662 | |||
| 906cd2fbbf | |||
| 9d0129da56 | |||
| aedcf12d99 | |||
| 86361523e2 | |||
| a7110ef441 | |||
| d3e4968546 | |||
| 03e34d56ab | |||
| b470d10416 | |||
| b8aea89039 | |||
| 4a1e89af1b | |||
| 81fbc94d36 | |||
| 6487671559 | |||
| 838c7f29b5 | |||
| df85a4c214 | |||
| 1bdbea238e | |||
| 97dbef92d9 | |||
| 85c1c0036c | |||
| 0653ee2c6b | |||
| d1db913c7d | |||
| d24b921cdc | |||
| b31b742787 | |||
| d820f0277f | |||
| 7219856177 | |||
| e6d302aabb | |||
| d73243ab60 | |||
| 8101e7d7b0 | |||
| 784c7e72ae | |||
| 93156fd2f1 | |||
| e4f6898498 | |||
| ef330135f9 | |||
| 54cbbdface | |||
| f3c9320837 | |||
| 0529629ac8 | |||
| 22af42ead6 | |||
| bd2efb68e1 | |||
| 37ba3d2efc | |||
| ed8afeaa87 | |||
| ee6efe5aa4 | |||
| 3a8113d8b0 | |||
| 7afce42943 | |||
| 67a6c25256 | |||
| 38dea8311c | |||
| 555e1f7924 | |||
| 8b87f63609 | |||
| f01b1d493f | |||
| a802245bf0 | |||
| 6dbbad158a | |||
| 877fe144b4 | |||
| 70e6bc0466 | |||
| 63f2e833eb | |||
| fb71abe534 | |||
| 8ccb39560e | |||
| e6b880be62 | |||
| d0016ac7c9 | |||
| 05035e0171 | |||
| 78b5bef879 | |||
| a6955b5db5 | |||
| a1a0a1b71e | |||
| 0bdde6d5fe | |||
| cf5447d5bd | |||
| b2dd2c205d | |||
| e52c9277c8 | |||
| 712ec2410d | |||
| dea2ca41d2 | |||
| ca0f32c02b | |||
| f21b296fba | |||
| 3224479b99 | |||
| f95950eedc | |||
| 4467376d0a | |||
| ac65dc5361 | |||
| 4957793c80 | |||
| ff7f4b502d | |||
| 816cb7188b | |||
| 6456d435eb | |||
| 63e338ed6f | |||
| 00211c8f03 | |||
| ebed9fe3aa | |||
| 734b5e7303 | |||
| 1696d501e2 | |||
| e65d2f8c50 | |||
| 9ea705b2ea | |||
| 5a5a811dca | |||
| df7b9419c2 | |||
| 37318f1106 | |||
| 19e9f6ac5d | |||
| 658b51a449 | |||
| 485303c0f2 | |||
| 885d902b7d | |||
| a35f02fb64 | |||
| 28d1f16ad5 | |||
| a04d7c3a9a | |||
| b876f8484c | |||
| 854c6d3d65 | |||
| f9a850a8fe | |||
| e808662fe7 | |||
| 7bbb02126e | |||
| aa101059a7 | |||
| d1f7fe02e4 | |||
| 3e26dc1373 | |||
| 0a9d819555 | |||
| b31dfeefb7 | |||
| fc640ec331 | |||
| 3382723457 | |||
| 1fc0722ad6 | |||
| b21e308357 | |||
| 738105314b | |||
| f3cdc99b29 | |||
| eb70376438 | |||
| dae1a4fa35 | |||
| 2ad351197e | |||
| 3d9235c4bc | |||
| 2cd5596def | |||
| d4191030d9 | |||
| 447630a051 | |||
| f7b53a4895 | |||
| 21896aa171 | |||
| e8a15697d2 | |||
| 0030993631 | |||
| 13ba2f72f5 | |||
| 9f39917895 | |||
| 1b0859fdbb | |||
| acd1561b1b | |||
| 9f2182949d | |||
| 6e5b3a4bf9 | |||
| d2ec323888 | |||
| 8b9645cf2d | |||
| 4ecfef0ddf | |||
| 84fb7bd622 | |||
| 0b261252e1 | |||
| d60b5ee39e | |||
| e2f887ec5f | |||
| 97da6a6694 | |||
| c0e9a6778d | |||
| 5c327a2e0b | |||
| 5ed45634cb | |||
| a50a373e84 | |||
| 86705d0c2f | |||
| b9581444f9 | |||
| 2a60b094b8 | |||
| 1ec567cabf | |||
| 4fd898b239 | |||
| 03d6b72a00 | |||
| 4d0382d580 | |||
| a0dd7481ec | |||
| 1c91480b0c | |||
| 85e5ec0a9a | |||
| 4ac04b0abc | |||
| d7e64a6e39 | |||
| 17d526632e | |||
| 43da481df7 | |||
| 5f5402833b | |||
| d59c4333f2 | |||
| 49114f36ce | |||
| b2039d99f3 | |||
| 94fd86fee0 | |||
| d70fdd3301 | |||
| 05b75efb43 | |||
| be56e92d65 | |||
| 69eb843604 | |||
| 84a7f0e90b | |||
| d1e105a29a | |||
| 9f0a568fa3 | |||
| 05b46cbe34 | |||
| c045586997 | |||
| 8f0707f697 | |||
| 36929b265c | |||
| 734ba64965 | |||
| 148e6742df | |||
| bcb7e8f4f3 | |||
| f678112099 | |||
| 60b0c5f256 | |||
| c8627939de | |||
| 9144f0158a | |||
| d541aca80f | |||
| c73b2b8d34 | |||
| e2493b489d | |||
| 8dee28ac7c | |||
| cdd3885a0c | |||
| 1a28d528d0 | |||
| 3ba12b8cee | |||
| 5a29ab6917 | |||
| 694144a0c8 | |||
| 8bed8e8741 | |||
| a81a348bce | |||
| fd9e8c5cbc | |||
| 8030b1919d | |||
| 72c789fdd7 | |||
| 1113a9aa0d | |||
| a5532614a2 | |||
| 122023fb70 | |||
| b8fa923ec9 | |||
| ae06b3e01a | |||
| 5599ec2809 | |||
| e795cbddb6 | |||
| 0cb087c37b | |||
| 983cbcc711 | |||
| 6d154b0c78 | |||
| f3f36e28c4 | |||
| fdf4797726 | |||
| 67d8a3be98 | |||
| 4001a60f6c | |||
| d94db41271 | |||
| 8abb78bb58 | |||
| a80db99aa3 | |||
| 69a300f21a | |||
| 1b024b8092 | |||
| a622689597 | |||
| 9943e66c49 | |||
| 7233c08281 | |||
| 0845d92fda | |||
| 1cc02e5a83 | |||
| aa4cd7a144 | |||
| b42ae0dfd7 | |||
| a6bd179726 | |||
| aac7b5117b | |||
| 8e8e99ed2e | |||
| 078ac23b20 | |||
| 8e61df6b6a | |||
| cf7fb56653 | |||
| da20d13c49 | |||
| 7a250aa8fc | |||
| af28ecb82d | |||
| 39a0e52a2a | |||
| a4f5a111c7 | |||
| c65e585493 | |||
| c9e94561aa | |||
| 1daa4c202b | |||
| 83ff361672 | |||
| 2c686f107d | |||
| ac8ec3d5ef | |||
| 21e70ef913 | |||
| c4f5b0e7c2 | |||
| 45d6c1389d | |||
| 0c9bc5a3af | |||
| 5b67d5a04a | |||
| f3396a5573 | |||
| e9f48788a3 | |||
| 6993a1ea46 | |||
| 8bcfb4585b | |||
| db45251f7f | |||
| 5582667b4f | |||
| 2c898aaf23 | |||
| e0999ffcdd | |||
| 03811768bb | |||
| fbef577c9f | |||
| 9434510ce9 | |||
| 354130c151 | |||
| 3e3cba016a | |||
| f75e120bef | |||
| 1d0294e430 | |||
| f786dd8254 | |||
| cd9d09fd53 | |||
| 7471bbcd4e | |||
| 43b04eccbd | |||
| 6a5d0b5e9f | |||
| 359d366de4 | |||
| 556d9f3a7b | |||
| 2cab2dcec0 | |||
| 99d4e78dc9 | |||
| 9aa99869ae | |||
| 08e0d87347 | |||
| 3f9e4057d3 | |||
| a29e40353c | |||
| 778cb2dd0f | |||
| f7d5514b94 | |||
| 954637f7b3 | |||
| 1ab46104c8 | |||
| 815776d473 | |||
| 8db1a7be90 | |||
| 7b11fa24dd | |||
| 1f0f2318d5 | |||
| 029b3e2a52 | |||
| 4fff823def | |||
| cab78275f4 | |||
| 5f60e4fedb | |||
| 96971a33a7 | |||
| 9a7409f521 | |||
| 80aa7e305b | |||
| 27d513cb01 | |||
| 9bf5cc8c03 | |||
| 7994b210cd | |||
| 46555bbe3f | |||
| 4d15dbc465 | |||
| 855d3c4320 | |||
| 4564862acc | |||
| 176dd70073 | |||
| a5e6f0c196 | |||
| 083bb5a96c | |||
| 04522281be | |||
| 0e8bb49b59 | |||
| 9abf6eea16 | |||
| 1d7a04ce7b | |||
| 49fb5792c3 | |||
| 5eebba09c5 | |||
| b86974688e | |||
| 74afe2ed13 | |||
| ed53a0b624 | |||
| 23e15d6459 | |||
| 71ea19d1c1 | |||
| fa621d076d | |||
| 4902f1328a | |||
| 2ee8ff484d | |||
| c872fe3c78 | |||
| a08b275463 | |||
| 9717208dd4 | |||
| c9a233f5e5 | |||
| 7389350ff9 | |||
| f46ac08cff | |||
| 296d5e7974 | |||
| fe0bea686c | |||
| 838d172512 | |||
| 2c02c51c37 | |||
| 67a4cbca2c | |||
| a2f97e727f | |||
| 462506113e | |||
| 5f2a72203f | |||
| d6febe2d02 | |||
| c2bd1e989a | |||
| f886c2c050 | |||
| ae770e603a | |||
| 7b79472d65 | |||
| 090a3a571b | |||
| f9d55fc425 | |||
| 4f57e8a5d1 | |||
| 1e6c9d935a | |||
| 00cfde169b | |||
| 02733ac718 | |||
| 55b55e62da | |||
| 5fccedd4c4 | |||
| b9ad78ec79 | |||
| 64ac6bcd1f | |||
| 45e4d80c4d | |||
| a5b1652d15 | |||
| f954eb7d88 | |||
| 53216813e5 | |||
| 1618203930 | |||
| 237a2ed426 | |||
| d33289503a | |||
| f5ff4c9725 | |||
| 62f932dcfc | |||
| b66112d0ca | |||
| b98354e63a | |||
| 94b3625718 | |||
| f7ee720281 | |||
| 4ab523bf01 | |||
| 2d4f1bfd02 | |||
| 38426c9143 | |||
| bdf151e0a7 | |||
| 9768b7888d | |||
| 740a48566f | |||
| 475cd1a106 | |||
| 38e7c39d69 | |||
| 774db6bead | |||
| a1a3e0412a | |||
| 0b39c89e60 | |||
| 53be4d8954 | |||
| 03812cc7eb | |||
| aa12b24293 | |||
| daf43009ba | |||
| 955d777ca5 | |||
| cc9472aa2f | |||
| e527f3cb1f | |||
| 3a375a8975 | |||
| 2698496592 | |||
| 0155d854e3 | |||
| c74cc8586f | |||
| 8eb89da9a0 | |||
| dee6ee3cef | |||
| beab89df09 | |||
| 5164d4ec32 | |||
| 878db851af | |||
| 686ff72ae0 | |||
| 2710d7098f | |||
| 7f41ff4035 | |||
| ed8d51014c | |||
| d09a51f47d | |||
| 59bae90454 | |||
| 13ee0ca94e | |||
| 5abc095050 | |||
| 7eb68c8388 | |||
| f69b644a77 | |||
| 6b93125ff2 | |||
| 43faef4569 | |||
| fe41d4c863 | |||
| 29830455ed | |||
| e50828093d | |||
| 880d29c5a9 | |||
| 77b2e9ba7a | |||
| 586fad7646 | |||
| fb636028fb | |||
| a8c3f8fc46 | |||
| 72f4227c5a | |||
| 8ccace8ef9 | |||
| 6d40c6dfe5 | |||
| 0b5562cdec | |||
| eeff0816f3 | |||
| f1f16dea3f | |||
| bfc6ef2049 | |||
| 5212de79d3 | |||
| b61c02e5df | |||
| f982954e8f | |||
| 3ba20e69ba | |||
| aea01fd893 | |||
| 950be14eca | |||
| 446deffc17 | |||
| e0863115ee | |||
| e34cb539d2 | |||
| d8ade8638a | |||
| 3067080474 | |||
| 886cc0f214 | |||
| 071d34b016 | |||
| a1564ca003 | |||
| 60f0e765c2 | |||
| 3f0ecea4bf | |||
| 2c9e6572c5 | |||
| 371a83f20f | |||
| b8cff1655a | |||
| 232856ca3a | |||
| 3f168ac6fd | |||
| c59cb1d0d3 | |||
| ec13df75d0 | |||
| 6fc02964ba | |||
| ed79e45680 | |||
| 1be983bf89 | |||
| b09d6a9d04 | |||
| db143d845d | |||
| 2e23501f9d | |||
| bd6addcd3a | |||
| 631e1fb604 | |||
| 30ee6726a8 | |||
| 1c397db9d8 | |||
| cc23ca80f4 | |||
| 449379a0ed | |||
| b3208b1c5b | |||
| 4df60b55a6 | |||
| 379553a1a5 | |||
| a2eaa5c7b5 | |||
| 175c46e68c | |||
| a58cc11079 | |||
| 218a375c27 | |||
| 567b1577c6 | |||
| 3c3687d11f | |||
| 19dfac8340 | |||
| b61feafe5a | |||
| 0c342c8b3e | |||
| dbcba8fad7 | |||
| b8053e20f2 | |||
| 1896901aa8 | |||
| 383c9132ed | |||
| 57b144c3e7 | |||
| eed5365fe0 | |||
| f5905568c4 | |||
| 096099470e | |||
| e7ed7aca3c | |||
| 6725b275b8 | |||
| 3447a7ef41 | |||
| 99f35fbea4 | |||
| 5c9a3912a9 | |||
| 5d43c0418c | |||
| 87c0076e12 | |||
| 95252ac697 | |||
| 5bb9f96701 | |||
| 750e9dfaa7 | |||
| f34f3c1661 | |||
| d4f83c978c | |||
| 212f280c19 | |||
| f3e2450636 | |||
| d6d496018d | |||
| 78be7fc772 | |||
| 6ebadd8469 | |||
| 557750c8d4 | |||
| e85ef27e6c | |||
| 4ca961a1b4 | |||
| 6a9110e9c1 | |||
| 51ffce09ae | |||
| 1c4e96b365 | |||
| 0db70e8edd | |||
| e46b3a5e19 | |||
| fdd3d4d85a | |||
| 37c9cba42e | |||
| c1544f66bb | |||
| d37f41f6a5 | |||
| b245dd2d51 | |||
| a1fcf11399 | |||
| 8f876da245 | |||
| 23b8e5a2b3 | |||
| 3b7e7c7192 | |||
| b7ecf6e2e0 | |||
| 2ec6aaff03 | |||
| 19f8553f2d | |||
| 05a64ff095 | |||
| a8fc78fcda | |||
| e0e8b40fa2 | |||
| 00165cd6ca | |||
| cd799ddfcd | |||
| fffd6b7c86 | |||
| 439b008a34 | |||
| f38e538892 | |||
| 6aa87a073f | |||
| c38198ccba | |||
| 3be88c8cbf | |||
| 558ced1afb | |||
| 0149e6935d | |||
| d97fdfd7c4 | |||
| 8b85d8c6fb | |||
| 673779490c | |||
| 48154e7e2d | |||
| 20f72b3f63 | |||
| e82c958af2 | |||
| 60c311ab9f | |||
| fbac81c245 | |||
| 9ca67d9228 | |||
| 5ffa18221f | |||
| aceb1f0f61 | |||
| cee5ca8873 | |||
| d961d4ab43 | |||
| 5205150a89 | |||
| 48e58cde5d | |||
| 033e91f8df | |||
| aab3705897 | |||
| d02efa81f2 | |||
| 95a8240da7 | |||
| dd0ddab610 | |||
| d23ac10f90 | |||
| ec18290b8a | |||
| 2c4cd39dc9 | |||
| 830bad0b85 | |||
| f14ef6fa15 | |||
| 7400b1c83d | |||
| e7caf39fba | |||
| 09fd0fb0ca | |||
| 72adb13c0f | |||
| ea0e382f82 | |||
| e70cba5143 | |||
| 8aec244c31 | |||
| 60e163164f | |||
| 86b9b5f3fa | |||
| 401a208767 | |||
| a1bfbda05b | |||
| 7d1f991ce4 | |||
| 1b10378f58 | |||
| 2bbb379994 | |||
| a835f119e1 | |||
| 91d8bac680 | |||
| 3db10a4ce8 | |||
| 590640645b | |||
| 7f02bfdf0c | |||
| e5cef0d9c0 | |||
| 85f9c33b2b | |||
| 148a430da4 | |||
| f7657679ac | |||
| f0479019c3 | |||
| a9a4ceaa78 | |||
| c55c905621 | |||
| 4db2289b7e | |||
| 93172ea1d0 | |||
| 2d935542e1 | |||
| f309ad7746 | |||
| a7ec1364f4 | |||
| eb71ced092 | |||
| 712ad0a73b | |||
| 48c0b137d5 | |||
| dfccfcc3e5 | |||
| 6abe667efb | |||
| c2472215ab | |||
| ac3c1e149c | |||
| cdf989427a | |||
| ebf129edd3 | |||
| 08c30f4baf | |||
| cf6bdc20ef | |||
| 3ece644af8 | |||
| 3991c82c91 | |||
| 9b635253f0 | |||
| b62f41336e | |||
| f7b777c79e | |||
| d18fa8e42a | |||
| 525c62ad26 | |||
| 4000a6a48c | |||
| 5b173ed4c4 | |||
| f56ad73565 | |||
| 003991c8c6 | |||
| e2a32afb80 | |||
| f305a69bb3 | |||
| 84e8babd9e | |||
| aeb46d9b54 | |||
| fafe0bd8e4 | |||
| 9a2ab45957 | |||
| 66978a8cdc | |||
| 1636012700 | |||
| 09206ae1e4 | |||
| 9188475746 | |||
| 34d158a632 | |||
| c06e6aa5ca | |||
| f4f670f048 | |||
| 778d742b6e | |||
| c8392b65b6 | |||
| c0ace9c2e5 | |||
| dfcab7dcbf | |||
| eb0870deb1 | |||
| 5b7ef34523 | |||
| 6ec728e466 | |||
| f12a562a08 | |||
| 17c4c95593 | |||
| 9b72c90944 | |||
| ec34da60a1 | |||
| daa4b6368a | |||
| 931a7a1a6c | |||
| 69d5790078 | |||
| 7571c18a55 | |||
| ff7ce9bdd0 | |||
| e5fc801899 | |||
| b362aa6813 | |||
| 652b961ac8 | |||
| 652713aec4 | |||
| 387b2f166b | |||
| 164b4a056a | |||
| 29e514fea6 | |||
| 310fff78c6 | |||
| f2efdc007c | |||
| b3be767923 | |||
| e86f2f3873 | |||
| 13d84f73d4 | |||
| e31342d3ba | |||
| daf0538bf3 | |||
| 451ce8b0c7 | |||
| b8cce14705 | |||
| bf1c9c650e | |||
| 8f6387536c | |||
| 56535ece11 | |||
| f1767719cb | |||
| c925b06114 | |||
| 402426884d | |||
| df6c8a5a75 | |||
| 99f5ae7125 | |||
| d50a1b7d07 | |||
| fab3bb76f7 | |||
| 5025c66bb2 | |||
| 800c153e96 | |||
| 71bbda0fb7 | |||
| 6e6bac429a | |||
| 1ce091a4d9 | |||
| a8f889be74 | |||
| 5f33c6bfee | |||
| 6a290c49d8 | |||
| b304d5d784 | |||
| cfe83b97d9 | |||
| 2fec2bf560 | |||
| 73dc1a7839 | |||
| 66fe951831 | |||
| 7991bcbf1a | |||
| de9516563a | |||
| 27fefb821c | |||
| c195894db9 | |||
| 6777b4d370 | |||
| 09269c22a2 | |||
| 2e24a2f079 | |||
| 5d9932dd61 | |||
| 062064213a | |||
| a2ae3ffb2b | |||
| 6cb4a0a3eb | |||
| f17c49091f | |||
| c16afc07df | |||
| 1616a96b2c | |||
| 261601230a | |||
| 453a38df54 | |||
| 5b004a849f | |||
| 29d811d3fd | |||
| 36c5739318 | |||
| b3f9c67d34 | |||
| bc8eb802f7 | |||
| a138eead74 | |||
| a700a0e1b1 | |||
| 205a33a241 | |||
| c88fd94c8b | |||
| a2b4e2e87c | |||
| 4a8f1e95ba | |||
| 3a847d921e | |||
| 806fdb9541 | |||
| cf1adbdb01 | |||
| 349d08e799 | |||
| d680c7ed83 | |||
| d4cb7a711b | |||
| bb6e19e7cd | |||
| 1c3ea53e63 | |||
| 88e17029c5 | |||
| 588e91b19f | |||
| 8cc2e7b6f1 | |||
| 222353b532 | |||
| b88b266fd5 | |||
| 60e6fb99af | |||
| 65b60e57b2 | |||
| 16a8402bf4 | |||
| 5896411136 | |||
| 0bb74a7885 | |||
| 86dfb9231f | |||
| 7198ce3eb0 | |||
| 08fecf1eb2 | |||
| 3eda26ca94 | |||
| d907914c7c | |||
| 266ab48fed | |||
| 3325cffa91 | |||
| 43469ac62a | |||
| a5c953fdb6 | |||
| 627c46e458 | |||
| 205eb34adc | |||
| 125e14d377 | |||
| a51c8a700b | |||
| 94e0400ea1 | |||
| 47c5b84093 | |||
| 8b1fbfd16d | |||
| cceb698899 | |||
| 01741df10d | |||
| f91ebf8baa | |||
| 4dde076030 | |||
| 3491001b7f | |||
| 2acec68649 | |||
| 51dab27374 | |||
| 145f5041bf | |||
| 6034505380 | |||
| 8533d74906 | |||
| b2ae57b982 | |||
| 49ffe9bec9 | |||
| fe5d92674e | |||
| 197d28f5c7 | |||
| cd48bb0789 | |||
| 90fc411e9a | |||
| c22b6a84aa | |||
| 9b65642f05 | |||
| 83547dce9c | |||
| efeecceb54 | |||
| ba9b5a40d2 | |||
| 47b5bda277 | |||
| a343b6b1b6 | |||
| 0fe48d3003 | |||
| 23e3760b08 | |||
| 3d31905562 | |||
| 9638c5266b | |||
| ad7ce9f55a | |||
| b0baf3b85a | |||
| d4d3687882 | |||
| faf55ca191 | |||
| d5096a23fb | |||
| ed5841d201 | |||
| bbfc095a00 | |||
| 0fcb68a13d | |||
| f97744c098 | |||
| d1cfa8d27a | |||
| 218dcf25c1 | |||
| 06e06973a4 | |||
| 6f73cfc5f2 | |||
| 6db5bbeaee | |||
| 6ef5077164 | |||
| 45e1ed7022 | |||
| c14b4535a6 | |||
| 411631d2f8 | |||
| f4c3690bd8 | |||
| 56fdea6b5d | |||
| 8a5c053d39 | |||
| 42870cfa23 | |||
| 6cf256cc05 | |||
| 9fec915f62 | |||
| f1d5ab73cd | |||
| cd62972945 | |||
| 998d09170c | |||
| 4ba57181ec | |||
| 8b9d8bdc62 | |||
| 4291d42dc0 | |||
| 79fcc1ce40 | |||
| bfc6778dca | |||
| 701e57c264 | |||
| 163d025c0d | |||
| d9befc6d8c | |||
| 9e50a4c241 | |||
| 9b0cae3794 | |||
| 6160dfb2f7 | |||
| cd013cdb06 | |||
| 26cc7c90e9 | |||
| f28ac3cf22 | |||
| 58fec4b082 | |||
| b91805a5df | |||
| 0fa0df1bdf | |||
| 3f7cacee3e | |||
| 72637fd650 | |||
| aba1284f8e | |||
| 179e1dc9e5 | |||
| 75879a494e | |||
| 73b1ea4713 | |||
| 55dc991c13 | |||
| c30316588a | |||
| db5d6e7481 | |||
| f8d52f58d4 | |||
| 227ee499e4 | |||
| dcdaf6a674 | |||
| d524ba3a37 | |||
| da5e288476 | |||
| baad7cd60d | |||
| e9d6fc33fd | |||
| c2fa0899e9 | |||
| 2dc09ec1f2 | |||
| fba640976f | |||
| 8e7df61a73 | |||
| 41776cf2df | |||
| 23983f0b75 | |||
| 84b457ede5 | |||
| a906e0bf0c | |||
| 3db1aad96a | |||
| 9c909e7a2c | |||
| ad2ef7cb33 | |||
| c851510ca9 | |||
| 71a21c2059 | |||
| d90537eb8d | |||
| 8266a15df5 | |||
| 9fce286f92 | |||
| fc244922cc | |||
| fce87fe20c | |||
| d420358248 | |||
| be829ff0ae | |||
| 0c9e224d45 | |||
| 58158c4d2b | |||
| b1e4222c93 | |||
| 70e5a016a6 | |||
| 6d7e7809a4 | |||
| 4afc1ced93 | |||
| 3c1807f04f | |||
| 6e9adac871 | |||
| 2c3a3b2e17 | |||
| c070cc3f1a | |||
| d47caebc97 | |||
| 224c2bbb2b | |||
| 48f1a0545e | |||
| ac5146dbce | |||
| 4fa82f4d7a | |||
| 4bdd3f9138 | |||
| 86bffbf62d | |||
| f7b0fb3f66 | |||
| 971be488d5 | |||
| f415e9814c | |||
| 01575a0b8d | |||
| a77492dae1 | |||
| 080d6d30da | |||
| ec9b20f87c | |||
| 7aa405c87d | |||
| af6257d364 | |||
| f930cbb2c9 | |||
| 6a5a22f035 | |||
| 53f87e5def | |||
| a00687cc0f | |||
| d1d66c0e78 | |||
| ee51f50809 | |||
| b067da2a1c | |||
| 3db2c00cd8 | |||
| 2f52ccbe4e | |||
| 25e9888438 | |||
| fabe7b9427 | |||
| 9e464c394d | |||
| dcaf9b61d4 | |||
| ecf0b2e57b | |||
| df32660754 | |||
| 8bf795c8e4 | |||
| 0fc765a1fd | |||
| 6e7d97e5c0 | |||
| a33ac8ed5f | |||
| 7ca264fabd | |||
| e75f195f7c | |||
| d3efa8b80d | |||
| 82b78b6022 | |||
| d8d1787e6f | |||
| d40cad2064 | |||
| cf323db503 | |||
| 39d9b05455 | |||
| 475c7a9571 | |||
| 7c4b6cf4f7 | |||
| 697807c2d7 | |||
| 85d900727b | |||
| fc0d811740 | |||
| 513867d242 | |||
| 07ba75d9d5 | |||
| 6fa5f9af0c | |||
| 3c796b95fd | |||
| 6de212d4bc | |||
| bb539c4d28 | |||
| f5d491667e | |||
| 042d9ebc6c | |||
| ac69ccae5e | |||
| 940ab9d762 | |||
| 00bfdf0e3e | |||
| f3bc57a566 | |||
| 50c9bc60f9 | |||
| ba384bb12a | |||
| fe9184048c | |||
| 90083f945e | |||
| eee17ca20b | |||
| b2d47abacd | |||
| 548fb229af | |||
| c00b259c43 | |||
| 2dbd6ac451 | |||
| aab82baad0 | |||
| ea2c5c3025 | |||
| b391b5622f | |||
| ea3cb8aa7b | |||
| 6e125f5f47 | |||
| 554500a314 | |||
| f1c9de7ace | |||
| 3bcfe89f2a | |||
| 151c7bd342 | |||
| b8d569129e | |||
| b421e2e925 | |||
| 79b09a5ae5 | |||
| b0d3c2de09 | |||
| 3201a46003 | |||
| 538c72f5bd | |||
| 6efe28bd54 | |||
| 7eaa95b7ee | |||
| d2f8b41e25 | |||
| 034aa19564 | |||
| bd049d6263 | |||
| ffb7468b44 | |||
| 6dfbde27ca | |||
| c6c0197d86 | |||
| 678596ace4 | |||
| 9295525b92 | |||
| fde2d5415f | |||
| fac80383d9 | |||
| f4cfe5639a | |||
| 87749e4288 | |||
| 787e056b7f | |||
| 62bd3d905b | |||
| 2122f1ef1c | |||
| 0bed48e756 | |||
| f2b2128675 | |||
| c0c5770a89 | |||
| e7dc55edf5 | |||
| 83fa6d6897 | |||
| 5d636ee9c4 | |||
| 1fdda366dd | |||
| 671499bb43 | |||
| 749a7efdef | |||
| 90eecdaa84 | |||
| 39f9cbfdbb | |||
| 5794ba890c | |||
| 4a3bf3a1aa | |||
| 1427ca0a35 | |||
| 5b7b0fcb8e | |||
| 6ac48de44c | |||
| 674efa3052 | |||
| 152a7153d0 |
@@ -0,0 +1,3 @@
|
||||
github: kennethreitz
|
||||
thanks_dev: kennethreitz
|
||||
custom: https://cash.app/$KennethReitz
|
||||
@@ -0,0 +1,16 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -0,0 +1,42 @@
|
||||
name: "Documentation"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request: ~
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel redundant in-progress jobs.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
- name: Install package and documentation dependencies
|
||||
run: uv pip install '.[docs]'
|
||||
|
||||
- name: Build static HTML documentation
|
||||
run: sphinx-build -W --keep-going docs/source docs/build
|
||||
@@ -0,0 +1,56 @@
|
||||
name: "Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request: ~
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel redundant in-progress jobs.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [
|
||||
"3.9",
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
"3.13",
|
||||
]
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ matrix.python-version }}
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
|
||||
- name: Install package
|
||||
run: uv pip install '.[develop,test]'
|
||||
|
||||
- name: Run tests
|
||||
run: pytest
|
||||
+18
@@ -1 +1,19 @@
|
||||
.venv*
|
||||
.vscode/
|
||||
.cache
|
||||
.idea
|
||||
.python-version
|
||||
.coverage
|
||||
.pytest_cache
|
||||
.DS_Store
|
||||
coverage.xml
|
||||
.coverage*
|
||||
|
||||
__pycache__
|
||||
tests/__pycache__
|
||||
|
||||
build
|
||||
responder.egg-info/
|
||||
dist/
|
||||
app.py
|
||||
app2.py
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
|
||||
# Details
|
||||
# - https://docs.readthedocs.io/en/stable/config-file/v2.html
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: "ubuntu-24.04"
|
||||
tools:
|
||||
python: "3.12"
|
||||
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
# Use standard HTML builder.
|
||||
builder: html
|
||||
|
||||
# Fail on all warnings to avoid broken references.
|
||||
fail_on_warning: true
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF
|
||||
#formats:
|
||||
# - pdf
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
|
||||
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.0.0] - 2026-03-22
|
||||
|
||||
### Added
|
||||
|
||||
- Platform: Added support for Python 3.10 - Python 3.13
|
||||
- CLI: `responder run` now also accepts a filesystem path on its `<target>`
|
||||
argument, enabling usage on single-file applications.
|
||||
- CLI: `responder run` now also accepts URLs.
|
||||
|
||||
### Changed
|
||||
|
||||
- Platform: Minimum Python version is now 3.9 (dropped 3.6, 3.7, 3.8)
|
||||
- Dependencies: Dramatically reduced core dependency count (10 → 5)
|
||||
- Removed `requests`, `requests-toolbelt`, `rfc3986`, `whitenoise`
|
||||
- Moved `apispec` and `marshmallow` to `openapi` optional extra
|
||||
- Replaced `rfc3986` with stdlib `urllib.parse`
|
||||
- Replaced `requests-toolbelt` multipart decoder with `python-multipart`
|
||||
- Replaced deprecated `starlette.middleware.wsgi` with `a2wsgi`
|
||||
- Switched from WhiteNoise to ServeStatic
|
||||
- Dependencies: Pinned `starlette[full]>=0.40` (was unpinned)
|
||||
- GraphQL: Upgraded to `graphene>=3` and `graphql-core>=3.1`
|
||||
(from `graphene<3` and `graphql-server-core`, which is unmaintained)
|
||||
- GraphQL: Updated GraphiQL UI from 0.12.0 (2018) to 3.0.6 with React 18
|
||||
- Extensions: All of CLI-, GraphQL-, and OpenAPI-Support modules are
|
||||
extensions now, found within the `responder.ext` module namespace.
|
||||
- Packaging: Migrated from `setup.py` to declarative `pyproject.toml`
|
||||
|
||||
### Removed
|
||||
|
||||
- Platform: Removed support for EOL Python 3.6, 3.7, 3.8
|
||||
- Status codes: Removed deprecated `resume_incomplete` and `resume`
|
||||
aliases for HTTP 308 (marked for removal in 3.0)
|
||||
- CLI: `responder run --build` ceased to exist
|
||||
|
||||
### Fixed
|
||||
|
||||
- Routing: Fixed dispatching `static_route=None` on Windows
|
||||
- uvicorn: `--debug` now maps to uvicorn's `log_level = "debug"`
|
||||
- Tests: Fixed deprecated httpx TestClient usage
|
||||
|
||||
## [v2.0.5] - 2019-12-15
|
||||
|
||||
### Added
|
||||
|
||||
- Update requirements to support python 3.8
|
||||
|
||||
## [v2.0.4] - 2019-11-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix static app resolving
|
||||
|
||||
## [v2.0.3] - 2019-09-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix template conflicts
|
||||
|
||||
## [v2.0.2] - 2019-09-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix template conflicts
|
||||
|
||||
## [v2.0.1] - 2019-09-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix template import
|
||||
|
||||
## [v2.0.0] - 2019-09-19
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactor Router and Schema
|
||||
|
||||
## [v1.3.2] - 2019-08-15
|
||||
|
||||
### Added
|
||||
|
||||
- ASGI 3 support
|
||||
- CI tests for python 3.8-dev
|
||||
- Now requests have `state` a mapping object
|
||||
|
||||
### Deprecated
|
||||
|
||||
- ASGI 2
|
||||
|
||||
## [v1.3.1] - 2019-04-28
|
||||
|
||||
### Added
|
||||
|
||||
- Route params Converters
|
||||
- Add search for documentation pages
|
||||
|
||||
### Changed
|
||||
|
||||
- Bump dependencies
|
||||
|
||||
## [v1.3.0] - 2019-02-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Versioning issue
|
||||
- Multiple cookies.
|
||||
- Whitenoise returns not found.
|
||||
- Other bugfixes.
|
||||
|
||||
### Added
|
||||
|
||||
- Stream support via `resp.stream`.
|
||||
- Cookie directives via `resp.set_cookie`.
|
||||
- Add `resp.html` to send HTML.
|
||||
- Other improvements.
|
||||
|
||||
## [v1.1.3] - 2019-01-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactor `_route_for`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resolve startup/shutdwown events
|
||||
|
||||
## [v1.2.0] - 2018-12-29
|
||||
|
||||
### Added
|
||||
|
||||
- Documentations
|
||||
|
||||
### Changed
|
||||
|
||||
- Use Starlette's LifeSpan middleware
|
||||
- Update denpendencies
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix route.is_class_based
|
||||
- Fix test_500
|
||||
- Typos
|
||||
|
||||
## [v1.1.2] - 2018-11-11
|
||||
|
||||
### Fixed
|
||||
|
||||
- Minor fixes for Open API
|
||||
- Typos
|
||||
|
||||
## [v1.1.1] - 2018-10-29
|
||||
|
||||
### Changed
|
||||
|
||||
- Run sync views in a threadpoolexecutor.
|
||||
|
||||
## [v1.1.0] - 2018-10-27
|
||||
|
||||
### Added
|
||||
|
||||
- Support for `before_request`.
|
||||
|
||||
## [v1.0.5]- 2018-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix sessions.
|
||||
|
||||
## [v1.0.4] - 2018-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Potential bufix for cookies.
|
||||
|
||||
## [v1.0.3] - 2018-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfix for redirects.
|
||||
|
||||
## [v1.0.2] - 2018-10-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Improvement for static file hosting.
|
||||
|
||||
## [v1.0.1] - 2018-10-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve cors configuration settings.
|
||||
|
||||
## [v1.0.0] - 2018-10-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Move GraphQL support into a built-in plugin.
|
||||
|
||||
## [v0.3.3] - 2018-10-25
|
||||
|
||||
### Added
|
||||
|
||||
- CORS support
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved exceptions.
|
||||
|
||||
## [v0.3.2] - 2018-10-25
|
||||
|
||||
### Changed
|
||||
|
||||
- Subtle improvements.
|
||||
|
||||
## [v0.3.1] - 2018-10-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Packaging fix.
|
||||
|
||||
## [v0.3.0] - 2018-10-24
|
||||
|
||||
### Changed
|
||||
|
||||
- Interactive Documentation endpoint.
|
||||
- Minor improvements.
|
||||
|
||||
## [v0.2.3] - 2018-10-24
|
||||
|
||||
### Changed
|
||||
|
||||
- Overall improvements.
|
||||
|
||||
## [v0.2.2] - 2018-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- Show traceback info when background tasks raise exceptions.
|
||||
|
||||
## [v0.2.1] - 2018-10-23
|
||||
|
||||
### Added
|
||||
|
||||
- api.requests.
|
||||
|
||||
## [v0.2.0] - 2018-10-22
|
||||
|
||||
### Added
|
||||
|
||||
- WebSocket support.
|
||||
|
||||
## [v0.1.6] - 2018-10-20
|
||||
|
||||
### Added
|
||||
|
||||
- 500 support.
|
||||
|
||||
## [v0.1.5] - 2018-10-20
|
||||
|
||||
### Added
|
||||
|
||||
- File upload support
|
||||
|
||||
### Changed
|
||||
|
||||
- Improvements to sequential media reading.
|
||||
|
||||
## [v0.1.4] - 2018-10-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stability.
|
||||
|
||||
## [v0.1.3] - 2018-10-18
|
||||
|
||||
### Added
|
||||
|
||||
- Sessions support.
|
||||
|
||||
## [v0.1.2] - 2018-10-18
|
||||
|
||||
### Added
|
||||
|
||||
- Cookies support.
|
||||
|
||||
## [v0.1.1] - 2018-10-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Default routes.
|
||||
|
||||
## [v0.1.0] - 2018-10-17
|
||||
|
||||
### Added
|
||||
|
||||
- Prototype of static application support.
|
||||
|
||||
## [v0.0.10] - 2018-10-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfix for async class-based views.
|
||||
|
||||
## [v0.0.9] - 2018-10-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugfix for async class-based views.
|
||||
|
||||
## [v0.0.8] - 2018-10-17
|
||||
|
||||
### Added
|
||||
|
||||
- GraphiQL Support.
|
||||
|
||||
### Changed
|
||||
|
||||
- Improvement to route selection.
|
||||
|
||||
## [v0.0.7] - 2018-10-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Immutable Request object.
|
||||
|
||||
## [v0.0.6] - 2018-10-16
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to mount WSGI apps.
|
||||
- Supply content-type when serving up the schema.
|
||||
|
||||
## [v0.0.5] - 2018-10-15
|
||||
|
||||
### Added
|
||||
|
||||
- OpenAPI Schema support.
|
||||
- Safe load/dump yaml.
|
||||
|
||||
## [v0.0.4] - 2018-10-15
|
||||
|
||||
### Added
|
||||
|
||||
- Asynchronous support for data uploads.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bug fixes.
|
||||
|
||||
## [v0.0.3] - 2018-10-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bug fixes.
|
||||
|
||||
## [v0.0.2] - 2018-10-13
|
||||
|
||||
### Changed
|
||||
|
||||
- Switch to ASGI/Starlette.
|
||||
|
||||
## [v0.0.1] - 2018-10-12
|
||||
|
||||
### Added
|
||||
|
||||
- Conception!
|
||||
|
||||
[unreleased]: https://github.com/kennethreitz/responder/compare/v3.0.0..HEAD
|
||||
[v3.0.0]: https://github.com/kennethreitz/responder/compare/v2.0.5..v3.0.0
|
||||
[v2.0.5]: https://github.com/kennethreitz/responder/compare/v2.0.4..v2.0.5
|
||||
[v2.0.4]: https://github.com/kennethreitz/responder/compare/v2.0.3..v2.0.4
|
||||
[v2.0.3]: https://github.com/kennethreitz/responder/compare/v2.0.2..v2.0.3
|
||||
[v2.0.2]: https://github.com/kennethreitz/responder/compare/v2.0.1..v2.0.2
|
||||
[v2.0.1]: https://github.com/kennethreitz/responder/compare/v2.0.0..v2.0.1
|
||||
[v2.0.0]: https://github.com/kennethreitz/responder/compare/v1.3.2..v2.0.0
|
||||
[v1.3.2]: https://github.com/kennethreitz/responder/compare/v1.3.1..v1.3.2
|
||||
[v1.3.1]: https://github.com/kennethreitz/responder/compare/v1.3.0..v1.3.1
|
||||
[v1.3.0]: https://github.com/kennethreitz/responder/compare/v1.2.0..v1.3.0
|
||||
[v1.2.0]: https://github.com/kennethreitz/responder/compare/v1.1.3..v1.2.0
|
||||
[v1.1.3]: https://github.com/kennethreitz/responder/compare/v1.1.2..v1.1.3
|
||||
[v1.1.2]: https://github.com/kennethreitz/responder/compare/v1.1.1..v1.1.2
|
||||
[v1.1.1]: https://github.com/kennethreitz/responder/compare/v1.1.0..v1.1.1
|
||||
[v1.1.0]: https://github.com/kennethreitz/responder/compare/v1.0.5..v1.1.0
|
||||
[v1.0.5]: https://github.com/kennethreitz/responder/compare/v1.0.4..v1.0.5
|
||||
[v1.0.4]: https://github.com/kennethreitz/responder/compare/v1.0.3..v1.0.4
|
||||
[v1.0.3]: https://github.com/kennethreitz/responder/compare/v1.0.2..v1.0.3
|
||||
[v1.0.2]: https://github.com/kennethreitz/responder/compare/v1.0.1..v1.0.2
|
||||
[v1.0.1]: https://github.com/kennethreitz/responder/compare/v1.0.0..v1.0.1
|
||||
[v1.0.0]: https://github.com/kennethreitz/responder/compare/v0.3.3..v1.0.0
|
||||
[v0.3.3]: https://github.com/kennethreitz/responder/compare/v0.3.2..v0.3.3
|
||||
[v0.3.2]: https://github.com/kennethreitz/responder/compare/v0.3.1..v0.3.2
|
||||
[v0.3.1]: https://github.com/kennethreitz/responder/compare/v0.3.0..v0.3.1
|
||||
[v0.3.0]: https://github.com/kennethreitz/responder/compare/v0.2.3..v0.3.0
|
||||
[v0.2.3]: https://github.com/kennethreitz/responder/compare/v0.2.2..v0.2.3
|
||||
[v0.2.2]: https://github.com/kennethreitz/responder/compare/v0.2.1..v0.2.2
|
||||
[v0.2.1]: https://github.com/kennethreitz/responder/compare/v0.2.0..v0.2.1
|
||||
[v0.2.0]: https://github.com/kennethreitz/responder/compare/v0.1.6..v0.2.0
|
||||
[v0.1.6]: https://github.com/kennethreitz/responder/compare/v0.1.5..v0.1.6
|
||||
[v0.1.5]: https://github.com/kennethreitz/responder/compare/v0.1.4..v0.1.5
|
||||
[v0.1.4]: https://github.com/kennethreitz/responder/compare/v0.1.3..v0.1.4
|
||||
[v0.1.3]: https://github.com/kennethreitz/responder/compare/v0.1.2..v0.1.3
|
||||
[v0.1.2]: https://github.com/kennethreitz/responder/compare/v0.1.1..v0.1.2
|
||||
[v0.1.1]: https://github.com/kennethreitz/responder/compare/v0.1.0..v0.1.1
|
||||
[v0.1.0]: https://github.com/kennethreitz/responder/compare/v0.0.10..v0.1.0
|
||||
[v0.0.10]: https://github.com/kennethreitz/responder/compare/v0.0.9..v0.0.10
|
||||
[v0.0.9]: https://github.com/kennethreitz/responder/compare/v0.0.8..v0.0.9
|
||||
[v0.0.8]: https://github.com/kennethreitz/responder/compare/v0.0.7..v0.0.8
|
||||
[v0.0.7]: https://github.com/kennethreitz/responder/compare/v0.0.6..v0.0.7
|
||||
[v0.0.6]: https://github.com/kennethreitz/responder/compare/v0.0.5..v0.0.6
|
||||
[v0.0.5]: https://github.com/kennethreitz/responder/compare/v0.0.4..v0.0.5
|
||||
[v0.0.4]: https://github.com/kennethreitz/responder/compare/v0.0.3..v0.0.4
|
||||
[v0.0.3]: https://github.com/kennethreitz/responder/compare/v0.0.2..v0.0.3
|
||||
[v0.0.2]: https://github.com/kennethreitz/responder/compare/v0.0.1..v0.0.2
|
||||
[v0.0.1]: https://github.com/kennethreitz/responder/compare/v0.0.0..v0.0.1
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,24 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
waitress = "*"
|
||||
werkzeug = "*"
|
||||
pyyaml = "*"
|
||||
requests = "*"
|
||||
requests-wsgi-adapter = "*"
|
||||
graphene = "*"
|
||||
whitenoise = "*"
|
||||
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
"flake8" = "*"
|
||||
black = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
Generated
-271
@@ -1,271 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f6b7cc3cf0ac2760ea99bcb8d18c743eff418c6269da29823ccfdbdea19a8c1e"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aniso8601": {
|
||||
"hashes": [
|
||||
"sha256:7849749cf00ae0680ad2bdfe4419c7a662bef19c03691a19e008c8b9a5267802",
|
||||
"sha256:94f90871fcd314a458a3d4eca1c84448efbd200e86f55fe4c733c7a40149ef50"
|
||||
],
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
|
||||
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
|
||||
],
|
||||
"version": "==2018.8.24"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"graphene": {
|
||||
"hashes": [
|
||||
"sha256:b8ec446d17fa68721636eaad3d6adc1a378cb6323e219814c8f98c9928fc9642",
|
||||
"sha256:faa26573b598b22ffd274e2fd7a4c52efa405dcca96e01a62239482246248aa3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.3"
|
||||
},
|
||||
"graphql-core": {
|
||||
"hashes": [
|
||||
"sha256:889e869be5574d02af77baf1f30b5db9ca2959f1c9f5be7b2863ead5a3ec6181",
|
||||
"sha256:9462e22e32c7f03b667373ec0a84d95fba10e8ce2ead08f29fbddc63b671b0c1"
|
||||
],
|
||||
"version": "==2.1"
|
||||
},
|
||||
"graphql-relay": {
|
||||
"hashes": [
|
||||
"sha256:2716b7245d97091af21abf096fabafac576905096d21ba7118fba722596f65db"
|
||||
],
|
||||
"version": "==0.4.5"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.7"
|
||||
},
|
||||
"promise": {
|
||||
"hashes": [
|
||||
"sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af",
|
||||
"sha256:348f5f6c3edd4fd47c9cd65aed03ac1b31136d375aa63871a57d3e444c85655c"
|
||||
],
|
||||
"version": "==2.2.1"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
||||
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
|
||||
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
|
||||
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
|
||||
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2b4"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"requests-wsgi-adapter": {
|
||||
"hashes": [
|
||||
"sha256:7080c98ae2614b8d0b7339b611d97a535470d2fb479731f7d588d5f8108ea134"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.0"
|
||||
},
|
||||
"rx": {
|
||||
"hashes": [
|
||||
"sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23",
|
||||
"sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"
|
||||
],
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"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": {
|
||||
"hashes": [
|
||||
"sha256:40b0f297a7f3af61fbfbdc67e59090c70dc150a1601c39ecc9f5f1d283fb931b",
|
||||
"sha256:d33cd3d62426c0f1b3cd84ee3d65779c7003aae3fc060dee60524d10a57f05a9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.14.1"
|
||||
},
|
||||
"whitenoise": {
|
||||
"hashes": [
|
||||
"sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02",
|
||||
"sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.1"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"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": {
|
||||
"hashes": [
|
||||
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||
],
|
||||
"version": "==18.2.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
|
||||
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==18.9b0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
||||
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.3.9"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
|
||||
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
||||
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
||||
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
||||
],
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:7e258ee50338f4e46957f9e09a0f10fb1c2d05493fa901d113a8dafd0790de4e",
|
||||
"sha256:9332147e9af2dcf46cd7ceb14d5acadb6564744ddff1fe8c17f0ce60ece7d9a2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.8.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,109 @@
|
||||
# Responder: a Sorta Familar HTTP Framework for Python
|
||||
# Responder
|
||||
|
||||

|
||||
A familiar HTTP Service Framework for Python, powered by [Starlette](https://www.starlette.io/).
|
||||
|
||||
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.
|
||||
```python
|
||||
import responder
|
||||
|
||||
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.
|
||||
api = responder.API()
|
||||
|
||||
# The Basic Idea
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
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.
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
```
|
||||
|
||||
## Old Ideas
|
||||
$ pip install responder
|
||||
|
||||
- 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.
|
||||
That's it. Supports Python 3.9+.
|
||||
|
||||
- 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.
|
||||
## The Basics
|
||||
|
||||
## New Ideas
|
||||
- `resp.text` sends back text. `resp.html` sends back HTML. `resp.content` sends back bytes.
|
||||
- `resp.media` sends back JSON (or YAML, with content negotiation).
|
||||
- `resp.file("path.pdf")` serves a file with automatic content-type detection.
|
||||
- `req.headers` is case-insensitive. `req.params` gives you query parameters.
|
||||
- Both sync and async views work — the `async` is optional.
|
||||
|
||||
- **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.
|
||||
## Highlights
|
||||
|
||||
## Future Ideas
|
||||
```python
|
||||
# Type-safe route parameters
|
||||
@api.route("/users/{user_id:int}")
|
||||
async def get_user(req, resp, *, user_id):
|
||||
resp.media = {"id": user_id}
|
||||
|
||||
- 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.
|
||||
# HTTP method filtering
|
||||
@api.route("/items", methods=["POST"])
|
||||
async def create_item(req, resp):
|
||||
data = await req.media()
|
||||
resp.media = {"created": data}
|
||||
|
||||
# The Goal
|
||||
# Class-based views
|
||||
@api.route("/things/{id}")
|
||||
class ThingResource:
|
||||
def on_get(self, req, resp, *, id):
|
||||
resp.media = {"id": id}
|
||||
def on_post(self, req, resp, *, id):
|
||||
resp.text = "created"
|
||||
|
||||
The primary goal here is to learn, not to get adoption. Though, who knows how these things will pan out.
|
||||
# Before-request hooks (auth, rate limiting, etc.)
|
||||
@api.route(before_request=True)
|
||||
def check_auth(req, resp):
|
||||
if not req.headers.get("Authorization"):
|
||||
resp.status_code = 401
|
||||
resp.media = {"error": "unauthorized"}
|
||||
|
||||
# Custom error handling
|
||||
@api.exception_handler(ValueError)
|
||||
async def handle_error(req, resp, exc):
|
||||
resp.status_code = 400
|
||||
resp.media = {"error": str(exc)}
|
||||
|
||||
# Lifespan events
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
print("starting up")
|
||||
yield
|
||||
print("shutting down")
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
# GraphQL
|
||||
import graphene
|
||||
api.graphql("/graphql", schema=graphene.Schema(query=Query))
|
||||
|
||||
# WebSockets
|
||||
@api.route("/ws", websocket=True)
|
||||
async def websocket(ws):
|
||||
await ws.accept()
|
||||
while True:
|
||||
name = await ws.receive_text()
|
||||
await ws.send_text(f"Hello {name}!")
|
||||
|
||||
# Mount WSGI/ASGI apps
|
||||
from flask import Flask
|
||||
flask_app = Flask(__name__)
|
||||
api.mount("/flask", flask_app)
|
||||
|
||||
# Background tasks
|
||||
@api.route("/work")
|
||||
def do_work(req, resp):
|
||||
@api.background.task
|
||||
def process():
|
||||
import time; time.sleep(10)
|
||||
process()
|
||||
resp.media = {"status": "processing"}
|
||||
```
|
||||
|
||||
Built-in OpenAPI docs, cookie-based sessions, gzip compression, static file serving, Jinja2 templates, and a production uvicorn server.
|
||||
|
||||
Route convertors: `str`, `int`, `float`, `uuid`, `path`.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://responder.kennethreitz.org
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
Binary file not shown.
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. -->
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/AA83D0999C9464BC6.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/AA83D0999C9464BC6.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 4r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/AA83D0999C9464BC6.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/AA83D0999C9464BC6.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/005CED86771E6F899.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/005CED86771E6F899.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 4i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/005CED86771E6F899.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/005CED86771E6F899.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/40351B0A9DF3B9622.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/40351B0A9DF3B9622.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 6r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/40351B0A9DF3B9622.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/40351B0A9DF3B9622.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3A39878E22934F8AD.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3A39878E22934F8AD.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 6i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3A39878E22934F8AD.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3A39878E22934F8AD.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/DD7AD6D5FDE05ABCA.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/DD7AD6D5FDE05ABCA.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 7r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/DD7AD6D5FDE05ABCA.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/DD7AD6D5FDE05ABCA.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/777143010DB6642D4.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/777143010DB6642D4.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 7i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/777143010DB6642D4.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/777143010DB6642D4.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/4A801A74B6CEC6B76.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/4A801A74B6CEC6B76.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 4r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/4A801A74B6CEC6B76.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/4A801A74B6CEC6B76.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7E9ADDBCA2C8BD433.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7E9ADDBCA2C8BD433.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 4i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7E9ADDBCA2C8BD433.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7E9ADDBCA2C8BD433.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/13B223000FA5C8685.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/13B223000FA5C8685.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 7r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/13B223000FA5C8685.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/13B223000FA5C8685.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/0AC552691F872135E.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/0AC552691F872135E.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 7i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/0AC552691F872135E.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/0AC552691F872135E.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7D0605C11BA3A93EF.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7D0605C11BA3A93EF.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 4r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7D0605C11BA3A93EF.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7D0605C11BA3A93EF.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/81A13EBFC10447CAC.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/81A13EBFC10447CAC.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 4i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/81A13EBFC10447CAC.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/81A13EBFC10447CAC.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/25E3F5D50DDE1C555.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/25E3F5D50DDE1C555.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 6r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/25E3F5D50DDE1C555.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/25E3F5D50DDE1C555.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/F526A6C670B9765E2.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/F526A6C670B9765E2.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 6i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/F526A6C670B9765E2.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/F526A6C670B9765E2.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7DCD4B5CCAEE3223E.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7DCD4B5CCAEE3223E.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 7r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7DCD4B5CCAEE3223E.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/7DCD4B5CCAEE3223E.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C08332ABD7F145352.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C08332ABD7F145352.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Mercury Text G1 7i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C08332ABD7F145352.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C08332ABD7F145352.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C579DF5B35B145D49.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C579DF5B35B145D49.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 4r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C579DF5B35B145D49.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/C579DF5B35B145D49.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/E3597D43523236FD8.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/E3597D43523236FD8.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 4i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/E3597D43523236FD8.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/E3597D43523236FD8.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3CBFC66855DD9B6EA.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3CBFC66855DD9B6EA.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 7r";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3CBFC66855DD9B6EA.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/3CBFC66855DD9B6EA.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm A";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/09E151FC31ECEF374.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/09E151FC31ECEF374.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Operator Mono SSm 7i";
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/09E151FC31ECEF374.eot");
|
||||
src: url("http://python-responder.org/en/latest/_static/fonts/692185/09E151FC31ECEF374.eot?#hco")
|
||||
format("embedded-opentype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
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
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
Binary file not shown.
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
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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 |
@@ -0,0 +1,242 @@
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cloud.typography.com/7584432/7586812/css/fonts.css"
|
||||
/>
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
|
||||
<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", monospace !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>
|
||||
@@ -0,0 +1,89 @@
|
||||
<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>
|
||||
<a class="github-button"
|
||||
href="https://github.com/kennethreitz/responder"
|
||||
data-color-scheme="no-preference: light; light: light; dark: light;"
|
||||
data-size="large"
|
||||
data-show-count="true"
|
||||
aria-label="Star kennethreitz/responder on GitHub">Star</a>
|
||||
</p>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||
/>
|
||||
<style>
|
||||
.algolia-autocomplete {
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
}
|
||||
.algolia-autocomplete a {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
#doc_search {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<input id="doc_search" placeholder="Search the doc" autofocus />
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
|
||||
onload="docsearch({
|
||||
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
||||
indexName: 'python-responder',
|
||||
inputSelector: '#doc_search',
|
||||
debug: false // Set debug to true if you want to inspect the dropdown
|
||||
})"
|
||||
async
|
||||
></script>
|
||||
|
||||
<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>
|
||||
<a class="github-button"
|
||||
href="https://github.com/kennethreitz"
|
||||
data-color-scheme="no-preference: light; light: light; dark: light;"
|
||||
data-size="medium"
|
||||
data-show-count="true"
|
||||
aria-label="Follow @kennethreitz on GitHub">Follow @kennethreitz</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a
|
||||
href="https://x.com/kennethreitz42"
|
||||
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>
|
||||
@@ -0,0 +1,94 @@
|
||||
<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>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||
/>
|
||||
<style>
|
||||
.algolia-autocomplete {
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
}
|
||||
.algolia-autocomplete a {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
#doc_search {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<input id="doc_search" placeholder="Search the doc" autofocus />
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
|
||||
onload="docsearch({
|
||||
apiKey: 'ac965312db252e0496283c75c6f76f0b',
|
||||
indexName: 'python-responder',
|
||||
inputSelector: '#doc_search',
|
||||
debug: false // Set debug to true if you want to inspect the dropdown
|
||||
})"
|
||||
async
|
||||
></script>
|
||||
|
||||
<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://x.com/kennethreitz42"
|
||||
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>
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
# Backlog
|
||||
|
||||
## Future Ideas
|
||||
- Consider adding `after_request` hooks (complement to `before_request`)
|
||||
- Explore WebSocket before_request short-circuit support
|
||||
- Add rate limiting middleware
|
||||
- Consider async template rendering by default
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../CHANGELOG.md
|
||||
@@ -0,0 +1,174 @@
|
||||
Responder CLI
|
||||
=============
|
||||
|
||||
Responder installs a command line program ``responder``. Use it to launch
|
||||
a Responder application from a file or module, either located on a local
|
||||
or remote filesystem, or object store.
|
||||
|
||||
Launch Module Entrypoint
|
||||
------------------------
|
||||
|
||||
For loading a Responder application from a Python module, you will refer to
|
||||
its ``API()`` instance using a `Python entry point object reference`_ that
|
||||
points to a Python object. It is either in the form ``importable.module``,
|
||||
or ``importable.module:object.attr``.
|
||||
|
||||
A basic invocation command to launch a Responder application:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
responder run acme.app
|
||||
|
||||
The command above assumes a Python package ``acme`` including an ``app``
|
||||
module ``acme/app.py`` that includes an attribute ``api`` that refers
|
||||
to a ``responder.API`` instance, reflecting the typical layout of
|
||||
a standard Responder application.
|
||||
|
||||
Loading a Responder application using an entrypoint specification will
|
||||
inherit the capacities of `Python's import system`_, as implemented by
|
||||
`importlib`_.
|
||||
|
||||
Launch Local File
|
||||
-----------------
|
||||
|
||||
Acquire a minimal example single-file application, ``helloworld.py`` [1]_,
|
||||
to your local filesystem, giving you the chance to edit it, and launch the
|
||||
Responder HTTP service.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wget https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
|
||||
responder run helloworld.py
|
||||
|
||||
.. note::
|
||||
|
||||
To validate the example application, invoke a HTTP request, for example using
|
||||
`curl`_, `HTTPie`_, or your favourite browser at hand.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
http http://127.0.0.1:5042/Hello
|
||||
|
||||
The response is no surprise.
|
||||
|
||||
::
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 13
|
||||
content-type: text/plain
|
||||
date: Sat, 26 Oct 2024 13:16:55 GMT
|
||||
encoding: utf-8
|
||||
server: uvicorn
|
||||
|
||||
Hello, world!
|
||||
|
||||
.. [1] The Responder application `helloworld.py`_ implements a basic echo handler.
|
||||
|
||||
Launch Remote File
|
||||
------------------
|
||||
|
||||
You can also launch a single-file application where its Python file is stored
|
||||
on a remote location.
|
||||
|
||||
Responder supports all filesystem adapters compatible with `fsspec`_, and
|
||||
installs the adapters for Azure Blob Storage (az), Google Cloud Storage (gs),
|
||||
GitHub, HTTP, and AWS S3 by default.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# Works 1:1.
|
||||
responder run https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
|
||||
responder run github://kennethreitz:responder@/examples/helloworld.py
|
||||
|
||||
If you need access other kinds of remote targets, see the `list of
|
||||
fsspec-supported filesystems and protocols`_. The next section enumerates
|
||||
a few synthetic examples. The corresponding storage buckets do not even
|
||||
exist, so don't expect those commands to work.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# Azure Blob Storage, Google Cloud Storage, and AWS S3.
|
||||
responder run az://kennethreitz-assets/responder/examples/helloworld.py
|
||||
responder run gs://kennethreitz-assets/responder/examples/helloworld.py
|
||||
responder run s3://kennethreitz-assets/responder/examples/helloworld.py
|
||||
|
||||
# Hadoop Distributed File System (hdfs), SSH File Transfer Protocol (sftp),
|
||||
# Common Internet File System (smb), Web-based Distributed Authoring and
|
||||
# Versioning (webdav).
|
||||
responder run hdfs://kennethreitz-assets/responder/examples/helloworld.py
|
||||
responder run sftp://user@host/kennethreitz/responder/examples/helloworld.py
|
||||
responder run smb://workgroup;user:password@server:port/responder/examples/helloworld.py
|
||||
responder run webdav+https://user:password@server:port/responder/examples/helloworld.py
|
||||
|
||||
.. tip::
|
||||
|
||||
In order to install support for all filesystem types supported by fsspec, run:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
uv pip install 'fsspec[full]'
|
||||
|
||||
When using ``uv``, this concludes within an acceptable time of approx.
|
||||
25 seconds. If you need to be more selectively instead of using ``full``,
|
||||
choose from one or multiple of the available `fsspec extras`_, which are:
|
||||
|
||||
abfs, arrow, dask, dropbox, fuse, gcs, git, github, hdfs, http, oci, s3,
|
||||
sftp, smb, ssh.
|
||||
|
||||
Launch with Non-Standard Instance Name
|
||||
--------------------------------------
|
||||
|
||||
By default, Responder will acquire an ``responder.API`` instance using the
|
||||
symbol name ``api`` from the specified Python module.
|
||||
|
||||
If your main application file uses a different name than ``api``, please
|
||||
append the designated symbol name to the launch target address.
|
||||
|
||||
It works like this for module entrypoints and local files:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
responder run acme.app:service
|
||||
responder run /path/to/acme/app.py:service
|
||||
|
||||
It works like this for URLs:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
responder run http://app.server.local/path/to/acme/app.py#service
|
||||
|
||||
Within your ``app.py``, the instance would have been defined to use
|
||||
the ``service`` symbol name instead of ``api``, like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
service = responder.API()
|
||||
|
||||
Build JavaScript Application
|
||||
----------------------------
|
||||
|
||||
The ``build`` subcommand invokes ``npm run build``, optionally accepting
|
||||
a target directory. By default, it uses the current working directory,
|
||||
where it expects a regular NPM ``package.json`` file.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
responder build
|
||||
|
||||
When specifying a target directory, Responder will change to that
|
||||
directory beforehand.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
responder build /path/to/project
|
||||
|
||||
|
||||
.. _curl: https://curl.se/
|
||||
.. _fsspec: https://filesystem-spec.readthedocs.io/en/latest/
|
||||
.. _fsspec extras: https://github.com/fsspec/filesystem_spec/blob/2024.12.0/pyproject.toml#L27-L69
|
||||
.. _helloworld.py: https://github.com/kennethreitz/responder/blob/main/examples/helloworld.py
|
||||
.. _HTTPie: https://httpie.io/docs/cli
|
||||
.. _importlib: https://docs.python.org/3/library/importlib.html
|
||||
.. _list of fsspec-supported filesystems and protocols: https://github.com/fsspec/universal_pathlib#currently-supported-filesystems-and-protocols
|
||||
.. _Python entry point object reference: https://packaging.python.org/en/latest/specifications/entry-points/
|
||||
.. _Python's import system: https://docs.python.org/3/reference/import.html
|
||||
@@ -0,0 +1,292 @@
|
||||
# -*- 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-2026, 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",
|
||||
"myst_parser",
|
||||
"sphinx_copybutton",
|
||||
"sphinx_design",
|
||||
"sphinx_design_elements",
|
||||
"sphinxext.opengraph",
|
||||
]
|
||||
|
||||
# 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": "restructuredtext"}
|
||||
|
||||
# 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 = "en"
|
||||
|
||||
# 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 link checker ----------------------------------------------
|
||||
linkcheck_ignore = [
|
||||
# Feldroy.com links are ignored because it blocks GHA.
|
||||
r"https://www.feldroy.com/.*",
|
||||
]
|
||||
linkcheck_anchors_ignore_for_url = [
|
||||
# Requires JavaScript.
|
||||
# After opting-in to new GitHub issues, Sphinx can no longer grok the HTML anchor references.
|
||||
r"https://github.com",
|
||||
]
|
||||
|
||||
# -- Options for intersphinx extension ---------------------------------------
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
|
||||
|
||||
# -- Options for todo extension ----------------------------------------------
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
# -- Options for MyST --------------------------------------------------------
|
||||
|
||||
myst_heading_anchors = 3
|
||||
myst_enable_extensions = [
|
||||
"attrs_block",
|
||||
"attrs_inline",
|
||||
"colon_fence",
|
||||
"deflist",
|
||||
"fieldlist",
|
||||
"html_admonition",
|
||||
"html_image",
|
||||
"linkify",
|
||||
"replacements",
|
||||
"strikethrough",
|
||||
"substitution",
|
||||
"tasklist",
|
||||
]
|
||||
myst_substitutions = {}
|
||||
|
||||
# -- Options for OpenGraph ---------------------------------------------------
|
||||
#
|
||||
# When making changes, check them using the RTD PR preview URL on https://www.opengraph.xyz/.
|
||||
#
|
||||
# About text lengths
|
||||
#
|
||||
# Original documentation says:
|
||||
# - ogp_description_length
|
||||
# Configure the amount of characters taken from a page. The default of 200 is probably good
|
||||
# for most people. If something other than a number is used, it defaults back to 200.
|
||||
# -- https://sphinxext-opengraph.readthedocs.io/en/latest/#options
|
||||
#
|
||||
# Other people say:
|
||||
# - og:title 40 chars
|
||||
# - og:description has 2 max lengths:
|
||||
# When the link is used in a Post, it's 300 chars. When a link is used in a Comment, it's 110 chars.
|
||||
# So you can either treat it as 110, or, write your Descriptions to 300 but make sure the first 110
|
||||
# is the critical part and still makes sense when it gets cut off.
|
||||
# -- https://stackoverflow.com/questions/8914476/facebook-open-graph-meta-tags-maximum-content-length
|
||||
ogp_site_url = "https://responder.kennethreitz.org/"
|
||||
ogp_description_length = 300
|
||||
ogp_site_name = "Responder Documentation"
|
||||
ogp_image = "https://responder.kennethreitz.org/_static/responder.png"
|
||||
ogp_image_alt = False
|
||||
ogp_use_first_image = False
|
||||
ogp_type = "website"
|
||||
ogp_enable_meta_description = True
|
||||
|
||||
# -- Options for sphinx-copybutton -------------------------------------------
|
||||
|
||||
copybutton_remove_prompts = True
|
||||
copybutton_line_continuation_character = "\\"
|
||||
copybutton_prompt_text = r">>> |\.\.\. |\$ |sh\$ |PS> |cr> |mysql> |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
|
||||
copybutton_prompt_is_regexp = True
|
||||
@@ -0,0 +1,55 @@
|
||||
Deploying Responder
|
||||
===================
|
||||
|
||||
You can deploy Responder anywhere you can deploy a basic Python application.
|
||||
|
||||
Docker Deployment
|
||||
-----------------
|
||||
|
||||
Assuming an existing ``api.py`` containing your Responder application.
|
||||
|
||||
``Dockerfile``::
|
||||
|
||||
FROM python:3.13-slim
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN pip install responder
|
||||
ENV PORT=80
|
||||
EXPOSE 80
|
||||
CMD ["python", "api.py"]
|
||||
|
||||
That's it!
|
||||
|
||||
Cloud Deployment
|
||||
----------------
|
||||
|
||||
Responder automatically honors the ``PORT`` environment variable, which is
|
||||
set by most cloud platforms (Fly.io, Railway, Render, Google Cloud Run, etc.).
|
||||
|
||||
The basics::
|
||||
|
||||
$ mkdir my-api
|
||||
$ cd my-api
|
||||
|
||||
Write out an ``api.py``::
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
@api.route("/")
|
||||
async def hello(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
|
||||
Deploy with your platform of choice. Responder will bind to ``0.0.0.0``
|
||||
on the port specified by ``PORT`` automatically.
|
||||
|
||||
Running with Uvicorn Directly
|
||||
-----------------------------
|
||||
|
||||
For production deployments, you can also run your app directly with uvicorn::
|
||||
|
||||
uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
|
||||
@@ -0,0 +1,175 @@
|
||||
.. 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
|
||||
=================================
|
||||
|
||||
|ci-tests| |version| |license| |python-versions| |downloads| |contributors| |say-thanks|
|
||||
|
||||
.. |ci-tests| image:: https://github.com/kennethreitz/responder/actions/workflows/test.yaml/badge.svg
|
||||
:target: https://github.com/kennethreitz/responder/actions/workflows/test.yaml
|
||||
.. |ci-docs| image:: https://github.com/kennethreitz/responder/actions/workflows/docs.yaml/badge.svg
|
||||
:target: https://github.com/kennethreitz/responder/actions/workflows/docs.yaml
|
||||
.. |version| image:: https://img.shields.io/pypi/v/responder.svg
|
||||
:target: https://pypi.org/project/responder/
|
||||
.. |license| image:: https://img.shields.io/pypi/l/responder.svg
|
||||
:target: https://pypi.org/project/responder/
|
||||
.. |python-versions| image:: https://img.shields.io/pypi/pyversions/responder.svg
|
||||
:target: https://pypi.org/project/responder/
|
||||
.. |downloads| image:: https://static.pepy.tech/badge/responder/month
|
||||
:target: https://www.pepy.tech/projects/responder
|
||||
.. |contributors| image:: https://img.shields.io/github/contributors/kennethreitz/responder.svg
|
||||
:target: https://github.com/kennethreitz/responder/graphs/contributors
|
||||
.. |say-thanks| image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg
|
||||
:target: https://saythanks.io/to/kennethreitz
|
||||
|
||||
.. 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()
|
||||
|
||||
Responder is powered by `Starlette`_.
|
||||
|
||||
The example program demonstrates an `ASGI`_ application using `Responder`_,
|
||||
including production-ready components like the `uvicorn`_ webserver, based
|
||||
on `uvloop`_, and the `Jinja`_ templating library pre-installed.
|
||||
The ``async`` declaration within the example program is optional.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- A pleasant API, with a single import statement.
|
||||
- Class-based views without inheritance.
|
||||
- `ASGI`_, the future of Python web services.
|
||||
- Asynchronous Python frameworks and applications.
|
||||
- Automatic gzip compression.
|
||||
- WebSocket support!
|
||||
- The ability to mount any ASGI / WSGI app at a subroute.
|
||||
- `f-string syntax`_ route declaration.
|
||||
- Mutable response object, passed into each view. No need to return anything.
|
||||
- Background tasks, spawned off in a ``ThreadPoolExecutor``.
|
||||
- GraphQL (with *GraphiQL*) support!
|
||||
- OpenAPI schema generation, with interactive documentation!
|
||||
- Single-page webapp support!
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
“Pleasantly very taken with python-responder.
|
||||
`@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`_
|
||||
|
||||
|
||||
User Guides
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quickstart
|
||||
tour
|
||||
deployment
|
||||
testing
|
||||
api
|
||||
cli
|
||||
|
||||
|
||||
Installing Responder
|
||||
--------------------
|
||||
|
||||
Use ``uv`` for fast installation.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
uv pip install --upgrade 'responder'
|
||||
|
||||
Or use standard pip where ``uv`` is not available.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip install --upgrade 'responder'
|
||||
|
||||
Responder supports **Python 3.9+**.
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
If you are looking at installing Responder
|
||||
for hacking on it, please refer to the :ref:`sandbox` documentation.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changes
|
||||
Sandbox <sandbox>
|
||||
backlog
|
||||
|
||||
|
||||
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.content`` sends back bytes.
|
||||
- Setting ``resp.text`` sends back unicode, while setting ``resp.html`` sends back HTML.
|
||||
- Setting ``resp.media`` sends back JSON/YAML (``.text``/``.html``/``.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 -- using Python's f-string syntax.
|
||||
- I love Falcon's "every request and response is passed into 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** powered by Starlette's TestClient.
|
||||
- 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`_ is 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.
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
.. _@kennethreitz: https://x.com/kennethreitz42
|
||||
.. _ASGI: https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface
|
||||
.. _Django REST Framework: https://www.django-rest-framework.org/
|
||||
.. _f-string syntax: https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals
|
||||
.. _Jinja: https://jinja.palletsprojects.com/en/stable/
|
||||
.. _ServeStatic: https://archmonger.github.io/ServeStatic/latest/
|
||||
.. _Slowloris: https://en.wikipedia.org/wiki/Slowloris_(computer_security)
|
||||
.. _Starlette: https://www.starlette.io/
|
||||
.. _Responder: https://responder.kennethreitz.org/
|
||||
.. _Two Scoops of Django: https://www.feldroy.com/two-scoops-press#two-scoops-of-django
|
||||
.. _uvicorn: https://www.uvicorn.org/
|
||||
.. _uvloop: https://uvloop.readthedocs.io/
|
||||
@@ -0,0 +1,176 @@
|
||||
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 and will set the listening address to ``0.0.0.0`` automatically (also configurable through the ``address`` keyword argument).
|
||||
|
||||
|
||||
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!``.
|
||||
|
||||
Type convertors are also available::
|
||||
|
||||
@api.route("/add/{a:int}/{b:int}")
|
||||
async def add(req, resp, *, a, b):
|
||||
resp.text = f"{a} + {b} = {a + b}"
|
||||
|
||||
Supported types: ``str``, ``int``, ``float``, ``uuid``, and ``path``.
|
||||
|
||||
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
|
||||
--------------------
|
||||
|
||||
Responder provides a built-in light `Jinja`_ wrapper ``templates.Templates``
|
||||
|
||||
Usage::
|
||||
|
||||
from responder.templates import Templates
|
||||
|
||||
templates = Templates()
|
||||
|
||||
@api.route("/hello/{name}/html")
|
||||
def hello(req, resp, name):
|
||||
resp.html = templates.render("hello.html", name=name)
|
||||
|
||||
|
||||
Also a ``render_async`` is available::
|
||||
|
||||
templates = Templates(enable_async=True)
|
||||
resp.html = await templates.render_async("hello.html", who=who)
|
||||
|
||||
You can also use the existing ``api.template(filename, *args, **kwargs)`` to render templates::
|
||||
|
||||
@api.route("/hello/{who}/html")
|
||||
def hello_html(req, resp, *, who):
|
||||
resp.html = api.template('hello.html', who=who)
|
||||
|
||||
|
||||
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 req.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}``.
|
||||
|
||||
|
||||
Here's a sample code to post a file with background::
|
||||
|
||||
@api.route("/")
|
||||
async def upload_file(req, resp):
|
||||
|
||||
@api.background.task
|
||||
def process_data(data):
|
||||
with open(f"./{data['file']['filename']}", 'wb') as f:
|
||||
f.write(data['file']['content'])
|
||||
|
||||
data = await req.media(format='files')
|
||||
process_data(data)
|
||||
|
||||
resp.media = {'success': 'ok'}
|
||||
|
||||
You can test file uploads using the built-in test client::
|
||||
|
||||
files = {'file': ('hello.txt', b'hello, world!', 'text/plain')}
|
||||
r = api.requests.post(api.url_for(upload_file), files=files)
|
||||
print(r.json())
|
||||
|
||||
|
||||
.. _Jinja: https://jinja.palletsprojects.com/en/stable/
|
||||
@@ -0,0 +1,36 @@
|
||||
(sandbox)=
|
||||
# Development Sandbox
|
||||
|
||||
## Setup
|
||||
Set up a development sandbox.
|
||||
|
||||
Acquire sources and create virtualenv.
|
||||
```shell
|
||||
git clone https://github.com/kennethreitz/responder.git
|
||||
cd responder
|
||||
uv venv
|
||||
```
|
||||
|
||||
Install project in editable mode, including
|
||||
all development tools.
|
||||
```shell
|
||||
uv pip install --upgrade --editable '.[develop,docs,release,test]'
|
||||
```
|
||||
|
||||
## Operations
|
||||
Run tests.
|
||||
```shell
|
||||
source .venv/bin/activate
|
||||
pytest
|
||||
```
|
||||
|
||||
Format code.
|
||||
```shell
|
||||
ruff format .
|
||||
ruff check --fix .
|
||||
```
|
||||
|
||||
Documentation authoring.
|
||||
```shell
|
||||
sphinx-autobuild --open-browser --watch docs/source docs/source docs/build
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
Building and Testing with Responder
|
||||
===================================
|
||||
|
||||
Responder comes with a first-class, well supported test client for your ASGI web services (powered by Starlette's TestClient).
|
||||
|
||||
Here, we'll go over the basics of setting up and testing a Responder application.
|
||||
|
||||
The Basics
|
||||
----------
|
||||
|
||||
Your project should look like this::
|
||||
|
||||
api.py test_api.py
|
||||
|
||||
``$ cat api.py``::
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
@api.route("/")
|
||||
def hello_world(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
|
||||
Writing Tests
|
||||
-------------
|
||||
|
||||
``$ cat test_api.py``::
|
||||
|
||||
import pytest
|
||||
import api as service
|
||||
|
||||
@pytest.fixture
|
||||
def api():
|
||||
return service.api
|
||||
|
||||
|
||||
def test_hello_world(api):
|
||||
r = api.requests.get("/")
|
||||
assert r.text == "hello, world!"
|
||||
|
||||
``$ pytest``::
|
||||
|
||||
...
|
||||
========================== 1 passed in 0.10 seconds ==========================
|
||||
@@ -0,0 +1,507 @@
|
||||
Feature Tour
|
||||
============
|
||||
|
||||
|
||||
Route Method Filtering
|
||||
----------------------
|
||||
|
||||
You can restrict routes to specific HTTP methods::
|
||||
|
||||
@api.route("/items", methods=["GET"])
|
||||
def list_items(req, resp):
|
||||
resp.media = {"items": [...]}
|
||||
|
||||
@api.route("/items", methods=["POST"], check_existing=False)
|
||||
async def create_item(req, resp):
|
||||
data = await req.media()
|
||||
resp.media = {"created": data}
|
||||
|
||||
|
||||
Class-Based Views
|
||||
-----------------
|
||||
|
||||
Class-based views (and setting some headers and stuff)::
|
||||
|
||||
@api.route("/{greeting}")
|
||||
class GreetingResource:
|
||||
def on_request(self, 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
|
||||
|
||||
|
||||
Lifespan Events
|
||||
---------------
|
||||
|
||||
Use the lifespan context manager for startup and shutdown logic::
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
# Startup: connect to database, etc.
|
||||
print("Starting up...")
|
||||
yield
|
||||
# Shutdown: clean up resources
|
||||
print("Shutting down...")
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
You can also use the traditional event decorators::
|
||||
|
||||
@api.on_event('startup')
|
||||
async def startup():
|
||||
print("Starting up...")
|
||||
|
||||
@api.on_event('shutdown')
|
||||
async def shutdown():
|
||||
print("Shutting down...")
|
||||
|
||||
|
||||
Serving Files
|
||||
-------------
|
||||
|
||||
Serve files from disk with automatic content-type detection::
|
||||
|
||||
@api.route("/download")
|
||||
def download(req, resp):
|
||||
resp.file("reports/annual.pdf")
|
||||
|
||||
You can also specify the content type explicitly::
|
||||
|
||||
@api.route("/image")
|
||||
def image(req, resp):
|
||||
resp.file("photos/cat.jpg", content_type="image/jpeg")
|
||||
|
||||
|
||||
Custom Error Handling
|
||||
---------------------
|
||||
|
||||
Register handlers for specific exception types::
|
||||
|
||||
@api.exception_handler(ValueError)
|
||||
async def handle_value_error(req, resp, exc):
|
||||
resp.status_code = 400
|
||||
resp.media = {"error": str(exc)}
|
||||
|
||||
|
||||
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
|
||||
-------
|
||||
Responder supports GraphQL, a query language for APIs that enables clients to
|
||||
request exactly the data they need.
|
||||
|
||||
For more information about GraphQL, visit https://graphql.org/.
|
||||
|
||||
Serve a GraphQL API::
|
||||
|
||||
import graphene
|
||||
from responder.ext.graphql import GraphQLView
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
hello = graphene.String(name=graphene.String(default_value="stranger"))
|
||||
|
||||
def resolve_hello(self, info, name):
|
||||
return f"Hello {name}"
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
view = GraphQLView(api=api, schema=schema)
|
||||
|
||||
api.add_route("/graph", view)
|
||||
|
||||
Visiting the endpoint will render a *GraphiQL* instance, in the browser.
|
||||
|
||||
You can make use of Responder's Request and Response objects in your GraphQL resolvers through ``info.context['request']`` and ``info.context['response']``.
|
||||
|
||||
|
||||
OpenAPI Schema Support
|
||||
----------------------
|
||||
|
||||
Responder comes with built-in support for OpenAPI / marshmallow.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're upgrading from a previous version, note that the OpenAPI module
|
||||
has been renamed from ``responder.ext.schema`` to ``responder.ext.openapi``.
|
||||
Update your imports accordingly.
|
||||
|
||||
New in Responder 1.4.0::
|
||||
|
||||
import responder
|
||||
from responder.ext.openapi import OpenAPISchema
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
contact = {
|
||||
"name": "API Support",
|
||||
"url": "http://www.example.com/support",
|
||||
"email": "support@example.com",
|
||||
}
|
||||
license = {
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
}
|
||||
|
||||
api = responder.API()
|
||||
|
||||
schema = OpenAPISchema(
|
||||
app=api,
|
||||
title="Web Service",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
description="A simple pet store",
|
||||
terms_of_service="http://example.com/terms/",
|
||||
contact=contact,
|
||||
license=license,
|
||||
)
|
||||
|
||||
@schema.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
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
"""
|
||||
resp.media = PetSchema().dump({"name": "little orange"})
|
||||
|
||||
|
||||
Old way *It's recommended to use the code above* ::
|
||||
|
||||
import responder
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
contact = {
|
||||
"name": "API Support",
|
||||
"url": "http://www.example.com/support",
|
||||
"email": "support@example.com",
|
||||
}
|
||||
license = {
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
}
|
||||
|
||||
api = responder.API(
|
||||
title="Web Service",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
description="A simple pet store",
|
||||
terms_of_service="http://example.com/terms/",
|
||||
contact=contact,
|
||||
license=license,
|
||||
)
|
||||
|
||||
@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
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
"""
|
||||
resp.media = PetSchema().dump({"name": "little orange"})
|
||||
|
||||
::
|
||||
|
||||
>>> r = api.session().get("http://;/schema.yml")
|
||||
|
||||
>>> print(r.text)
|
||||
components:
|
||||
parameters: {}
|
||||
responses: {}
|
||||
schemas:
|
||||
Pet:
|
||||
properties:
|
||||
name: {type: string}
|
||||
type: object
|
||||
securitySchemes: {}
|
||||
info:
|
||||
contact: {email: support@example.com, name: API Support, url: 'http://www.example.com/support'}
|
||||
description: This is a sample server for a pet store.
|
||||
license: {name: Apache 2.0, url: 'https://www.apache.org/licenses/LICENSE-2.0.html'}
|
||||
termsOfService: http://example.com/terms/
|
||||
title: Web Service
|
||||
version: 1.0
|
||||
openapi: 3.0.2
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
description: Get a random pet
|
||||
responses:
|
||||
200: {description: A pet to be returned, schema: $ref: "#/components/schemas/Pet"}
|
||||
tags: []
|
||||
|
||||
|
||||
Interactive Documentation
|
||||
-------------------------
|
||||
|
||||
Responder can automatically supply API Documentation for you. Using the example above
|
||||
|
||||
The new and recommended way::
|
||||
|
||||
from responder.ext.openapi import OpenAPISchema
|
||||
|
||||
api = responder.API()
|
||||
|
||||
schema = OpenAPISchema(
|
||||
app=api,
|
||||
title="Web Service",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
...
|
||||
docs_route='/docs',
|
||||
...
|
||||
description=description,
|
||||
terms_of_service=terms_of_service,
|
||||
contact=contact,
|
||||
license=license,
|
||||
)
|
||||
|
||||
|
||||
The old way::
|
||||
|
||||
api = responder.API(
|
||||
title="Web Service",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
docs_route='/docs',
|
||||
description=description,
|
||||
terms_of_service=terms_of_service,
|
||||
contact=contact,
|
||||
license=license,
|
||||
)
|
||||
|
||||
This will make ``/docs`` render interactive documentation for your API.
|
||||
|
||||
Mount a WSGI / ASGI Apps (e.g. Flask, Starlette,...)
|
||||
----------------------------------------------------
|
||||
|
||||
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!
|
||||
|
||||
Single-Page Web Apps
|
||||
--------------------
|
||||
|
||||
If you have a single-page webapp, you can tell Responder to serve up your ``static/index.html`` at a route, like so::
|
||||
|
||||
api.add_route("/", static=True)
|
||||
|
||||
This will make ``index.html`` the default response to all undefined routes.
|
||||
|
||||
Reading / Writing Cookies
|
||||
-------------------------
|
||||
|
||||
Responder makes it very easy to interact with cookies from a Request, or add some to a Response::
|
||||
|
||||
>>> resp.cookies["hello"] = "world"
|
||||
|
||||
>>> req.cookies
|
||||
{"hello": "world"}
|
||||
|
||||
|
||||
To set cookies directives, you should use `resp.set_cookie`::
|
||||
|
||||
>>> resp.set_cookie("hello", value="world", max_age=60)
|
||||
|
||||
Supported directives:
|
||||
|
||||
* ``key`` - **Required**
|
||||
* ``value`` - [OPTIONAL] - Defaults to ``""``.
|
||||
* ``expires`` - Defaults to ``None``.
|
||||
* ``max_age`` - Defaults to ``None``.
|
||||
* ``domain`` - Defaults to ``None``.
|
||||
* ``path`` - Defaults to ``"/"``.
|
||||
* ``secure`` - Defaults to ``False``.
|
||||
* ``httponly`` - Defaults to ``True``.
|
||||
|
||||
For more information see `directives <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie>`_
|
||||
|
||||
|
||||
Using Cookie-Based Sessions
|
||||
---------------------------
|
||||
|
||||
Responder has built-in support for cookie-based sessions. To enable cookie-based sessions, simply add something to the ``resp.session`` dictionary::
|
||||
|
||||
>>> resp.session['username'] = 'kennethreitz'
|
||||
|
||||
A cookie called ``Responder-Session`` will be set, which contains all the data in ``resp.session``. It is signed, for verification purposes.
|
||||
|
||||
You can easily read a Request's session data, that can be trusted to have originated from the API::
|
||||
|
||||
>>> req.session
|
||||
{'username': 'kennethreitz'}
|
||||
|
||||
**Note**: if you are using this in production, you should pass the ``secret_key`` argument to ``API(...)``::
|
||||
|
||||
api = responder.API(secret_key=os.environ['SECRET_KEY'])
|
||||
|
||||
Using ``before_request``
|
||||
------------------------
|
||||
|
||||
If you'd like a view to be executed before every request, simply do the following::
|
||||
|
||||
@api.route(before_request=True)
|
||||
def prepare_response(req, resp):
|
||||
resp.headers["X-Pizza"] = "42"
|
||||
|
||||
Now all requests to your HTTP Service will include an ``X-Pizza`` header.
|
||||
|
||||
For ``websockets``::
|
||||
|
||||
@api.route(before_request=True, websocket=True)
|
||||
def prepare_response(ws):
|
||||
await ws.accept()
|
||||
|
||||
|
||||
WebSocket Support
|
||||
-----------------
|
||||
|
||||
Responder supports WebSockets::
|
||||
|
||||
@api.route('/ws', websocket=True)
|
||||
async def websocket(ws):
|
||||
await ws.accept()
|
||||
while True:
|
||||
name = await ws.receive_text()
|
||||
await ws.send_text(f"Hello {name}!")
|
||||
await ws.close()
|
||||
|
||||
Accepting the connection::
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
Sending and receiving data::
|
||||
|
||||
await websocket.send_{format}(data)
|
||||
await websocket.receive_{format}(data)
|
||||
|
||||
Supported formats: ``text``, ``json``, ``bytes``.
|
||||
|
||||
Closing the connection::
|
||||
|
||||
await websocket.close()
|
||||
|
||||
Using Requests Test Client
|
||||
--------------------------
|
||||
|
||||
Responder comes with a first-class, well supported test client for your ASGI web services (powered by Starlette's TestClient).
|
||||
|
||||
Here's an example of a test (written with pytest)::
|
||||
|
||||
import myapi
|
||||
|
||||
@pytest.fixture
|
||||
def api():
|
||||
return myapi.api
|
||||
|
||||
def test_response(api):
|
||||
hello = "hello, world!"
|
||||
|
||||
@api.route('/some-url')
|
||||
def some_view(req, resp):
|
||||
resp.text = hello
|
||||
|
||||
r = api.requests.get(url=api.url_for(some_view))
|
||||
assert r.text == hello
|
||||
|
||||
HSTS (Redirect to HTTPS)
|
||||
------------------------
|
||||
|
||||
Want HSTS (to redirect all traffic to HTTPS)?
|
||||
|
||||
::
|
||||
|
||||
api = responder.API(enable_hsts=True)
|
||||
|
||||
|
||||
Boom.
|
||||
|
||||
CORS
|
||||
----
|
||||
|
||||
Want `CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/>`_ ?
|
||||
|
||||
::
|
||||
|
||||
api = responder.API(cors=True)
|
||||
|
||||
|
||||
The default parameters used by **Responder** are restrictive by default, so you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be permitted to use them in a Cross-Domain context.
|
||||
|
||||
In order to set custom parameters, you need to set the ``cors_params`` argument of ``api``, a dictionary containing the following entries:
|
||||
|
||||
* ``allow_origins`` - A list of origins that should be permitted to make cross-origin requests. eg. ``['https://example.org', 'https://www.example.org']``. You can use ``['*']`` to allow any origin.
|
||||
* ``allow_origin_regex`` - A regex string to match against origins that should be permitted to make cross-origin requests. eg. ``'https://.*\.example\.org'``.
|
||||
* ``allow_methods`` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use ``['*']`` to allow all standard methods.
|
||||
* ``allow_headers`` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to ``[]``. You can use ``['*']`` to allow all headers. The ``Accept``, ``Accept-Language``, ``Content-Language`` and ``Content-Type`` headers are always allowed for CORS requests.
|
||||
* ``allow_credentials`` - Indicate that cookies should be supported for cross-origin requests. Defaults to ``False``.
|
||||
* ``expose_headers`` - Indicate any response headers that should be made accessible to the browser. Defaults to ``[]``.
|
||||
* ``max_age`` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to ``60``.
|
||||
|
||||
Trusted Hosts
|
||||
-------------
|
||||
|
||||
Make sure that all the incoming requests headers have a valid ``host``, that matches one of the provided patterns in the ``allowed_hosts`` attribute, in order to prevent HTTP Host Header attacks.
|
||||
|
||||
A 400 response will be raised, if a request does not match any of the provided patterns in the ``allowed_hosts`` attribute.
|
||||
|
||||
::
|
||||
|
||||
api = responder.API(allowed_hosts=['example.com', 'tenant.example.com'])
|
||||
|
||||
* ``allowed_hosts`` - A list of allowed hostnames.
|
||||
|
||||
Note:
|
||||
|
||||
* By default, all hostnames are allowed.
|
||||
* Wildcard domains such as ``*.example.com`` are supported.
|
||||
* To allow any hostname use ``allowed_hosts=["*"]``.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Example HTTP service definition, using Responder.
|
||||
# https://pypi.org/project/responder/
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
async def index(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
|
||||
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -0,0 +1,26 @@
|
||||
# Example showing the lifespan context manager pattern.
|
||||
# https://pypi.org/project/responder/
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import responder
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
# Startup: initialize resources
|
||||
print("Starting up...")
|
||||
yield
|
||||
# Shutdown: clean up resources
|
||||
print("Shutting down...")
|
||||
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
|
||||
@api.route("/{greeting}")
|
||||
async def greet_world(req, resp, *, greeting):
|
||||
resp.text = f"{greeting}, world!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -0,0 +1,25 @@
|
||||
# Example HTTP service definition, using Responder.
|
||||
# https://pypi.org/project/responder/
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
async def index(req, resp):
|
||||
resp.text = "Welcome"
|
||||
|
||||
|
||||
@api.route("/user")
|
||||
async def user_create(req, resp):
|
||||
data = await req.media()
|
||||
resp.text = f"Hello, {data['username']}"
|
||||
|
||||
|
||||
@api.route("/user/{identifier}")
|
||||
async def user_get(req, resp, *, identifier):
|
||||
resp.text = f"Hello, user {identifier}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
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 |
@@ -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
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user