|
@@ -53,19 +53,36 @@ class Endpoint:
|
|
return self.user
|
|
return self.user
|
|
|
|
|
|
def route(self, *args, **kwargs):
|
|
def route(self, *args, **kwargs):
|
|
|
|
+ """Forward to the appropriate routing method"""
|
|
try:
|
|
try:
|
|
if flask.request.method == "POST":
|
|
if flask.request.method == "POST":
|
|
|
|
+ # all POST methods are "API methods": if we want to
|
|
|
|
+ # display information in response to a post, then we
|
|
|
|
+ # should redirect to the page where that information
|
|
|
|
+ # can be viewed instead of returning that
|
|
|
|
+ # information. (I think.)
|
|
return flask.jsonify(self.api_post(*args, **kwargs))
|
|
return flask.jsonify(self.api_post(*args, **kwargs))
|
|
elif (
|
|
elif (
|
|
flask.request.method in ["GET", "HEAD"]
|
|
flask.request.method in ["GET", "HEAD"]
|
|
and flask.request.content_type == "application/json"
|
|
and flask.request.content_type == "application/json"
|
|
):
|
|
):
|
|
|
|
+ # Here we're distinguishing between an API GET (i.e. a
|
|
|
|
+ # client trying to get JSON data about an endpoint)
|
|
|
|
+ # versus a user-level GET (i.e. a user in a browser.)
|
|
|
|
+ # I like using the HTTP headers to distinguish these
|
|
|
|
+ # cases, while other APIs tend to have a separate /api
|
|
|
|
+ # endpoint to do this.
|
|
return flask.jsonify(self.api_get(*args, **kwargs))
|
|
return flask.jsonify(self.api_get(*args, **kwargs))
|
|
|
|
+ # if an exception arose from an "API method", then we should
|
|
|
|
+ # report it as JSON
|
|
except e.LCException as exn:
|
|
except e.LCException as exn:
|
|
return ({"status": exn.http_code(), "error": str(exn)}, exn.http_code())
|
|
return ({"status": exn.http_code(), "error": str(exn)}, exn.http_code())
|
|
|
|
+ # also maybe we tried to redirect, so just do that
|
|
except e.LCRedirect as exn:
|
|
except e.LCRedirect as exn:
|
|
return flask.redirect(exn.to_path())
|
|
return flask.redirect(exn.to_path())
|
|
|
|
|
|
|
|
+ # if we're here, it means we're just trying to get a typical
|
|
|
|
+ # HTML request.
|
|
try:
|
|
try:
|
|
return self.html(*args, **kwargs)
|
|
return self.html(*args, **kwargs)
|
|
except e.LCException as exn:
|
|
except e.LCException as exn:
|
|
@@ -77,16 +94,35 @@ class Endpoint:
|
|
return flask.redirect(exn.to_path())
|
|
return flask.redirect(exn.to_path())
|
|
|
|
|
|
|
|
|
|
-def endpoint(route):
|
|
|
|
- def do_endpoint(cls):
|
|
|
|
|
|
+# Decorators result in some weird code in Python, especially 'cause it
|
|
|
|
+# doesn't make higher-order functions terse. Let's break this down a
|
|
|
|
+# bit. This out method, `endpoint`, takes the route...
|
|
|
|
+def endpoint(route: str):
|
|
|
|
+ """Route an endpoint using our semi-smart routing machinery"""
|
|
|
|
+ # but `endpoint` returns another function which is going to be
|
|
|
|
+ # called with the result of the definition after it. The argument
|
|
|
|
+ # to what we're calling `do_endpoint` here is going to be the
|
|
|
|
+ # class object defined afterwards.
|
|
|
|
+ def do_endpoint(endpoint_class):
|
|
|
|
+ # we'll just make that explicit here
|
|
|
|
+ assert Endpoint in endpoint_class.__bases__
|
|
|
|
+ # finally, we need a function that we'll give to Flask in
|
|
|
|
+ # order to actually dispatch to. This is the actual routing
|
|
|
|
+ # function, which is why it just creates an instance of the
|
|
|
|
+ # endpoint provided above and calls the `route` method on it
|
|
def func(*args, **kwargs):
|
|
def func(*args, **kwargs):
|
|
- return cls().route(*args, **kwargs)
|
|
|
|
|
|
+ return endpoint_class().route(*args, **kwargs)
|
|
|
|
|
|
|
|
+ # use reflection over the methods defined by the endpoint
|
|
|
|
+ # class to decide if it needs to accept POST requests or not.
|
|
methods = ["GET"]
|
|
methods = ["GET"]
|
|
if "api_post" in dir(cls):
|
|
if "api_post" in dir(cls):
|
|
methods.append("POST")
|
|
methods.append("POST")
|
|
|
|
|
|
|
|
+ # this is just for making error messages nicer
|
|
func.__name__ = cls.__name__
|
|
func.__name__ = cls.__name__
|
|
|
|
+
|
|
|
|
+ # finally, use the Flask routing machinery to register our callback
|
|
return c.app.route(route, methods=methods)(func)
|
|
return c.app.route(route, methods=methods)(func)
|
|
|
|
|
|
return do_endpoint
|
|
return do_endpoint
|