Add initial web app scaffolding
This commit is contained in:
23
section7/router/__init__.py
Normal file
23
section7/router/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import Callable
|
||||
from typing import NamedTuple
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
from section7.router.error_handler import handle_error
|
||||
from section7.router.home import display_home
|
||||
|
||||
|
||||
class Routable(NamedTuple):
|
||||
"""Structure for storing details for a given route
|
||||
|
||||
:param route: String indicating the Flask URL rule to apply to the route entry
|
||||
:param entrypoint: The callable that Flask should invoke when the route is accessed
|
||||
:param methods: Sequence of HTTP method verbs that should be accepted by the route
|
||||
"""
|
||||
|
||||
route: str
|
||||
entrypoint: Callable
|
||||
methods: Sequence[str] = ("GET",)
|
||||
|
||||
|
||||
ROUTES: Tuple[Routable, ...] = (Routable(route="/", entrypoint=display_home),)
|
||||
122
section7/router/error_handler.py
Normal file
122
section7/router/error_handler.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Custom error handler and error page rendering endpoint"""
|
||||
import enum
|
||||
import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
from typing import Tuple
|
||||
|
||||
import flask
|
||||
import werkzeug
|
||||
|
||||
|
||||
class ErrorColor(enum.Enum):
|
||||
"""Enum of possible colors for the error page to be rendered with
|
||||
|
||||
Colors can be used to indicate the severity of the problem to a user. Generally, red is reserved
|
||||
for fatal errors (errors that the user cannot fix themselves), orange for user input or
|
||||
temporary errors (errors that the user can potentially fix themselves), and blue for
|
||||
informational errors that don't necessarily indicate a problem, but still needs to block user
|
||||
access.
|
||||
|
||||
Names are the intended usage of the color while values are the CSS classes that the page
|
||||
uses for the corresponding color.
|
||||
"""
|
||||
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
INFORMATIONAL = "informational"
|
||||
|
||||
|
||||
class ErrorIcon(enum.Enum):
|
||||
"""Enum of possible icons for the error page
|
||||
|
||||
Names are the intended usage of the icon while values are the Font Awesome CSS class structure
|
||||
for the corresponding icon.
|
||||
"""
|
||||
|
||||
EXCLAMATION = "fas fa-exclamation-triangle"
|
||||
INFORMATION = "fas fa-exclamation-circle"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorDetails:
|
||||
"""Container of details for an error that can be used to render an error page
|
||||
|
||||
:param code: The HTTP response code for the error
|
||||
:param title: Page title/short description of the error
|
||||
:param description: Long description of the error. Ideally should include an action the user
|
||||
can take to debug or fix the problem.
|
||||
:param icon: The icon that should be added to the page
|
||||
:param color: The color of the page highlights
|
||||
"""
|
||||
|
||||
code: int = 500
|
||||
title: str = "Internal Server Error"
|
||||
description: str = "The application encountered an unhandled error while trying to fulfill the request."
|
||||
icon: ErrorIcon = ErrorIcon.EXCLAMATION
|
||||
color: ErrorColor = ErrorColor.ERROR
|
||||
event_id: str = field(default_factory=lambda: uuid.uuid4().hex)
|
||||
|
||||
|
||||
def handle_error(error: Exception) -> Tuple[str, int]:
|
||||
"""Handle an exception raised elsewhere in the application
|
||||
|
||||
:returns: Rendered template of an error page for the raised exception
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
# If the application is running in debug mode then we re-raise the exception so that the
|
||||
# developer gets the helpful browser-rendered stack trace werkzeug provides
|
||||
if flask.current_app.config["DEBUG"]:
|
||||
logger.warning(
|
||||
"Application is running in debug mode, stack trace will be returned to client"
|
||||
)
|
||||
raise error
|
||||
|
||||
details = ErrorDetails()
|
||||
|
||||
if isinstance(error, NotImplementedError):
|
||||
details.title = "Not Implemented"
|
||||
details.description = "The functionality for this resource has not yet been implemented. The resource name is reserved for for future use"
|
||||
details.color = ErrorColor.INFORMATIONAL
|
||||
details.icon = ErrorIcon.INFORMATION
|
||||
|
||||
elif isinstance(error, werkzeug.exceptions.HTTPException):
|
||||
details.code = error.code
|
||||
details.title = error.name
|
||||
details.description = error.description
|
||||
if 400 <= error.code < 500:
|
||||
details.color = ErrorColor.WARNING
|
||||
details.icon = ErrorIcon.INFORMATION
|
||||
|
||||
else:
|
||||
logger.exception(
|
||||
f"Event ID {details.event_id}: unhandled application error at '{flask.request.full_path}'"
|
||||
)
|
||||
|
||||
logger.error(
|
||||
f"Event ID {details.event_id}: Error {details.code} {details.description}"
|
||||
)
|
||||
|
||||
def _render_template(
|
||||
title: str,
|
||||
error: ErrorDetails,
|
||||
) -> str:
|
||||
"""Stub function to type, enumerate, and document the template parameters
|
||||
|
||||
:param title: Value of the page title header
|
||||
:param error: Container of error details about the error to render on the page
|
||||
"""
|
||||
return flask.render_template(
|
||||
"error.html.j2",
|
||||
title=title,
|
||||
error=error,
|
||||
)
|
||||
|
||||
return (
|
||||
_render_template(
|
||||
title=f"{details.title} ({details.code})",
|
||||
error=details,
|
||||
),
|
||||
details.code,
|
||||
)
|
||||
24
section7/router/home.py
Normal file
24
section7/router/home.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Endpoint for displaying the main homepage of the application"""
|
||||
import flask
|
||||
|
||||
|
||||
def display_home() -> str:
|
||||
"""Display the home page
|
||||
|
||||
:returns: A response rendering the home page template
|
||||
"""
|
||||
|
||||
def _render_template() -> str:
|
||||
"""Stub function to type, enumerate, and document the template parameters
|
||||
|
||||
:param version: Semantic version of the currently running application
|
||||
:param common_urls: Container of common application URLs used in mutliple templates
|
||||
:param testers: List of tester names that the logged in user can read to link to for
|
||||
easy access search access
|
||||
:param messages: Optional list of Flask flash messages to render as popup notifications
|
||||
"""
|
||||
return flask.render_template(
|
||||
"home.html.j2",
|
||||
)
|
||||
|
||||
return _render_template()
|
||||
Reference in New Issue
Block a user