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.
+ ]]>
+
+
+"""