Skip to content

Code Quality

This guide covers the code quality assurance tools and practices used in this Python package template, including linting, security scanning, and spell checking.

Overview

Code quality is maintained through a combination of automated tools and manual reviews. The template includes:

  • Linting: Ruff for code style and errors
  • Security scanning: Bandit for vulnerability detection
  • Spell checking: CSpell for documentation and comments
  • Type checking: Pyright for static analysis
  • Automated enforcement: Pre-commit hooks and CI checks

Linting Tools

Ruff

Ruff is a fast Python linter written in Rust that combines multiple tools.

Features:

  • Extremely fast (10-100x faster than other linters)
  • Comprehensive rule set covering style, errors, and complexity
  • Auto-fixing capabilities
  • Built-in formatter

Configuration (pyproject.toml):

[tool.ruff]
line-length = 120 # Matches Black's default
target-version = "py310" # Align with CI (3.10–3.12) and requires-python (>=3.10)
lint.extend-select = [
  "E",
  "F",
  "W",   # pycodestyle errors and warnings
  "I",   # isort rules
  "UP",  # pyupgrade rules
  "C90", # McCabe complexity
  "N",   # PEP8 naming
  "B",   # Bugbear (opinionated checks)
  "A",   # Flake8-builtins
  "C4",  # Flake8-comprehensions
  "PT",  # Flake8-pytest
  "RUF", # Ruff-specific rules
  "PL",  # Add pylint rules for fuller coverage
  "SIM", # Similarities
  "T10", # Debugger statements
  "DOC", # Docstring
  "D",   # Doc style
]
lint.ignore = [
  "E501", # Line length (handled by formatter)
  "C901", # Disable overly strict complexity checks (optional, adjust as needed)
]
fix = true # Enable autofix
lint.unfixable = [] # List rules that should not be autofixed, if any

[tool.ruff.format]
quote-style = "double"       # Use double quotes for strings
indent-style = "space"       # Use spaces for indentation
docstring-code-format = true # Format code in docstrings

[tool.ruff.lint.pydocstyle]

Usage:

# Lint code
ruff check src/

# Auto-fix issues
ruff check --fix src/

# Format code
ruff format src/

Security Scanning

Bandit

Bandit finds common security issues in Python code.

Configuration (pyproject.toml):

[tool.bandit]
exclude_dirs = ["build", "dist", "tests", "scripts"]
number = 4
recursive = true

Common security issues detected:

  • Use of assert statements
  • Shell injection vulnerabilities
  • Weak cryptographic practices
  • Hardcoded passwords
  • Unsafe deserialization

Usage:

bandit -r src/

Spell Checking

CSpell

CSpell checks spelling in code comments, documentation, and strings.

Configuration (cspell.json):

{
    "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
    "version": "0.2",
    "language": "en",
    "dictionaries": ["en", "ignore-words", "python", "softwareTerms", "node"],
    "dictionaryDefinitions": [
        {
            "name": "ignore-words",
            "path": ".ignore_words.txt",
            "addWords": true
        }
    ],
    "languageSettings": [
        {
            "languageId": "*",
            "locale": "en",
            "dictionaries": ["en", "ignore-words"]
        },
        {
            "languageId": "python",
            "locale": "en",
            "dictionaries": [
                "en",
                "ignore-words",
                "python",
                "softwareTerms",
                "node"
            ]
        }
    ],
    "ignorePaths": ["node_modules/**", ".ignore_words.txt", "**/dist/**"],
    "ignoreRegExpList": [
        "np(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+",
        "scipy(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+"
    ]
}

Usage:

cspell "**/*.{py,md,yml,yaml}"

Pre-commit Integration

All quality tools run automatically via pre-commit hooks.

Configuration (.pre-commit-config.yaml):

