Skip to content

Commit

Permalink
♻️ Refactor app configuration into modular files (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
connormaglynn committed Feb 8, 2024
1 parent cf6e3f8 commit bdfc932
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 83 deletions.
91 changes: 23 additions & 68 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,32 @@
import logging

import sentry_sdk
from flask import Flask
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
from sentry_sdk.integrations.flask import FlaskIntegration

from app.app_config import app_config
from app.main.middleware.error_handler import (
client_error,
page_not_found,
server_forbidden,
unknown_server_error,
)
from app.main.routes.auth import auth_route
from app.main.routes.join import join_route
from app.main.routes.main import main
from app.main.routes.robots import robot_route
from app.main.config.app_config import app_config
from app.main.config.cors_config import configure_cors
from app.main.config.error_handlers_config import configure_error_handlers
from app.main.config.jinja_config import configure_jinja
from app.main.config.limiter_config import configure_limiter
from app.main.config.logging_config import configure_logging
from app.main.config.routes_config import configure_routes
from app.main.config.sentry_config import configure_sentry
from app.main.services.auth0_service import Auth0_Service
from app.main.services.github_service import GithubService

logger = logging.getLogger(__name__)


def create_app(
github_service=GithubService(app_config.github.token), rate_limit: bool = True
github_service=GithubService(app_config.github.token), is_rate_limit_enabled=True
) -> Flask:
logging.basicConfig(
format="%(asctime)s %(levelname)s in %(module)s: %(message)s",
)
configure_logging(app_config.logging_level)

if app_config.sentry.dsn_key and app_config.sentry.environment:
sentry_sdk.init(
dsn=app_config.sentry.dsn_key,
environment=app_config.sentry.environment,
integrations=[FlaskIntegration()],
enable_tracing=True,
traces_sample_rate=0.1,
)
logger.info("Starting app...")

app = Flask(__name__, instance_relative_config=True)

app.logger.info("Start App Setup")

limiter = Limiter(
get_remote_address,
app=app,
default_limits=["10 per minute", "2 per second"],
storage_uri="memory://",
strategy="moving-window",
)

limiter.init_app(app)
limiter.enabled = rate_limit
app = Flask(__name__)

app.secret_key = app_config.flask.app_secret_key

app.register_blueprint(auth_route, url_prefix="/auth")
app.register_blueprint(join_route, url_prefix="/join")
app.register_blueprint(main)
app.register_blueprint(robot_route)

app.jinja_loader = ChoiceLoader(
[
PackageLoader("app"),
PrefixLoader(
{"govuk_frontend_jinja": PackageLoader("govuk_frontend_jinja")}
),
]
)
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
app.jinja_env.globals["phase_banner_text"] = app_config.phase_banner_text

app.register_error_handler(400, client_error)
app.register_error_handler(403, server_forbidden)
app.register_error_handler(404, page_not_found)
app.register_error_handler(500, unknown_server_error)

CORS(app, resources={r"/*": {"origins": "*", "send_wildcard": "False"}})

app.github_service = github_service
app.auth0_service = Auth0_Service(
app,
Expand All @@ -88,5 +35,13 @@ def create_app(
app_config.auth0.domain,
)

app.logger.info("App Setup complete, running App...")
configure_routes(app)
configure_error_handlers(app)
configure_sentry(app_config.sentry.dsn_key, app_config.sentry.environment)
configure_limiter(app, is_rate_limit_enabled)
configure_jinja(app)
configure_cors(app)

logger.info("Running app...")

return app
Empty file added app/main/config/__init__.py
Empty file.
22 changes: 11 additions & 11 deletions app/app_config.py → app/main/config/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def __get_env_var_as_boolean(name: str) -> bool | None:
auth0=SimpleNamespace(
domain=__get_env_var("AUTH0_DOMAIN"),
client_id=__get_env_var("AUTH0_CLIENT_ID"),
client_secret=__get_env_var("AUTH0_CLIENT_SECRET")
client_secret=__get_env_var("AUTH0_CLIENT_SECRET"),
),
flask=SimpleNamespace(
app_secret_key=__get_env_var("APP_SECRET_KEY")
app_secret_key=__get_env_var("APP_SECRET_KEY"),
),
github=SimpleNamespace(
send_email_invites_is_enabled=__get_env_var_as_boolean("SEND_EMAIL_INVITES"),
Expand All @@ -40,29 +40,29 @@ def __get_env_var_as_boolean(name: str) -> bool | None:
"judiciary.uk",
"ppo.gov.uk",
"sentencingcouncil.gov.uk",
"yjb.gov.uk"
"yjb.gov.uk",
],
organisations=[
SimpleNamespace(
name="ministryofjustice",
enabled=__get_env_var_as_boolean("MOJ_ORG_ENABLED"),
display_text="Ministry of Justice"
display_text="Ministry of Justice",
),
SimpleNamespace(
name="moj-analytical-services",
enabled=__get_env_var_as_boolean("MOJ_AS_ORG_ENABLED"),
display_text="MoJ Analytical Services"
display_text="MoJ Analytical Services",
),
SimpleNamespace(
name="ministryofjustice-test",
enabled=__get_env_var_as_boolean("MOJ_TEST_ORG_ENABLED"),
display_text="Ministry of Justice Test Organisation"
)
]
display_text="Ministry of Justice Test Organisation",
),
],
),
logging_level=__get_env_var("LOGGING_LEVEL"),
phase_banner_text=__get_env_var("PHASE_BANNER_TEXT"),
sentry=SimpleNamespace(
dsn_key=__get_env_var("SENTRY_DSN_KEY"),
environment=__get_env_var("SENTRY_ENV")
)
dsn_key=__get_env_var("SENTRY_DSN_KEY"), environment=__get_env_var("SENTRY_ENV")
),
)
6 changes: 6 additions & 0 deletions app/main/config/cors_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask import Flask
from flask_cors import CORS


