diff --git a/.gitignore b/.gitignore index ecc3336..6e43a82 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ .mypy_cache/ venv/ +instance/ .vscode/ .idea/ diff --git a/Makefile b/Makefile index 8788c91..d505860 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ dev: - FLASK_DEBUG="true" flask run + FLASK_DEBUG="true" python3 -m flask run prod: - flask run + python3 -m gunicorn -w 4 "app:create_app()" format: python3 -m autopep8 -r --in-place flaskapp/ diff --git a/app.py b/app.py index 338aac7..3847282 100644 --- a/app.py +++ b/app.py @@ -1 +1 @@ -from flaskapp.app import app +from flaskapp.app import create_app diff --git a/flaskapp/__init__.py b/flaskapp/__init__.py index 3d5eb81..e69de29 100644 --- a/flaskapp/__init__.py +++ b/flaskapp/__init__.py @@ -1,3 +0,0 @@ -"""Init file for the module""" - -from .app import app diff --git a/flaskapp/app.py b/flaskapp/app.py index ffa173c..31a7105 100644 --- a/flaskapp/app.py +++ b/flaskapp/app.py @@ -1,20 +1,39 @@ -"""Flask web application -main script""" +"""Flask web application main script""" -from pathlib import Path +import os +import secrets from flask import Flask +from . import routes +from . import errors -root = Path('..') -static = str(root / 'static') -tmpl = str(root / 'templates') -app = Flask( - '${REPO_NAME_SNAKE}', - static_folder=static, - template_folder=tmpl, -) +def create_app() -> Flask: + """Flask app factory function""" -if __name__ == '__main__': - app.run() + # Create an app object + app = Flask( + __name__, + static_folder='../static', + template_folder='../templates', + instance_relative_config=True, + ) + # Get the token from environment + # or generate it using secrets + app.config['SECRET_KEY'] = os.getenv( + 'SECRET_KEY', + secrets.token_hex(32), + ) + + # Create instance/ directory + try: + os.makedirs(app.instance_path) + except OSError: + pass + + # Add routes + routes.add_routes(app) + errors.add_routes(app) + + return app diff --git a/flaskapp/errors.py b/flaskapp/errors.py new file mode 100644 index 0000000..d0382d6 --- /dev/null +++ b/flaskapp/errors.py @@ -0,0 +1,48 @@ +"""Flask app error handlers""" + +from pathlib import Path + +from flask import Flask +from flask import render_template + +# Add other HTTP error codes here +CODES = [404, 500] + + +def add_routes(app: Flask) -> None: + """Add all error handlers + + Args: + app (Flask): Flask application + """ + + tmpl_dir = app.template_folder + if tmpl_dir is None: + return + + tmpl = Path(__file__).parent / tmpl_dir + + for code in CODES: + add_handler(app, tmpl, code) + + +def add_handler( + app: Flask, + tmpl: Path, + code: int) -> None: + """Add Flask app error handler. + Only for internal use + + Args: + app (Flask): Flask application + file (str): Template filename + code (int): Error code + """ + + file = f'{code}.html' + + if (tmpl / file).exists(): + + @app.errorhandler(code) + def handler(_e): + return render_template(file), code diff --git a/flaskapp/routes.py b/flaskapp/routes.py new file mode 100644 index 0000000..785b5dd --- /dev/null +++ b/flaskapp/routes.py @@ -0,0 +1,16 @@ +"""Main Flask app routes""" + +from flask import Flask +from flask import render_template + + +def add_routes(app: Flask) -> None: + """Add main routes + + Args: + app (Flask): Flask application + """ + + @app.route('/') + def index(): + return render_template('index.html') diff --git a/requirements.txt b/requirements.txt index f87e031..72e3f57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ flask==2.2.2 +gunicorn==20.1.0 diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..0d629db --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,31 @@ +body { + height: 100vh; + padding: 0; + margin: 0; + font-family: sans-serif; + + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + + background: #fff; + color: #000; +} + +@media (prefers-color-scheme: dark) { + body { + background: #202023; + color: #eee; + } +} + +header { margin-top: 5px; } +footer { margin-bottom: 5px; } + +a { + color: #5b8a55; +} +a:hover { + filter: brightness(120%); +} diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..65e1260 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,4 @@ +addEventListener('load', () => { + document.getElementById('js') + .innerText = new Date().toLocaleString() +}) diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..3faabe7 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %}404{% endblock %} + +{% block content %} +
+ Go to the + main page +
+{% endblock %} diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000..2835a07 --- /dev/null +++ b/templates/500.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}500{% endblock %} + +{% block content %} ++ An error occured while + processing your request. +
++ Please, try again later + or contact web site admin. +
+{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..aa55818 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,18 @@ + + + + + + +