mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
responder
This commit is contained in:
+37
-3
@@ -124,12 +124,46 @@ Future Ideas
|
||||
- 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.
|
||||
- If frontend websites are supported, provide an official way to run webpack.
|
||||
|
||||
When can I use it?
|
||||
------------------
|
||||
|
||||
When it's ready. It's not. I started work on this a few days ago. It works surprisingly well, considering! :)
|
||||
Installation
|
||||
============
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ pipenv install responder
|
||||
✨🍰✨
|
||||
|
||||
Only **Python 3.6+** is supported.
|
||||
|
||||
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
Requests & Responses
|
||||
--------------------
|
||||
.. module:: responder
|
||||
|
||||
.. autoclass:: Request
|
||||
:inherited-members:
|
||||
|
||||
.. autoclass:: Response
|
||||
:inherited-members:
|
||||
|
||||
API Class
|
||||
---------
|
||||
|
||||
|
||||
.. autoclass:: API
|
||||
:inherited-members:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Utility Functions
|
||||
-----------------
|
||||
|
||||
.. autofunction::
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
|
||||
+72
-6
@@ -21,6 +21,13 @@ from .formats import get_formats
|
||||
|
||||
# TODO: consider moving status codes here
|
||||
class API:
|
||||
"""The primary web-service class.
|
||||
|
||||
:param static_dir: The directory to use for static files. Will be created for you if it doesn't already exist.
|
||||
:param templates_dir: The directory to use for templates. Will be created for you if it doesn't already exist.
|
||||
:param enable_hsts: If ``True``, send all responses to HTTPS URLs.
|
||||
"""
|
||||
|
||||
status_codes = status_codes
|
||||
|
||||
def __init__(
|
||||
@@ -79,6 +86,7 @@ class API:
|
||||
return self.whitenoise(environ, start_response)
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
"""Returns the WSGI app for this application (including all mounted WSGI apps)."""
|
||||
apps = self.apps.copy()
|
||||
main = apps.pop("/")
|
||||
|
||||
@@ -90,9 +98,13 @@ class API:
|
||||
wrapped to applying middleware."""
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
def path_matches_route(self, url):
|
||||
def path_matches_route(self, path):
|
||||
"""Given a path portion of a URL, tests that it matches against any registered route.
|
||||
|
||||
:param path: The path portion of a URL, to test all known routes against.
|
||||
"""
|
||||
for (route, route_object) in self.routes.items():
|
||||
if route_object.does_match(url):
|
||||
if route_object.does_match(path):
|
||||
return route
|
||||
|
||||
def _dispatch_request(self, req):
|
||||
@@ -149,20 +161,40 @@ class API:
|
||||
|
||||
return resp
|
||||
|
||||
def add_route(self, route, view, *, check_existing=True, graphiql=False):
|
||||
def add_route(self, route, endpoint, *, check_existing=True):
|
||||
# TODO: add graphiql
|
||||
"""Add a route to the API.
|
||||
|
||||
:param route: A string representation of the route.
|
||||
:param endpoint: The endpoint for the route -- can be a callable, a class, a WSGI application, or graphene schema (GraphQL).
|
||||
:param check_existing: If ``True``, an AssertionError will be raised, if the route is already defined.
|
||||
"""
|
||||
if check_existing:
|
||||
assert route not in self.routes
|
||||
|
||||
# TODO: Support grpahiql.
|
||||
self.routes[route] = Route(route, view)
|
||||
self.routes[route] = Route(route, endpoint)
|
||||
|
||||
def default_response(self, req, resp):
|
||||
resp.status_code = HTTP_404
|
||||
resp.text = "Not found."
|
||||
|
||||
def redirect(self, resp, location, *, status_code=status_codes.HTTP_301):
|
||||
def redirect(
|
||||
self, resp, location, *, set_text=True, status_code=status_codes.HTTP_301
|
||||
):
|
||||
"""Redirects a given response to a given location.
|
||||
|
||||
:param resp: The Response to mutate.
|
||||
:param location: The location of the redirect.
|
||||
:param set_text: If ``True``, sets the Redirect body content automatically.
|
||||
:param status_code: an `API.status_codes` attribute, or an integer, representing the HTTP status code of the redirect.
|
||||
"""
|
||||
|
||||
assert resp.status_code.is_300(status_code)
|
||||
|
||||
resp.status_code = status_code
|
||||
resp.text = f"Redirecting to: {location}"
|
||||
if set_text:
|
||||
resp.text = f"Redirecting to: {location}"
|
||||
resp.headers.update({"Location": location})
|
||||
|
||||
@staticmethod
|
||||
@@ -199,6 +231,16 @@ class API:
|
||||
return (query, result, status_code)
|
||||
|
||||
def route(self, route, **options):
|
||||
"""Decorator for creating new routes around function and class defenitions.
|
||||
|
||||
Usage::
|
||||
|
||||
@api.route("/hello")
|
||||
def hello(req, resp):
|
||||
req.text = "hello, world!"
|
||||
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
self.add_route(route, f, **options)
|
||||
return f
|
||||
@@ -206,9 +248,19 @@ class API:
|
||||
return decorator
|
||||
|
||||
def mount(self, route, wsgi_app):
|
||||
"""Mounts a WSGI application at a given route.
|
||||
|
||||
:param route: String representation of the route to be used (shouldn't be parameterized).
|
||||
:param wsgi_app: The other WSGI app (e.g. a Flask app).
|
||||
"""
|
||||
self.apps.update({route: wsgi_app})
|
||||
|
||||
def session(self, base_url="http://;"):
|
||||
"""Testing HTTP client. Returns a Requests session object, able to send HTTP requests to the WSGI application.
|
||||
|
||||
:param base_url: The URL to mount the connection adaptor to.
|
||||
"""
|
||||
|
||||
if self._session is None:
|
||||
session = RequestsSession()
|
||||
session.mount(base_url, RequestsWSGIAdapter(self))
|
||||
@@ -216,12 +268,26 @@ class API:
|
||||
return self._session
|
||||
|
||||
def url_for(self, view, absolute_url=False, **params):
|
||||
# TODO: Absolute_url
|
||||
"""Given a view, returns the URL for that view.
|
||||
|
||||
:param view: The route endpoint you're searching for.
|
||||
:param params: Data to pass into the URL generator (for parameterized URLs).
|
||||
"""
|
||||
for (route, route_object) in self.routes.items():
|
||||
if route_object.endpoint == _view:
|
||||
return route_object.url(**params)
|
||||
raise ValueError
|
||||
|
||||
def template(self, name, auto_escape=True, **values):
|
||||
"""Renders the given `jinja2 <http://jinja.pocoo.org/docs/>`_ template, with provided values supplied.
|
||||
|
||||
Note: The current ``api`` instance is always passed into the view.
|
||||
|
||||
:param name: The filename of the jinja2 template, in ``templates_dir``.
|
||||
:param auto_escape: If ``True``, HTML and XML will automatically be escaped.
|
||||
:param params: Data to pass into the template.
|
||||
"""
|
||||
# Give reference to self.
|
||||
values.update(api=self)
|
||||
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
from .api import API
|
||||
from . import status_codes
|
||||
from .models import Request, Response
|
||||
|
||||
+1
-1
@@ -30,12 +30,12 @@ def flatten(d):
|
||||
# TODO: add slots
|
||||
class Request:
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._wz = None
|
||||
|
||||
@classmethod
|
||||
def from_environ(kls, environ, start_response=None):
|
||||
self = kls()
|
||||
#: The Werkzeug object, powering the Request.
|
||||
self._wz = WerkzeugRequest(environ)
|
||||
self.headers = CaseInsensitiveDict(self._wz.headers.to_wsgi_list())
|
||||
self.method = self._wz.method.lower()
|
||||
|
||||
@@ -88,3 +88,27 @@ for number in codes:
|
||||
|
||||
for label in codes[number]:
|
||||
locals()[label] = number
|
||||
|
||||
|
||||
def _is_category(category, status_code):
|
||||
return all([(status_code >= category), (status_code < category + 100)])
|
||||
|
||||
|
||||
def is_100(status_code):
|
||||
return _is_category(100, status_code)
|
||||
|
||||
|
||||
def is_200(status_code):
|
||||
return _is_category(200, status_code)
|
||||
|
||||
|
||||
def is_300(status_code):
|
||||
return _is_category(300, status_code)
|
||||
|
||||
|
||||
def is_400(status_code):
|
||||
return _is_category(400, status_code)
|
||||
|
||||
|
||||
def is_500(status_code):
|
||||
return _is_category(500, status_code)
|
||||
|
||||
Reference in New Issue
Block a user