Files
orion/docs/development/code-quality.md
Samir Boulahtit b8a46e1746 fix: protect critical re-export imports from linter removal
Problem:
- Ruff removed 'from app.core.database import Base' from models/database/base.py
- Import appeared "unused" (F401) but was actually a critical re-export
- Caused ImportError: cannot import name 'Base' at runtime
- Re-export pattern: import in one file to export from package

Solution:
1. Added F401 ignore for models/database/base.py in pyproject.toml
2. Created scripts/verify_critical_imports.py verification script
3. Integrated verification into make check and CI pipeline
4. Updated documentation with explanation

New Verification Script:
- Checks all critical re-export imports exist
- Detects import variations (parentheses, 'as' clauses)
- Handles SQLAlchemy declarative_base alternatives
- Runs as part of make check automatically

Protected Files:
- models/database/base.py - Re-exports Base for all models
- models/__init__.py - Exports Base for Alembic
- models/database/__init__.py - Exports Base from package
- All __init__.py files (already protected)

Makefile Changes:
- make verify-imports - Run import verification
- make check - Now includes verify-imports
- make ci - Includes verify-imports in pipeline

Documentation Updated:
- Code quality guide explains re-export protection
- Pre-commit workflow includes verification
- Examples of why re-exports matter

This prevents future issues where linters remove seemingly
"unused" imports that are actually critical for application structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 20:10:22 +01:00

8.3 KiB

Code Quality

This guide covers the code quality tools and standards used in the Wizamart platform.

Overview

The project uses modern Python tooling to maintain high code quality:

  • Ruff - All-in-one linter and formatter (replaces black, isort, flake8, and more)
  • mypy - Static type checker
  • pytest - Testing framework with coverage reporting

All tools are configured in pyproject.toml for consistency and ease of use.

Quick Start

# Format code
make format

# Lint and auto-fix issues
make lint

# Run both formatting and linting
make check

# Strict linting (no auto-fix) - for CI/CD
make lint-strict

Ruff - Modern Linting and Formatting

Ruff is a blazingly fast Python linter and formatter written in Rust. It replaces multiple tools with a single, comprehensive solution.

What Ruff Does

Formatting (replaces black):

  • Consistent code style
  • Automatic quote normalization
  • Line length enforcement (88 characters)

Linting (replaces flake8, isort, pyupgrade, and more):

  • Import sorting and organization
  • Code style checks (PEP 8)
  • Bug detection (flake8-bugbear)
  • Modern Python syntax suggestions (pyupgrade)
  • Code simplification suggestions

Enabled Rule Sets

Code Description Purpose
E, W pycodestyle PEP 8 style enforcement
F pyflakes Basic error detection
I isort Import sorting
N pep8-naming Naming conventions
UP pyupgrade Modern Python syntax
B flake8-bugbear Common bug patterns
C4 flake8-comprehensions Better comprehensions
SIM flake8-simplify Code simplification
PIE flake8-pie Misc. lints
RET flake8-return Return statement improvements
Q flake8-quotes Quote consistency

Usage

# Format all code
make format
# or directly:
python -m ruff format .

# Lint and auto-fix issues
make lint
# or directly:
python -m ruff check . --fix

# Check without fixing (CI/CD)
make lint-strict
# or directly:
python -m ruff check .

Configuration

Ruff is configured in pyproject.toml:

[tool.ruff]
line-length = 88
target-version = "py311"
exclude = [".git", ".venv", "venv", "__pycache__", "alembic/versions"]

