Skip to content

Commit 8997cf1

Browse files
authored
Improve backend documentation and lint compliance (#4)
* Add docstrings and lint fixes for backend modules * Fix lint issues for security and user modules
1 parent 16a4352 commit 8997cf1

9 files changed

Lines changed: 106 additions & 33 deletions

File tree

Backend/app/core/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
"""Configuration objects and helpers for the FastAPI application."""
2+
13
import os
24

35
from pydantic_settings import BaseSettings, SettingsConfigDict
46

57

68
class Settings(BaseSettings):
9+
"""Application settings loaded from environment variables or ``.env`` files."""
710
# --- App Settings ---
811
APP_NAME: str = "Inquiro API"
912
ENVIRONMENT: str = "dev"

Backend/app/core/database.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
"""Database configuration and session utilities for the API."""
2+
13
import logging
4+
from importlib import import_module
25

36
from sqlalchemy import create_engine
4-
from sqlalchemy.orm import sessionmaker, declarative_base
7+
from sqlalchemy.orm import declarative_base, sessionmaker
58

69
from app.core.config import settings
710

@@ -14,20 +17,20 @@
1417
echo=(settings.ENVIRONMENT == "dev"),
1518
)
1619

17-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
20+
SESSION_LOCAL = sessionmaker(autocommit=False, autoflush=False, bind=engine)
1821

1922

20-
def init_db():
21-
"""Automatically create or update tables based on models."""
22-
import app.models # noqa: F401 (ensures models are imported)
23+
def init_db() -> None:
24+
"""Automatically create or update tables based on SQLAlchemy models."""
25+
import_module("app.models.user")
2326
logger.info("🔄 Creating / updating database schema...")
2427
Base.metadata.create_all(bind=engine)
2528
logger.info("✅ Database schema up to date.")
2629

2730

2831
def get_db():
29-
"""Provide a database session for FastAPI routes."""
30-
db = SessionLocal()
32+
"""Yield a database session for FastAPI routes."""
33+
db = SESSION_LOCAL()
3134
try:
3235
yield db
3336
finally:

Backend/app/core/security.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,53 @@
1-
import secrets
1+
"""Security utilities for creating and validating JWT tokens."""
2+
23
from datetime import datetime, timedelta, timezone
3-
from typing import Optional
4+
from typing import Any, Dict, Optional
5+
46
from fastapi import Depends, HTTPException, status
57
from fastapi.security import OAuth2PasswordBearer
6-
from jose import jwt, JWTError
8+
from jose import JWTError, jwt
79

810
from app.core.config import settings
911

1012
ALGORITHM = "HS256"
1113

1214
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
1315

14-
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
16+
17+
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
18+
"""Create a signed JWT access token."""
19+
1520
to_encode = data.copy()
16-
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES))
21+
expire = datetime.now(timezone.utc) + (
22+
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
23+
)
1724
to_encode.update({"exp": expire})
1825
return jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=ALGORITHM)
1926

2027

21-
def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
28+
def create_refresh_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
29+
"""Create a signed JWT refresh token."""
30+
2231
to_encode = data.copy()
23-
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS))
32+
expire = datetime.now(timezone.utc) + (
33+
expires_delta or timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
34+
)
2435
to_encode.update({"exp": expire, "type": "refresh"})
2536
return jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=ALGORITHM)
2637

2738

28-
def verify_token(token: str) -> Optional[dict]:
39+
def verify_token(token: str) -> Optional[Dict[str, Any]]:
40+
"""Decode a JWT token and return its payload, if valid."""
41+
2942
try:
30-
payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[ALGORITHM])
31-
return payload
43+
return jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[ALGORITHM])
3244
except JWTError:
3345
return None
3446

35-
def get_current_user(token: str = Depends(oauth2_scheme)):
47+
48+
def get_current_user(token: str = Depends(oauth2_scheme)) -> str:
49+
"""Return the username encoded in the access token."""
50+
3651
payload = verify_token(token)
3752
if not payload:
3853
raise HTTPException(
@@ -43,6 +58,9 @@ def get_current_user(token: str = Depends(oauth2_scheme)):
4358

4459
username = payload.get("sub")
4560
if not username:
46-
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload.")
61+
raise HTTPException(
62+
status_code=status.HTTP_401_UNAUTHORIZED,
63+
detail="Invalid token payload.",
64+
)
4765

48-
return username
66+
return username

Backend/app/main.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
"""Application entry point for the Inquiro FastAPI service."""
2+
13
import logging
24
from contextlib import asynccontextmanager
35

46
from fastapi import FastAPI
57

68
from app.core.config import settings
79
from app.core.database import init_db
8-
from app.routes import user_routes, auth_routes
10+
from app.routes import auth_routes, user_routes
911

1012
# ---------------------------------------------------------
1113
# Configure Logging
@@ -21,8 +23,10 @@
2123
# Lifespan Event Handlers (Modern FastAPI)
2224
# ---------------------------------------------------------
2325
@asynccontextmanager
24-
async def lifespan(app: FastAPI):
25-
logger.info(f"🚀 Starting Inquiro API in '{settings.ENVIRONMENT}' mode...")
26+
async def lifespan(_app: FastAPI):
27+
"""Initialize and tear down application resources."""
28+
29+
logger.info("🚀 Starting Inquiro API in '%s' mode...", settings.ENVIRONMENT)
2630
if settings.ENVIRONMENT == "dev":
2731
init_db() # Auto-create tables only in dev
2832
logger.info("✅ Startup complete.")
@@ -40,7 +44,7 @@ async def lifespan(app: FastAPI):
4044
title="Inquiro API",
4145
description="AI-powered research discovery backend for Inquiro.",
4246
version="0.1.0",
43-
lifespan=lifespan
47+
lifespan=lifespan,
4448
)
4549

