python-doctor 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. python_doctor-0.1.0/.github/workflows/publish-pypi.yml +30 -0
  2. python_doctor-0.1.0/.github/workflows/test.yml +30 -0
  3. python_doctor-0.1.0/.gitignore +9 -0
  4. python_doctor-0.1.0/CHANGELOG.md +23 -0
  5. python_doctor-0.1.0/CONTRIBUTING.md +48 -0
  6. python_doctor-0.1.0/LICENSE +21 -0
  7. python_doctor-0.1.0/PKG-INFO +191 -0
  8. python_doctor-0.1.0/README.md +159 -0
  9. python_doctor-0.1.0/pyproject.toml +60 -0
  10. python_doctor-0.1.0/python_doctor/__init__.py +3 -0
  11. python_doctor-0.1.0/python_doctor/analyzers/__init__.py +1 -0
  12. python_doctor-0.1.0/python_doctor/analyzers/bandit_analyzer.py +62 -0
  13. python_doctor-0.1.0/python_doctor/analyzers/complexity.py +49 -0
  14. python_doctor-0.1.0/python_doctor/analyzers/dependency_analyzer.py +87 -0
  15. python_doctor-0.1.0/python_doctor/analyzers/docstring_analyzer.py +91 -0
  16. python_doctor-0.1.0/python_doctor/analyzers/exceptions_analyzer.py +72 -0
  17. python_doctor-0.1.0/python_doctor/analyzers/imports_analyzer.py +104 -0
  18. python_doctor-0.1.0/python_doctor/analyzers/ruff_analyzer.py +53 -0
  19. python_doctor-0.1.0/python_doctor/analyzers/structure.py +269 -0
  20. python_doctor-0.1.0/python_doctor/analyzers/vulture_analyzer.py +41 -0
  21. python_doctor-0.1.0/python_doctor/cli.py +149 -0
  22. python_doctor-0.1.0/python_doctor/py.typed +0 -0
  23. python_doctor-0.1.0/python_doctor/rules.py +76 -0
  24. python_doctor-0.1.0/python_doctor/scorer.py +27 -0
  25. python_doctor-0.1.0/tests/__init__.py +0 -0
  26. python_doctor-0.1.0/tests/test_docstring_analyzer.py +33 -0
  27. python_doctor-0.1.0/tests/test_exceptions_analyzer.py +29 -0
  28. python_doctor-0.1.0/tests/test_imports_analyzer.py +26 -0
  29. python_doctor-0.1.0/tests/test_rules.py +29 -0
  30. python_doctor-0.1.0/tests/test_scorer.py +52 -0
  31. python_doctor-0.1.0/uv.lock +360 -0
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tools
24
+ run: pip install build
25
+
26
+ - name: Build package
27
+ run: python -m build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,30 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v4
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --extra dev
28
+
29
+ - name: Run tests
30
+ run: uv run pytest tests/ -v
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+ .mypy_cache/
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2026-02-18)
4
+
5
+ ### Added
6
+ - 4 new analyzers: dependencies, docstrings, imports, exceptions
7
+ - Enhanced structure checks: test-to-source ratio, README, LICENSE, .gitignore, linter config, type checker config, py.typed
8
+ - Bandit B101 (assert) auto-filtered in test files
9
+ - Tests (22 tests via pytest)
10
+ - CI: health check on every push, tests across Python 3.10-3.13
11
+ - PyPI publishing via Trusted Publishers
12
+
13
+ ### Changed
14
+ - Score is now `max(0, 100 - total_deductions)` — categories keep their own maxes
15
+ - All high-complexity functions refactored (CC 27 → <10)
16
+ - Docstrings added to all public functions/classes
17
+
18
+ ## 0.1.0 (2026-02-18)
19
+
20
+ ### Added
21
+ - Initial release with 5 analyzers: security (Bandit), lint (Ruff), dead code (Vulture), complexity (Radon), structure
22
+ - CLI with `--verbose`, `--score`, `--json`, `--fix` flags
23
+ - Exit code 1 if score < 50
@@ -0,0 +1,48 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in python-doctor!
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ git clone https://github.com/saikatkumardey/python-doctor.git
9
+ cd python-doctor
10
+ uv sync --extra dev
11
+ uv run pytest tests/ -v
12
+ uv run python-doctor .
13
+ ```
14
+
15
+ ## Adding a New Analyzer
16
+
17
+ 1. Create `python_doctor/analyzers/your_analyzer.py`
18
+ 2. Implement `analyze(path: str, **kw) -> AnalyzerResult`
19
+ 3. Add the category to `rules.py` `CATEGORIES` dict
20
+ 4. Register it in `cli.py` `ANALYZERS` list
21
+ 5. Add tests in `tests/test_your_analyzer.py`
22
+ 6. Run `python-doctor .` — the score should still be 80+
23
+
24
+ Every analyzer follows the same pattern: scan the codebase, produce `Finding` objects, cap the deduction at the category max.
25
+
26
+ ## Running Tests
27
+
28
+ ```bash
29
+ uv run pytest tests/ -v
30
+ ```
31
+
32
+ ## Code Quality
33
+
34
+ We dogfood python-doctor on itself. The CI runs it on every push and fails if the score drops below 50.
35
+
36
+ ```bash
37
+ uv run python-doctor . --verbose
38
+ ```
39
+
40
+ ## Releases
41
+
42
+ Tags trigger PyPI + ClawHub publishing via CI. Don't publish manually.
43
+
44
+ ```bash
45
+ # Bump version in pyproject.toml and __init__.py
46
+ git tag v0.3.0
47
+ git push origin v0.3.0
48
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saikat Kumar Dey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-doctor
3
+ Version: 0.1.0
4
+ Summary: One command. One score. Built for AI agents. Scans Python codebases and returns a 0-100 health score.
5
+ Project-URL: Homepage, https://github.com/saikatkumardey/python-doctor
6
+ Project-URL: Repository, https://github.com/saikatkumardey/python-doctor
7
+ Project-URL: Issues, https://github.com/saikatkumardey/python-doctor/issues
8
+ Project-URL: Changelog, https://github.com/saikatkumardey/python-doctor/blob/main/CHANGELOG.md
9
+ Author-email: Saikat Kumar Dey <deysaikatkumar@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-agents,code-quality,developer-tools,linting,python,static-analysis
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: bandit>=1.7.0
26
+ Requires-Dist: radon>=6.0.0
27
+ Requires-Dist: ruff>=0.4.0
28
+ Requires-Dist: vulture>=2.11
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Python Doctor 🐍
34
+
35
+ [![Tests](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml/badge.svg)](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/python-doctor)](https://pypi.org/project/python-doctor/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/python-doctor)](https://pypi.org/project/python-doctor/)
38
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
39
+
40
+ **One command. One score. Built for AI agents.**
41
+
42
+ Python Doctor scans a Python codebase and returns a 0-100 health score with structured, actionable output. It's designed so an AI agent can run it, read the results, fix the issues, and verify the fix — in a loop, without human intervention.
43
+
44
+ ```bash
45
+ python-doctor .
46
+ # 📊 Score: 98/100 (Excellent)
47
+ ```
48
+
49
+ ## Why?
50
+
51
+ Setting up linting, security scanning, dead code detection, and complexity analysis means configuring 5+ tools, reading 5 different output formats, and deciding what matters. Python Doctor wraps them all into a single command with a single score.
52
+
53
+ An agent doesn't need to know what Bandit is. It just needs to know the score dropped and which lines to fix.
54
+
55
+ ## Install the CLI
56
+
57
+ ```bash
58
+ # Using uv (recommended)
59
+ uv tool install git+https://github.com/saikatkumardey/python-doctor
60
+
61
+ # Using pip
62
+ pip install git+https://github.com/saikatkumardey/python-doctor
63
+
64
+ # Or clone and run directly
65
+ git clone https://github.com/saikatkumardey/python-doctor.git
66
+ cd python-doctor
67
+ uv run python-doctor /path/to/project
68
+ ```
69
+
70
+ ## Add to Your Coding Agent
71
+
72
+ Python Doctor works with any agent that can run shell commands. Install the CLI (above), then add the rule to your agent:
73
+
74
+ ### Claude Code
75
+
76
+ Add to your `CLAUDE.md`:
77
+
78
+ ```markdown
79
+ ## Python Health Check
80
+
81
+ Before finishing work on Python files, run:
82
+ python-doctor . --json
83
+
84
+ Fix any findings with severity "error". Target score: 80+.
85
+ If score drops below 50, do not commit — fix the issues first.
86
+ ```
87
+
88
+ ### Cursor
89
+
90
+ Add to `.cursor/rules/python-doctor.mdc`:
91
+
92
+ ```markdown
93
+ ---
94
+ description: Python codebase health check
95
+ globs: "**/*.py"
96
+ alwaysApply: false
97
+ ---
98
+
99
+ Run `python-doctor . --json` after modifying Python files.
100
+ Fix findings. Target score: 80+. Do not commit below 50.
101
+ ```
102
+
103
+ ### OpenAI Codex
104
+
105
+ Add to `AGENTS.md`:
106
+
107
+ ```markdown
108
+ ## Python Health Check
109
+
110
+ After modifying Python files, run `python-doctor . --json` to check codebase health.
111
+ Fix any findings. Target score: 80+. Exit code 1 means score < 50 — fix before committing.
112
+ ```
113
+
114
+ ### Windsurf / Cline / Aider
115
+
116
+ Add to your project rules or system prompt:
117
+
118
+ ```
119
+ After modifying Python files, run: python-doctor . --json
120
+ Read the output. Fix findings with severity "error" first, then warnings.
121
+ Re-run to verify the score improved. Target: 80+.
122
+ ```
123
+
124
+ ### OpenClaw
125
+
126
+ ```bash
127
+ clawhub install python-doctor
128
+ ```
129
+
130
+ ### GitHub Actions (CI)
131
+
132
+ ```yaml
133
+ - name: Health Check
134
+ run: |
135
+ uv tool install git+https://github.com/saikatkumardey/python-doctor
136
+ python-doctor . --verbose
137
+ ```
138
+
139
+ Exits with code 1 if score < 50.
140
+
141
+ ## Usage
142
+
143
+ ```bash
144
+ # Scan current directory
145
+ python-doctor .
146
+
147
+ # Verbose — show all findings with line numbers
148
+ python-doctor . --verbose
149
+
150
+ # Just the score (for CI or quick checks)
151
+ python-doctor . --score
152
+
153
+ # Structured JSON for agents
154
+ python-doctor . --json
155
+
156
+ # Auto-fix what Ruff can handle, then report the rest
157
+ python-doctor . --fix
158
+ ```
159
+
160
+ ## What It Checks
161
+
162
+ 9 categories, 5 external tools + 4 custom AST analyzers:
163
+
164
+ | Category | Max | What |
165
+ |----------|-----|------|
166
+ | 🔒 Security | -30 | Bandit (SQLi, hardcoded secrets, unsafe calls). Auto-skips `assert` in test files. |
167
+ | 🧹 Lint | -25 | Ruff (unused imports, undefined names, style) |
168
+ | 💀 Dead Code | -15 | Vulture (unused functions, variables, imports) |
169
+ | 🔄 Complexity | -15 | Radon (cyclomatic complexity > 10) |
170
+ | 🏗 Structure | -15 | File sizes, test ratio, type hints, README, LICENSE, linter/type-checker config |
171
+ | 📦 Dependencies | -15 | Build file exists, no mixed systems, pip-audit vulnerabilities |
172
+ | 📝 Docstrings | -10 | Public function/class docstring coverage |
173
+ | 🔗 Imports | -10 | Star imports, circular import detection |
174
+ | ⚡ Exceptions | -10 | Bare `except:`, silently swallowed exceptions |
175
+
176
+ Score = `max(0, 100 - total_deductions)`. Each category is capped at its max.
177
+
178
+ ## The Loop
179
+
180
+ This is how an agent uses it:
181
+
182
+ 1. `python-doctor . --json` → read the report
183
+ 2. Fix the findings (auto-fix with `--fix`, manual fixes for the rest)
184
+ 3. `python-doctor . --score` → verify improvement
185
+ 4. Repeat until score target met
186
+
187
+ We built Python Doctor, then ran it on itself. Score: 47. Fixed everything it flagged. Score: 98. The tool eats its own dogfood.
188
+
189
+ ## License
190
+
191
+ MIT — Saikat Kumar Dey, 2026
@@ -0,0 +1,159 @@
1
+ # Python Doctor 🐍
2
+
3
+ [![Tests](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml/badge.svg)](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/python-doctor)](https://pypi.org/project/python-doctor/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/python-doctor)](https://pypi.org/project/python-doctor/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ **One command. One score. Built for AI agents.**
9
+
10
+ Python Doctor scans a Python codebase and returns a 0-100 health score with structured, actionable output. It's designed so an AI agent can run it, read the results, fix the issues, and verify the fix — in a loop, without human intervention.
11
+
12
+ ```bash
13
+ python-doctor .
14
+ # 📊 Score: 98/100 (Excellent)
15
+ ```
16
+
17
+ ## Why?
18
+
19
+ Setting up linting, security scanning, dead code detection, and complexity analysis means configuring 5+ tools, reading 5 different output formats, and deciding what matters. Python Doctor wraps them all into a single command with a single score.
20
+
21
+ An agent doesn't need to know what Bandit is. It just needs to know the score dropped and which lines to fix.
22
+
23
+ ## Install the CLI
24
+
25
+ ```bash
26
+ # Using uv (recommended)
27
+ uv tool install git+https://github.com/saikatkumardey/python-doctor
28
+
29
+ # Using pip
30
+ pip install git+https://github.com/saikatkumardey/python-doctor
31
+
32
+ # Or clone and run directly
33
+ git clone https://github.com/saikatkumardey/python-doctor.git
34
+ cd python-doctor
35
+ uv run python-doctor /path/to/project
36
+ ```
37
+
38
+ ## Add to Your Coding Agent
39
+
40
+ Python Doctor works with any agent that can run shell commands. Install the CLI (above), then add the rule to your agent:
41
+
42
+ ### Claude Code
43
+
44
+ Add to your `CLAUDE.md`:
45
+
46
+ ```markdown
47
+ ## Python Health Check
48
+
49
+ Before finishing work on Python files, run:
50
+ python-doctor . --json
51
+
52
+ Fix any findings with severity "error". Target score: 80+.
53
+ If score drops below 50, do not commit — fix the issues first.
54
+ ```
55
+
56
+ ### Cursor
57
+
58
+ Add to `.cursor/rules/python-doctor.mdc`:
59
+
60
+ ```markdown
61
+ ---
62
+ description: Python codebase health check
63
+ globs: "**/*.py"
64
+ alwaysApply: false
65
+ ---
66
+
67
+ Run `python-doctor . --json` after modifying Python files.
68
+ Fix findings. Target score: 80+. Do not commit below 50.
69
+ ```
70
+
71
+ ### OpenAI Codex
72
+
73
+ Add to `AGENTS.md`:
74
+
75
+ ```markdown
76
+ ## Python Health Check
77
+
78
+ After modifying Python files, run `python-doctor . --json` to check codebase health.
79
+ Fix any findings. Target score: 80+. Exit code 1 means score < 50 — fix before committing.
80
+ ```
81
+
82
+ ### Windsurf / Cline / Aider
83
+
84
+ Add to your project rules or system prompt:
85
+
86
+ ```
87
+ After modifying Python files, run: python-doctor . --json
88
+ Read the output. Fix findings with severity "error" first, then warnings.
89
+ Re-run to verify the score improved. Target: 80+.
90
+ ```
91
+
92
+ ### OpenClaw
93
+
94
+ ```bash
95
+ clawhub install python-doctor
96
+ ```
97
+
98
+ ### GitHub Actions (CI)
99
+
100
+ ```yaml
101
+ - name: Health Check
102
+ run: |
103
+ uv tool install git+https://github.com/saikatkumardey/python-doctor
104
+ python-doctor . --verbose
105
+ ```
106
+
107
+ Exits with code 1 if score < 50.
108
+
109
+ ## Usage
110
+
111
+ ```bash
112
+ # Scan current directory
113
+ python-doctor .
114
+
115
+ # Verbose — show all findings with line numbers
116
+ python-doctor . --verbose
117
+
118
+ # Just the score (for CI or quick checks)
119
+ python-doctor . --score
120
+
121
+ # Structured JSON for agents
122
+ python-doctor . --json
123
+
124
+ # Auto-fix what Ruff can handle, then report the rest
125
+ python-doctor . --fix
126
+ ```
127
+
128
+ ## What It Checks
129
+
130
+ 9 categories, 5 external tools + 4 custom AST analyzers:
131
+
132
+ | Category | Max | What |
133
+ |----------|-----|------|
134
+ | 🔒 Security | -30 | Bandit (SQLi, hardcoded secrets, unsafe calls). Auto-skips `assert` in test files. |
135
+ | 🧹 Lint | -25 | Ruff (unused imports, undefined names, style) |
136
+ | 💀 Dead Code | -15 | Vulture (unused functions, variables, imports) |
137
+ | 🔄 Complexity | -15 | Radon (cyclomatic complexity > 10) |
138
+ | 🏗 Structure | -15 | File sizes, test ratio, type hints, README, LICENSE, linter/type-checker config |
139
+ | 📦 Dependencies | -15 | Build file exists, no mixed systems, pip-audit vulnerabilities |
140
+ | 📝 Docstrings | -10 | Public function/class docstring coverage |
141
+ | 🔗 Imports | -10 | Star imports, circular import detection |
142
+ | ⚡ Exceptions | -10 | Bare `except:`, silently swallowed exceptions |
143
+
144
+ Score = `max(0, 100 - total_deductions)`. Each category is capped at its max.
145
+
146
+ ## The Loop
147
+
148
+ This is how an agent uses it:
149
+
150
+ 1. `python-doctor . --json` → read the report
151
+ 2. Fix the findings (auto-fix with `--fix`, manual fixes for the rest)
152
+ 3. `python-doctor . --score` → verify improvement
153
+ 4. Repeat until score target met
154
+
155
+ We built Python Doctor, then ran it on itself. Score: 47. Fixed everything it flagged. Score: 98. The tool eats its own dogfood.
156
+
157
+ ## License
158
+
159
+ MIT — Saikat Kumar Dey, 2026
@@ -0,0 +1,60 @@
1
+ [project]
2
+ name = "python-doctor"
3
+ version = "0.1.0"
4
+ description = "One command. One score. Built for AI agents. Scans Python codebases and returns a 0-100 health score."
5
+ readme = "README.md"
6
+ license = {text = "MIT"}
7
+ requires-python = ">=3.10"
8
+ authors = [
9
+ {name = "Saikat Kumar Dey", email = "deysaikatkumar@gmail.com"},
10
+ ]
11
+ keywords = ["python", "linting", "code-quality", "ai-agents", "static-analysis", "developer-tools"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Software Development :: Quality Assurance",
23
+ "Topic :: Software Development :: Testing",
24
+ ]
25
+ dependencies = [
26
+ "ruff>=0.4.0",
27
+ "bandit>=1.7.0",
28
+ "vulture>=2.11",
29
+ "radon>=6.0.0",
30
+ ]
31
+
32
+ [project.scripts]
33
+ python-doctor = "python_doctor.cli:main"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/saikatkumardey/python-doctor"
37
+ Repository = "https://github.com/saikatkumardey/python-doctor"
38
+ Issues = "https://github.com/saikatkumardey/python-doctor/issues"
39
+ Changelog = "https://github.com/saikatkumardey/python-doctor/blob/main/CHANGELOG.md"
40
+
41
+ [project.optional-dependencies]
42
+ dev = ["pytest>=7.0"]
43
+
44
+ [build-system]
45
+ requires = ["hatchling"]
46
+ build-backend = "hatchling.build"
47
+
48
+ [tool.ruff]
49
+ target-version = "py310"
50
+ line-length = 120
51
+ exclude = [".venv", "venv", "node_modules", "__pycache__", ".git", ".tox"]
52
+
53
+ [tool.ruff.lint]
54
+ select = ["E", "F", "W", "I"]
55
+
56
+ [tool.mypy]
57
+ python_version = "3.10"
58
+ warn_return_any = true
59
+ warn_unused_configs = true
60
+ ignore_missing_imports = true
@@ -0,0 +1,3 @@
1
+ """Python Doctor — health scoring for Python codebases."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1 @@
1
+ """Analyzers for Python Doctor."""
@@ -0,0 +1,62 @@
1
+ """Bandit security analyzer."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess # nosec B404 — required for running CLI tools
6
+
7
+ from ..rules import BANDIT_SEVERITY_COST, CATEGORIES, AnalyzerResult, Finding
8
+
9
+ _EXCLUDE_DIRS = [".venv", "venv", "node_modules", "__pycache__", ".git", ".tox", ".mypy_cache", ".ruff_cache"]
10
+
11
+
12
+ def _is_test_file(filepath: str) -> bool:
13
+ """Check if a file is a test file (in test dir or test-named)."""
14
+ parts = os.path.normpath(filepath).split(os.sep)
15
+ if any(p in ("tests", "test") for p in parts):
16
+ return True
17
+ basename = os.path.basename(filepath)
18
+ return basename.startswith("test_") or basename.endswith("_test.py")
19
+
20
+
21
+ def analyze(path: str, **_kw) -> AnalyzerResult:
22
+ """Run bandit security analysis on the project."""
23
+ result = AnalyzerResult(category="security")
24
+ max_ded = CATEGORIES["security"]["max_deduction"]
25
+ abs_path = os.path.abspath(path)
26
+ excludes = ",".join(os.path.join(abs_path, d) for d in _EXCLUDE_DIRS)
27
+
28
+ try:
29
+ proc = subprocess.run( # nosec B603 B607 — intentional subprocess call to bandit CLI tool
30
+ ["bandit", "-r", "-f", "json", "-q",
31
+ "--exclude", excludes,
32
+ abs_path],
33
+ capture_output=True, text=True, timeout=120
34
+ )
35
+ data = json.loads(proc.stdout) if proc.stdout.strip() else {}
36
+ items = data.get("results", [])
37
+ except FileNotFoundError:
38
+ result.error = "bandit not found (skipped)"
39
+ return result
40
+ except Exception as e:
41
+ result.error = str(e)
42
+ return result
43
+
44
+ for item in items:
45
+ sev = item.get("issue_severity", "LOW").upper()
46
+ cost = BANDIT_SEVERITY_COST.get(sev, 1)
47
+ test_id = item.get("test_id", "?")
48
+ msg = item.get("issue_text", "")
49
+ filename = item.get("filename", "")
50
+ line = item.get("line_number", 0)
51
+
52
+ # Skip B101 (assert) in test files
53
+ if test_id == "B101" and _is_test_file(filename):
54
+ continue
55
+
56
+ result.findings.append(Finding(
57
+ category="security", rule=f"bandit/{test_id}", message=msg,
58
+ file=filename, line=line, severity=sev.lower(), cost=cost,
59
+ ))
60
+
61
+ result.deduction = min(sum(f.cost for f in result.findings), max_ded)
62
+ return result