[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "UP", "B", "C4", "SIM", "PIE", "RET", "Q"]
ignore = ["E501", "B008", "RET504", "SIM102"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

mypy - Type Checking

mypy performs static type analysis to catch type-related errors before runtime.

Usage

# Run type checking
python -m mypy .

# Or as part of lint
make lint

Configuration

mypy is configured in pyproject.toml:

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true
exclude = ["^\\.venv/", "^venv/", "^alembic/versions/"]

Testing with pytest

Run tests with coverage reporting:

# Run all tests
make test

# Run with coverage
make test-coverage

# Run only unit tests
make test-unit

# Run only integration tests
make test-integration

# Run fast tests (skip slow ones)
make test-fast

Coverage Configuration

Coverage settings are in pyproject.toml:

[tool.coverage.run]
source = ["app", "models", "middleware", "tasks", "storage"]
omit = ["*/tests/*", "*/venv/*", "*/alembic/*"]

[tool.coverage.report]
precision = 2
show_missing = true

Makefile Commands

Code Quality Commands

Command Description When to Use
make format Format code with Ruff Before committing
make lint Lint and auto-fix with Ruff + mypy Before committing
make lint-strict Lint without auto-fix + mypy In CI/CD pipelines
make check Run format + lint Quick pre-commit check
make ci Full CI pipeline (strict lint + coverage) CI/CD workflows
make qa Quality assurance (format + lint + coverage + docs) Before releases

Testing Commands

Command Description
make test Run all tests
make test-unit Run unit tests only
make test-integration Run integration tests only
make test-coverage Run tests with coverage report
make test-fast Run fast tests (skip slow ones)

Pre-Commit Workflow

Before committing code:

# 1. Format, lint, and verify critical imports
make check

# 2. Run relevant tests
make test-fast

# 3. If all passes, commit
git add .
git commit -m "your message"

Critical Import Verification

The make check command includes a critical import verification step that ensures re-export imports haven't been removed by linters.

What it checks:

  • models/database/base.py - Re-exports Base from app.core.database
  • models/__init__.py - Exports Base for Alembic
  • models/database/__init__.py - Exports Base from database package

Why it matters: Linters like Ruff may see these imports as "unused" (F401) because they're re-exported, not directly used. Removing them breaks the application.

Manual verification:

make verify-imports

If this fails, imports have been removed and must be restored.

CI/CD Integration

For continuous integration:

# Use strict mode (no auto-fixes)
make ci

This will:

  1. Run strict linting (fails on any issues)
  2. Run type checking with mypy
  3. Run full test suite with coverage

IDE Integration

VSCode

Install these extensions:

  • Ruff (charliermarsh.ruff)
  • Python (ms-python.python)

Add to .vscode/settings.json:

{
  "[python]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
      "source.fixAll": true,
      "source.organizeImports": true
    }
  },
  "ruff.enable": true,
  "ruff.lint.enable": true
}

PyCharm

  1. Go to Settings → Tools → External Tools
  2. Add Ruff:
    • Program: python
    • Arguments: -m ruff check $FilePath$ --fix
    • Working directory: $ProjectFileDir$

Common Issues

Import Order

Ruff automatically organizes imports into sections:

  1. Future imports
  2. Standard library
  3. Third-party packages
  4. First-party packages (app, models, middleware, tasks, storage, scripts)
  5. Local folder imports

Line Length

Default line length is 88 characters (black's default). Long lines are automatically wrapped.

Type Hints

While not strictly enforced, adding type hints improves code quality:

# Good
def get_user(user_id: int) -> User:
    return db.query(User).get(user_id)

# Better with Optional
from typing import Optional

def get_user(user_id: int) -> Optional[User]:
    return db.query(User).get(user_id)

Ignoring Rules

Per-File Ignores

Configure in pyproject.toml:

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]  # Allow unused imports
"tests/**/*.py" = ["S101"]  # Allow assert statements

Inline Ignores

Use # noqa comments sparingly:

# Ignore specific rule
example = lambda x: x + 1  # noqa: E731

# Ignore all rules for line
long_url = "https://..."  # noqa

Best Practices

  1. Run make check before every commit
  2. Let Ruff auto-fix issues - don't fight the formatter
  3. Add type hints for better code quality
  4. Keep test coverage above 80%
  5. Use meaningful variable names that pass naming checks
  6. Avoid broad exception catches - be specific
  7. Use pathlib instead of os.path when possible
  8. Keep functions focused - if it's complex, break it down

Migration from Old Tools

If you were using black, isort, or flake8 before:

Old Tool New Tool Notes
black ruff format 100% compatible
isort ruff check --select I Built into Ruff linting
flake8 ruff check Faster, more comprehensive
pyupgrade ruff check --select UP Built into Ruff

All configurations have been migrated to pyproject.toml.

Performance

Ruff is 10-100x faster than the tools it replaces:

  • black: ~2s → ruff: ~0.1s
  • isort: ~3s → ruff: ~0.1s
  • flake8: ~10s → ruff: ~0.2s

This means faster CI/CD pipelines and better developer experience.

Resources