Compare commits

...

6 Commits

Author SHA1 Message Date
Felipe Cardoso
5c47be2ee5 Refactor user_organization model, pyproject.toml, and database configuration for enhanced typing and mypy compliance
- Annotated `role` column in `user_organization` with a specific type for better clarity.
- Added `mypy` overrides in `pyproject.toml` to suppress type-checking issues in models, CRUD operations, API routes, and dependencies.
- Updated comment for `Base` re-export in `models.base` to clarify its purpose.
- Suppressed mypy assignment warning for `engine_config["connect_args"]` in database setup.
2025-11-10 14:11:06 +01:00
Felipe Cardoso
e9f787040a Update pyproject.toml and models.base for improved re-export 2025-11-10 12:31:45 +01:00
Felipe Cardoso
2532d1ac3c Update Makefile to run tests with coverage across 16 threads for improved performance 2025-11-10 12:30:52 +01:00
Felipe Cardoso
1f45ca2b50 Update session cleanup logic, pyproject.toml, and Makefile for consistency and improved tooling support
- Replaced `not UserSession.is_active` with `UserSession.is_active == False` in cleanup queries for explicit comparison.
- Added `mypy` overrides for `app.alembic` and external libraries (`starlette`).
- Refactored `Makefile` to use virtual environment binaries for commands like `ruff`, `mypy`, and `pytest`.
2025-11-10 12:28:10 +01:00
Felipe Cardoso
8a343580ce Import Base from app.core.database in models.base to fix unresolved reference warning 2025-11-10 12:24:37 +01:00
Felipe Cardoso
424ca166b8 Update pyproject.toml and logging for stricter rules and improved error handling
- Ignored additional Ruff rules for test files, SQLAlchemy fixtures, and FastAPI-specific naming conventions.
- Suppressed passlib bcrypt warnings in `core.auth`.
- Improved exception handling in session marking with explicit logging for token parsing failures.
2025-11-10 12:14:43 +01:00
8 changed files with 91 additions and 20 deletions

View File

@@ -1,5 +1,13 @@
.PHONY: help lint lint-fix format format-check type-check test test-cov validate clean install-dev
# Virtual environment binaries
VENV_BIN = .venv/bin
PYTHON = $(VENV_BIN)/python
PIP = $(VENV_BIN)/pip
PYTEST = $(VENV_BIN)/pytest
RUFF = $(VENV_BIN)/ruff
MYPY = $(VENV_BIN)/mypy
# Default target
help:
@echo "🚀 FastAPI Backend - Development Commands"
@@ -26,23 +34,23 @@ help:
lint:
@echo "🔍 Running Ruff linter..."
@ruff check app/ tests/
@$(RUFF) check app/ tests/
lint-fix:
@echo "🔧 Running Ruff linter with auto-fix..."
@ruff check --fix app/ tests/
@$(RUFF) check --fix app/ tests/
format:
@echo "✨ Formatting code with Ruff..."
@ruff format app/ tests/
@$(RUFF) format app/ tests/
format-check:
@echo "📋 Checking code formatting..."
@ruff format --check app/ tests/
@$(RUFF) format --check app/ tests/
type-check:
@echo "🔎 Running mypy type checking..."
@mypy app/
@$(MYPY) app/
validate: lint format-check type-check
@echo "✅ All quality checks passed!"
@@ -53,11 +61,11 @@ validate: lint format-check type-check
test:
@echo "🧪 Running tests..."
@IS_TEST=True pytest
@IS_TEST=True PYTHONPATH=. $(PYTEST)
test-cov:
@echo "🧪 Running tests with coverage..."
@IS_TEST=True pytest --cov=app --cov-report=term-missing --cov-report=html -n 0
@IS_TEST=True PYTHONPATH=. $(PYTEST) --cov=app --cov-report=term-missing --cov-report=html -n 16
@echo "📊 Coverage report generated in htmlcov/index.html"
# ============================================================================
@@ -66,7 +74,7 @@ test-cov:
install-dev:
@echo "📦 Installing development dependencies..."
@pip install -r requirements.txt
@$(PIP) install -r requirements.txt
@echo "✅ Development environment ready!"
clean:

View File

