aboutsummaryrefslogtreecommitdiffstats
path: root/pysite/base_route.py
diff options
context:
space:
mode:
authorGravatar Gareth Coles <[email protected]>2018-02-14 23:10:31 +0000
committerGravatar Sam Wedgwood <[email protected]>2018-02-14 23:10:31 +0000
commit70f0a9166b15645845370f4db8b1c9d1cfb75e6a (patch)
treea9ff85c2ecdb1db529a935f070b2d54ecbee93ef /pysite/base_route.py
parent[API] You need to return the value of `self.error()` (diff)
Database API Improvements #1qcra (#13)
* A large set of changes, including: * A mixin for views that need the DB * Many changes to the database class in order to make things more fluid * Provide the route manager in view setup() methods * Pushing up the progress so far * snekchek * Full (undocumented) database implementation * snekchek * Don't rely on exceptions for table deletion * Add RethinkDB data to gitignore * Documentation for DB class * Make Flake8 ignore P102 What even is that? What does "docstring does contain unindexed parameters" mean? * Document the base_routes module * Cleanup RE latest reviews * snekchek (bah)
Diffstat (limited to 'pysite/base_route.py')
-rw-r--r--pysite/base_route.py130
1 files changed, 127 insertions, 3 deletions
diff --git a/pysite/base_route.py b/pysite/base_route.py
index e0871e49..730b3e10 100644
--- a/pysite/base_route.py
+++ b/pysite/base_route.py
@@ -4,13 +4,22 @@ import random
import string
from functools import wraps
-from flask import Blueprint, jsonify, render_template, request
+from flask import Blueprint, g, jsonify, render_template, request
from flask.views import MethodView
+from rethinkdb.ast import Table
+
from pysite.constants import ErrorCodes
+from pysite.database import RethinkDB
class BaseView(MethodView):
+ """
+ Base view class with functions and attributes that should be common to all view classes.
+
+ This class should be subclassed, and is not intended to be used directly.
+ """
+
name = None # type: str
def render(self, *template_names, **context):
@@ -21,10 +30,39 @@ class BaseView(MethodView):
class RouteView(BaseView):
+ """
+ Standard route-based page view. For a standard page, this is what you want.
+
+ This class is intended to be subclassed - use it as a base class for your own views, and set the class-level
+ attributes as appropriate. For example:
+
+ >>> class MyView(RouteView):
+ ... name = "my_view" # Flask internal name for this route
+ ... path = "/my_view" # Actual URL path to reach this route
+ ...
+ ... def get(self): # Name your function after the relevant HTTP method
+ ... return self.render("index.html")
+
+ For more complicated routing, see http://exploreflask.com/en/latest/views.html#built-in-converters
+ """
+
path = None # type: str
@classmethod
- def setup(cls: "RouteView", blueprint: Blueprint):
+ def setup(cls: "RouteView", manager: "pysite.route_manager.RouteManager", blueprint: Blueprint):
+ """
+ Set up the view by adding it to the blueprint passed in - this will also deal with multiple inheritance by
+ calling `super().setup()` as appropriate.
+
+ This is for a standard route view. Nothing special here.
+
+ :param manager: Instance of the current RouteManager
+ :param blueprint: Current Flask blueprint to register this route to
+ """
+
+ if hasattr(super(), "setup"):
+ super().setup(manager, blueprint)
+
if not cls.path or not cls.name:
raise RuntimeError("Route views must have both `path` and `name` defined")
@@ -32,6 +70,20 @@ class RouteView(BaseView):
class APIView(RouteView):
+ """
+ API route view, with extra methods to help you add routes to the JSON API with ease.
+
+ This class is intended to be subclassed - use it as a base class for your own views, and set the class-level
+ attributes as appropriate. For example:
+
+ >>> class MyView(APIView):
+ ... name = "my_view" # Flask internal name for this route
+ ... path = "/my_view" # Actual URL path to reach this route
+ ...
+ ... def get(self): # Name your function after the relevant HTTP method
+ ... return self.error(ErrorCodes.unknown_route)
+ """
+
def validate_key(self, api_key: str):
""" Placeholder! """
return api_key == os.environ.get("API_KEY")
@@ -81,11 +133,83 @@ class APIView(RouteView):
return response
+class DBViewMixin:
+ """
+ Mixin for views that make use of RethinkDB. It can automatically create a table with the specified primary
+ key using the attributes set at class-level.
+
+ This class is intended to be mixed in alongside one of the other view classes. For example:
+
+ >>> class MyView(APIView, DBViewMixin):
+ ... name = "my_view" # Flask internal name for this route
+ ... path = "/my_view" # Actual URL path to reach this route
+ ... table_name = "my_table" # Name of the table to create
+ ... table_primary_key = "username" # Primary key to set for this table
+
+ You may omit `table_primary_key` and it will be defaulted to RethinkDB's default column - "id".
+ """
+
+ table_name = "" # type: str
+ table_primary_key = "id" # type: str
+
+ @classmethod
+ def setup(cls: "DBViewMixin", manager: "pysite.route_manager.RouteManager", blueprint: Blueprint):
+ """
+ Set up the view by creating the table specified by the class attributes - this will also deal with multiple
+ inheritance by calling `super().setup()` as appropriate.
+
+ :param manager: Instance of the current RouteManager (used to get a handle for the database object)
+ :param blueprint: Current Flask blueprint
+ """
+
+ if hasattr(super(), "setup"):
+ super().setup(manager, blueprint)
+
+ if not cls.table_name:
+ raise RuntimeError("Routes using DBViewMixin must define `table_name`")
+
+ manager.db.create_table(cls.table_name, primary_key=cls.table_primary_key)
+
+ @property
+ def table(self) -> Table:
+ return self.db.query(self.table_name)
+
+ @property
+ def db(self) -> RethinkDB:
+ return g.db
+
+
class ErrorView(BaseView):
+ """
+ Error view, shown for a specific HTTP status code, as defined in the class attributes.
+
+ This class is intended to be subclassed - use it as a base class for your own views, and set the class-level
+ attributes as appropriate. For example:
+
+ >>> class MyView(ErrorView):
+ ... name = "my_view" # Flask internal name for this route
+ ... path = "/my_view" # Actual URL path to reach this route
+ ... error_code = 404
+ ...
+ ... def get(self): # Name your function after the relevant HTTP method
+ ... return "Replace me with a template, 404 not found", 404
+ """
+
error_code = None # type: int
@classmethod
- def setup(cls: "ErrorView", blueprint: Blueprint):
+ def setup(cls: "ErrorView", manager: "pysite.route_manager.RouteManager", blueprint: Blueprint):
+ """
+ Set up the view by registering it as the error handler for the HTTP status code specified in the class
+ attributes - this will also deal with multiple inheritance by calling `super().setup()` as appropriate.
+
+ :param manager: Instance of the current RouteManager
+ :param blueprint: Current Flask blueprint to register the error handler for
+ """
+
+ if hasattr(super(), "setup"):
+ super().setup(manager, blueprint)
+
if not cls.name or not cls.error_code:
raise RuntimeError("Error views must have both `name` and `error_code` defined")