4650

Backend/app/models/user.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
from sqlalchemy import String, Integer
1+
"""SQLAlchemy models used by the application."""
2+
3+
# pylint: disable=too-few-public-methods
4+
5+
from sqlalchemy import Integer, String
26
from sqlalchemy.orm import Mapped, mapped_column
7+
38
from app.core.database import Base
49

10+
511
class User(Base):
12+
"""Database representation of an application user."""
13+
614
__tablename__ = "users"
715

816
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)

Backend/app/routes/auth_routes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Routes related to authentication and JWT management."""
2+
13
from fastapi import APIRouter, Depends, HTTPException, status
24
from sqlalchemy.orm import Session
35

@@ -22,6 +24,8 @@
2224
summary="Authenticate a user and return JWT tokens",
2325
)
2426
def login(request: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse:
27+
"""Authenticate a user and return access plus refresh tokens."""
28+
2529
user = db.query(User).filter(User.username == request.username).first()
2630
if not user:
2731
raise HTTPException(
@@ -49,6 +53,8 @@ def login(request: LoginRequest, db: Session = Depends(get_db)) -> LoginResponse
4953
summary="Generate a new access token using a refresh token",
5054
)
5155
def refresh_access_token(request: RefreshRequest) -> RefreshResponse:
56+
"""Validate a refresh token and return a new access token."""
57+
5258
payload = verify_token(request.refresh_token)
5359
if not payload or payload.get("type") != "refresh":
5460
raise HTTPException(

Backend/app/routes/user_routes.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Routes for managing and retrieving user information."""
2+
13
from fastapi import APIRouter, Depends, HTTPException
24
from pydantic import BaseModel
35
from sqlalchemy.orm import Session
@@ -12,26 +14,36 @@
1214

1315

1416
class UserCreate(BaseModel):
17+
"""Payload used when creating a new user."""
18+
1519
username: str
1620

1721

18-
@router.post("/", response_model=UserResponse, status_code=201)
19-
def create_user(request: UserCreate, db: Session = Depends(get_db)):
22+
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
23+
def create_user(request: UserCreate, db: Session = Depends(get_db)) -> User:
24+
"""Create a new user record if the username is available."""
25+
2026
if db.query(User).filter(User.username == request.username).first():
21-
raise HTTPException(status_code=400, detail="Username already exists")
27+
raise HTTPException(
28+
status_code=status.HTTP_400_BAD_REQUEST,
29+
detail="Username already exists",
30+
)
2231
user = User(username=request.username)
2332
db.add(user)
2433
db.commit()
2534
db.refresh(user)
2635
return user
2736

37+
2838
@router.get("/me", response_model=UserResponse, status_code=status.HTTP_200_OK)
2939
def get_current_user_profile(
3040
current_username: str = Depends(get_current_user),
3141
db: Session = Depends(get_db),
32-
):
42+
) -> User:
43+
"""Return the user information for the authenticated principal."""
44+
3345
user = db.query(User).filter(User.username == current_username).first()
3446
if not user:
3547
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")
3648

37-
return user
49+
return user

Backend/app/schemas/auth_dto.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1+
"""Pydantic models for authentication requests and responses."""
2+
3+
from typing import Any, Dict
4+
15
from pydantic import BaseModel
26

37

48
class LoginRequest(BaseModel):
9+
"""Payload for authenticating a user."""
10+
511
username: str
612

713

814
class LoginResponse(BaseModel):
15+
"""Response returned after a successful login."""
16+
917
access_token: str
1018
refresh_token: str
1119
token_type: str
12-
user: dict
20+
user: Dict[str, Any]
1321

1422

1523
class RefreshRequest(BaseModel):
24+
"""Payload containing the refresh token."""
25+
1626
refresh_token: str
1727

1828

1929
class RefreshResponse(BaseModel):
30+
"""Response containing a fresh access token."""
31+
2032
access_token: str
2133
token_type: str

Backend/app/schemas/user_dto.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
"""Pydantic models for user-facing responses."""
2+
13
from pydantic import BaseModel
24

5+
36
class UserResponse(BaseModel):
7+
"""Representation of a user returned by the API."""
8+
49
id: int
510
username: str
611

7-
class Config:
8-
from_attributes = True # For Pydantic v2
12+
class Config: # pylint: disable=too-few-public-methods
13+
"""Pydantic configuration options."""
14+
15+
from_attributes = True # For Pydantic v2

0 commit comments

Comments
 (0)