Skip to content

Commit e1c210c

Browse files
ColinToftCopilot
andauthored
Add mypy and ruff for linting (#6)
* Add mypy and ruff for linting * Remove some ignores * Fix linting errors * Fix formatting * Fix more errors * Update requirements * Update Python version * Use | for unions and fix other linting issues * Remove ignore-missing-imports * Fix the table for adding users * Update backend/python/app/utilities/csv_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix review comments and linting --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5c5d06d commit e1c210c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1188
-725
lines changed

.cursor/rules/project-overview.mdc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,32 @@ This is a full-stack web application with:
3131
- Maintain consistent naming conventions across the stack
3232
- All database changes must go through Alembic migrations
3333

34+
## Code Quality Standards
35+
36+
### Python Backend
37+
- **Type Safety**: Use mypy for static type checking. All functions must have proper type hints.
38+
- **Code Style**: Use ruff for linting and formatting. Follow the configuration in `backend/python/pyproject.toml`.
39+
- **Type Hints**: Always include return types and parameter types for functions and methods.
40+
- **Imports**: Use absolute imports and organize them with ruff's isort integration.
41+
42+
### Development Workflow
43+
Before committing:
44+
1. Run `docker-compose exec backend ruff check .` to check for linting issues
45+
2. Run `docker-compose exec backend ruff format .` to format code
46+
3. Run `docker-compose exec backend mypy .` to check type safety
47+
4. Run tests: `docker-compose exec backend python -m pytest`
48+
49+
### Code Quality Commands
50+
```bash
51+
# Backend quality checks
52+
docker-compose exec backend ruff check --fix . # Fix linting issues
53+
docker-compose exec backend ruff format . # Format code
54+
docker-compose exec backend mypy . # Type checking
55+
56+
# Combined quality check
57+
docker-compose exec backend ruff check . && docker-compose exec backend ruff format --check . && docker-compose exec backend mypy .
58+
```
59+
3460
## Container Commands
3561

3662
Start all services: `docker-compose up`

.github/workflows/lint.yml

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,39 @@ jobs:
5959
working-directory: ./backend/typescript
6060
run: yarn lint
6161

62-
- name: Lint Python backend
62+
- name: Set up Python
63+
if: steps.changes.outputs.python-backend == 'true'
64+
uses: actions/setup-python@v4
65+
with:
66+
python-version: '3.9'
67+
68+
- name: Cache pip dependencies
69+
if: steps.changes.outputs.python-backend == 'true'
70+
uses: actions/cache@v3
71+
with:
72+
path: ~/.cache/pip
73+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
74+
restore-keys: |
75+
${{ runner.os }}-pip-
76+
77+
- name: Install Python dependencies
78+
if: steps.changes.outputs.python-backend == 'true'
79+
working-directory: ./backend/python
80+
run: |
81+
python -m pip install --upgrade pip
82+
pip install -r requirements.txt
83+
84+
- name: Run Ruff linter
85+
if: steps.changes.outputs.python-backend == 'true'
86+
working-directory: ./backend/python
87+
run: ruff check . --config pyproject.toml
88+
89+
- name: Run Ruff formatter check
90+
if: steps.changes.outputs.python-backend == 'true'
91+
working-directory: ./backend/python
92+
run: ruff format --check . --config pyproject.toml
93+
94+
- name: Run MyPy type checker
6395
if: steps.changes.outputs.python-backend == 'true'
6496
working-directory: ./backend/python
65-
run: pip install black && python -m black --check .
97+
run: mypy . --config-file mypy.ini

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
**/*.log
77
**/firebaseServiceAccount.json
88
**/.DS_Store
9-
.vscode
9+
# VS Code - ignore everything except extensions.json
10+
.vscode/*
11+
!.vscode/extensions.json
1012
**/*.cache
1113
**/*.egg-info

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"ms-python.python",
4+
"charliermarsh.ruff",
5+
"ms-python.mypy-type-checker"
6+
]
7+
}

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ docker system prune -a --volumes
189189

190190
### Backend (Python)
191191

192-
The project will use **Ruff** for Python linting and formatting:
192+
The project uses **Ruff** for Python linting and formatting, and **mypy** for static type checking:
193+
194+
#### Ruff (Linting & Formatting)
193195

194196
```bash
195197
# Check for linting issues
@@ -200,9 +202,28 @@ docker-compose exec backend ruff check --fix .
200202

201203
# Format code
202204
docker-compose exec backend ruff format .
205+
206+
# Check formatting without making changes
207+
docker-compose exec backend ruff format --check .
208+
```
209+
210+
#### mypy (Static Type Checking)
211+
212+
```bash
213+
# Run type checking
214+
docker-compose exec backend mypy .
215+
```
216+
217+
#### Combined Quality Checks
218+
219+
```bash
220+
# Run all quality checks (linting, formatting, type checking)
221+
docker-compose exec backend ruff check . && docker-compose exec backend ruff format --check . && docker-compose exec backend mypy .
203222
```
204223

205-
_Note: Ruff configuration will be added in future updates._
224+
**Configuration Files:**
225+
- Ruff: `backend/python/pyproject.toml` (under `[tool.ruff]`)
226+
- mypy: `backend/python/mypy.ini`
206227

207228
### Frontend (TypeScript/React)
208229

backend/python/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.9
1+
FROM python:3.10
22

33
WORKDIR /app
44

backend/python/app/__init__.py

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1-
import os
2-
import re
3-
import firebase_admin
4-
import logging
1+
from collections.abc import AsyncGenerator
52
from contextlib import asynccontextmanager
6-
from typing import AsyncGenerator
3+
from logging.config import dictConfig
74

5+
import firebase_admin
86
from fastapi import FastAPI
97
from fastapi.middleware.cors import CORSMiddleware
10-
from logging.config import dictConfig
118

129
from .config import settings
1310
from .models import init_app as init_models
1411
from .routers import init_app as init_routers
1512

1613

17-
def configure_logging():
14+
def configure_logging() -> None:
1815
"""Configure application logging based on environment"""
19-
16+
2017
# Base configuration that applies to all environments
2118
base_config = {
2219
"version": 1,
@@ -25,14 +22,12 @@ def configure_logging():
2522
"detailed": {
2623
"format": "%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s"
2724
},
28-
"simple": {
29-
"format": "%(levelname)s - %(message)s"
30-
},
25+
"simple": {"format": "%(levelname)s - %(message)s"},
3126
},
3227
"handlers": {},
3328
"root": {},
3429
}
35-
30+
3631
if settings.is_development:
3732
# Development: Log to console with INFO level, and errors to file
3833
base_config["handlers"] = {
@@ -49,19 +44,18 @@ def configure_logging():
4944
"formatter": "detailed",
5045
},
5146
}
52-
base_config["root"] = {
53-
"level": "INFO",
54-
"handlers": ["console", "file"]
55-
}
56-
47+
base_config["root"] = {"level": "INFO", "handlers": ["console", "file"]}
48+
5749
# Set specific loggers to appropriate levels
5850
base_config["loggers"] = {
5951
"uvicorn": {"level": "INFO"},
6052
"uvicorn.access": {"level": "INFO"},
61-
"sqlalchemy.engine": {"level": "INFO"}, # Use "WARNING" to avoid SQL query noise
53+
"sqlalchemy.engine": {
54+
"level": "INFO"
55+
}, # Use "WARNING" to avoid SQL query noise
6256
"app": {"level": "DEBUG"}, # Your app logs at DEBUG level
6357
}
64-
58+
6559
elif settings.is_testing:
6660
# Testing: Minimal logging to avoid test output noise
6761
base_config["handlers"] = {
@@ -72,11 +66,8 @@ def configure_logging():
7266
"stream": "ext://sys.stdout",
7367
},
7468
}
75-
base_config["root"] = {
76-
"level": "WARNING",
77-
"handlers": ["console"]
78-
}
79-
69+
base_config["root"] = {"level": "WARNING", "handlers": ["console"]}
70+
8071
else: # Production
8172
# Production: Only errors to file, warnings and above to console
8273
base_config["handlers"] = {
@@ -93,23 +84,22 @@ def configure_logging():
9384
"formatter": "detailed",
9485
},
9586
}
96-
base_config["root"] = {
97-
"level": "WARNING",
98-
"handlers": ["console", "file"]
99-
}
100-
87+
base_config["root"] = {"level": "WARNING", "handlers": ["console", "file"]}
88+
10189
dictConfig(base_config)
10290

10391

104-
def initialize_firebase():
92+
def initialize_firebase() -> None:
10593
"""Initialize Firebase Admin SDK"""
10694
firebase_admin.initialize_app(
10795
firebase_admin.credentials.Certificate(
10896
{
10997
"type": "service_account",
11098
"project_id": settings.firebase_project_id,
11199
"private_key_id": settings.firebase_svc_account_private_key_id,
112-
"private_key": settings.firebase_svc_account_private_key.replace("\\n", "\n"),
100+
"private_key": settings.firebase_svc_account_private_key.replace(
101+
"\\n", "\n"
102+
),
113103
"client_email": settings.firebase_svc_account_client_email,
114104
"client_id": settings.firebase_svc_account_client_id,
115105
"auth_uri": settings.firebase_svc_account_auth_uri,
@@ -122,23 +112,23 @@ def initialize_firebase():
122112

123113

124114
@asynccontextmanager
125-
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
115+
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
126116
"""Application lifespan management"""
127117
# Startup
128118
configure_logging()
129119
initialize_firebase()
130120
init_models()
131-
121+
132122
yield
133-
123+
134124
# Shutdown
135125
# Add any cleanup code here if needed
136126
pass
137127

138128

139129
def create_app() -> FastAPI:
140130
"""Create and configure FastAPI application"""
141-
131+
142132
app = FastAPI(
143133
title="Food4Kids API",
144134
description="Backend API for the Food4Kids application",
@@ -151,11 +141,13 @@ def create_app() -> FastAPI:
151141
# Configure CORS
152142
cors_origins = settings.cors_origins.copy()
153143
if settings.is_development:
154-
cors_origins.extend([
155-
"http://localhost:3000",
156-
"http://127.0.0.1:3000",
157-
])
158-
144+
cors_origins.extend(
145+
[
146+
"http://localhost:3000",
147+
"http://127.0.0.1:3000",
148+
]
149+
)
150+
159151
# Add regex pattern for preview deployments
160152
cors_origins.append("https://uw-blueprint-starter-code--pr.*\\.web\\.app")
161153

0 commit comments

Comments
 (0)