Revert "idk what's happening"

This reverts commit e34cb539d2.
This commit is contained in:
2018-10-23 08:00:56 -04:00
parent 950be14eca
commit aea01fd893
7 changed files with 75 additions and 175 deletions
-3
View File
@@ -1,6 +1,3 @@
# v0.2.0
- WebSocket support.
# v0.1.6
- 500 support.
Generated
+10 -10
View File
@@ -215,9 +215,9 @@
},
"starlette": {
"hashes": [
"sha256:eac0f6cab6b48846a0c1af16615430ae0e7a95f669ee0841a7e2f242d51d8935"
"sha256:ce5c684fad4edb2967cd491518cd3c2724e420508202c2d48f519ea68dcec9d6"
],
"version": "==0.5.5"
"version": "==0.5.4"
},
"urllib3": {
"hashes": [
@@ -228,9 +228,9 @@
},
"uvicorn": {
"hashes": [
"sha256:e2b742fdaa0b52f4aac92fd2c078e7f1f17d11322bb3efb09d341d5c6998b4b5"
"sha256:7c4550c7e6f7c8727fa5ccd5200baf62c9e055895e058933ee88f5d0c246ca0c"
],
"version": "==0.3.16"
"version": "==0.3.14"
},
"websockets": {
"hashes": [
@@ -591,11 +591,11 @@
},
"pytest": {
"hashes": [
"sha256:212be78a6fa5352c392738a49b18f74ae9aeec1040f47c81cadbfd8d1233c310",
"sha256:6f6c1efc8d0ccc21f8f6c34d8330baca883cf109b66b3df954b0a117e5528fb4"
"sha256:10e59f84267370ab20cec9305bafe7505ba4d6b93ecbf66a1cce86193ed511d5",
"sha256:8c827e7d4816dfe13e9329c8226aef8e6e75d65b939bc74fda894143b6d1df59"
],
"index": "pypi",
"version": "==3.9.2"
"version": "==3.9.1"
},
"pytest-cov": {
"hashes": [
@@ -671,10 +671,10 @@
},
"tqdm": {
"hashes": [
"sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392",
"sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb"
"sha256:a0be569511161220ff709a5b60d0890d47921f746f1c737a11d965e1b29e7b2e",
"sha256:e293e6d7a7f41a529a27f8d6624ab11544ccbfe82a205af6fad102545099fc21"
],
"version": "==4.28.1"
"version": "==4.27.0"
},
"twine": {
"hashes": [
-1
View File
@@ -50,7 +50,6 @@ Features
- A pleasant API, with a single import statement.
- Class-based views without inheritence.
- ASGI framework, the future of Python web services.
- WebSocket support!
- The ability to mount any ASGI / WSGI app at a subroute.
- *f-string syntax* route declaration.
- Mutable response object, passed into each view. No need to return anything.
+2 -17
View File
@@ -9,7 +9,7 @@ Class-based views (and setting some headers and stuff)::
@api.route("/{greeting}")
class GreetingResource:
def on_request(self, req, resp, *, greeting): # or on_get...
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
@@ -147,11 +147,7 @@ If you have a single-page webapp, you can tell Responder to serve up your ``stat
api.add_route("/", static=True)
This will make ``index.html`` the default response to all undefined routes. Responder's CLI comes with a ``build`` command that will call ``npm run build`` for you::
responder build
For an example of how to seamlessly integrate a React single page app with Responder check out `this project <https://github.com/metakermit/responder-react>`_.
This will make ``index.html`` the default response to all undefined routes.
Reading / Writing Cookies
-------------------------
@@ -180,17 +176,6 @@ 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(...)``.
WebSocket Support
-----------------
Responder supports WebSockets::
@api.ws_route('/ws')
async def hello(ws):
await ws.accept()
await ws.send_text("Hello via websocket!")
await ws.close()
HSTS (Redirect to HTTPS)
------------------------
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.2.0"
__version__ = "0.1.6"
+62 -80
View File
@@ -9,6 +9,8 @@ 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
@@ -25,7 +27,6 @@ from .routes import Route
from .formats import get_formats
from .background import BackgroundQueue
from .templates import GRAPHIQL
from .models import WebSocket
# TODO: consider moving status codes here
class API:
@@ -42,6 +43,7 @@ class API:
def __init__(
self,
*,
debug=False,
title=None,
version=None,
openapi=None,
@@ -87,6 +89,8 @@ class API:
self.default_endpoint = None
self.app = self.dispatch
self.add_middleware(GZipMiddleware)
if debug:
self.add_middleware(DebugMiddleware)
if self.hsts_enabled:
self.add_middleware(HTTPSRedirectMiddleware)
@@ -100,8 +104,6 @@ class API:
)
self.jinja_values_base = {"api": self} # Give reference to self.
self.requests = self.session()
@property
def _apispec(self):
spec = APISpec(
@@ -151,22 +153,15 @@ class API:
# Call the main dispatcher.
async def asgi(receive, send):
nonlocal scope, self
if scope["type"] == "websocket":
ws = WebSocket(scope, receive, send)
await self._dispatch_ws(ws)
else:
req = models.Request(scope, receive=receive, api=self)
resp = await self._dispatch_request(req)
await resp(receive, send)
req = models.Request(scope, receive=receive, api=self)
resp = await self._dispatch_request(
req, scope=scope, send=send, receive=receive
)
await resp(receive, send)
return asgi
async def _dispatch_ws(self, ws):
route = self.path_matches_route(ws.url.path, protocol="ws")
route = self.routes.get(route)
await self._dispatch(route, ws=ws)
def add_schema(self, name, schema, check_existing=True):
"""Adds a mashmallow schema to the API specification."""
if check_existing:
@@ -182,7 +177,7 @@ class API:
from marshmallow import Schema, fields
@api.schema("Pet")
class PetScrhema(Schema):
class PetSchema(Schema):
name = fields.Str()
"""
@@ -193,17 +188,17 @@ class API:
return decorator
# TODO: Remove protocol
def path_matches_route(self, path, protocol="http"):
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(path, protocol):
if route_object.does_match(path):
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
@@ -222,7 +217,7 @@ class API:
def no_response(req, resp, **params):
pass
async def _dispatch_request(self, req):
async def _dispatch_request(self, req, **options):
# Set formats on Request object.
req.formats = self.formats
@@ -231,43 +226,31 @@ class API:
route = self.routes.get(route)
# Create the response object.
resp = models.Response(req=req, formats=self.formats)
self.default_response(req, resp)
await self._dispatch(route, req=req, resp=resp)
self._prepare_session(resp)
self._prepare_cookies(resp)
return resp
async def _dispatch(self, route, **kwargs):
cont = False
if route:
if "req" in kwargs:
params = route.incoming_matches(kwargs["req"].url.path)
elif "ws" in kwargs:
params = route.incoming_matches(kwargs["ws"].url.path)
if not route.uses_websocket:
resp = models.Response(req=req, formats=self.formats)
else:
params = {}
resp = WebSocket(**options)
params = route.incoming_matches(req.url.path)
if route.is_graphql:
await self.graphql_response(schema=route.endpoint, **kwargs)
await self.graphql_response(req, resp, schema=route.endpoint)
elif route.is_function:
try:
try:
# Run the view.
r = route.endpoint(**kwargs, **params)
r = route.endpoint(req, resp, **params)
# If it's async, await it.
if hasattr(r, "cr_running"):
await r
except TypeError as e:
cont = True
except Exception:
self.default_response(error=True, **kwargs)
self.default_response(req, resp, error=True)
if route.is_class_based or cont:
try:
@@ -279,38 +262,40 @@ class API:
try:
# Run the view.
r = getattr(view, "on_request", self.no_response)(
**kwargs, **params
)
# If it's async, await it.
if hasattr(r, "send"):
await r
except Exception:
self.default_response(error=True, **kwargs)
# Then on_get.
if "req" in kwargs:
method = kwargs["req"].method
elif "ws" in kwargs:
method = kwargs["ws"].method
else:
method = "get"
# Run on_request first.
try:
# Run the view.
r = getattr(view, f"on_{method}", self.no_response)(
**kwargs, **params
req, resp, **params
)
# If it's async, await it.
if hasattr(r, "send"):
await r
except Exception as e:
self.default_response(error=True, **kwargs)
self.default_response(req, resp, error=True)
# Then on_get.
method = req.method
# Run on_request first.
try:
# Run the view.
r = getattr(view, f"on_{method}", self.no_response)(
req, resp, **params
)
# If it's async, await it.
if hasattr(r, "send"):
await r
except Exception as e:
self.default_response(req, resp, error=True)
else:
self.default_response(notfound=True, **kwargs)
resp = models.Response(req=req, formats=self.formats)
self.default_response(req, resp, notfound=True)
return kwargs
self.default_response(req, resp)
self._prepare_session(resp)
self._prepare_cookies(resp)
return resp
def add_route(
self,
@@ -318,28 +303,20 @@ class API:
endpoint=None,
*,
default=False,
websocket=False,
static=False,
check_existing=True,
websocket=False,
):
"""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, or graphene schema (GraphQL).
:param default: If ``True``, all unknown requests will route to this view.
:param websocket: If ``True``, Requests to route will be treated as websockets.
:param static: If ``True``, and no endpoint was passed, render "static/index.html", and it will become a default route.
:param check_existing: If ``True``, an AssertionError will be raised, if the route is already defined.
"""
if websocket:
protocol = "ws"
else:
protocol = "http"
if check_existing:
assert not (
route in self.routes and self.routes[route].protocol == protocol
)
assert route not in self.routes
if not endpoint and static:
endpoint = self.static_response
@@ -354,7 +331,7 @@ class API:
except AttributeError:
pass
self.routes[route] = Route(route, endpoint, protocol)
self.routes[route] = Route(route, endpoint, websocket=websocket)
# TODO: A better datastructer or sort it once the app is loaded
self.routes = dict(
sorted(self.routes.items(), key=lambda item: item[1]._weight())
@@ -487,6 +464,13 @@ class API:
self._session = TestClient(self)
return self._session
def _route_for(self, endpoint):
for (route, route_object) in self.routes.items():
if route_object.endpoint == endpoint:
return route_object
elif route_object.endpoint_name == endpoint:
return route_object
def url_for(self, endpoint, testing=False, **params):
# TODO: Absolute_url
"""Given an endpoint, returns a rendered URL for its route.
@@ -494,11 +478,9 @@ class API:
: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 == endpoint:
return route_object.url(testing=testing, **params)
elif route_object.endpoint_name == endpoint:
return route_object.url(testing=testing, **params)
route_object = self._route_for(endpoint)
if route_object:
return route_object.url(testing=testing, **params)
raise ValueError
def static_url(self, asset):
-63
View File
@@ -13,7 +13,6 @@ from requests.cookies import RequestsCookieJar
from starlette.datastructures import MutableHeaders
from starlette.requests import Request as StarletteRequest
from starlette.responses import Response as StarletteResponse
from starlette.websockets import WebSocket as StarletteWebSocket
from urllib.parse import parse_qs
@@ -288,65 +287,3 @@ class Response:
body, status_code=self.status_code, headers=headers
)
await response(receive, send)
class WebSocket:
__slots__ = ("_starlette", "_headers")
def __init__(self, scope, receive, send):
self._starlette = StarletteWebSocket(scope, receive, send)
headers = CaseInsensitiveDict()
for header, value in self._starlette.headers.items():
headers[header] = value
self._headers = headers
@property
def url(self):
return rfc3986.urlparse(str(self._starlette.url))
@property
def params(self):
"""A dictionary of the parsed query parameters used for the Request."""
try:
return QueryDict(self.url.query)
except AttributeError:
return QueryDict({})
async def accept(self):
return await self._starlette.accept()
async def receive(self):
return await self._starlette.receive()
async def content(self):
"""Receive bytes."""
return await self._starlette.receive_bytes()
async def text(self):
"""Receive text."""
return await self._starlette.receive_text()
# async def json(self):
# """Receive json"""
# return await self._starlette.receive_json()
# async def media(self):
# """Receive json"""
# return (await self.receive_json())
async def send(self):
await self._starlette.send()
async def send_text(self, data):
await self._starlette.send_text(data)
# async def send_json(self, data):
# await self._starlette.send_json(data)
# async def send_media(self, data):
# await self.send_json(data)
async def close(self, code=1000):
await self._starlette.close(code)