diff options
author | 2018-02-14 23:10:31 +0000 | |
---|---|---|
committer | 2018-02-14 23:10:31 +0000 | |
commit | 70f0a9166b15645845370f4db8b1c9d1cfb75e6a (patch) | |
tree | a9ff85c2ecdb1db529a935f070b2d54ecbee93ef /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.py | 130 |
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") |