repos:
    - repo: https://github.com/pre-commit/pre-commit-hooks
      rev: v6.0.0
      hooks:
          - id: check-added-large-files
            stages: [pre-commit]
          - id: check-case-conflict
            stages: [pre-commit]
          - id: check-merge-conflict
            stages: [pre-commit]
          - id: check-symlinks
            stages: [pre-commit]
          - id: check-yaml
            stages: [pre-commit]
          - id: check-toml
            stages: [pre-commit]
          - id: debug-statements
            stages: [pre-commit]
          - id: end-of-file-fixer
            stages: [pre-commit]
          - id: mixed-line-ending
            stages: [pre-commit]
          - id: requirements-txt-fixer
            stages: [pre-commit]
          - id: trailing-whitespace
            stages: [pre-commit]
          - id: check-docstring-first
            stages: [pre-commit]
          - id: check-json
            stages: [pre-commit]

    - repo: https://github.com/astral-sh/ruff-pre-commit
      rev: v0.15.9
      hooks:
          - id: ruff
            args: [--fix, --config=pyproject.toml]
            stages: [pre-commit]
          - id: ruff-format
            stages: [pre-commit]

    - repo: https://github.com/streetsidesoftware/cspell-cli
      rev: v9.7.0 # Keep this version or update to the latest stable
      hooks:
          - id: cspell
            name: Check spelling in Python, reSt, and Markdown files.
            args: ['--config', 'cspell.json']
            files: ^.*\.(py|rst|md)$
            stages: [pre-commit]
          - id: cspell
            name: Check commit message spelling.
            args:
                - --config
                - cspell.json
                - --no-must-find-files
                - --no-progress
                - --no-summary
                - --files
                - .git/COMMIT_EDITMSG
            stages: [commit-msg]
            always_run: true

    - repo: https://github.com/ComPWA/taplo-pre-commit
      rev: v0.9.3
      hooks:
          - id: taplo-format

    - repo: https://github.com/pre-commit/pygrep-hooks
      rev: v1.10.0
      hooks:
          - id: python-check-blanket-noqa
            stages: [pre-commit]
          - id: python-check-blanket-type-ignore
            stages: [pre-commit]
          - id: python-no-log-warn
            stages: [pre-commit]
          - id: python-no-eval
            stages: [pre-commit]
          - id: python-use-type-annotations
            stages: [pre-commit]
          - id: rst-backticks
            stages: [pre-commit]
          - id: rst-directive-colons
            stages: [pre-commit]
          - id: rst-inline-touching-normal
            stages: [pre-commit]

    - repo: https://github.com/PyCQA/bandit
      rev: '1.9.4'
      hooks:
          - id: bandit
            args: ['-c', 'pyproject.toml']
            stages: [pre-commit]
            additional_dependencies: ['.[toml]']

    - repo: https://github.com/rbubley/mirrors-prettier
      rev: v3.8.1
      hooks:
          - id: prettier
            stages: [pre-commit]

    - repo: https://github.com/DavidAnson/markdownlint-cli2
      rev: v0.22.0
      hooks:
          - id: markdownlint-cli2
            stages: [pre-commit]
            args:
                - --config
                - .markdownlint.yaml

    - repo: https://github.com/astral-sh/uv-pre-commit
      rev: 0.11.3
      hooks:
          # Update the uv lockfile
          - id: uv-lock

    - repo: https://github.com/Yelp/detect-secrets
      rev: v1.5.0 # Use the latest stable version
      hooks:
          - id: detect-secrets
            name: Detect secrets within the codebase
            description:
                Detect and block secrets from committing to the codebase

CI/CD Quality Checks

Quality checks run automatically in GitHub Actions and pre-commit.ci:

  • Pull requests: Pre-commit.ci runs all hooks and auto-fixes issues
  • Main branch: Full quality suite via GitHub Actions
  • Releases: Comprehensive checks

Pre-commit.ci Setup

Pre-commit.ci provides automated pre-commit hook execution:

  1. Install the GitHub App: Go to pre-commit.ci and install the app on your repository
  2. Configuration (.pre-commit-config.yaml):

    repos:
        - repo: https://github.com/pre-commit/pre-commit-hooks
          rev: v6.0.0
          hooks:
              - id: check-added-large-files
                stages: [pre-commit]
              - id: check-case-conflict
                stages: [pre-commit]
              - id: check-merge-conflict
                stages: [pre-commit]
              - id: check-symlinks
                stages: [pre-commit]
    
  3. Automatic execution: Pre-commit.ci runs on every PR and push

