Ruff Score 🎯
A Pylint-style scoring system for Ruff linter output. Get a numerical quality score (0-10) for your Python code based on Ruff's analysis, using a rule-category mapping that covers every prefix in Ruff's rule set.
Features
- 🎯 Pylint-style scoring: Familiar 0-10 quality score system, using Pylint's own formula
- 🔧 Ruff integration: Built on top of the fast Ruff linter
- 🗂️ Full rule coverage: Every Ruff prefix (E, F, PLE/PLC/PLR/PLW, ASYNC, S, TRY, RUF, and 40+ more) is mapped to a Pylint-style category and severity weight — not just a handful of common ones
- ⚖️ Two-level weighting: Issues are bucketed into
error/warning/convention/refactor, and within a category, individual rules can carry different weights (e.g. a SQL-injection finding counts for more than a strayprint) - 📊 Detailed reports: Categorized issue breakdown with a printable summary
- 📁 File & directory support: Analyze single files or entire projects
- ⚙️ Configurable: Use your existing Ruff configuration file, and override weights/categories in Python
Installation
pip install ruff-score
Quick Start
from pathlib import Path
from ruff_score import RuffScorer
scorer = RuffScorer()
# Score a single file
result = scorer.score_file(Path("myfile.py"))
print(f"Score: {result['score']:.2f}/10.00")
# Score a directory
result = scorer.score_directory(Path("src/"))
scorer.print_report(result)
# Score against a specific Ruff config
result = scorer.score_directory(Path("src/"), config_file=Path("pyproject.toml"))
How It Works
Ruff Score uses the same scoring formula as Pylint:
Score = 10.0 - ((5*errors + warnings + refactor + convention) / statements * 10)
statements is the number of AST statement nodes in the analyzed file(s), and errors / warnings / refactor / convention are the weighted issue totals for each category — not just raw counts. So a category's contribution to the score is the sum of each issue's individual rule weight, which means two "warning"-category issues aren't necessarily worth the same amount if one rule is riskier than another.
Rule Categories & Weights
Every Ruff prefix is mapped to one of four Pylint-style categories, based on Ruff's documented rule set:
| Category | Meaning | Example prefixes | Typical weight |
|---|---|---|---|
| Error | Real bugs / correctness issues | E (pycodestyle), F (Pyflakes), PLE (Pylint errors), AIR (Airflow) |
3–5 |
| Warning | Security risks and likely-bug patterns | S (bandit/security), B (bugbear), ASYNC, TRY, PERF, T20/T10, DTZ, PLW |
1–4 |
| Convention | Naming, docs, style, imports | N (naming), D (docstrings), ANN, I (isort), Q, COM, PLC |
1 |
| Refactor | Modernization / simplification | UP (pyupgrade), SIM (simplify), PLR, PTH, C4, RUF |
1 |
The full mapping (RULE_CATEGORIES and RULE_WEIGHTS) lives at the top of ruff_scorer.py and covers ~50 rule prefixes, including ones that embed a digit in the prefix itself (e.g. C4, C90, T10, T20) and multi-letter prefixes (e.g. PLE, ASYNC, TRY). Any prefix Ruff adds in the future that isn't in the table falls back to default_weight / default_category.
Score Interpretation
- 9.0-10.0: Excellent code quality
- 7.0-8.9: Good code quality
- 5.0-6.9: Needs improvement
- 0.0-4.9: Poor code quality
Example Output
==================================================
RUFF QUALITY SCORE REPORT
==================================================
Target: src/
Score: 8.45/10.00
Statements analyzed: 1,247
Files analyzed: 23
Total issues: 12
Issues by category:
Error: 0
Warning: 3
Convention: 8
Refactor: 1
Good code quality
Advanced Usage
Custom Weights and Categories
from ruff_scorer import RuffScorer
# Change the fallback used for any rule prefix not in RULE_WEIGHTS/RULE_CATEGORIES
scorer = RuffScorer(default_weight=2, default_category="warning")
# Override the weight or category for a specific prefix
scorer.RULE_WEIGHTS["D"] = 1 # docstring issues
scorer.RULE_CATEGORIES["D"] = "convention"
# Inspect how a given rule code resolves
scorer.get_rule_weight("S608") # -> 4
scorer.get_rule_category("S608") # -> "warning"
Integration with CI/CD
# check_quality.py
import sys
from pathlib import Path
from ruff_scorer import RuffScorer
scorer = RuffScorer()
result = scorer.score_directory(Path("src/"))
scorer.print_report(result)
THRESHOLD = 7.0
sys.exit(0 if result["score"] >= THRESHOLD else 1)
python check_quality.py && echo "Quality check passed!" || echo "Quality check failed!"
Configuration
Ruff Score respects your existing Ruff configuration by passing it straight through to ruff check --config. Place your settings in:
pyproject.tomlruff.toml.ruff.toml
Example configuration:
[tool.ruff]
line-length = 88
target-version = "py38"
[tool.ruff.lint]
select = ["E", "F", "W", "C", "N", "D", "S", "B"]
ignore = ["E501", "D100"]
Requirements
- Python 3.8+
- Ruff installed and available on your
PATH
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Related Projects
Metadata
Release files for ruff-score 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ruff_score-0.4.0.tar.gz | 43.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ruff_score-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.3 kB
Release files / ruff_score-0.4.0.tar.gz
| Download URL | ruff_score-0.4.0.tar.gz |
|---|---|
| Size | 43.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
befeee358e5f55f4b4ef20445a980c4c31d4f489a37ba48ed396126c8dde0c66
|
|
BLAKE2b-256 checksum How to use checksums |
4e1373fb8f42bb49229b1114d416a1ac64914dc000356df89f2cb103b84ff69e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruff_score-0.4.0-py3-none-any.whl
| Download URL | ruff_score-0.4.0-py3-none-any.whl |
|---|---|
| Size | 10.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
36bcab78035fc2fbd63bc9a91b268c57e2107b388507108c65db7ee0c1e9a505
|
|
BLAKE2b-256 checksum How to use checksums |
6868444a9e6f2f6ac4fbca0b5624c43a5db8d5e9f691eef6a7d1ffea0813e788
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|