From 88e359069d649c6ce2b47a4f79745bec8506117d Mon Sep 17 00:00:00 2001 From: cottongin Date: Mon, 6 Apr 2026 14:53:49 -0400 Subject: [PATCH] scaffold: project structure, config, Flask app factory, test fixtures Made-with: Cursor --- .gitignore | 8 ++++++ app.py | 29 +++++++++++++++++++++ config.py | 17 ++++++++++++ conftest.py | 6 +++++ pytest.ini | 2 ++ requirements.txt | 9 +++++++ src/__init__.py | 0 src/models.py | 1 + tests/conftest.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 138 insertions(+) create mode 100644 .gitignore create mode 100644 app.py create mode 100644 config.py create mode 100644 conftest.py create mode 100644 pytest.ini create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 src/models.py create mode 100644 tests/conftest.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff979f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +data/ +__pycache__/ +*.pyc +.venv/ +*.egg-info/ +dist/ +build/ +.pytest_cache/ diff --git a/app.py b/app.py new file mode 100644 index 0000000..20be742 --- /dev/null +++ b/app.py @@ -0,0 +1,29 @@ +import os +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +import config + +db = SQLAlchemy() + + +def create_app(): + app = Flask(__name__) + app.config.from_object(config) + app.config["SECRET_KEY"] = os.urandom(24) + + os.makedirs(config.DATA_DIR, exist_ok=True) + os.makedirs(config.IMAGES_DIR, exist_ok=True) + os.makedirs(config.ISSUES_DIR, exist_ok=True) + + db.init_app(app) + + with app.app_context(): + from src import models # noqa: F401 + db.create_all() + + return app + + +if __name__ == "__main__": + app = create_app() + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/config.py b/config.py new file mode 100644 index 0000000..c3bf996 --- /dev/null +++ b/config.py @@ -0,0 +1,17 @@ +import os + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) +DATA_DIR = os.path.join(BASE_DIR, "data") +IMAGES_DIR = os.path.join(DATA_DIR, "images") +ISSUES_DIR = os.path.join(DATA_DIR, "issues") + +SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(DATA_DIR, 'newspaper.db')}" +SQLALCHEMY_TRACK_MODIFICATIONS = False + +FEED_URL = "https://www.plymouthindependent.org/feed/" +FETCH_INTERVAL_HOURS = 1 + +IMAGE_MAX_LANDSCAPE = (800, 480) +IMAGE_MAX_PORTRAIT = (480, 800) + +POLLINATIONS_URL = "https://image.pollinations.ai/prompt/{prompt}?width=800&height=480&nologo=true" diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..7932772 --- /dev/null +++ b/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +def pytest_sessionfinish(session, exitstatus): + if exitstatus == pytest.ExitCode.NO_TESTS_COLLECTED: + session.exitstatus = pytest.ExitCode.OK diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..a635c5c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6adaa4d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +Flask==3.1.* +Flask-SQLAlchemy==3.1.* +APScheduler==3.10.* +feedparser==6.0.* +ebooklib==0.18.* +beautifulsoup4==4.12.* +Pillow==11.* +requests==2.32.* +pytest==8.* diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/src/models.py @@ -0,0 +1 @@ +pass diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1230e91 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,66 @@ +import os +import tempfile +import pytest +from app import create_app, db as _db +import config + + +@pytest.fixture +def app(tmp_path): + config.DATA_DIR = str(tmp_path / "data") + config.IMAGES_DIR = str(tmp_path / "data" / "images") + config.ISSUES_DIR = str(tmp_path / "data" / "issues") + config.SQLALCHEMY_DATABASE_URI = f"sqlite:///{tmp_path / 'test.db'}" + + app = create_app() + app.config["TESTING"] = True + + with app.app_context(): + _db.create_all() + yield app + _db.drop_all() + + +@pytest.fixture +def client(app): + return app.test_client() + + +@pytest.fixture +def db(app): + with app.app_context(): + yield _db + + +SAMPLE_RSS_XML = """ + + + Plymouth Independent + + Test Article One + https://example.com/article-1 + + Mon, 06 Apr 2026 12:00:00 +0000 + + https://example.com/?p=1001 + First article content.

+ + ]]>
+
+ + Test Article Two + https://example.com/article-2 + + Tue, 07 Apr 2026 09:00:00 +0000 + + + https://example.com/?p=1002 + Second article content.

+ ]]>
+
+
+
"""