From 4e27d3b407aba28041bf59a54cac8290259fbac5 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 17 Sep 2023 09:21:33 -0400 Subject: [PATCH] Adding app factory and settings --- .gitignore | 1 + README.md | 23 +++++++++++++++++++++++ dev-requirements.in | 1 + dev-requirements.txt | 2 ++ env.template | 1 + htmx_contact/__init__.py | 15 +++++++++++++++ htmx_contact/config.py | 5 +++++ htmx_contact/{app.py => main.py} | 8 ++++---- requirements.in | 2 ++ requirements.txt | 17 ++++++++++++++++- tasks.py | 2 +- tests/conftest.py | 26 ++++++++++++++++++++++++-- 12 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 env.template create mode 100644 htmx_contact/config.py rename htmx_contact/{app.py => main.py} (56%) diff --git a/.gitignore b/.gitignore index e8afa11..318d318 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ target/ # Pycharm .idea +.env diff --git a/README.md b/README.md index cfd0f6d..5756dab 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,26 @@ inv --list ``` For a list of current utilities. + + +## Features + +- [x] Application factory + - [x] View Blueprints + - [x] Configuration with Pydantic + - [ ] dotenv support for local dev +- [ ] Sqlalchemy integration + - [ ] Model migrations using Alembic +- [ ] User accounts + - [ ] +- [ ] Email integration +- [ ] Admin portal +- [ ] Hypermedia Application driven by HTMX under `/` +- [ ] JSON API under `/api` route +- [ ] Tilt local development environment +- [ ] Helm Chart Deployment +- [x] Continous Integration with Drone.io + - [x] Lint Checks + - [x] Unit Tests + - [ ] Integration Tests +- [ ] Continuous Delivery pipeline with Drone.io diff --git a/dev-requirements.in b/dev-requirements.in index 1e77b75..0318625 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -5,5 +5,6 @@ pip-tools pre-commit pytest pytest-cov +python-dotenv shellcheck-py==0.9.0.5 ruff diff --git a/dev-requirements.txt b/dev-requirements.txt index d5cead1..8103f8d 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -57,6 +57,8 @@ pytest==7.4.2 # pytest-cov pytest-cov==4.1.0 # via -r dev-requirements.in +python-dotenv==1.0.0 + # via -r dev-requirements.in pyyaml==6.0.1 # via pre-commit ruff==0.0.290 diff --git a/env.template b/env.template new file mode 100644 index 0000000..26283e6 --- /dev/null +++ b/env.template @@ -0,0 +1 @@ +SECRET_KEY=6755ecb085f9c8d2b7da9f6fd3d9b3590758a1602f1a98724abbe204ff525153 diff --git a/htmx_contact/__init__.py b/htmx_contact/__init__.py index e69de29..dc25902 100644 --- a/htmx_contact/__init__.py +++ b/htmx_contact/__init__.py @@ -0,0 +1,15 @@ +from flask import Flask + +from .config import ContactSettings + + +def create_app(config: ContactSettings = None): + app = Flask("htmx_contact") + + app.config.from_object(config if config else ContactSettings()) + + from . import main + + app.register_blueprint(main.bp) + + return app diff --git a/htmx_contact/config.py b/htmx_contact/config.py new file mode 100644 index 0000000..abcb0e6 --- /dev/null +++ b/htmx_contact/config.py @@ -0,0 +1,5 @@ +from pydantic_settings import BaseSettings + + +class ContactSettings(BaseSettings): + SECRET_KEY: bytes diff --git a/htmx_contact/app.py b/htmx_contact/main.py similarity index 56% rename from htmx_contact/app.py rename to htmx_contact/main.py index e102ecf..fbb76b2 100644 --- a/htmx_contact/app.py +++ b/htmx_contact/main.py @@ -1,15 +1,15 @@ -from flask import Flask +from flask import Blueprint from flask import redirect from flask import render_template -app = Flask("htmx_contact") +bp = Blueprint("main", __name__, url_prefix="/") -@app.route("/", methods=["GET"]) +@bp.route("/", methods=["GET"]) def index(): return redirect("/contacts") -@app.route("/contacts", methods=["GET"]) +@bp.route("/contacts", methods=["GET"]) def contacts(): return render_template("contacts.html", message="Hello HTMX") diff --git a/requirements.in b/requirements.in index e39cea1..bfe5eca 100644 --- a/requirements.in +++ b/requirements.in @@ -1,2 +1,4 @@ flask +pydantic +pydantic-settings sqlalchemy diff --git a/requirements.txt b/requirements.txt index 3fa448e..84465d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,8 @@ # # pip-compile requirements.in # +annotated-types==0.5.0 + # via pydantic blinker==1.6.2 # via flask click==8.1.7 @@ -20,9 +22,22 @@ markupsafe==2.1.3 # via # jinja2 # werkzeug +pydantic==2.3.0 + # via + # -r requirements.in + # pydantic-settings +pydantic-core==2.6.3 + # via pydantic +pydantic-settings==2.0.3 + # via -r requirements.in +python-dotenv==1.0.0 + # via pydantic-settings sqlalchemy==2.0.20 # via -r requirements.in typing-extensions==4.7.1 - # via sqlalchemy + # via + # pydantic + # pydantic-core + # sqlalchemy werkzeug==2.3.7 # via flask diff --git a/tasks.py b/tasks.py index 2f96d15..1c515dc 100644 --- a/tasks.py +++ b/tasks.py @@ -20,7 +20,7 @@ def serve_dev(c, debugger=True, reload=True, threads=True, port=8888, host="0.0. @task def test(c): """executes test suite""" - c.run("pytest -vvv tests/", pty=True) + c.run("pytest -vvv --cov tests/", pty=True) @task diff --git a/tests/conftest.py b/tests/conftest.py index de9d3c5..8844909 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,32 @@ import pytest -from htmx_contact.app import app +from htmx_contact import create_app +from htmx_contact.config import ContactSettings @pytest.fixture() -def client(): +def test_config(): + return ContactSettings(SECRET_KEY=b"secrettestingvalue") + + +@pytest.fixture() +def app(test_config): + app = create_app(config=test_config) + app.config.update( + { + "TESTING": True, + } + ) + + # other setup can go here + + yield app + + # clean up / reset resources here + + +@pytest.fixture() +def client(app): return app.test_client()