Some checks failed
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
Mandatory pre-push gates for all three language stacks with exact commands, common pitfalls, and architecture rules. CLAUDE.md updated with quick-reference section linking to the new files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
158 lines
4.7 KiB
Markdown
158 lines
4.7 KiB
Markdown
# AGENTS.python.md — Python Agent Rules
|
|
|
|
Applies to: `backend-compliance/`, `ai-compliance-sdk/` (Python path), `compliance-tts-service/`, `document-crawler/`, `dsms-gateway/` (Python services)
|
|
|
|
---
|
|
|
|
## NON-NEGOTIABLE: Pre-Push Checklist
|
|
|
|
**BEFORE every `git push`, run ALL of the following from the service directory. A single failure blocks the push.**
|
|
|
|
```bash
|
|
# 1. Fast lint (Ruff — catches syntax errors, unused imports, style violations)
|
|
ruff check .
|
|
|
|
# 2. Auto-fix safe issues, then re-check
|
|
ruff check --fix . && ruff check .
|
|
|
|
# 3. Type checking (mypy strict on new modules, standard on legacy)
|
|
mypy . --ignore-missing-imports --no-error-summary
|
|
|
|
# 4. Unit tests only (fast, no external deps)
|
|
pytest tests/unit/ -x -q --no-header
|
|
|
|
# 5. Verify the service starts (catches import errors, missing env vars with defaults)
|
|
python -c "import app" 2>/dev/null || python -c "import main" 2>/dev/null || true
|
|
```
|
|
|
|
**One-liner pre-push gate (run from service root):**
|
|
```bash
|
|
ruff check . && mypy . --ignore-missing-imports --no-error-summary && pytest tests/ -x -q --no-header
|
|
```
|
|
|
|
### Why each check matters
|
|
|
|
| Check | Catches | Time |
|
|
|-------|---------|------|
|
|
| `ruff check` | Syntax errors, unused imports, undefined names | <2s |
|
|
| `mypy` | Type mismatches, wrong argument types | 5-15s |
|
|
| `pytest -x` | Logic errors, regressions | 10-60s |
|
|
| import check | Missing packages, circular imports | <1s |
|
|
|
|
---
|
|
|
|
## Code Style (Ruff)
|
|
|
|
Config lives in `pyproject.toml`. Do **not** add per-file `# noqa` suppressions without a comment explaining why.
|
|
|
|
```toml
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py311"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "TCH"]
|
|
ignore = ["E501"] # line length handled by formatter
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"tests/*" = ["S101"] # assert is fine in tests
|
|
```
|
|
|
|
**Blocked patterns:**
|
|
- `from module import *` — always name imports explicitly
|
|
- Bare `except:` — use `except Exception as e:` at minimum
|
|
- `print()` in production code — use `logger`
|
|
- Mutable default arguments: `def f(x=[])` → `def f(x=None)`
|
|
|
|
---
|
|
|
|
## Type Annotations
|
|
|
|
All new functions **must** have complete type annotations. Use `from __future__ import annotations` for forward references.
|
|
|
|
```python
|
|
# Required
|
|
async def get_tenant(tenant_id: str, db: AsyncSession) -> TenantModel | None:
|
|
...
|
|
|
|
# Required for complex types
|
|
from typing import Sequence
|
|
def list_risks(filters: dict[str, str]) -> Sequence[RiskModel]:
|
|
...
|
|
```
|
|
|
|
**Mypy rules:**
|
|
- `--disallow-untyped-defs` on new files
|
|
- `--strict` on new modules (not legacy)
|
|
- Never use `type: ignore` without a comment
|
|
|
|
---
|
|
|
|
## FastAPI-Specific Rules
|
|
|
|
```python
|
|
# Handlers stay thin — delegate to service layer
|
|
@router.get("/risks/{risk_id}", response_model=RiskResponse)
|
|
async def get_risk(risk_id: UUID, service: RiskService = Depends(get_risk_service)):
|
|
return await service.get(risk_id) # ≤5 lines per handler
|
|
|
|
# Always use response_model — never return raw dicts from endpoints
|
|
# Always validate input with Pydantic — no manual dict parsing
|
|
# Use HTTPException with specific status codes, never bare 500
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Requirements
|
|
|
|
```
|
|
tests/
|
|
├── unit/ # Pure logic tests, no DB/HTTP (run on every push)
|
|
├── integration/ # Requires running services (run in CI only)
|
|
└── contracts/ # OpenAPI snapshot tests (run on API changes)
|
|
```
|
|
|
|
**Unit test requirements:**
|
|
- Every new function → at least one happy-path test
|
|
- Every bug fix → regression test that would have caught it
|
|
- Mock all I/O: DB calls, HTTP calls, filesystem reads
|
|
|
|
```bash
|
|
# Run unit tests only (fast, for pre-push)
|
|
pytest tests/unit/ -x -q
|
|
|
|
# Run with coverage (for CI)
|
|
pytest tests/ --cov=. --cov-report=term-missing --cov-fail-under=70
|
|
```
|
|
|
|
---
|
|
|
|
## Dependency Management
|
|
|
|
```bash
|
|
# Check new package license before adding
|
|
pip show <package> | grep -E "License|Home-page"
|
|
|
|
# After adding to requirements.txt — verify no GPL/AGPL
|
|
pip-licenses --fail-on="GPL;AGPL" 2>/dev/null || echo "Check licenses manually"
|
|
```
|
|
|
|
**Never add:**
|
|
- GPL/AGPL licensed packages
|
|
- Packages with known CVEs (`pip audit`)
|
|
- Packages that only exist for dev (`pytest`, `ruff`) to production requirements
|
|
|
|
---
|
|
|
|
## Common Pitfalls That Break CI
|
|
|
|
| Pitfall | Prevention |
|
|
|---------|------------|
|
|
| `const x = ...` inside dict literal (wrong language!) | Run ruff before push |
|
|
| Pydantic v1 syntax in v2 project | Use `model_config`, not `class Config` |
|
|
| Sync function called inside async without `run_in_executor` | mypy + async linter |
|
|
| Missing `await` on coroutine | mypy catches this |
|
|
| `datetime.utcnow()` (deprecated) | Use `datetime.now(timezone.utc)` |
|
|
| Bare `except:` swallowing errors silently | ruff B001/E722 catches this |
|
|
| Unused imports left in committed code | ruff F401 catches this |
|