Code Quality Metrics

Complexity Analysis

Use tools to measure code complexity:

# McCabe complexity
python -m mccabe src/your_package/

# Radon metrics
radon cc src/ -a
radon mi src/ -s

Coverage Requirements

Test coverage is enforced:

envlist = py, integration, spark, all

Quality Gates

  • Linting: Zero errors allowed
  • Security: Critical vulnerabilities must be fixed
  • Coverage: X% coverage required (You may gradually increase this)
  • Spelling: No spelling errors in documentation

Best Practices

Code Style

  • Follow PEP 8: Use consistent formatting
  • Descriptive names: Use clear, meaningful identifiers
  • DRY principle: Avoid code duplication
  • SOLID principles: Write maintainable code

Documentation

  • Docstrings: Document all public functions/classes
  • Comments: Explain complex logic
  • README: Clear project description
  • Changelog: Track changes systematically

Security

  • Input validation: Validate all inputs
  • Secure defaults: Use secure defaults
  • Dependency updates: Keep dependencies current
  • Secrets management: Never commit secrets

Performance

  • Efficient algorithms: Choose appropriate data structures
  • Memory management: Avoid memory leaks
  • Profiling: Use profiling tools for optimization
  • Caching: Implement caching where beneficial

Tool Selection Rationale

Why Multiple Linters

  • Ruff: Fast, comprehensive, auto-fixing
  • Pylint: Detailed analysis, custom rules
  • Flake8: Legacy compatibility, extensive plugins

Why Bandit

  • Security focus: Catches common vulnerabilities
  • Python specific: Understands Python security issues
  • Configurable: Can be tuned for project needs

Why CSpell

  • Multi-language: Works with Python, Markdown, YAML
  • Custom dictionaries: Project-specific terminology
  • IDE integration: Works with editors

Customizing Quality Checks

Adding New Rules

[tool.ruff]
select = ["E", "F", "B", "I", "CUSTOM"]

Ignoring False Positives

[tool.ruff.per-file-ignores]
"specific_file.py" = ["RULE_CODE"]

Custom Security Rules

Extend Bandit with custom plugins:

# custom_bandit_plugin.py
import bandit
from bandit.core import test_properties as test

@test.checks('Call')
@test.test_id('B999')
def custom_check(context):
    # Custom security check logic
    pass

Troubleshooting

Common Issues

  • False positives: Use ignore rules judiciously
  • Performance: Ruff is fastest, use for large codebases
  • Configuration conflicts: Ensure consistent settings
  • CI failures: Check tool versions and configurations

Debugging Quality Issues

  • Verbose output: Use --verbose flags
  • Specific files: Run tools on individual files
  • Configuration validation: Test configurations separately

Performance Optimization

  • Parallel execution: Use multiple cores where possible
  • Incremental checks: Only check changed files
  • Caching: Leverage tool caching features

Integration with IDEs

VS Code

{
    "cSpell.words": ["pypi", "mkdocs"]
}

PyCharm

  • Configure external tools for Ruff, Bandit
  • Enable spell checking
  • Set up pre-commit integration

Continuous Improvement

Metrics Tracking

Track quality metrics over time:

  • Code coverage trends
  • Complexity measurements
  • Security vulnerability counts
  • Performance benchmarks

Regular Audits

  • Dependency audits: Check for vulnerabilities
  • Code reviews: Manual quality checks
  • Performance reviews: Optimization opportunities
  • Security assessments: Penetration testing

Tool Updates

Keep tools current:

# Update pre-commit hooks
pre-commit autoupdate

# Update Python packages
pip install --upgrade ruff bandit

# Update Node.js packages
npm update cspell

For more information, see the documentation for individual tools: Ruff, Bandit, CSpell, Pyright.