def configure_cors(app: Flask) -> None:
CORS(app, resources={r"/*": {"origins": "*", "send_wildcard": "False"}})
15 changes: 15 additions & 0 deletions app/main/config/error_handlers_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from flask import Flask

from app.main.middleware.error_handler import (
client_error,
page_not_found,
server_forbidden,
unknown_server_error,
)


def configure_error_handlers(app: Flask) -> None:
app.register_error_handler(400, client_error)
app.register_error_handler(403, server_forbidden)
app.register_error_handler(404, page_not_found)
app.register_error_handler(500, unknown_server_error)
18 changes: 18 additions & 0 deletions app/main/config/jinja_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from flask import Flask
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader

from app.main.config.app_config import app_config


def configure_jinja(app: Flask) -> None:
app.jinja_loader = ChoiceLoader(
[
PackageLoader("app"),
PrefixLoader(
{"govuk_frontend_jinja": PackageLoader("govuk_frontend_jinja")}
),
]
)
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
app.jinja_env.globals["phase_banner_text"] = app_config.phase_banner_text
14 changes: 14 additions & 0 deletions app/main/config/limiter_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address


def configure_limiter(app: Flask, is_rate_limit_enabled: bool = True) -> None:
Limiter(
get_remote_address,
app=app,
default_limits=["10 per minute", "2 per second"],
storage_uri="memory://",
strategy="moving-window",
enabled=is_rate_limit_enabled,
)
10 changes: 10 additions & 0 deletions app/main/config/logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import logging


def configure_logging(logging_level: str) -> None:
logging.basicConfig(
format="{asctime:s} | {levelname:>8s} | {filename:s}:{lineno:d} | {message:s}",
datefmt="%Y-%m-%dT%H:%M:%S",
style="{",
level=logging_level.upper() if logging_level else "INFO",
)
13 changes: 13 additions & 0 deletions app/main/config/routes_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from flask import Flask

from app.main.routes.auth import auth_route
from app.main.routes.join import join_route
from app.main.routes.main import main
from app.main.routes.robots import robot_route


def configure_routes(app: Flask) -> None:
app.register_blueprint(auth_route, url_prefix="/auth")
app.register_blueprint(join_route, url_prefix="/join")
app.register_blueprint(main)
app.register_blueprint(robot_route)
29 changes: 29 additions & 0 deletions app/main/config/sentry_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging

import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

logger = logging.getLogger(__name__)


def configure_sentry(dsn_key: str, environment: str) -> None:
if not dsn_key:
logger.warning("Missing Sentry DSN Key")

if not environment:
logger.warning("Missing Sentry Environment")

if dsn_key and environment:
logger.info(f"Configuring Sentry for environment [ {environment} ]")
sentry_sdk.init(
dsn=dsn_key,
environment=environment,
integrations=[FlaskIntegration()],
enable_tracing=True,
traces_sample_rate=0.1,
)
logger.info("Sentry configured successfully")
else:
logger.warning(
"Sentry not configured due to either missing DSN Key or Environment"
)
2 changes: 1 addition & 1 deletion app/main/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from flask import Blueprint, current_app, redirect, session, url_for

from app.app_config import app_config
from app.main.config.app_config import app_config
from app.main.services.auth0_service import Auth0_Service

logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion app/main/routes/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
url_for,
)

from app.app_config import app_config
from app.main.config.app_config import app_config
from app.main.middleware.auth import requires_auth
from app.main.validators.index import is_valid_email_pattern

Expand Down
2 changes: 1 addition & 1 deletion app/main/services/github_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import requests
from github import Github

from app.app_config import app_config
from app.main.config.app_config import app_config

logger = logging.getLogger(__name__)

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ services:
MOJ_TEST_ORG_ENABLED: true
SEND_EMAIL_INVITES: true
PHASE_BANNER_TEXT: "LOCAL DEV"
LOGGING_LEVEL: "DEBUG"
ports:
- "4567:4567"
2 changes: 1 addition & 1 deletion tests/test_app_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import unittest
from unittest.mock import patch
from app.app_config import __get_env_var_as_boolean as get_env_var_as_boolean
from app.main.config.app_config import __get_env_var_as_boolean as get_env_var_as_boolean


class TestGetEnvVarAsBoolean(unittest.TestCase):
Expand Down

0 comments on commit bdfc932

Please sign in to comment.