This commit is contained in:
2018-10-09 07:15:17 -04:00
parent abb8153b64
commit f4c252e2d9
5 changed files with 69 additions and 6 deletions
+1
View File
@@ -10,6 +10,7 @@ pyyaml = "*"
requests = "*"
requests-wsgi-adapter = "*"
graphene = "*"
whitenoise = "*"
[dev-packages]
pytest = "*"
Generated
+9 -1
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "35fa1f64486f787f137dd769806ab0b753ec56ae94e6f9ebb953be588c81d3be"
"sha256": "f6b7cc3cf0ac2760ea99bcb8d18c743eff418c6269da29823ccfdbdea19a8c1e"
},
"pipfile-spec": 6,
"requires": {
@@ -135,6 +135,14 @@
],
"index": "pypi",
"version": "==0.14.1"
},
"whitenoise": {
"hashes": [
"sha256:133a92ff0ab8fb9509f77d4f7d0de493eca19c6fea973f4195d4184f888f2e02",
"sha256:32b57d193478908a48acb66bf73e7a3c18679263e3e64bfebcfac1144a430039"
],
"index": "pypi",
"version": "==4.1"
}
},
"develop": {
+34
View File
@@ -0,0 +1,34 @@
# Responder: a Sorta Familar HTTP Framework for Python
![](https://farm2.staticflickr.com/1937/30196007887_604e2f10d8_k_d.jpg)
I'm adept to keep the "for humans" tagline off this project, until it comes out of the prototyping phase. I'm building this to learn, and to have fun -- while, at the same time, trying to bring something new to the table.
The Python world certianly doesn't need more web frameworks. But, it does need more creativity, so I thought I'd bring some of my ideas to the table and see what I could come up with.
# The Basic Idea
The primary concept here is to bring the nicities that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitaves that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.
## Old Ideas
- Flask-style route expression, with new capabilities -- primarily, the ability to cast a parameter to integers as well as other types that are missing from Flask, all while using Python 3.6+'s new f-string syntax.
- I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially `response.media`, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
## New Ideas
- In addition to Falcon's `on_get`, `on_post`, etc methods, Responder features an `on_request` method, which gets called on every type of request, much like Requests.
- WhiteNoise is built-in, for serving static files (this has yet to be built out, there's no templating or `static_url` yet)
- Waitress (will-be) built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Waitress serves well to protect against slowloris attacks, making nginx unneccessary in production.
- GraphQL support, via Graphene. The goal here is to eventually have an embedded version of GraphiQL exposable at any route.
## Future Ideas
- I want to be able to "mount" any WSGI app into a sub-route.
- Cooke-based sessions are currently an afterthrought, as this is an API framework, but websites are APIs too.
- 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.
# The Goal
The primary goal here is to learn, not to get adoption. Though, who knows how these things will pan out.
+1 -1
View File
@@ -1,7 +1,7 @@
import responder
import graphene
api = responder.API()
api = responder.API(static="static")
# api.mount('/subapp', other_wsgi_app)
+24 -4
View File
@@ -1,5 +1,9 @@
import os
from pathlib import Path
import graphene
from whitenoise import WhiteNoise
from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
from requests import Session as RequestsSession
@@ -13,7 +17,7 @@ class BaseAPI:
def __init__(self):
self.routes = {}
def wsgi_app(self, environ, start_response):
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
@@ -45,6 +49,9 @@ class BaseAPI:
return resp(environ, start_response)
def wsgi_app(self, environ, start_response):
return self.whitenoise(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
@@ -90,18 +97,31 @@ class BaseAPI:
return resp
@property
def static_dir(self):
return Path(".")
class API(BaseAPI):
__slots__ = ("routes", "_session")
__slots__ = ("routes", "_session", "whitenoise", "static_dir")
def __init__(self):
def __init__(self, static="static"):
super().__init__()
self._session = None
self.static_dir = Path(os.path.abspath(static))
def add_route(self, route, view, *, check_existing=True):
# 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
# TODO: Support grpahiql.
self.routes[route] = view
def default_response(self, req, resp):