scaffold: project structure, config, Flask app factory, test fixtures
Made-with: Cursor
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
data/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.pytest_cache/
|
||||||
29
app.py
Normal file
29
app.py
Normal file
@@ -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)
|
||||||
17
config.py
Normal file
17
config.py
Normal file
@@ -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"
|
||||||
6
conftest.py
Normal file
6
conftest.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
|
if exitstatus == pytest.ExitCode.NO_TESTS_COLLECTED:
|
||||||
|
session.exitstatus = pytest.ExitCode.OK
|
||||||
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[pytest]
|
||||||
|
pythonpath = .
|
||||||
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@@ -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.*
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
1
src/models.py
Normal file
1
src/models.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pass
|
||||||
66
tests/conftest.py
Normal file
66
tests/conftest.py
Normal file
@@ -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 = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0"
|
||||||
|
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<channel>
|
||||||
|
<title>Plymouth Independent</title>
|
||||||
|
<item>
|
||||||
|
<title>Test Article One</title>
|
||||||
|
<link>https://example.com/article-1</link>
|
||||||
|
<dc:creator><![CDATA[Test Author]]></dc:creator>
|
||||||
|
<pubDate>Mon, 06 Apr 2026 12:00:00 +0000</pubDate>
|
||||||
|
<category><![CDATA[Government]]></category>
|
||||||
|
<guid isPermaLink="false">https://example.com/?p=1001</guid>
|
||||||
|
<content:encoded><![CDATA[
|
||||||
|
<p>First article content.</p>
|
||||||
|
<img src="https://example.com/image1.jpg" width="1024" height="768" />
|
||||||
|
]]></content:encoded>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>Test Article Two</title>
|
||||||
|
<link>https://example.com/article-2</link>
|
||||||
|
<dc:creator><![CDATA[Another Author]]></dc:creator>
|
||||||
|
<pubDate>Tue, 07 Apr 2026 09:00:00 +0000</pubDate>
|
||||||
|
<category><![CDATA[Culture Calendar]]></category>
|
||||||
|
<category><![CDATA[Feature]]></category>
|
||||||
|
<guid isPermaLink="false">https://example.com/?p=1002</guid>
|
||||||
|
<content:encoded><![CDATA[
|
||||||
|
<p>Second article content.</p>
|
||||||
|
]]></content:encoded>
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>"""
|
||||||
Reference in New Issue
Block a user