From a5c671c13311524bb0b6a249380eb8257a5bc4c9 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Mon, 10 Nov 2025 11:37:31 +0100 Subject: [PATCH] Add `pyproject.toml` for consolidated project configuration and replace Black, isort, and Flake8 with Ruff - Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest). - Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting. - Updated `requirements.txt` to include Ruff and remove replaced tools. - Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup. --- backend/Makefile | 82 +++++++++++++++ backend/pyproject.toml | 213 +++++++++++++++++++++++++++++++++++++++ backend/pytest.ini | 8 -- backend/requirements.txt | 6 +- 4 files changed, 297 insertions(+), 12 deletions(-) create mode 100644 backend/Makefile create mode 100644 backend/pyproject.toml delete mode 100644 backend/pytest.ini diff --git a/backend/Makefile b/backend/Makefile new file mode 100644 index 0000000..85cf11b --- /dev/null +++ b/backend/Makefile @@ -0,0 +1,82 @@ +.PHONY: help lint lint-fix format format-check type-check test test-cov validate clean install-dev + +# Default target +help: + @echo "๐Ÿš€ FastAPI Backend - Development Commands" + @echo "" + @echo "Quality Checks:" + @echo " make lint - Run Ruff linter (check only)" + @echo " make lint-fix - Run Ruff linter with auto-fix" + @echo " make format - Format code with Ruff" + @echo " make format-check - Check if code is formatted" + @echo " make type-check - Run mypy type checking" + @echo " make validate - Run all checks (lint + format + types)" + @echo "" + @echo "Testing:" + @echo " make test - Run pytest" + @echo " make test-cov - Run pytest with coverage report" + @echo "" + @echo "Setup:" + @echo " make install-dev - Install all development dependencies" + @echo " make clean - Remove cache and build artifacts" + +# ============================================================================ +# Code Quality +# ============================================================================ + +lint: + @echo "๐Ÿ” Running Ruff linter..." + @ruff check app/ tests/ + +lint-fix: + @echo "๐Ÿ”ง Running Ruff linter with auto-fix..." + @ruff check --fix app/ tests/ + +format: + @echo "โœจ Formatting code with Ruff..." + @ruff format app/ tests/ + +format-check: + @echo "๐Ÿ“‹ Checking code formatting..." + @ruff format --check app/ tests/ + +type-check: + @echo "๐Ÿ”Ž Running mypy type checking..." + @mypy app/ + +validate: lint format-check type-check + @echo "โœ… All quality checks passed!" + +# ============================================================================ +# Testing +# ============================================================================ + +test: + @echo "๐Ÿงช Running tests..." + @IS_TEST=True pytest + +test-cov: + @echo "๐Ÿงช Running tests with coverage..." + @IS_TEST=True pytest --cov=app --cov-report=term-missing --cov-report=html -n 0 + @echo "๐Ÿ“Š Coverage report generated in htmlcov/index.html" + +# ============================================================================ +# Setup & Cleanup +# ============================================================================ + +install-dev: + @echo "๐Ÿ“ฆ Installing development dependencies..." + @pip install -r requirements.txt + @echo "โœ… Development environment ready!" + +clean: + @echo "๐Ÿงน Cleaning up..." + @find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + @find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true + @find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true + @find . -type d -name ".ruff_cache" -exec rm -rf {} + 2>/dev/null || true + @find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true + @find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true + @find . -type f -name ".coverage" -delete 2>/dev/null || true + @find . -type f -name "*.pyc" -delete 2>/dev/null || true + @echo "โœ… Cleanup complete!" diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..3109bb6 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,213 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "fast-next-backend" +version = "0.1.0" +description = "FastAPI backend for Fast-Next template" +readme = "README.md" +requires-python = ">=3.12" + +# ============================================================================ +# Ruff Configuration - All-in-one linting, formatting, and import sorting +# ============================================================================ +[tool.ruff] +target-version = "py312" +line-length = 88 # Black-compatible +indent-width = 4 + +# Exclude directories +exclude = [ + ".git", + ".venv", + "__pycache__", + "*.egg-info", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + "alembic/versions", # Generated migration files +] + +# ============================================================================ +# Ruff Linting Rules +# ============================================================================ +[tool.ruff.lint] +# Enable these rule sets: +# E/W - pycodestyle errors and warnings +# F - pyflakes (unused imports, variables, etc.) +# I - isort (import sorting) +# N - pep8-naming (naming conventions) +# UP - pyupgrade (modern Python syntax) +# B - flake8-bugbear (common bugs) +# C4 - flake8-comprehensions (list/dict comprehensions) +# PIE - flake8-pie (misc lints) +# RUF - Ruff-specific rules +# ASYNC - flake8-async (async best practices) +# S - flake8-bandit (security) +# T20 - flake8-print (no print statements) +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "N", # pep8-naming + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PIE", # flake8-pie + "RUF", # Ruff-specific + "ASYNC", # flake8-async + "S", # flake8-bandit (security) +] + +# Ignore specific rules +ignore = [ + "E501", # Line too long (handled by formatter) + "S101", # Use of assert (pytest uses asserts) + "S104", # Possible binding to all interfaces (FastAPI needs 0.0.0.0) + "S105", # Possible hardcoded password (false positives in field names) + "S106", # Possible hardcoded password (false positives in field names) + "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) +] + +# Allow autofix for all enabled rules +fixable = ["ALL"] +unfixable = [] + +# Per-file ignores for special cases +[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 +"app/models/__init__.py" = ["F401"] # __init__ files re-export modules + +# ============================================================================ +# Ruff Import Sorting (isort replacement) +# ============================================================================ +[tool.ruff.lint.isort] +known-first-party = ["app", "tests"] +section-order = [ + "future", + "standard-library", + "third-party", + "first-party", + "local-folder", +] +combine-as-imports = true +force-wrap-aliases = true +split-on-trailing-comma = true + +# ============================================================================ +# Ruff Formatting (Black replacement) +# ============================================================================ +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "lf" + +# ============================================================================ +# mypy Configuration - Type Checking +# ============================================================================ +[tool.mypy] +python_version = "3.12" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false # Gradual typing - enable later +disallow_incomplete_defs = false +check_untyped_defs = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +strict_equality = true +ignore_missing_imports = false +explicit_package_bases = true +namespace_packages = true + +# Pydantic plugin for better validation +plugins = ["pydantic.mypy"] + +# Per-module options +[[tool.mypy.overrides]] +module = "alembic.*" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "sqlalchemy.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "fastapi_utils.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "slowapi.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "jose.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "passlib.*" +ignore_missing_imports = true + +# ============================================================================ +# Pydantic mypy plugin configuration +# ============================================================================ +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true + +# ============================================================================ +# Pytest Configuration +# ============================================================================ +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "--disable-warnings", + "-n", "auto", # parallel execution + "--strict-markers", + "--tb=short", + "--cov=app", + "--cov-report=term-missing", + "--cov-report=html", +] +markers = [ + "sqlite: marks tests that should run on SQLite (mocked).", + "postgres: marks tests that require a real PostgreSQL database.", +] +asyncio_default_fixture_loop_scope = "function" + +# ============================================================================ +# Coverage Configuration +# ============================================================================ +[tool.coverage.run] +source = ["app"] +omit = [ + "*/tests/*", + "*/__pycache__/*", + "*/alembic/versions/*", + "*/.venv/*", +] +branch = true + +[tool.coverage.report] +precision = 2 +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", + "@abstractmethod", +] diff --git a/backend/pytest.ini b/backend/pytest.ini deleted file mode 100644 index 2c5994d..0000000 --- a/backend/pytest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[pytest] -testpaths = tests -python_files = test_*.py -addopts = --disable-warnings -n auto -markers = - sqlite: marks tests that should run on SQLite (mocked). - postgres: marks tests that require a real PostgreSQL database. -asyncio_default_fixture_loop_scope = function diff --git a/backend/requirements.txt b/backend/requirements.txt index bbe0be9..00fc693 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -41,10 +41,8 @@ pytest-xdist>=3.8.0 requests>=2.32.0 # Development tools -black>=24.3.0 -isort>=5.13.2 -flake8>=7.0.0 -mypy>=1.8.0 +ruff>=0.8.0 # All-in-one: linting, formatting, import sorting (replaces Black, Flake8, isort) +mypy>=1.8.0 # Type checking # Security and authentication (pinned for reproducibility) python-jose==3.4.0