Compare commits

..

3 Commits

Author SHA1 Message Date
Barcode Betty 5a6f4cd44c fix(api): document dispose_engine lazy import + regression test (CAR-1135)
CI / lint (pull_request) Failing after 5s
CI / typecheck (pull_request) Failing after 33s
CI / test (pull_request) Failing after 1m20s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
- main.py: add docstring inside the lifespan function explaining why
  dispose_engine is lazy-imported rather than top-level. The original
  import path (top-level) crashed the container at import time with
  'ImportError: cannot import name dispose_engine from cartsnitch_api.database'
  when database.py was stale or stripped during a CI build. Lazy import
  keeps the engine disposal behavior while preventing the module-load
  crash.
- tests/test_openapi.py: add test_dispose_engine_importable_from_database
  that asserts dispose_engine is importable and callable. This is the
  exact path the deployed UAT image was failing on, captured as a
  regression test so a future regression lands in CI before deploy.

Refs CAR-1135.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-03 12:16:03 +00:00
Savannah Savings 71cf0a4563 Merge pull request 'ci: migrate from ghcr.io to Gitea built-in registry' (#25) from fix/cart-995-gitea-registry-migration into dev
CI / lint (push) Failing after 5s
CI / lint (pull_request) Failing after 5s
CI / typecheck (pull_request) Failing after 16s
CI / typecheck (push) Failing after 30s
CI / test (push) Failing after 51s
CI / build-and-push (push) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (push) Has been skipped
CI / test (pull_request) Failing after 1m51s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (push) Failing after 32s
CI / deploy-uat (pull_request) Has been skipped
ci: migrate from ghcr.io to Gitea built-in registry (#25)

CAR-995: Update CI workflow to use Gitea built-in container registry.
- REGISTRY env var: ghcr.io -> git.farh.net
- Replace Docker Hub/GHCR login with direct docker login using github.token
- Remove Docker Hub credentials from service containers
- Update deploy kustomize image refs to use env vars
2026-05-23 22:31:36 +00:00
Barcode Betty 9659e63208 ci: migrate from ghcr.io to Gitea built-in registry
CI / lint (pull_request) Failing after 8s
CI / typecheck (pull_request) Failing after 29s
CI / test (pull_request) Failing after 50s
CI / build-and-push (pull_request) Has been skipped
CI / deploy-dev (pull_request) Has been skipped
CI / deploy-uat (pull_request) Has been skipped
- Update REGISTRY env var: ghcr.io -> git.farh.net
- Replace Docker Hub + GHCR login with Gitea login step
- Remove credentials blocks from postgres and redis service definitions
- Update deploy-dev/deploy-uat kustomize image refs to use $REGISTRY var

Fixes QA FAIL from PR #23: missing Gitea login step.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-23 22:14:55 +00:00
5 changed files with 32 additions and 27 deletions
+6 -23
View File
@@ -15,7 +15,7 @@ permissions:
packages: write
env:
REGISTRY: ghcr.io
REGISTRY: git.farh.net
IMAGE_NAME: cartsnitch/api
jobs:
@@ -51,9 +51,6 @@ jobs:
services:
postgres:
image: postgres:15-alpine
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: cartsnitch
POSTGRES_PASSWORD: cartsnitch_test
@@ -67,9 +64,6 @@ jobs:
--health-retries 5
redis:
image: redis:7-alpine
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
ports:
- 6379:6379
options: >-
@@ -122,19 +116,8 @@ jobs:
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "CalVer tag: $VERSION"
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GHCR
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Gitea Container Registry
run: echo "${{ github.token }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin
- name: Extract metadata
id: meta
@@ -171,7 +154,7 @@ jobs:
only-fixed: "true"
output-format: sarif
- name: Push Docker image
if: github.event_name == 'push'
@@ -224,7 +207,7 @@ jobs:
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/dev
kustomize edit set image ghcr.io/cartsnitch/api:${{ steps.api_tag.outputs.tag }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.api_tag.outputs.tag }}
- name: Commit and push to infra
run: |
@@ -268,7 +251,7 @@ jobs:
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/uat
kustomize edit set image ghcr.io/cartsnitch/api:${{ steps.api_tag.outputs.tag }}
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.api_tag.outputs.tag }}
- name: Commit and push to infra
run: |
+1 -2
View File
@@ -5,8 +5,7 @@ Sessions are verified by querying the shared sessions table directly.
"""
from datetime import UTC, datetime
from fastapi import Depends, Header, HTTPException, Request, status
from fastapi import Cookie, Depends, Header, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
+3
View File
@@ -6,10 +6,13 @@ endpoints that query our own user data from the shared database.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from cartsnitch_api.auth.dependencies import get_current_user
from cartsnitch_api.database import get_db
from cartsnitch_api.models import User
from cartsnitch_api.schemas import (
UpdateUserRequest,
UserResponse,
+9 -2
View File
@@ -6,11 +6,10 @@ from fastapi import APIRouter, FastAPI
from cartsnitch_api.auth.routes import router as auth_router
from cartsnitch_api.cache import cache_client
from cartsnitch_api.database import dispose_engine
from cartsnitch_api.middleware.audit import add_audit_middleware
from cartsnitch_api.middleware.cors import add_cors_middleware
from cartsnitch_api.middleware.error_handler import add_error_handlers, add_error_monitor_middleware
from cartsnitch_api.middleware.rate_limit import add_rate_limit_middleware
from cartsnitch_api.middleware.audit import add_audit_middleware
from cartsnitch_api.routes.alerts import router as alerts_router
from cartsnitch_api.routes.coupons import router as coupons_router
from cartsnitch_api.routes.health import router as health_router
@@ -26,6 +25,14 @@ from cartsnitch_api.routes.user import router as user_router
@asynccontextmanager
async def lifespan(app: FastAPI):
# Lazy import: keep `dispose_engine` out of the top-level imports so a
# stale or partially-built database.py never breaks module load on
# container start. The function is required for graceful pool cleanup
# on shutdown; if the import fails, the cache_client.close() that
# follows the yield would mask it. See CAR-1135 for the original
# ImportError that motivated this pattern.
from cartsnitch_api.database import dispose_engine
await cache_client.initialize()
yield
await cache_client.close()
+13
View File
@@ -3,8 +3,21 @@
import pytest
from httpx import ASGITransport, AsyncClient
from cartsnitch_api.database import dispose_engine
from cartsnitch_api.main import app
def test_dispose_engine_importable_from_database():
"""Regression for CAR-1135: api main.py used to import dispose_engine
at module level. A stale database.py (no dispose_engine) crashed the
container at import time with ImportError on line 9. The fix moved
the import inside the lifespan function, but `dispose_engine` must
still be importable from `cartsnitch_api.database` for the lifespan
teardown to actually close pooled connections.
"""
assert callable(dispose_engine)
assert dispose_engine.__name__ == "dispose_engine"
EXPECTED_ROUTES = [
# Auth (7)
("post", "/auth/register"),