merge: combine local dev updates into dev
This commit is contained in:
+7
-69
@@ -6,67 +6,16 @@
|
||||
- Add dependencies: `uv add <package>`
|
||||
- All dependencies declared in `pyproject.toml`
|
||||
|
||||
## Process Entrypoints
|
||||
## Code Quality Checks
|
||||
|
||||
### Bootstrap Gate (REQUIRED)
|
||||
**Git pre-commit hook enforces code quality before commit.**
|
||||
|
||||
**The bootstrap gate is the ONLY allowed entry point for deployment.**
|
||||
Pre-commit hook automatically runs on backend/ directory:
|
||||
- `ruff check` - code style and linting
|
||||
- `basedpyright` - type checking with error level
|
||||
|
||||
```bash
|
||||
# Using Makefile (recommended)
|
||||
make runtime-bootstrap-gate
|
||||
|
||||
# Or directly using the script
|
||||
bash infra/scripts/runtime-bootstrap-gate.sh
|
||||
```
|
||||
|
||||
This gate:
|
||||
1. Runs `init-job bootstrap` (migrate + init-data)
|
||||
2. Starts web and worker services
|
||||
3. Aborts if bootstrap fails (prevents web/worker startup)
|
||||
|
||||
**Deployment without passing the bootstrap gate is PROHIBITED.**
|
||||
|
||||
### New Entrypoints (Phase 1-2, 2026-02-24)
|
||||
|
||||
**Primary (recommended):** Use Docker Compose orchestration.
|
||||
|
||||
```bash
|
||||
# Bootstrap gate (required before web/worker)
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml run --rm init-job bootstrap
|
||||
|
||||
# Web
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml up -d web
|
||||
|
||||
# Worker (grouped)
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml up -d \
|
||||
worker-critical worker-default worker-bulk
|
||||
```
|
||||
|
||||
**One-shot jobs:**
|
||||
```bash
|
||||
# Migrate only
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml run --rm init-job migrate
|
||||
|
||||
# Init data only
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml run --rm init-job init-data
|
||||
|
||||
# Full bootstrap (migrate + init-data)
|
||||
docker compose --env-file .env -f infra/docker/docker-compose.yml run --rm init-job bootstrap
|
||||
```
|
||||
|
||||
### One-shot CLI (local development)
|
||||
|
||||
```bash
|
||||
# Bootstrap (migrate + init-data)
|
||||
PYTHONPATH=backend/src uv run python -m core.runtime.cli bootstrap
|
||||
|
||||
# Migrate only
|
||||
PYTHONPATH=backend/src uv run python -m core.runtime.cli migrate
|
||||
|
||||
# Init data only
|
||||
PYTHONPATH=backend/src uv run python -m core.runtime.cli init-data
|
||||
```
|
||||
If any error detected, commit is rejected. Fix errors before committing.
|
||||
Do not bypass or weaken checks (no ignores, disables, or config relaxations). Resolve the underlying issues.
|
||||
|
||||
## Logging
|
||||
|
||||
@@ -94,17 +43,6 @@ PYTHONPATH=backend/src uv run python -m core.runtime.cli init-data
|
||||
- Tests can set env vars via `monkeypatch.setenv`, and should read values via `Settings()` unless the test is explicitly validating env plumbing
|
||||
- Canonical principle: one source of truth per setting; no duplicate/derived env vars in backend code
|
||||
|
||||
## Code Quality Checks
|
||||
|
||||
**Git pre-commit hook enforces code quality before commit.**
|
||||
|
||||
Pre-commit hook automatically runs on backend/ directory:
|
||||
- `ruff check` - code style and linting
|
||||
- `basedpyright` - type checking with error level
|
||||
|
||||
If any error detected, commit is rejected. Fix errors before committing.
|
||||
Do not bypass or weaken checks (no ignores, disables, or config relaxations). Resolve the underlying issues.
|
||||
|
||||
## TDD First Policy
|
||||
|
||||
**Principle: tests before implementation.**
|
||||
|
||||
+13
-1
@@ -9,12 +9,17 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from core.config.settings import config
|
||||
from core.http.models import HealthResponse
|
||||
from core.http.response import build_problem_details
|
||||
from core.logging import configure_logging, get_logger
|
||||
from core.logging import configure_logging, get_logger, log_service_banner
|
||||
from v1.router import router as mobile_router
|
||||
|
||||
|
||||
configure_logging(config)
|
||||
|
||||
log_service_banner(
|
||||
service_name=config.runtime.service_name,
|
||||
environment=config.runtime.environment,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -26,6 +31,13 @@ app.add_middleware(
|
||||
app.include_router(mobile_router)
|
||||
logger = get_logger("api.app")
|
||||
|
||||
logger.info(
|
||||
"Web application initialized",
|
||||
environment=config.runtime.environment,
|
||||
debug=config.runtime.debug,
|
||||
log_level=config.runtime.log_level,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health", response_model=HealthResponse)
|
||||
async def health() -> HealthResponse:
|
||||
|
||||
@@ -43,8 +43,6 @@ def create_celery_app() -> Celery:
|
||||
worker_prefetch_multiplier=1,
|
||||
)
|
||||
|
||||
app.autodiscover_tasks(["tasks"])
|
||||
|
||||
configure_celery_app(app, settings=config)
|
||||
|
||||
return app
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from core.logging import celery
|
||||
from core.logging.banner import log_service_banner
|
||||
from core.logging.config import configure_logging
|
||||
from core.logging.context import bind_context, clear_context, get_context
|
||||
from core.logging.logger import get_logger
|
||||
@@ -12,4 +13,5 @@ __all__ = [
|
||||
"configure_logging",
|
||||
"get_context",
|
||||
"get_logger",
|
||||
"log_service_banner",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import structlog
|
||||
|
||||
|
||||
def build_service_banner(service_name: str, environment: str) -> str:
|
||||
service_upper = service_name.upper()
|
||||
border = "=" * 50
|
||||
lines = [
|
||||
border,
|
||||
f" {service_upper}",
|
||||
f" Environment: {environment}",
|
||||
border,
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def log_service_banner(service_name: str, environment: str) -> None:
|
||||
logger = structlog.get_logger("banner")
|
||||
banner = build_service_banner(service_name, environment)
|
||||
logger.info(banner)
|
||||
@@ -7,6 +7,7 @@ from typing import cast
|
||||
from celery import Celery, signals
|
||||
|
||||
from core.config.settings import Settings
|
||||
from core.logging.banner import log_service_banner
|
||||
from core.logging.config import configure_logging
|
||||
from core.logging.context import bind_context, clear_context
|
||||
|
||||
@@ -22,8 +23,14 @@ class CelerySignalHandlers:
|
||||
def build_celery_signal_handlers(
|
||||
settings: Settings | None = None,
|
||||
) -> CelerySignalHandlers:
|
||||
active_settings = settings or Settings()
|
||||
|
||||
def on_setup_logging(*_args: object, **_kwargs: object) -> None:
|
||||
configure_logging(settings)
|
||||
log_service_banner(
|
||||
service_name=active_settings.runtime.service_name,
|
||||
environment=active_settings.runtime.environment,
|
||||
)
|
||||
|
||||
def on_after_setup_task_logger(*_args: object, **_kwargs: object) -> None:
|
||||
configure_logging(settings)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from core.logging.banner import build_service_banner
|
||||
|
||||
|
||||
def test_build_service_banner_contains_service_name() -> None:
|
||||
banner = build_service_banner(
|
||||
service_name="web",
|
||||
environment="dev",
|
||||
)
|
||||
|
||||
assert "WEB" in banner
|
||||
assert "dev" in banner
|
||||
|
||||
|
||||
def test_build_service_banner_uppercases_service_name() -> None:
|
||||
banner = build_service_banner(
|
||||
service_name="worker-critical",
|
||||
environment="prod",
|
||||
)
|
||||
|
||||
assert "WORKER-CCRITICAL" in banner.upper() or "WORKER" in banner
|
||||
|
||||
|
||||
def test_build_service_banner_includes_border() -> None:
|
||||
banner = build_service_banner(
|
||||
service_name="web",
|
||||
environment="dev",
|
||||
)
|
||||
|
||||
lines = banner.strip().split("\n")
|
||||
assert len(lines) >= 3
|
||||
assert all(line.startswith("=") or "WEB" in line or "dev" in line for line in lines)
|
||||
Reference in New Issue
Block a user