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" = "*"
black = "*"
twine = "*"
flask = "*"
[requires]
python_version = "3.7"
Generated
+38 -14
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "13d6d0b2229e162611f4355a6887db0719a0c47471facb391b524635567d6884"
"sha256": "5843d79d019341544a1c9456b537125203079f127721132c8111421095660524"
},
"pipfile-spec": 6,
"requires": {
@@ -117,7 +117,6 @@
"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": {
@@ -155,7 +154,6 @@
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==1.2.1"
},
"attrs": {
@@ -175,10 +173,10 @@
},
"bleach": {
"hashes": [
"sha256:9c471c0dd9c820f6bf4ee5ca3e348ceccefbc1475d9a40c397ed5d04e0b42c54",
"sha256:b407b2612b37e6cdc6704f84cec18c1f140b78e6c625652a844e89d6b9855f6b"
"sha256:c39d25d9ada62009853b0281efdc35a792db8cdee89465433e6d0aaaf5defc3f",
"sha256:f680cc08e2eea821f3173b875f68763960006f1e764c92b5d2b8354af3a47468"
],
"version": "==3.0.0"
"version": "==3.0.1"
},
"certifi": {
"hashes": [
@@ -236,7 +234,6 @@
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7'",
"version": "==7.0"
},
"cmarkgfm": {
@@ -270,7 +267,6 @@
"sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09",
"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"
},
"colorama": {
@@ -297,6 +293,14 @@
"index": "pypi",
"version": "==3.5.0"
},
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"index": "pypi",
"version": "==1.0.2"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
@@ -310,6 +314,25 @@
],
"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": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@@ -337,7 +360,6 @@
"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": {
@@ -345,7 +367,6 @@
"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": {
@@ -359,7 +380,6 @@
"hashes": [
"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"
},
"pyflakes": {
@@ -389,7 +409,6 @@
"sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2",
"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"
},
"requests": {
@@ -425,7 +444,6 @@
"sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889",
"sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9"
],
"markers": "python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.1.*'",
"version": "==4.26.0"
},
"twine": {
@@ -441,7 +459,6 @@
"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"
},
"webencodings": {
@@ -450,6 +467,13 @@
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"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
- **A built in testing client that uses the actual Requests you know and love**.
- The ability to mount other WSGI apps easily.
- Automatic gzipped-responses (still working on that).
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests.
- WhiteNoise is built-in, for serving static files (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 graphene
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "Hello, World from flask!"
api = responder.API()
# api.mount('/subapp', other_wsgi_app)
api.mount("/hello", app)
@api.route("/")
@@ -44,7 +55,7 @@ print(
print(
api.session()
.get(
"http://app/graph",
"http://app/hello/",
data="{ hello }",
headers={"Accept": "application/x-yaml"},
# data="hello",
@@ -53,4 +64,4 @@ print(
)
# {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 wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
from requests import Session as RequestsSession
from werkzeug.wsgi import DispatcherMiddleware
from . import models
from .status import HTTP_404
class BaseAPI:
__slots__ = ["routes"]
def __init__(self):
class API:
def __init__(self, static="static"):
self.static_dir = Path(os.path.abspath(static))
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):
"""The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without
@@ -44,14 +54,20 @@ class BaseAPI:
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)
return resp(environ, start_response)
def wsgi_app(self, environ, start_response):
def _wsgi_app(self, environ, start_response):
return self.whitenoise(environ, start_response)
def 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):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
@@ -74,10 +90,18 @@ class BaseAPI:
except TypeError:
try:
view = self.routes[route]()
# GraphQL Schema.
except TypeError:
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.
try:
@@ -97,25 +121,6 @@ class BaseAPI:
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):
if check_existing:
assert route not in self.routes
@@ -158,6 +163,9 @@ class API(BaseAPI):
return decorator
def mount(self, route, wsgi_app):
self.apps.update({route: wsgi_app})
def session(self, base_url="http://app"):
if self._session is None:
session = RequestsSession()
+5 -2
View File
@@ -8,7 +8,7 @@ from requests.models import Request as RequestsRequest
from requests.structures import CaseInsensitiveDict
from werkzeug.wrappers import Request as WerkzeugRequest
from werkzeug.wrappers import BaseResponse as WerkzeugResponse
from werkzeug.wsgi import DispatcherMiddleware
from urllib.parse import parse_qs
@@ -25,7 +25,7 @@ class Request:
self._wz = None
@classmethod
def from_environ(kls, environ):
def from_environ(kls, environ, start_response=None):
self = kls()
self._wz = WerkzeugRequest(environ)
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.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.dispatched = False
self._start_response = start_response
self._environ = environ
return self