@@ -72,8 +72,11 @@ async def list_my_sessions(
decode_token(access_token)
# Note: Access tokens don't have JTI by default, but we can try
# For now, we'll mark current based on most recent activity
except Exception:
pass
except Exception as e:
# Optional token parsing - silently ignore failures
logger.debug(
f"Failed to decode access token for session marking: {e!s}"
)
# Convert to response format
session_responses = []

View File

@@ -1,8 +1,5 @@
import logging
logging.getLogger("passlib").setLevel(logging.ERROR)
import asyncio
import logging
import uuid
from datetime import UTC, datetime, timedelta
from functools import partial
@@ -15,6 +12,9 @@ from pydantic import ValidationError
from app.core.config import settings
from app.schemas.users import TokenData, TokenPayload
# Suppress passlib bcrypt warnings about ident
logging.getLogger("passlib").setLevel(logging.ERROR)
# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

View File

@@ -75,7 +75,7 @@ def create_async_production_engine() -> AsyncEngine:
# Add PostgreSQL-specific connect_args
if "postgresql" in async_url:
engine_config["connect_args"] = {
engine_config["connect_args"] = { # type: ignore[assignment]
"server_settings": {
"application_name": settings.PROJECT_NAME,
"timezone": "UTC",

View File

@@ -309,7 +309,7 @@ class CRUDSession(CRUDBase[UserSession, SessionCreate, SessionUpdate]):
# Use bulk DELETE with WHERE clause - single query
stmt = delete(UserSession).where(
and_(
not UserSession.is_active,
UserSession.is_active == False, # noqa: E712
UserSession.expires_at < now,
UserSession.created_at < cutoff_date,
)
@@ -356,7 +356,7 @@ class CRUDSession(CRUDBase[UserSession, SessionCreate, SessionUpdate]):
stmt = delete(UserSession).where(
and_(
UserSession.user_id == uuid_obj,
not UserSession.is_active,
UserSession.is_active == False, # noqa: E712
UserSession.expires_at < now,
)
)

View File

@@ -5,6 +5,7 @@ from sqlalchemy import Column, DateTime
from sqlalchemy.dialects.postgresql import UUID
# noinspection PyUnresolvedReferences
from app.core.database import Base # Re-exported for other models
class TimestampMixin:

View File

@@ -40,7 +40,7 @@ class UserOrganization(Base, TimestampMixin):
primary_key=True,
)
role = Column(
role: Column[OrganizationRole] = Column(
Enum(OrganizationRole),
default=OrganizationRole.MEMBER,
nullable=False,

View File

@@ -71,6 +71,7 @@ ignore = [
"S603", # subprocess without shell=True (safe usage)
"S607", # Starting a process with a partial path (safe usage)
"B008", # FastAPI Depends() in function defaults (required by framework)
"B904", # Exception chaining (overly strict for FastAPI error handlers)
]
# Allow autofix for all enabled rules
@@ -81,8 +82,11 @@ unfixable = []
[tool.ruff.lint.per-file-ignores]
"app/alembic/env.py" = ["E402", "F403", "F405"] # Alembic requires specific import order
"app/alembic/versions/*.py" = ["E402"] # Migration files have specific structure
"tests/**/*.py" = ["S101"] # pytest uses assert statements
"tests/**/*.py" = ["S101", "N806", "B017", "N817", "S110", "ASYNC251", "RUF043"] # pytest: asserts, CamelCase fixtures, blind exceptions, try-pass patterns, and async test helpers are intentional
"app/models/__init__.py" = ["F401"] # __init__ files re-export modules
"app/models/base.py" = ["F401"] # Re-exports Base for use by other models
"app/utils/test_utils.py" = ["N806"] # SQLAlchemy session factories use CamelCase convention
"app/main.py" = ["N806"] # Constants use UPPER_CASE convention
# ============================================================================
# Ruff Import Sorting (isort replacement)
@@ -114,7 +118,7 @@ line-ending = "lf"
# ============================================================================
[tool.mypy]
python_version = "3.12"
warn_return_any = true
warn_return_any = false # SQLAlchemy queries return Any - overly strict
warn_unused_configs = true
disallow_untyped_defs = false # Gradual typing - enable later
disallow_incomplete_defs = false
@@ -136,6 +140,10 @@ plugins = ["pydantic.mypy"]
module = "alembic.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "app.alembic.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "sqlalchemy.*"
ignore_missing_imports = true
@@ -156,6 +164,57 @@ ignore_missing_imports = true
module = "passlib.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "pydantic_settings.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "fastapi.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "apscheduler.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "starlette.*"
ignore_missing_imports = true
# SQLAlchemy ORM models - Column descriptors cause type confusion
[[tool.mypy.overrides]]
module = "app.models.*"
disable_error_code = ["assignment", "arg-type", "return-value"]
# CRUD operations - Generic ModelType and SQLAlchemy Result issues
[[tool.mypy.overrides]]
module = "app.crud.*"
disable_error_code = ["attr-defined", "assignment", "arg-type", "return-value"]
# API routes - SQLAlchemy Column to Pydantic schema conversions
[[tool.mypy.overrides]]
module = "app.api.routes.*"
disable_error_code = ["arg-type", "call-arg", "call-overload", "assignment"]
# API dependencies - Similar SQLAlchemy Column issues
[[tool.mypy.overrides]]
module = "app.api.dependencies.*"
disable_error_code = ["arg-type"]
# FastAPI exception handlers have correct signatures despite mypy warnings
[[tool.mypy.overrides]]
module = "app.main"
disable_error_code = ["arg-type"]
# Auth service - SQLAlchemy Column issues
[[tool.mypy.overrides]]
module = "app.services.auth_service"
disable_error_code = ["assignment", "arg-type"]
# Test utils - Testing patterns
[[tool.mypy.overrides]]
module = "app.utils.auth_test_utils"
disable_error_code = ["assignment", "arg-type"]
# ============================================================================
# Pydantic mypy plugin configuration
# ============================================================================