From a8c3f8fc46f8d83fc0318b3e963d8f02388d5d37 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Tue, 23 Oct 2018 19:52:30 +0100 Subject: [PATCH 01/50] Fix Route.is_function --- responder/api.py | 1 + responder/routes.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/responder/api.py b/responder/api.py index 406cb89..e376237 100644 --- a/responder/api.py +++ b/responder/api.py @@ -328,6 +328,7 @@ class API: if default: self.default_endpoint = endpoint + # Can we remove it ? try: if callable(endpoint): endpoint.is_routed = True diff --git a/responder/routes.py b/responder/routes.py index 1183a99..d5b6485 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -77,8 +77,10 @@ class Route: def is_class_based(self): return hasattr(self.endpoint, "__class__") + @property def is_function(self): + # TODO: Should we remove is_routed ? routed = hasattr(self.endpoint, "is_routed") code = hasattr(self.endpoint, "__code__") kwdefaults = hasattr(self.endpoint, "__kwdefaults__") - return all((routed, code, kwdefaults)) + return all((callable(self.endpoint), code, kwdefaults)) From 586fad76460ebbce20320dbb1a0d7ae0259bc826 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Tue, 23 Oct 2018 21:19:57 +0100 Subject: [PATCH 02/50] Fix CBV --- responder/api.py | 4 ++-- responder/routes.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/responder/api.py b/responder/api.py index 1b04783..a50bb73 100644 --- a/responder/api.py +++ b/responder/api.py @@ -255,11 +255,11 @@ class API: except Exception: self.default_response(req, resp, error=True) - if route.is_class_based or cont: + elif route.is_class_based or cont: try: view = route.endpoint(**params) except TypeError: - view = route.endpoint + view = route.endpoint() # Run on_request first. try: diff --git a/responder/routes.py b/responder/routes.py index a4c99cc..7e3eb81 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -73,8 +73,9 @@ class Route: def is_class_based(self): return hasattr(self.endpoint, "__class__") + @property def is_function(self): routed = hasattr(self.endpoint, "is_routed") code = hasattr(self.endpoint, "__code__") kwdefaults = hasattr(self.endpoint, "__kwdefaults__") - return all((routed, code, kwdefaults)) + return all((callable(self.endpoint), code, kwdefaults)) From 77b2e9ba7ace1a2a7b561afe4b375daf02400a85 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Tue, 23 Oct 2018 21:20:09 +0100 Subject: [PATCH 03/50] tests --- tests/test_responder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_responder.py b/tests/test_responder.py index 92de409..88a9b47 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -65,7 +65,7 @@ def test_class_based_view_registration(api): def test_class_based_view_parameters(api): @api.route("/{greeting}") class Greeting: - def on_request(req, resp, *, greeting): + def on_request(self, req, resp, *, greeting): resp.text = f"{greeting}, world!" assert api.session().get("http://;/Hello").ok From 880d29c5a937620207f4ded0ec75a5e2f18560ec Mon Sep 17 00:00:00 2001 From: Jayjeet Chakraborty Date: Wed, 24 Oct 2018 02:46:33 +0530 Subject: [PATCH 04/50] Fix Typo in api.py --- responder/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/responder/api.py b/responder/api.py index 1b04783..be99543 100644 --- a/responder/api.py +++ b/responder/api.py @@ -335,7 +335,7 @@ class API: pass self.routes[route] = Route(route, endpoint, websocket=websocket) - # TODO: A better datastructer or sort it once the app is loaded + # TODO: A better data structure or sort it once the app is loaded self.routes = dict( sorted(self.routes.items(), key=lambda item: item[1]._weight()) ) From e50828093d9436d882554ac770e1dfa2b891c448 Mon Sep 17 00:00:00 2001 From: Jayjeet Chakraborty Date: Wed, 24 Oct 2018 02:56:43 +0530 Subject: [PATCH 05/50] Clean print statement --- responder/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/responder/api.py b/responder/api.py index be99543..cbc5ccb 100644 --- a/responder/api.py +++ b/responder/api.py @@ -201,7 +201,6 @@ class API: return route def _prepare_cookies(self, resp): - # print(resp.cookies) if resp.cookies: header = " ".join([f"{k}={v}" for k, v in resp.cookies.items()]) resp.headers["Set-Cookie"] = header From 29830455edac41e2cdd192f31d9b2e65c89a3323 Mon Sep 17 00:00:00 2001 From: Taoufik Date: Wed, 24 Oct 2018 00:11:27 +0100 Subject: [PATCH 06/50] Typo --- docs/source/tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 2f94d20..1abc640 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -159,7 +159,7 @@ You can easily read a Request's session data, that can be trusted to have origin **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'] + api = responder.API(secret_key=os.environ['SECRET_KEY']) Using Requests Test Client -------------------------- From fe41d4c863888877904ba43ac5722efac8d3de52 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Wed, 24 Oct 2018 01:17:02 +0100 Subject: [PATCH 07/50] Fix static response --- responder/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/responder/api.py b/responder/api.py index 1b04783..9ce0be1 100644 --- a/responder/api.py +++ b/responder/api.py @@ -230,7 +230,6 @@ class API: # Create the response object. cont = False - if route: if not route.uses_websocket: resp = models.Response(req=req, formats=self.formats) @@ -292,7 +291,6 @@ class API: else: resp = models.Response(req=req, formats=self.formats) self.default_response(req, resp, notfound=True) - self.default_response(req, resp) self._prepare_session(resp) @@ -356,6 +354,7 @@ class API: def static_response(self, req, resp): index = (self.static_dir / "index.html").resolve() + resp.content = "" if os.path.exists(index): with open(index, "r") as f: resp.text = f.read() From f69b644a77670f53eec9f51828baefe6b03bba21 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Wed, 24 Oct 2018 12:28:11 +0800 Subject: [PATCH 08/50] Typo in tour.rst --- docs/source/tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 2f94d20..a757e1d 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -182,7 +182,7 @@ Here's an example of a test (written with pytest):: resp.text = hello r = api.requests.get(url=api.url_for(some_view)) - assert r.text = hello + assert r.text == hello HSTS (Redirect to HTTPS) ------------------------ From 2710d7098f901ee495b2530844b913cc18c8e35b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 07:02:44 -0400 Subject: [PATCH 09/50] v0.2.3 --- CHANGELOG.md | 3 +++ responder/__version__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4246a4f..4ab0631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v0.3.2 +- Overall improvements. + # v0.2.2 - Show traceback info when background tasks raise exceptions. diff --git a/responder/__version__.py b/responder/__version__.py index b5fdc75..d31c31e 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "0.2.2" +__version__ = "0.2.3" From 686ff72ae055d8322c31e5e892aefcdc76f00797 Mon Sep 17 00:00:00 2001 From: Jayjeet Chakraborty Date: Wed, 24 Oct 2018 16:37:02 +0530 Subject: [PATCH 10/50] Improve readability --- responder/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/responder/models.py b/responder/models.py index ebce351..15af75a 100644 --- a/responder/models.py +++ b/responder/models.py @@ -100,8 +100,8 @@ class Request: self._content = None headers = CaseInsensitiveDict() - for header, value in self._starlette.headers.items(): - headers[header] = value + for key, value in self._starlette.headers.items(): + headers[key] = value self._headers = headers From 878db851af9f599e6889b1cd63f4679628411745 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Wed, 24 Oct 2018 12:47:47 +0100 Subject: [PATCH 11/50] Return default endpoint if the route is not found --- responder/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/responder/api.py b/responder/api.py index 5f04dca..e0f6fe5 100644 --- a/responder/api.py +++ b/responder/api.py @@ -342,7 +342,7 @@ class API: if resp.status_code is None: resp.status_code = 200 - if self.default_endpoint: + if self.default_endpoint and notfound: self.default_endpoint(req, resp) else: if notfound: From 5164d4ec3237d7024d74b35fc73593aad2eb2e77 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:12:39 -0400 Subject: [PATCH 12/50] apistar --- Pipfile.lock | 132 ++++++++++++--------------------------------------- 1 file changed, 31 insertions(+), 101 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 1573d49..4ac6530 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -37,6 +37,12 @@ ], "version": "==1.0.0b3" }, + "apistar": { + "hashes": [ + "sha256:4338b24468b49526ceac4a8f84046056081ee747f373ca8d0647bd6b2344c895" + ], + "version": "==0.6.0" + }, "asgiref": { "hashes": [ "sha256:9b05dcd41a6a89ca8c6e7f7e4089c3f3e76b5af60aebb81ae6d455ad81989c97", @@ -215,9 +221,9 @@ }, "starlette": { "hashes": [ - "sha256:ce5c684fad4edb2967cd491518cd3c2724e420508202c2d48f519ea68dcec9d6" + "sha256:eac0f6cab6b48846a0c1af16615430ae0e7a95f669ee0841a7e2f242d51d8935" ], - "version": "==0.5.4" + "version": "==0.5.5" }, "urllib3": { "hashes": [ @@ -228,9 +234,9 @@ }, "uvicorn": { "hashes": [ - "sha256:7c4550c7e6f7c8727fa5ccd5200baf62c9e055895e058933ee88f5d0c246ca0c" + "sha256:e2b742fdaa0b52f4aac92fd2c078e7f1f17d11322bb3efb09d341d5c6998b4b5" ], - "version": "==0.3.14" + "version": "==0.3.16" }, "websockets": { "hashes": [ @@ -317,43 +323,6 @@ ], "version": "==2018.10.15" }, - "cffi": { - "hashes": [ - "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", - "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", - "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", - "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", - "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", - "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", - "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" - ], - "version": "==1.11.5" - }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", @@ -368,39 +337,6 @@ ], "version": "==7.0" }, - "cmarkgfm": { - "hashes": [ - "sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061", - "sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c", - "sha256:207b7673ff4e177374c572feeae0e4ef33be620ec9171c08fd22e2b796e03e3d", - "sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de", - "sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2", - "sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63", - "sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655", - "sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889", - "sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce", - "sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190", - "sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87", - "sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957", - "sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5", - "sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7", - "sha256:a3d17ee4ae739fe16f7501a52255c2e287ac817cfd88565b9859f70520afffea", - "sha256:ba5b5488719c0f2ced0aa1986376f7baff1a1653a8eb5fdfcf3f84c7ce46ef8d", - "sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f", - "sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1", - "sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034", - "sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65", - "sha256:cf5014eb214d814a83a7a47407272d5db10b719dbeaf4d3cfe5969309d0fcf4b", - "sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697", - "sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34", - "sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9", - "sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061", - "sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a", - "sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09", - "sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c" - ], - "version": "==0.4.2" - }, "colorama": { "hashes": [ "sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3", @@ -451,11 +387,11 @@ }, "flake8": { "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.6.0" }, "flask": { "hashes": [ @@ -557,23 +493,17 @@ }, "pycodestyle": { "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" ], - "version": "==2.3.1" - }, - "pycparser": { - "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" - ], - "version": "==2.19" + "version": "==2.4.0" }, "pyflakes": { "hashes": [ - "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", - "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" ], - "version": "==1.6.0" + "version": "==2.0.0" }, "pygments": { "hashes": [ @@ -591,11 +521,11 @@ }, "pytest": { "hashes": [ - "sha256:10e59f84267370ab20cec9305bafe7505ba4d6b93ecbf66a1cce86193ed511d5", - "sha256:8c827e7d4816dfe13e9329c8226aef8e6e75d65b939bc74fda894143b6d1df59" + "sha256:212be78a6fa5352c392738a49b18f74ae9aeec1040f47c81cadbfd8d1233c310", + "sha256:6f6c1efc8d0ccc21f8f6c34d8330baca883cf109b66b3df954b0a117e5528fb4" ], "index": "pypi", - "version": "==3.9.1" + "version": "==3.9.2" }, "pytest-cov": { "hashes": [ @@ -607,17 +537,17 @@ }, "pytz": { "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" + "sha256:642253af8eae734d1509fc6ac9c1aee5e5b69d76392660889979b9870610a46b", + "sha256:91e3ccf2c344ffaa6defba1ce7f38f97026943f675b7703f44789768e4cb0ece" ], - "version": "==2018.5" + "version": "==2018.6" }, "readme-renderer": { "hashes": [ - "sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2", - "sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d" + "sha256:219a02f5359b6631f5ab952f6906c6c105bdd8bc4bf19c1ec5ee8bd9ea2dc1eb", + "sha256:f8f122ad9fd6d138337531379575a01a0b6ca70aedca78f094cb833da38c8c0c" ], - "version": "==22.0" + "version": "==23.0" }, "requests": { "hashes": [ @@ -671,10 +601,10 @@ }, "tqdm": { "hashes": [ - "sha256:a0be569511161220ff709a5b60d0890d47921f746f1c737a11d965e1b29e7b2e", - "sha256:e293e6d7a7f41a529a27f8d6624ab11544ccbfe82a205af6fad102545099fc21" + "sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392", + "sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb" ], - "version": "==4.27.0" + "version": "==4.28.1" }, "twine": { "hashes": [ From beab89df09e4e553e4d6bfea12fe09bce4f3e6c7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:12:45 -0400 Subject: [PATCH 13/50] apistar --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e92701b..325561c 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ required = [ "docopt", "itsdangerous", "requests-toolbelt", + "apistar", ] From dee6ee3ceff539b308d57df85402ba4cee5b1944 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:13:15 -0400 Subject: [PATCH 14/50] whitenoise, apistar --- responder/api.py | 68 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/responder/api.py b/responder/api.py index 5f04dca..c4245fc 100644 --- a/responder/api.py +++ b/responder/api.py @@ -54,6 +54,7 @@ class API: auto_escape=True, secret_key="NOTASECRET", enable_hsts=False, + docs_route=None, ): self.secret_key = secret_key self.title = title @@ -66,12 +67,28 @@ class API: os.path.abspath(os.path.dirname(__file__) + "/templates") ) self.routes = {} + self.docs_theme = "swaggerui" + self.docs_route = docs_route self.schemas = {} self.session_cookie = "Responder-Session" self.hsts_enabled = enable_hsts - self.static_files = StaticFiles(directory=str(self.static_dir)) - self.apps = {self.static_route: self.static_files} + from whitenoise import WhiteNoise + + self.whitenoise = WhiteNoise( + application=self._default_wsgi_app, index_file=True + ) + self.whitenoise.add_files(str(self.static_dir)) + import apistar + + self.whitenoise.add_files( + ( + Path(apistar.__file__).parent / "themes" / self.docs_theme / "static" + ).resolve() + ) + + self.apps = {} + self.mount(self.static_route, self.whitenoise) self.formats = get_formats() @@ -86,6 +103,9 @@ class API: if self.openapi_version: self.add_route(openapi_route, self.schema_response) + if self.docs_route: + self.add_route(self.docs_route, self.docs_response) + self.default_endpoint = None self.app = self.dispatch self.add_middleware(GZipMiddleware) @@ -107,6 +127,10 @@ class API: self.session() ) #: A Requests session that is connected to the ASGI app. + @staticmethod + def _default_wsgi_app(*args, **kwargs): + pass + @property def _apispec(self): spec = APISpec( @@ -230,11 +254,12 @@ class API: # Create the response object. cont = False if route: - if not route.uses_websocket: - resp = models.Response(req=req, formats=self.formats) - else: + if route.uses_websocket: resp = WebSocket(**options) + else: + resp = models.Response(req=req, formats=self.formats) + params = route.incoming_matches(req.url.path) if route.is_graphql: @@ -352,6 +377,9 @@ class API: resp.status_code = status_codes.HTTP_500 resp.text = "Application error." + def docs_response(self, req, resp): + resp.text = self.docs + def static_response(self, req, resp): index = (self.static_dir / "index.html").resolve() resp.content = "" @@ -489,6 +517,36 @@ class API: """Given a static asset, return its URL path.""" return f"{self.static_route}/{str(asset)}" + @property + def docs(self): + import apistar + import yaml + + loader = jinja2.PrefixLoader( + { + self.docs_theme: jinja2.PackageLoader( + "apistar", os.path.join("themes", self.docs_theme, "templates") + ) + } + ) + env = jinja2.Environment(autoescape=True, loader=loader) + document = apistar.document.Document() + document.content = yaml.safe_load(self.openapi) + + template = env.get_template("/".join([self.docs_theme, "index.html"])) + + def static_url(asset): + return f"{self.static_route}/{asset}" + # return asset + + return template.render( + document=document, + langs=["javascript", "python"], + code_style=None, + static_url=static_url, + schema_url="/schema.yml", + ) + def template(self, name_, **values): """Renders the given `jinja2 `_ template, with provided values supplied. From 8eb89da9a06c80dd843758db5ffd7477aedafb76 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:14:24 -0400 Subject: [PATCH 15/50] improve imports --- responder/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/responder/api.py b/responder/api.py index c4245fc..6e1cd02 100644 --- a/responder/api.py +++ b/responder/api.py @@ -4,7 +4,8 @@ from functools import partial from pathlib import Path import uvicorn - +import apistar +import yaml import asyncio import jinja2 import itsdangerous @@ -519,8 +520,6 @@ class API: @property def docs(self): - import apistar - import yaml loader = jinja2.PrefixLoader( { From c74cc8586ff377b43f7cddbb98fd058bfb0debdf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:15:21 -0400 Subject: [PATCH 16/50] improve imports --- responder/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/responder/api.py b/responder/api.py index 6e1cd02..9e74da2 100644 --- a/responder/api.py +++ b/responder/api.py @@ -6,14 +6,11 @@ from pathlib import Path import uvicorn import apistar import yaml -import asyncio import jinja2 import itsdangerous from graphql_server import encode_execution_results, json_encode, default_format_error from starlette.websockets import WebSocket from starlette.debug import DebugMiddleware -from starlette.routing import Router -from starlette.staticfiles import StaticFiles from starlette.testclient import TestClient from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware @@ -21,6 +18,7 @@ from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec import yaml_utils from asgiref.wsgi import WsgiToAsgi +from whitenoise import WhiteNoise from . import models from . import status_codes @@ -74,7 +72,6 @@ class API: self.session_cookie = "Responder-Session" self.hsts_enabled = enable_hsts - from whitenoise import WhiteNoise self.whitenoise = WhiteNoise( application=self._default_wsgi_app, index_file=True From 0155d854e3e311a3d3b80511b55802ce1dd61217 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:16:22 -0400 Subject: [PATCH 17/50] v0.3.0 --- responder/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/responder/__version__.py b/responder/__version__.py index d31c31e..493f741 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "0.2.3" +__version__ = "0.3.0" From 26984965925bcc7d57767206566f6c1aa19eb9ee Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:16:55 -0400 Subject: [PATCH 18/50] documentation endpoint --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab0631..cff0d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -# v0.3.2 +# v0.3.0 +- Documentation endpoint. + +# v0.2.3 - Overall improvements. # v0.2.2 From 3a375a89753569ec3d5e4a27eba7ef90126225f9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:17:26 -0400 Subject: [PATCH 19/50] documentation --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 18c03fc..bd9d443 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -55,7 +55,7 @@ Features - 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. +- OpenAPI schema generation, with interactive documentation! - Single-page webapp support! Testimonials From e527f3cb1f1f26c4cbecea66955a184d38c231dc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:19:28 -0400 Subject: [PATCH 20/50] interactive documentation --- docs/source/tour.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 89a495f..bbe1e19 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -104,6 +104,15 @@ Responder comes with built-in support for OpenAPI / marshmallow:: tags: [] +Interactive Documentation +------------------------- + +Responder can automatically supply API Documentation for you. Using the example above:: + + api = responder.API(title="Web Service", version="1.0", openapi="3.0", docs_route="/docs") + +This will make ``/docs`` render interactive documentation for your API. + Mount a WSGI App (e.g. Flask) ----------------------------- From daf43009ba33211ce5cb1af257b869834017d6d2 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:21:08 -0400 Subject: [PATCH 21/50] changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cff0d49..db1ddce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v0.3.0 -- Documentation endpoint. +- Interactive Documentation endpoint. +- Minor improvements. # v0.2.3 - Overall improvements. From aa12b2429364c909a1ef1aeaa1b5032e3fcae6c9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:34:06 -0400 Subject: [PATCH 22/50] whitenoise --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 325561c..2ae2d2f 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ required = [ "apispec>=1.0.0b1", "marshmallow", "asgiref", + "whitenoise", "docopt", "itsdangerous", "requests-toolbelt", From 03812cc7eb469529b83e2bee1ab6dbebdbdacebf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 08:34:46 -0400 Subject: [PATCH 23/50] v0.3.1 --- CHANGELOG.md | 3 +++ Pipfile.lock | 7 +++++++ responder/__version__.py | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db1ddce..e4bf6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v0.3.1 +- Packaging fix. + # v0.3.0 - Interactive Documentation endpoint. - Minor improvements. diff --git a/Pipfile.lock b/Pipfile.lock index 4ac6530..2c5d527 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -263,6 +263,13 @@ "sha256:f1414e6cbcea8d22843e7eafdfdfae3dd1aba41d1945f6ca66e4806c07c4f454" ], "version": "==6.0" + }, + "whitenoise": { + "hashes": [ + "sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02", + "sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039" + ], + "version": "==4.1" } }, "develop": { diff --git a/responder/__version__.py b/responder/__version__.py index 493f741..260c070 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "0.3.0" +__version__ = "0.3.1" From 53be4d8954ab3350ac5c672d480b3109feda4af3 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 24 Oct 2018 10:32:28 -0400 Subject: [PATCH 24/50] index.rst --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index bd9d443..8fa0e72 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -39,7 +39,7 @@ spread some `Hacktoberfest `_ spirit ar That ``async`` declaration is optional. -This gets you a ASGI app, with a production static files server +This gets you a ASGI app, with a production static files server (WhiteNoise) pre-installed, jinja2 templating (without additional imports), and a production webserver based on uvloop, serving up requests with gzip compression automatically. From 0b39c89e60dd6f7276d28b003d1806605a1e8fa6 Mon Sep 17 00:00:00 2001 From: Taoufik Date: Wed, 24 Oct 2018 19:17:53 +0100 Subject: [PATCH 25/50] Quick fix --- docs/source/tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index bbe1e19..238fb9b 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -9,7 +9,7 @@ Class-based views (and setting some headers and stuff):: @api.route("/{greeting}") class GreetingResource: - def on_request(req, resp, *, greeting): # or on_get... + 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 From 774db6beada3a11fc3d27be4aa19e102c0da6a11 Mon Sep 17 00:00:00 2001 From: "T.Kameyama" <41364327+tkamenoko@users.noreply.github.com> Date: Thu, 25 Oct 2018 18:52:00 +0900 Subject: [PATCH 26/50] fix: license mismatch in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2ae2d2f..3216f58 100644 --- a/setup.py +++ b/setup.py @@ -145,7 +145,7 @@ setup( include_package_data=True, license="Apache 2.0", classifiers=[ - "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", From 475cd1a106fe1c743e513567144469707377a1de Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 06:47:58 -0400 Subject: [PATCH 27/50] removed --- static/index.html | 1 - templates/test.html | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 static/index.html delete mode 100644 templates/test.html diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 3e9ffe0..0000000 --- a/static/index.html +++ /dev/null @@ -1 +0,0 @@ -lorem diff --git a/templates/test.html b/templates/test.html deleted file mode 100644 index ae024f8..0000000 --- a/templates/test.html +++ /dev/null @@ -1,3 +0,0 @@ -this is a test - -{{ api.static_url('test') }} From 740a48566f37e2a60b867993a880bc1731ffb26c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 06:58:57 -0400 Subject: [PATCH 28/50] important stuff --- tests/test_responder.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_responder.py b/tests/test_responder.py index 88a9b47..610482b 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -355,6 +355,34 @@ def test_schema_generation(): assert dump["openapi"] == "3.0" +def test_documentation(): + import responder + from marshmallow import Schema, fields + + api = responder.API(title="Web Service", openapi="3.0", docs_route="/docs") + + @api.schema("Pet") + class PetSchema(Schema): + name = fields.Str() + + @api.route("/") + def route(req, resp): + """A cute furry animal endpoint. + --- + get: + description: Get a random pet + responses: + 200: + description: A pet to be returned + schema: + $ref = "#/components/schemas/Pet" + """ + resp.media = PetSchema().dump({"name": "little orange"}) + + r = api.requests.get("/docs") + assert r.content + + def test_mount_wsgi_app(api, flask): @api.route("/") def hello(req, resp): From bdf151e0a7fe5b1015f6287d2ee1ef1a2fde6844 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:08:45 -0400 Subject: [PATCH 29/50] test responder docs --- tests/test_responder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_responder.py b/tests/test_responder.py index 610482b..02da415 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -380,7 +380,7 @@ def test_documentation(): resp.media = PetSchema().dump({"name": "little orange"}) r = api.requests.get("/docs") - assert r.content + assert "html" in r.text def test_mount_wsgi_app(api, flask): From 38426c91432fbd7454a3df6271880acd3bb19d88 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:10:42 -0400 Subject: [PATCH 30/50] for tests --- static/index.html | 1 + templates/test.html | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 static/index.html create mode 100644 templates/test.html diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..3e9ffe0 --- /dev/null +++ b/static/index.html @@ -0,0 +1 @@ +lorem diff --git a/templates/test.html b/templates/test.html new file mode 100644 index 0000000..ae024f8 --- /dev/null +++ b/templates/test.html @@ -0,0 +1,3 @@ +this is a test + +{{ api.static_url('test') }} From 2d4f1bfd0219b1f52d4140839d370983b65a0956 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:10:51 -0400 Subject: [PATCH 31/50] test static --- static/test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 static/test.txt diff --git a/static/test.txt b/static/test.txt new file mode 100644 index 0000000..90bfcb5 --- /dev/null +++ b/static/test.txt @@ -0,0 +1 @@ +this is a test From 4ab523bf017c1c9e105c85165e49a6ff268d9981 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:25:35 -0400 Subject: [PATCH 32/50] fix static --- responder/api.py | 9 ++++----- static/index.html | 1 - static/test.txt | 1 - templates/test.html | 3 --- 4 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 static/index.html delete mode 100644 static/test.txt delete mode 100644 templates/test.html diff --git a/responder/api.py b/responder/api.py index 5ecb592..4a81f46 100644 --- a/responder/api.py +++ b/responder/api.py @@ -73,11 +73,14 @@ class API: self.hsts_enabled = enable_hsts + # Make the static/templates directory if they don't exist. + for _dir in (self.static_dir, self.templates_dir): + os.makedirs(_dir, exist_ok=True) + self.whitenoise = WhiteNoise( application=self._default_wsgi_app, index_file=True ) self.whitenoise.add_files(str(self.static_dir)) - import apistar self.whitenoise.add_files( ( @@ -90,10 +93,6 @@ class API: self.formats = get_formats() - # Make the static/templates directory if they don't exist. - for _dir in (self.static_dir, self.templates_dir): - os.makedirs(_dir, exist_ok=True) - # Cached requests session. self._session = None self.background = BackgroundQueue() diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 3e9ffe0..0000000 --- a/static/index.html +++ /dev/null @@ -1 +0,0 @@ -lorem diff --git a/static/test.txt b/static/test.txt deleted file mode 100644 index 90bfcb5..0000000 --- a/static/test.txt +++ /dev/null @@ -1 +0,0 @@ -this is a test diff --git a/templates/test.html b/templates/test.html deleted file mode 100644 index ae024f8..0000000 --- a/templates/test.html +++ /dev/null @@ -1,3 +0,0 @@ -this is a test - -{{ api.static_url('test') }} From f7ee720281dd27ec71a57f39d4ff848f6d1f3381 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:26:05 -0400 Subject: [PATCH 33/50] subtle improvements --- CHANGELOG.md | 3 +++ responder/__version__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bf6ad..8bf4292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v0.3.2 +- Subtle improvements. + # v0.3.1 - Packaging fix. diff --git a/responder/__version__.py b/responder/__version__.py index 260c070..f9aa3e1 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "0.3.1" +__version__ = "0.3.2" From 94b3625718cb6cba525b53c6640d0942140bb4cd Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:29:05 -0400 Subject: [PATCH 34/50] ideas --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index e1525b4..d7d4a35 100644 --- a/README.md +++ b/README.md @@ -154,15 +154,7 @@ The primary concept here is to bring the niceties that are brought forth from bo - A production static file server is built-in. - Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unnecessary in production. - GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically. - -## Future Ideas - -- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too. -- If frontend websites are supported, provide an official way to run webpack. - -# The Goal - -The primary goal here is to learn, not to get adoption. Though, who knows how these things will pan out. +- Provide an official way to run webpack. ---------- From b98354e63a17bcc7d60ae44d5fe4a63a72eba4fc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 07:31:34 -0400 Subject: [PATCH 35/50] simplify readme --- README.md | 76 +------------------------------------------------------ 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/README.md b/README.md index d7d4a35..a9b3c75 100644 --- a/README.md +++ b/README.md @@ -41,81 +41,7 @@ This gets you a ASGI app, with a production static files server pre-installed, j ## More Examples -Class-based views (and setting some headers and stuff): - -```python -@api.route("/{greeting}") -class GreetingResource: - def on_request(req, resp, *, greeting): # or on_get... - resp.text = f"{greeting}, world!" - resp.headers.update({'X-Life': '42'}) - resp.status_code = api.status_codes.HTTP_416 -``` - -Render a template, with arguments: - -```python -@api.route("/{greeting}") -def greet_world(req, resp, *, greeting): - resp.content = api.template("index.html", greeting=greeting) -``` - -The `api` instance is available as an object during template rendering. - -Here, you can spawn off a background thread to run any function, out-of-request: - -```python -@api.route("/") -def hello(req, resp): - - @api.background.task - def sleep(s=10): - time.sleep(s) - print("slept!") - - sleep() - resp.content = "processing" -``` - -And even serve a GraphQL API: - -```python -import graphene - -class Query(graphene.ObjectType): - hello = graphene.String(name=graphene.String(default_value="stranger")) - - def resolve_hello(self, info, name): - return f"Hello {name}" - -api.add_route("/graph", graphene.Schema(query=Query)) -``` - -We can then send a query to our service: - -```pycon ->>> requests = api.session() ->>> r = requests.get("http://;/graph", params={"query": "{ hello }"}) ->>> r.json() -{'data': {'hello': 'Hello stranger'}} -``` - -Or, request YAML back: - -```pycon ->>> r = requests.get("http://;/graph", params={"query": "{ hello(name:\"john\") }"}, headers={"Accept": "application/x-yaml"}) ->>> print(r.text) -data: {hello: Hello john} - -``` - -Want HSTS? - -``` -api = responder.API(enable_hsts=True) -``` - -Boom. +See [the documentation's feature tour](http://python-responder.org/en/latest/tour.html) for more details on features available in Responder. # Installing Responder From b66112d0ca4adecf6a60c61d783818ca4fbabe0d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 08:20:06 -0400 Subject: [PATCH 36/50] improvements --- responder/api.py | 5 +++-- responder/statics.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/responder/api.py b/responder/api.py index 4a81f46..f4244b6 100644 --- a/responder/api.py +++ b/responder/api.py @@ -26,6 +26,7 @@ from .routes import Route from .formats import get_formats from .background import BackgroundQueue from .templates import GRAPHIQL +from .statics import DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE # TODO: consider moving status codes here class API: @@ -66,10 +67,10 @@ class API: os.path.abspath(os.path.dirname(__file__) + "/templates") ) self.routes = {} - self.docs_theme = "swaggerui" + self.docs_theme = DEFAULT_API_THEME self.docs_route = docs_route self.schemas = {} - self.session_cookie = "Responder-Session" + self.session_cookie = DEFAULT_SESSION_COOKIE self.hsts_enabled = enable_hsts diff --git a/responder/statics.py b/responder/statics.py index 662d001..8d52141 100644 --- a/responder/statics.py +++ b/responder/statics.py @@ -1 +1,3 @@ DEFAULT_ENCODING = "utf-8" +DEFAULT_API_THEME = "swaggerui" +DEFAULT_SESSION_COOKIE = "Responder-Session" From 62f932dcfcd6cf336a58ddc26541c11a5e706a3e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 08:22:14 -0400 Subject: [PATCH 37/50] default secret key --- responder/api.py | 4 ++-- responder/statics.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/responder/api.py b/responder/api.py index f4244b6..bfb964d 100644 --- a/responder/api.py +++ b/responder/api.py @@ -26,7 +26,7 @@ from .routes import Route from .formats import get_formats from .background import BackgroundQueue from .templates import GRAPHIQL -from .statics import DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE +from .statics import DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY # TODO: consider moving status codes here class API: @@ -52,7 +52,7 @@ class API: static_route="/static", templates_dir="templates", auto_escape=True, - secret_key="NOTASECRET", + secret_key=DEFAULT_SECRET_KEY, enable_hsts=False, docs_route=None, ): diff --git a/responder/statics.py b/responder/statics.py index 8d52141..b00a530 100644 --- a/responder/statics.py +++ b/responder/statics.py @@ -1,3 +1,4 @@ DEFAULT_ENCODING = "utf-8" DEFAULT_API_THEME = "swaggerui" DEFAULT_SESSION_COOKIE = "Responder-Session" +DEFAULT_SECRET_KEY = "NOTASECRET" From f5ff4c9725fa936e47da5990aa9fa31dc781be59 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 08:23:22 -0400 Subject: [PATCH 38/50] clean up --- responder/routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/responder/routes.py b/responder/routes.py index a88d878..b812e9c 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -15,7 +15,7 @@ def memoize(f): class Route: _param_pattern = re.compile(r"{([^{}]*)}") - def __init__(self, route, endpoint, websocket=False): + def __init__(self, route, endpoint, *, websocket=False): self.route = route self.endpoint = endpoint self.uses_websocket = websocket @@ -72,10 +72,10 @@ class Route: @property def is_class_based(self): return hasattr(self.endpoint, "__class__") - + @property def is_function(self): - # TODO: Should we remove is_routed ? + # TODO: Should we remove is_routed ? routed = hasattr(self.endpoint, "is_routed") code = hasattr(self.endpoint, "__code__") kwdefaults = hasattr(self.endpoint, "__kwdefaults__") From d33289503a5f2c3436f65f584143ab6b91ef9de3 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 08:24:45 -0400 Subject: [PATCH 39/50] remove is_routed --- responder/api.py | 7 ------- responder/routes.py | 2 -- 2 files changed, 9 deletions(-) diff --git a/responder/api.py b/responder/api.py index bfb964d..517ec6b 100644 --- a/responder/api.py +++ b/responder/api.py @@ -348,13 +348,6 @@ class API: if default: self.default_endpoint = endpoint - # Can we remove it ? - try: - if callable(endpoint): - endpoint.is_routed = True - except AttributeError: - pass - self.routes[route] = Route(route, endpoint, websocket=websocket) # TODO: A better data structure or sort it once the app is loaded self.routes = dict( diff --git a/responder/routes.py b/responder/routes.py index b812e9c..2011555 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -75,8 +75,6 @@ class Route: @property def is_function(self): - # TODO: Should we remove is_routed ? - routed = hasattr(self.endpoint, "is_routed") code = hasattr(self.endpoint, "__code__") kwdefaults = hasattr(self.endpoint, "__kwdefaults__") return all((callable(self.endpoint), code, kwdefaults)) From 237a2ed42694e28208317daa7a62a70edb181519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Lu=C4=8Danin?= Date: Thu, 25 Oct 2018 16:43:19 +0200 Subject: [PATCH 40/50] document how to customise the address --- docs/source/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index fc12ab7..83768a7 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -34,7 +34,7 @@ Next, we can run our web service easily, with ``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. +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 From 53216813e5f9845bcf4315d92d8767588da9ee21 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Thu, 25 Oct 2018 20:12:21 +0100 Subject: [PATCH 41/50] Add CORSMiddleware --- responder/api.py | 13 +++++++++++-- responder/statics.py | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/responder/api.py b/responder/api.py index 517ec6b..df40638 100644 --- a/responder/api.py +++ b/responder/api.py @@ -14,6 +14,7 @@ from starlette.debug import DebugMiddleware from starlette.testclient import TestClient from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware +from starlette.middleware.cors import CORSMiddleware from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec import yaml_utils @@ -26,7 +27,9 @@ from .routes import Route from .formats import get_formats from .background import BackgroundQueue from .templates import GRAPHIQL -from .statics import DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY +from .statics import ( + DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY, CORS_PARAMS +) # TODO: consider moving status codes here class API: @@ -55,6 +58,7 @@ class API: secret_key=DEFAULT_SECRET_KEY, enable_hsts=False, docs_route=None, + cors=False ): self.secret_key = secret_key self.title = title @@ -73,7 +77,7 @@ class API: self.session_cookie = DEFAULT_SESSION_COOKIE self.hsts_enabled = enable_hsts - + self.cors = cors # Make the static/templates directory if they don't exist. for _dir in (self.static_dir, self.templates_dir): os.makedirs(_dir, exist_ok=True) @@ -109,9 +113,14 @@ class API: self.add_middleware(GZipMiddleware) if debug: self.add_middleware(DebugMiddleware) + if self.hsts_enabled: self.add_middleware(HTTPSRedirectMiddleware) + if self.cors: + # TODO: DOCS + self.add_middleware(CORSMiddleware, **CORS_PARAMS) + # Jinja enviroment self.jinja_env = jinja2.Environment( loader=jinja2.FileSystemLoader( diff --git a/responder/statics.py b/responder/statics.py index b00a530..a10cf34 100644 --- a/responder/statics.py +++ b/responder/statics.py @@ -2,3 +2,13 @@ DEFAULT_ENCODING = "utf-8" DEFAULT_API_THEME = "swaggerui" DEFAULT_SESSION_COOKIE = "Responder-Session" DEFAULT_SECRET_KEY = "NOTASECRET" + +CORS_PARAMS = { + "allow_origins": (), + "allow_methods": ("GET",), + "allow_headers": (), + "allow_credentials": False, + "allow_origin_regex": None, + "expose_headers": (), + "max_age": 600, +} From a5b1652d1566ae8fa962828244a360e701c4f762 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Thu, 25 Oct 2018 20:43:50 +0100 Subject: [PATCH 42/50] Custom CORS params via cors_params --- responder/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/responder/api.py b/responder/api.py index df40638..26a461b 100644 --- a/responder/api.py +++ b/responder/api.py @@ -28,7 +28,7 @@ from .formats import get_formats from .background import BackgroundQueue from .templates import GRAPHIQL from .statics import ( - DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY, CORS_PARAMS + DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY, DEFAULT_CORS_PARAMS ) # TODO: consider moving status codes here @@ -58,7 +58,8 @@ class API: secret_key=DEFAULT_SECRET_KEY, enable_hsts=False, docs_route=None, - cors=False + cors=False, + cors_params=DEFAULT_CORS_PARAMS ): self.secret_key = secret_key self.title = title @@ -78,6 +79,7 @@ class API: self.hsts_enabled = enable_hsts self.cors = cors + self.cors_params = cors_params # Make the static/templates directory if they don't exist. for _dir in (self.static_dir, self.templates_dir): os.makedirs(_dir, exist_ok=True) @@ -118,8 +120,7 @@ class API: self.add_middleware(HTTPSRedirectMiddleware) if self.cors: - # TODO: DOCS - self.add_middleware(CORSMiddleware, **CORS_PARAMS) + self.add_middleware(CORSMiddleware, **self.cors_params) # Jinja enviroment self.jinja_env = jinja2.Environment( From 45e4d80c4d183f928e3b5cbaeff37dce1e1ae28e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 15:44:01 -0400 Subject: [PATCH 43/50] --pre --- docs/source/deployment.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst index ab6a9c2..e5fbde4 100644 --- a/docs/source/deployment.rst +++ b/docs/source/deployment.rst @@ -30,7 +30,7 @@ The basics:: Install Responder:: - $ pipenv install responder + $ pipenv install responder --pre ... Write out an ``api.py``:: From 64ac6bcd1f538f5c79a1f0fac6291b89230db569 Mon Sep 17 00:00:00 2001 From: taoufik07 Date: Thu, 25 Oct 2018 20:44:04 +0100 Subject: [PATCH 44/50] Add CORS docs --- docs/source/tour.rst | 23 +++++++++++++++++++++++ responder/statics.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 238fb9b..157cacd 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -204,3 +204,26 @@ Want HSTS (to redirect all traffic to HTTPS)? Boom. + +CORS +---- + +Want [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) ? + +:: + + api = responder.API(cors=True) + + +The default parameters used by the **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 your custom parameters, you need to pass the `cors_params` argument, a dictionnary 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`. + diff --git a/responder/statics.py b/responder/statics.py index a10cf34..ff5c461 100644 --- a/responder/statics.py +++ b/responder/statics.py @@ -3,7 +3,7 @@ DEFAULT_API_THEME = "swaggerui" DEFAULT_SESSION_COOKIE = "Responder-Session" DEFAULT_SECRET_KEY = "NOTASECRET" -CORS_PARAMS = { +DEFAULT_CORS_PARAMS = { "allow_origins": (), "allow_methods": ("GET",), "allow_headers": (), From 5fccedd4c4703c293c1b528411d9e0188c86ccac Mon Sep 17 00:00:00 2001 From: Taoufik Date: Thu, 25 Oct 2018 20:55:14 +0100 Subject: [PATCH 45/50] Improvement --- docs/source/tour.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 157cacd..660db2d 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -219,11 +219,11 @@ The default parameters used by the **Responder** are restrictive by default, so In order to set your custom parameters, you need to pass the `cors_params` argument, a dictionnary 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`. +* ``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`. From 55b55e62da2ef26d548f546419234bf250a5736f Mon Sep 17 00:00:00 2001 From: Taoufik Date: Thu, 25 Oct 2018 20:58:01 +0100 Subject: [PATCH 46/50] Improvements --- docs/source/tour.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 660db2d..63e3a5c 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -215,15 +215,15 @@ Want [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) ? api = responder.API(cors=True) -The default parameters used by the **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. +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 your custom parameters, you need to pass the `cors_params` argument, a dictionnary containing the following entries : +In order to set your custom parameters, you need to pass the ``cors_params`` argument, a dictionnary 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`. +* ``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``. From 00cfde169b24d5c58e8ee65bcaabf1870e02493b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 17:41:58 -0400 Subject: [PATCH 47/50] remove future ideas --- docs/source/index.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 8fa0e72..9a5390b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -144,14 +144,6 @@ Ideas - GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically. -Future Ideas ------------- - -- Cookie-based sessions are currently an afterthought, as this is an API framework, but websites are APIs too. -- If frontend websites are supported, provide an official way to run webpack. - - - Indices and tables ================== From 1e6c9d935a7cfbaf8220d41ab7d9bb120e3f7c22 Mon Sep 17 00:00:00 2001 From: Taoufik Date: Thu, 25 Oct 2018 22:45:29 +0100 Subject: [PATCH 48/50] Fix link formatting --- docs/source/tour.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 63e3a5c..1e2632b 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -208,7 +208,7 @@ Boom. CORS ---- -Want [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) ? +Want `CORS `_ ? :: @@ -217,7 +217,7 @@ Want [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) ? 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 your custom parameters, you need to pass the ``cors_params`` argument, a dictionnary containing the following entries : +In order to set custom parameters, you need to pass the ``cors_params`` argument, a dictionnary 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'``. From 4f57e8a5d169d0d78f0c585bbeff10fe9765462d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steinn=20Eldj=C3=A1rn=20Sigur=C3=B0arson?= Date: Thu, 25 Oct 2018 22:04:00 +0000 Subject: [PATCH 49/50] Improve exception handling Re-raise exceptions caught in _dispatch_request. Added starlette ExceptionMiddleware to be able to test this gracefully. --- responder/api.py | 7 ++++++- tests/test_responder.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/responder/api.py b/responder/api.py index 26a461b..65b6613 100644 --- a/responder/api.py +++ b/responder/api.py @@ -15,6 +15,7 @@ from starlette.testclient import TestClient from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware from starlette.middleware.cors import CORSMiddleware +from starlette.exceptions import ExceptionMiddleware from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec import yaml_utils @@ -31,6 +32,7 @@ from .statics import ( DEFAULT_API_THEME, DEFAULT_SESSION_COOKIE, DEFAULT_SECRET_KEY, DEFAULT_CORS_PARAMS ) + # TODO: consider moving status codes here class API: """The primary web-service class. @@ -121,6 +123,7 @@ class API: if self.cors: self.add_middleware(CORSMiddleware, **self.cors_params) + self.add_middleware(ExceptionMiddleware, debug=debug) # Jinja enviroment self.jinja_env = jinja2.Environment( @@ -285,6 +288,7 @@ class API: cont = True except Exception: self.default_response(req, resp, error=True) + raise elif route.is_class_based or cont: try: @@ -301,8 +305,9 @@ class API: # If it's async, await it. if hasattr(r, "send"): await r - except Exception as e: + except Exception: self.default_response(req, resp, error=True) + raise # Then on_get. method = req.method diff --git a/tests/test_responder.py b/tests/test_responder.py index 02da415..1524dd9 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -3,6 +3,8 @@ import yaml import responder import io +from starlette.responses import PlainTextResponse + def test_api_basic_route(api): @api.route("/") @@ -460,12 +462,18 @@ def test_file_uploads(api): def test_500(api): + def catcher(request, exc): + return PlainTextResponse("Suppressed error", 500) + + api.app.add_exception_handler(ValueError, catcher) + @api.route("/") def view(req, resp): raise ValueError r = api.requests.get(api.url_for(view)) assert not r.ok + assert r.content == b'Suppressed error' def test_404(api): From 7b79472d6516187e4db18f2fd710bfb911aad374 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 25 Oct 2018 18:15:22 -0400 Subject: [PATCH 50/50] v0.3.3 --- CHANGELOG.md | 4 ++++ responder/__version__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf4292..f8abe67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v0.3.3 +- Improved exceptions. +- CORS support. + # v0.3.2 - Subtle improvements. diff --git a/responder/__version__.py b/responder/__version__.py index f9aa3e1..e19434e 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "0.3.2" +__version__ = "0.3.3"