When developing ReadmeGenie, I aimed to ensure consistent code quality with an automated setup for linting and formatting. After considering several tools, I selected Ruff as the linter and Black as the code formatter. Although Ruff can also handle both linting and formatting, I decided to set up Black as a separate formatter to gain experience with the configuration of both tools. Below, I’ll share why I chose these tools, how I configured them for my project, the challenges I faced, and the lessons I learned along the way.
1. Tool Selection
Why Ruff?
Ruff is a fast linter for Python that supports various linting rules from other linters (like Flake8 and Pyflakes) and offers significant performance improvements. It’s highly customizable, which allowed me to specify a mix of rules while ensuring compatibility with Black for formatting. Ruff’s design for speed and extensibility is ideal for projects that prioritize efficiency without sacrificing quality.
- Ruff Documentation: https://github.com/charliermarsh/ruff
Why Black?
Black is a Python formatter that strictly enforces one formatting style, helping reduce discussions and inconsistencies over code styling. While Ruff offers basic formatting capabilities, Black’s dedicated approach provides a few advantages:
- Consistency: Black enforces a strict, standard style that minimizes debates over code formatting.
Broad Adoption: Black is widely used, making it easier to integrate into most development workflows, especially in collaborative projects.
Black Documentation: https://github.com/psf/black
2. Project Setup
To ensure that Ruff and Black worked seamlessly in ReadmeGenie, I configured them in both pyproject.toml
and
.pre-commit-config.yaml
, allowing developers to automatically format and lint code when making commits.
Configuration for Ruff and Black in pyproject.toml
This setup ensures Ruff is used solely for linting and Black for formatting:
# pyproject.toml
# Set up black as formatter
[tool.black]
line-length = 88
target-version = ["py311"]
# Set up ruff as linter only
[tool.ruff]
# Exclude directories that don’t need linting (e.g., virtual environments)
exclude = [
"venv/",
"__pycache__/"
]
fix = true
# Enable specific linting rules
select = ["F", "E", "W", "B", "I", "S"] # Example codes: F=flake8, E=errors, W=warnings, B=bugbear, I=import, S=safety
# Exclude Black-compatible rules to avoid conflicts with Black's formatting.
ignore = ["E501", "E203", "E231"] # Exclude Black-incompatible style issues
-
ignore
: Black handles specific styling, so we excluded these rules in Ruff. -
fix
: Enables Ruff to fix issues where possible, leaving formatting to Black.
Adding Pre-commit Hook for Ruff and Black
Using pre-commit hooks, I configured .pre-commit-config.yaml
to enforce linting and formatting on every commit:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.7.1
hooks:
- id: ruff
3. Running Ruff and Black from the Command Line
With the above setup, you can use the following commands:
- Run Ruff:
ruff check . --fix
- Run Black:
black .
These commands apply fixes to all Python files, ensuring consistent styling and quality checks.
4. VS Code Integration
To automate Ruff and Black on save, I added the following configuration in .vscode/settings.json
:
{
// Enable Black as the default formatter
"python.formatting.provider": "black",
"editor.formatOnSave": true,
// Set up Ruff as the linter
"python.linting.enabled": true,
"python.linting.ruffEnabled": true,
// Ensure only Ruff is used as the linter
"python.linting.flake8Enabled": false,
"python.linting.pylintEnabled": false,
// Format and lint on save
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
}
}
This setup makes Black the default formatter and Ruff the only active linter in VS Code, allowing both to run
automatically upon saving.
5. Findings and Fixes
Once configured, Ruff and Black identified several issues:
- Line Length (E501): Ruff initially flagged long lines, which Black auto-formatted.
- Unused Imports and Variables: Ruff caught several unused imports and variables.
- Indentation and Styling Consistency: Black applied consistent spacing and indentation, enhancing readability.
6. Challenges
One notable challenge was understanding that some styles are incompatible between Ruff and Black. For example:
-
Line Length (E501): Ruff initially flagged long lines exceeding 88 characters, which Black handles by wrapping
lines. To prevent conflicts, I added
E501
to Ruff’s ignore list. Despite this, Ruff sometimes flagged E501 errors if Black didn’t apply the expected breakpoints. These discrepancies underscored the importance of adjusting each tool’s configuration and understanding where they may overlap.
7. Lessons Learned
Using Ruff and Black together has been a great way to improve code quality. Here’s what I learned:
- Consistency: Black’s opinionated style reduces ambiguity in code styling.
- Automation: Pre-commit hooks save time and ensure consistent formatting.
- Editor Integration: Configuring Ruff and Black to run on save within VS Code streamlined development.
-
Compatibility: Learning how to resolve conflicts like
E501
taught me about tool configurations and helped fine-tune project workflows.
Top comments (0)