diff options
author | 2018-02-05 12:22:49 +0000 | |
---|---|---|
committer | 2018-02-05 12:22:49 +0000 | |
commit | beaa61362aed31f229c935a347e382024eee5a90 (patch) | |
tree | 4d9cf54e44b7fea4124389098fd0fa37a17cdad9 | |
parent | Add templates & static folder with .gitkeep's (diff) |
Dynamic route loader; proper application structure
Also fixed flake8-imports getting the other wrong
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | .snekrc | 3 | ||||
-rw-r--r-- | app.py | 35 | ||||
-rw-r--r-- | pysite/__init__.py | 3 | ||||
-rw-r--r-- | pysite/base_route.py | 29 | ||||
-rw-r--r-- | pysite/route_manager.py | 41 | ||||
-rw-r--r-- | pysite/views/__init__.py | 3 | ||||
-rw-r--r-- | pysite/views/error_handlers/__init__.py | 3 | ||||
-rw-r--r-- | pysite/views/error_handlers/http_404.py | 14 | ||||
-rw-r--r-- | pysite/views/healthcheck.py | 15 | ||||
-rw-r--r-- | pysite/views/index.py | 12 | ||||
-rw-r--r-- | pysite/views/invite.py | 15 | ||||
-rw-r--r-- | requirements-ci.txt | 1 |
13 files changed, 145 insertions, 35 deletions
@@ -94,8 +94,8 @@ ENV/ # Rope project settings .ropeproject -# mkdocs documentation -/site - # mypy .mypy_cache/ + +# PyCharm +.idea/ @@ -2,4 +2,5 @@ linters = flake8, safety, dodgy [flake8] -max-line-length=100
\ No newline at end of file +max-line-length=120 +application_import_names=pysite
\ No newline at end of file @@ -1,35 +1,10 @@ -#!/usr/bin/env python3 +# coding=utf-8 -import os +from pysite.route_manager import RouteManager -from flask import Flask -from flask import jsonify -from flask import redirect - -app = Flask(__name__) - -app.secret_key = os.environ.get("WEBPAGE_SECRET_KEY") - - [email protected]("/") -def index(): - return "Coming soon:tm:" - - [email protected]("/invite") -def invite(): - return redirect("http://invite.pythondiscord.com/") - - [email protected]("/healthcheck") -def healthcheck(): - return jsonify({"status": "ok"}) - - [email protected](404) -def page_not_found(e): - return "replace me with a template, 404 not found", 404 +manager = RouteManager() +app = manager.app if __name__ == '__main__': - app.run(port=int(os.environ.get("WEBPAGE_PORT")), debug=False) + manager.run() diff --git a/pysite/__init__.py b/pysite/__init__.py new file mode 100644 index 00000000..ba286add --- /dev/null +++ b/pysite/__init__.py @@ -0,0 +1,3 @@ +# coding=utf-8 + +__author__ = "Gareth Coles" diff --git a/pysite/base_route.py b/pysite/base_route.py new file mode 100644 index 00000000..76338280 --- /dev/null +++ b/pysite/base_route.py @@ -0,0 +1,29 @@ +# coding=utf-8 +from flask import Flask +from flask.views import MethodView + +__author__ = "Gareth Coles" + + +class BaseView(MethodView): + path = None #: str + name = None #: str + + @classmethod + def setup(cls: "BaseView", app: Flask): + if not cls.path or not cls.name: + raise RuntimeError("Route views must have both `path` and `name` defined") + + app.add_url_rule(cls.path, view_func=cls.as_view(cls.name)) + + +class ErrorView(MethodView): + name = None #: str + error_code = None #: int + + @classmethod + def setup(cls: "ErrorView", app: Flask): + if not cls.name or not cls.error_code: + raise RuntimeError("Error views must have both `name` and `error_code` defined") + + app._register_error_handler(None, 404, cls.as_view(cls.name)) diff --git a/pysite/route_manager.py b/pysite/route_manager.py new file mode 100644 index 00000000..501076b7 --- /dev/null +++ b/pysite/route_manager.py @@ -0,0 +1,41 @@ +# coding=utf-8 +import importlib +import inspect +import os + +from flask import Flask + +from pysite.base_route import BaseView, ErrorView + +__author__ = "Gareth Coles" + + +class RouteManager: + def __init__(self): + self.app = Flask(__name__) + self.app.secret_key = os.environ.get("WEBPAGE_SECRET_KEY") + + self.load_views() + + def run(self): + self.app.run(port=int(os.environ.get("WEBPAGE_PORT")), debug=False) + + def load_views(self, location="pysite/views"): + for filename in os.listdir(location): + if os.path.isdir(f"{location}/{filename}"): + # Recurse if it's a directory; load ALL the views! + self.load_views(location=f"{location}/{filename}") + continue + + if filename.endswith(".py") and not filename.startswith("__init__"): + module = importlib.import_module(f"{location}/{filename}".replace("/", ".")[:-3]) + + for cls_name, cls in inspect.getmembers(module): + if ( + inspect.isclass(cls) and + cls is not BaseView and + cls is not ErrorView and + (BaseView in cls.__mro__ or ErrorView in cls.__mro__) + ): + cls.setup(self.app) + print(f"View loaded: {cls.name: <25} ({module.__name__}.{cls_name})") diff --git a/pysite/views/__init__.py b/pysite/views/__init__.py new file mode 100644 index 00000000..ba286add --- /dev/null +++ b/pysite/views/__init__.py @@ -0,0 +1,3 @@ +# coding=utf-8 + +__author__ = "Gareth Coles" diff --git a/pysite/views/error_handlers/__init__.py b/pysite/views/error_handlers/__init__.py new file mode 100644 index 00000000..ba286add --- /dev/null +++ b/pysite/views/error_handlers/__init__.py @@ -0,0 +1,3 @@ +# coding=utf-8 + +__author__ = "Gareth Coles" diff --git a/pysite/views/error_handlers/http_404.py b/pysite/views/error_handlers/http_404.py new file mode 100644 index 00000000..eea1e630 --- /dev/null +++ b/pysite/views/error_handlers/http_404.py @@ -0,0 +1,14 @@ +# coding=utf-8 +from werkzeug.exceptions import NotFound + +from pysite.base_route import ErrorView + +__author__ = "Gareth Coles" + + +class Error404View(ErrorView): + name = "error_404" + error_code = 404 + + def get(self, error: NotFound): + return "replace me with a template, 404 not found", 404 diff --git a/pysite/views/healthcheck.py b/pysite/views/healthcheck.py new file mode 100644 index 00000000..660c8a96 --- /dev/null +++ b/pysite/views/healthcheck.py @@ -0,0 +1,15 @@ +# coding=utf-8 +from flask import jsonify + +from pysite.base_route import BaseView + + +__author__ = "Gareth Coles" + + +class IndexView(BaseView): + path = "/healthcheck" + name = "healthcheck" + + def get(self): + return jsonify({"status": "ok"}) diff --git a/pysite/views/index.py b/pysite/views/index.py new file mode 100644 index 00000000..2e779003 --- /dev/null +++ b/pysite/views/index.py @@ -0,0 +1,12 @@ +# coding=utf-8 +from pysite.base_route import BaseView + +__author__ = "Gareth Coles" + + +class IndexView(BaseView): + path = "/" + name = "index" + + def get(self): + return "Coming soon:tm:" diff --git a/pysite/views/invite.py b/pysite/views/invite.py new file mode 100644 index 00000000..d035fc99 --- /dev/null +++ b/pysite/views/invite.py @@ -0,0 +1,15 @@ +# coding=utf-8 +from flask import redirect + +from pysite.base_route import BaseView + + +__author__ = "Gareth Coles" + + +class InviteView(BaseView): + path = "/invite" + name = "invite" + + def get(self): + return redirect("http://invite.pythondiscord.com/") diff --git a/requirements-ci.txt b/requirements-ci.txt index 85205d10..1ae17a79 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -8,4 +8,3 @@ flake8-todo flake8-string-format safety dodgy - |