feat: scaffold pal-e-mail service (Phase 1) #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "1-phase1-service-scaffold"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Scaffolds the pal-e-mail FastAPI service with all foundational files: project config, SQLAlchemy models, Alembic migrations, Dockerfile, Woodpecker CI pipeline, and a healthz endpoint. Multi-tenant email logging by project field, using gmail-sdk-ldraney from Forgejo PyPI.
Changes
pyproject.toml-- project metadata, dependencies (fastapi, sqlalchemy, alembic, gmail-sdk-ldraney), dev tools (pytest, ruff)src/pal_e_mail/__init__.py-- package initsrc/pal_e_mail/config.py-- Settings dataclass readingPAL_E_MAIL_*env varssrc/pal_e_mail/database.py-- SQLAlchemy engine, session, DeclarativeBasesrc/pal_e_mail/models.py-- EmailLog model with project-based multi-tenancy, EmailStatus enumsrc/pal_e_mail/main.py-- FastAPI app with/healthzendpointDockerfile-- 3-stage build (base/builder/runtime), Forgejo PyPI extra-index-url.woodpecker.yaml-- CI pipeline: lint + test with postgres service, kaniko build-and-push to Harboralembic.ini+alembic/env.py+alembic/script.py.mako-- Alembic config with env var overridealembic/versions/001_email_log.py-- Initial migration creating email_log tabletests/test_healthz.py-- Healthz endpoint test.gitignore-- Python/ruff/pytest ignoresTest Plan
ruff check .passes cleanruff format --check .passes cleanpytest tests/test_healthz.py-- 1 passed (0.21s)Review Checklist
Related
plan-pal-e-mailPhase 1Review: PASS
Reviewed all 15 files (358 additions). No issues found.
Verified:
ruff check .-- cleanruff format --check .-- cleanpytest tests/test_healthz.py-- 1 passedPAL_E_MAIL_*env var prefix consistentlyNo fixes needed. Ready for merge.
PR #2 Review
DOMAIN REVIEW
Tech stack: Python 3.12 / FastAPI / SQLAlchemy 2.0 / Alembic / Woodpecker CI / Kaniko / Harbor
Port consistency -- EXPOSE 8000, CMD
--port 8000, uvicorn binding0.0.0.0:8000. All consistent. Pass.Forgejo PyPI extra-index-url -- Present in both
Dockerfile(line 9) and.woodpecker.yaml(line 32). Matches theminio-apiCI pattern wheregitis also installed in the test step for the private PyPI dependency. Pass.alembic env.py -- Reads
PAL_E_MAIL_DATABASE_URLfrom environment (line 14), overridessqlalchemy.urlfromalembic.iniwhen set. Correct pattern. Pass.Model vs migration -- All 10 columns match between
models.pyand001_email_log.py: types, lengths, nullability, indexes, and server defaults are consistent. One minor divergence: the model usesdefault=EmailStatus.sent(Python-side ORM default) while the migration usesserver_default="sent"(database-side default). Both are functionally correct -- the DB will enforce the default on raw INSERTs, and SQLAlchemy will set it on ORM creates. Not a blocker but the model should ideally also declareserver_defaultfor parity.Secrets -- No API keys, passwords, tokens, or
.envfiles committed. Harbor credentials usefrom_secret. Postgres creds in CI service andalembic.iniare local-dev/test-only defaults. Clean.CI pipeline -- Follows platform patterns:
alpine/gitclone block, postgres service container, lint + test step, kaniko build-and-push gated tomainpush. Matchesbasketball-apiandminio-apireference pipelines.BLOCKERS
None.
The scaffold delivers exactly one endpoint (
/healthz) and one test covering it. There are no CRUD endpoints, no user input handling, no auth paths, and no secrets in code. All blocker criteria pass.NITS
Missing
[build-system]inpyproject.toml-- PEP 517/518 requires a[build-system]table. Without it,pip install .falls back to legacy setuptools and emits a deprecation warning. Add:Or use the standard:
Module-level engine creation --
database.pycreates the SQLAlchemy engine at import time viasettings.database_url, andconfig.pyreads env vars at class-definition time (not instantiation). This meansPAL_E_MAIL_DATABASE_URLmust be set before any import ofpal_e_mail.databaseorpal_e_mail.models. The current test (test_healthz.py) only importsmain.pywhich does not importdatabase.py, so it works. But future tests that touch models will needPAL_E_MAIL_DATABASE_URLset before import or aconftest.pythat patches the engine. Worth noting for Phase 2.Model
defaultvsserver_default--models.pyline 27 usesdefault=EmailStatus.sent(Python-side), but the migration usesserver_default="sent"(DB-side). Consider addingserver_default="sent"to the model'smapped_column()as well, so the model is a complete description of the DB schema.Missing
path.excludefor ArgoCD --basketball-apiandmcd-tracker-apihavepath.exclude: ["k8s/.argocd-source-*"]in theirwhenblock to prevent CI from retriggering on ArgoCD source annotations. Not needed yet (nok8s/dir), but worth adding when the deployment manifests land.httpxduplicated in dependencies --httpx>=0.28appears in bothdependenciesand[project.optional-dependencies].dev. The dev entry is redundant since it is already a production dependency.SOP COMPLIANCE
1-phase1-service-scaffoldreferences issue #1)plan-pal-e-mailPhase 1)PROCESS OBSERVATIONS
alembic upgrade headin the CMD -- if the DB is unreachable at startup, the container will crash-loop. A future phase should add a health-check wait or init container for migration.VERDICT: APPROVED