mount other wsgi apps

This commit is contained in:
2018-10-10 07:14:29 -04:00
parent 39f9cbfdbb
commit 90eecdaa84
6 changed files with 96 additions and 48 deletions
+1
View File
@@ -11,6 +11,7 @@ pytest = "*"
"flake8" = "*" "flake8" = "*"
black = "*" black = "*"
twine = "*" twine = "*"
flask = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"
Generated
+38 -14
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "13d6d0b2229e162611f4355a6887db0719a0c47471facb391b524635567d6884" "sha256": "5843d79d019341544a1c9456b537125203079f127721132c8111421095660524"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -117,7 +117,6 @@
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" "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" "version": "==1.23"
}, },
"waitress": { "waitress": {
@@ -155,7 +154,6 @@
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" "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" "version": "==1.2.1"
}, },
"attrs": { "attrs": {
@@ -175,10 +173,10 @@
}, },
"bleach": { "bleach": {
"hashes": [ "hashes": [
"sha256:9c471c0dd9c820f6bf4ee5ca3e348ceccefbc1475d9a40c397ed5d04e0b42c54", "sha256:c39d25d9ada62009853b0281efdc35a792db8cdee89465433e6d0aaaf5defc3f",
"sha256:b407b2612b37e6cdc6704f84cec18c1f140b78e6c625652a844e89d6b9855f6b" "sha256:f680cc08e2eea821f3173b875f68763960006f1e764c92b5d2b8354af3a47468"
], ],
"version": "==3.0.0" "version": "==3.0.1"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@@ -236,7 +234,6 @@
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" "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" "version": "==7.0"
}, },
"cmarkgfm": { "cmarkgfm": {
@@ -270,7 +267,6 @@
"sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09", "sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09",
"sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c" "sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c"
], ],
"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.4.2" "version": "==0.4.2"
}, },
"colorama": { "colorama": {
@@ -297,6 +293,14 @@
"index": "pypi", "index": "pypi",
"version": "==3.5.0" "version": "==3.5.0"
}, },
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"index": "pypi",
"version": "==1.0.2"
},
"future": { "future": {
"hashes": [ "hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb" "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
@@ -310,6 +314,25 @@
], ],
"version": "==2.7" "version": "==2.7"
}, },
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
],
"version": "==0.24"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@@ -337,7 +360,6 @@
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" "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" "version": "==0.7.1"
}, },
"py": { "py": {
@@ -345,7 +367,6 @@
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" "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" "version": "==1.6.0"
}, },
"pycodestyle": { "pycodestyle": {
@@ -359,7 +380,6 @@
"hashes": [ "hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
], ],
"markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*'",
"version": "==2.19" "version": "==2.19"
}, },
"pyflakes": { "pyflakes": {
@@ -389,7 +409,6 @@
"sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2", "sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2",
"sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d" "sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d"
], ],
"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": "==22.0" "version": "==22.0"
}, },
"requests": { "requests": {
@@ -425,7 +444,6 @@
"sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889", "sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889",
"sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9" "sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9"
], ],
"markers": "python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==4.26.0" "version": "==4.26.0"
}, },
"twine": { "twine": {
@@ -441,7 +459,6 @@
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" "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" "version": "==1.23"
}, },
"webencodings": { "webencodings": {
@@ -450,6 +467,13 @@
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
], ],
"version": "==0.5.1" "version": "==0.5.1"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"version": "==0.14.1"
} }
} }
} }
+1
View File
@@ -19,6 +19,7 @@ The primary concept here is to bring the nicities that are brought forth from bo
## New Ideas ## New Ideas
- **A built in testing client that uses the actual Requests you know and love**. - **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses (still working on that). - Automatic gzipped-responses (still working on that).
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests. - 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) - WhiteNoise is built-in, for serving static files (this has yet to be built out, there's no templating or `static_url` yet)
+14 -3
View File
@@ -1,8 +1,19 @@
import responder import responder
import graphene import graphene
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World from flask!"
api = responder.API() api = responder.API()
# api.mount('/subapp', other_wsgi_app) api.mount("/hello", app)
@api.route("/") @api.route("/")
@@ -44,7 +55,7 @@ print(
print( print(
api.session() api.session()
.get( .get(
"http://app/graph", "http://app/hello/",
data="{ hello }", data="{ hello }",
headers={"Accept": "application/x-yaml"}, headers={"Accept": "application/x-yaml"},
# data="hello", # data="hello",
@@ -53,4 +64,4 @@ print(
) )
# {hello: Hello stranger} # {hello: Hello stranger}
api.run(port=5000, expose_tracebacks=True) # api.run(port=5000, expose_tracebacks=True)
+37 -29
View File
@@ -6,18 +6,28 @@ import waitress
from whitenoise import WhiteNoise from whitenoise import WhiteNoise
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
from requests import Session as RequestsSession from requests import Session as RequestsSession
from werkzeug.wsgi import DispatcherMiddleware
from . import models from . import models
from .status import HTTP_404 from .status import HTTP_404
class BaseAPI: class API:
__slots__ = ["routes"] def __init__(self, static="static"):
self.static_dir = Path(os.path.abspath(static))
def __init__(self):
self.routes = {} self.routes = {}
self.apps = {"/": self._wsgi_app}
def _wsgi_app(self, environ, start_response): # Make the static directory if it doesn't exist.
os.makedirs(self.static_dir, exist_ok=True)
# Mount the whitenoise application.
self.whitenoise = WhiteNoise(self.__wsgi_app, root=str(self.static_dir))
# Cached requests session.
self._session = None
def __wsgi_app(self, environ, start_response):
# def wsgi_app(self, request): # def wsgi_app(self, request):
"""The actual WSGI application. This is not implemented in """The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without :meth:`__call__` so that middlewares can be applied without
@@ -44,14 +54,20 @@ class BaseAPI:
start the response. start the response.
""" """
req = models.Request.from_environ(environ) req = models.Request.from_environ(environ, start_response)
# if not req.dispatched:
resp = self._dispatch_request(req) resp = self._dispatch_request(req)
return resp(environ, start_response) return resp(environ, start_response)
def wsgi_app(self, environ, start_response): def _wsgi_app(self, environ, start_response):
return self.whitenoise(environ, start_response) return self.whitenoise(environ, start_response)
def wsgi_app(self, environ, start_response):
apps = self.apps.copy()
main = apps.pop("/")
return DispatcherMiddleware(main, apps)(environ, start_response)
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the """The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be WSGI application. This calls :meth:`wsgi_app` which can be
@@ -74,10 +90,18 @@ class BaseAPI:
except TypeError: except TypeError:
try: try:
view = self.routes[route]() view = self.routes[route]()
# GraphQL Schema.
except TypeError: except TypeError:
view = self.routes[route] view = self.routes[route]
self.graphql_response(req, resp, schema=view) try:
# GraphQL Schema.
assert hasattr(view, "execute")
self.graphql_response(req, resp, schema=view)
except AssertionError:
# WSGI App.
req.dispatched = True
return view(
environ=req._environ, start_response=req._start_response
)
# Run on_request first. # Run on_request first.
try: try:
@@ -97,25 +121,6 @@ class BaseAPI:
return resp return resp
@property
def static_dir(self):
return Path(".")
class API(BaseAPI):
__slots__ = ("routes", "_session", "whitenoise", "static_dir")
def __init__(self, static="static"):
super().__init__()
self._session = None
self.static_dir = Path(os.path.abspath(static))
# Make the static directory if it doesn't exist.
os.makedirs(self.static_dir, exist_ok=True)
# Mount the whitenoise application.
self.whitenoise = WhiteNoise(self._wsgi_app, root=str(self.static_dir))
def add_route(self, route, view, *, check_existing=True, graphiql=False): def add_route(self, route, view, *, check_existing=True, graphiql=False):
if check_existing: if check_existing:
assert route not in self.routes assert route not in self.routes
@@ -158,6 +163,9 @@ class API(BaseAPI):
return decorator return decorator
def mount(self, route, wsgi_app):
self.apps.update({route: wsgi_app})
def session(self, base_url="http://app"): def session(self, base_url="http://app"):
if self._session is None: if self._session is None:
session = RequestsSession() session = RequestsSession()
+5 -2
View File
@@ -8,7 +8,7 @@ from requests.models import Request as RequestsRequest
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from werkzeug.wrappers import Request as WerkzeugRequest from werkzeug.wrappers import Request as WerkzeugRequest
from werkzeug.wrappers import BaseResponse as WerkzeugResponse from werkzeug.wrappers import BaseResponse as WerkzeugResponse
from werkzeug.wsgi import DispatcherMiddleware
from urllib.parse import parse_qs from urllib.parse import parse_qs
@@ -25,7 +25,7 @@ class Request:
self._wz = None self._wz = None
@classmethod @classmethod
def from_environ(kls, environ): def from_environ(kls, environ, start_response=None):
self = kls() self = kls()
self._wz = WerkzeugRequest(environ) self._wz = WerkzeugRequest(environ)
self.headers = CaseInsensitiveDict(self._wz.headers.to_list()) self.headers = CaseInsensitiveDict(self._wz.headers.to_list())
@@ -39,6 +39,9 @@ class Request:
self.content = self._wz.get_data(cache=True, as_text=False) self.content = self._wz.get_data(cache=True, as_text=False)
self.text = self._wz.get_data(cache=True, as_text=True) self.text = self._wz.get_data(cache=True, as_text=True)
self.data = self._wz.get_data(cache=True, as_text=True, parse_form_data=True) self.data = self._wz.get_data(cache=True, as_text=True, parse_form_data=True)
self.dispatched = False
self._start_response = start_response
self._environ = environ
return self return self