pycodegate 0.2.0__tar.gz → 0.5.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.
- {pycodegate-0.2.0 → pycodegate-0.5.0}/.gitignore +1 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/AGENTS.md +23 -6
- pycodegate-0.5.0/CHANGELOG.md +20 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/PKG-INFO +77 -25
- {pycodegate-0.2.0 → pycodegate-0.5.0}/README.md +75 -23
- pycodegate-0.5.0/benchmarks/README.md +31 -0
- pycodegate-0.5.0/benchmarks/results/0.5.0.json +227 -0
- pycodegate-0.5.0/benchmarks/results/0.5.0.md +84 -0
- pycodegate-0.5.0/benchmarks/run_corpus.py +140 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/pyproject.toml +11 -1
- {pycodegate-0.2.0 → pycodegate-0.5.0}/skills/pycodegate/SKILL.md +3 -1
- pycodegate-0.5.0/src/pycodegate/__init__.py +10 -0
- pycodegate-0.5.0/src/pycodegate/__main__.py +6 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/api.py +2 -2
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/cli.py +8 -4
- pycodegate-0.5.0/src/pycodegate/config.py +130 -0
- pycodegate-0.5.0/src/pycodegate/constants.py +61 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/discover.py +27 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/output.py +20 -52
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/profile.py +1 -1
- pycodegate-0.5.0/src/pycodegate/rules/__init__.py +58 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/architecture.py +30 -3
- pycodegate-0.5.0/src/pycodegate/rules/celery.py +165 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/complexity.py +2 -1
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/correctness.py +36 -44
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/dead_code.py +5 -2
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/django.py +33 -1
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/fastapi.py +39 -8
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/flask.py +22 -15
- pycodegate-0.5.0/src/pycodegate/rules/import_boundaries.py +70 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/imports.py +13 -1
- pycodegate-0.5.0/src/pycodegate/rules/logging_rules.py +165 -0
- pycodegate-0.5.0/src/pycodegate/rules/numpy_rules.py +164 -0
- pycodegate-0.5.0/src/pycodegate/rules/pandas_rules.py +165 -0
- pycodegate-0.5.0/src/pycodegate/rules/performance.py +117 -0
- pycodegate-0.5.0/src/pycodegate/rules/pydantic.py +271 -0
- pycodegate-0.5.0/src/pycodegate/rules/pytest_rules.py +142 -0
- pycodegate-0.5.0/src/pycodegate/rules/requests_rules.py +84 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/security.py +39 -0
- pycodegate-0.5.0/src/pycodegate/rules/sqlalchemy.py +145 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/structure.py +18 -3
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/scan.py +51 -10
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/score.py +32 -16
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/types.py +18 -1
- pycodegate-0.5.0/src/pycodegate/utils/ast_helpers.py +34 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/badge.py +2 -2
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/file_discovery.py +22 -10
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/fixer.py +4 -4
- pycodegate-0.5.0/src/pycodegate/utils/is_test_file.py +22 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/precommit.py +10 -13
- pycodegate-0.5.0/tests/rules/__init__.py +0 -0
- pycodegate-0.5.0/tests/rules/test_celery.py +99 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_correctness.py +25 -10
- pycodegate-0.5.0/tests/rules/test_import_boundaries.py +135 -0
- pycodegate-0.5.0/tests/rules/test_imports.py +91 -0
- pycodegate-0.5.0/tests/rules/test_logging_rules.py +49 -0
- pycodegate-0.5.0/tests/rules/test_numpy_rules.py +71 -0
- pycodegate-0.5.0/tests/rules/test_pandas_rules.py +76 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_performance.py +0 -15
- pycodegate-0.5.0/tests/rules/test_pydantic.py +206 -0
- pycodegate-0.5.0/tests/rules/test_pytest_rules.py +59 -0
- pycodegate-0.5.0/tests/rules/test_requests_rules.py +80 -0
- pycodegate-0.5.0/tests/rules/test_sqlalchemy.py +73 -0
- pycodegate-0.5.0/tests/test_audit_fixes.py +180 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_cli.py +3 -2
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_config.py +20 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_fixer.py +0 -5
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_output.py +0 -32
- pycodegate-0.5.0/tests/test_precision.py +75 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_profile.py +3 -2
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_score.py +2 -30
- {pycodegate-0.2.0 → pycodegate-0.5.0}/uv.lock +1 -1
- pycodegate-0.2.0/src/pycodegate/__init__.py +0 -3
- pycodegate-0.2.0/src/pycodegate/config.py +0 -81
- pycodegate-0.2.0/src/pycodegate/constants.py +0 -32
- pycodegate-0.2.0/src/pycodegate/rules/__init__.py +0 -38
- pycodegate-0.2.0/src/pycodegate/rules/performance.py +0 -104
- pycodegate-0.2.0/src/pycodegate/utils/ast_helpers.py +0 -19
- pycodegate-0.2.0/tests/rules/test_imports.py +0 -49
- {pycodegate-0.2.0 → pycodegate-0.5.0}/.github/workflows/ci.yml +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/.github/workflows/publish.yml +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/LICENSE +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/docs/superpowers/plans/2026-03-22-python-doctor.md +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/_compat.py +0 -0
- /pycodegate-0.2.0/tests/rules/__init__.py → /pycodegate-0.5.0/src/pycodegate/py.typed +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/base.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/rules/dependencies.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/__init__.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/src/pycodegate/utils/diff.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/conftest.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/fixtures/basic_python/app.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/fixtures/clean_project/app.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_architecture.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_complexity.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_dead_code.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_dependencies.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_django.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_fastapi.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_flask.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_security.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_security_new.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/rules/test_structure.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_discover.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_integration.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_scan.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/tests/test_types.py +0 -0
- {pycodegate-0.2.0 → pycodegate-0.5.0}/whitelist.py +0 -0
|
@@ -63,16 +63,33 @@ src/pycodegate/
|
|
|
63
63
|
constants.py — Magic numbers and thresholds
|
|
64
64
|
rules/
|
|
65
65
|
base.py — Abstract BaseRules with AST parsing
|
|
66
|
-
security.py — eval, exec, pickle, yaml, secrets, hashes
|
|
67
|
-
performance.py — string concat
|
|
66
|
+
security.py — eval, exec, pickle, yaml, secrets, hashes, os.system, shell
|
|
67
|
+
performance.py — string concat in loops, star imports
|
|
68
68
|
architecture.py — giant modules, nesting, god functions, too many args
|
|
69
|
+
complexity.py — cyclomatic complexity
|
|
69
70
|
correctness.py — mutable defaults, bare except, assert, return in init
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
imports.py — circular imports (project-level)
|
|
72
|
+
structure.py — tests/README/LICENSE/linter/type-checker, type coverage
|
|
73
|
+
dependencies.py — pip-audit vulnerability scan
|
|
73
74
|
dead_code.py — Vulture integration
|
|
75
|
+
django.py / fastapi.py / flask.py — web framework rules
|
|
76
|
+
pydantic.py / sqlalchemy.py / celery.py / requests_rules.py /
|
|
77
|
+
logging_rules.py / pandas_rules.py / pytest_rules.py / numpy_rules.py — library rules
|
|
74
78
|
utils/
|
|
75
|
-
file_discovery.py — Python file discovery (git + fallback)
|
|
79
|
+
file_discovery.py — Python file discovery (git + fallback), excludes non-source dirs
|
|
80
|
+
is_test_file.py — test-file detection (drives test-only rule suppression)
|
|
76
81
|
ast_helpers.py — Common AST utilities
|
|
77
82
|
diff.py — Git diff file resolution
|
|
78
83
|
```
|
|
84
|
+
|
|
85
|
+
## Precision is the product
|
|
86
|
+
|
|
87
|
+
PyCodeGate is a quality **gate** — false positives destroy its value. Be precision-first:
|
|
88
|
+
|
|
89
|
+
- A new rule MUST ship with a negative fixture/test proving it does NOT fire on the
|
|
90
|
+
idiomatic, correct form of the pattern, not just a positive test.
|
|
91
|
+
- Rules that are idiomatic in tests (asserts, long fixtures) belong in
|
|
92
|
+
`RULES_SUPPRESSED_IN_TESTS` in `constants.py`; suppression happens centrally in
|
|
93
|
+
`scan._suppress_test_noise`, keyed on the project-relative path.
|
|
94
|
+
- Validate rule changes against the corpus benchmark: `python benchmarks/run_corpus.py`.
|
|
95
|
+
A rule lighting up respected codebases needs to be narrowed, suppressed, or removed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
- Add opt-in, path-scoped import boundaries to turn architectural constraints into
|
|
6
|
+
deterministic `imports/boundary` errors with project-specific remediation guidance.
|
|
7
|
+
- Support absolute and relative static imports, aliases, nested imports, and
|
|
8
|
+
type-checking blocks in conventional flat and `src/` layouts.
|
|
9
|
+
- Integrate boundaries with JSON, SARIF, CI failure thresholds, rule ignores,
|
|
10
|
+
per-file suppression, and the lint toggle.
|
|
11
|
+
- Reject malformed boundary configuration with a readable CLI error.
|
|
12
|
+
- Enforce separation between PyCodeGate's analysis rules, shared data types, and
|
|
13
|
+
presentation using the same boundary configuration available to users.
|
|
14
|
+
|
|
15
|
+
No import restrictions are enabled for projects without boundary configuration.
|
|
16
|
+
Dynamic imports, re-export tracing, and custom import roots are outside the scope
|
|
17
|
+
of this check. See the README for configuration and matching semantics.
|
|
18
|
+
|
|
19
|
+
This release also includes the precision, scoring, and output fixes already on
|
|
20
|
+
main after the published 0.3.0 release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pycodegate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Trust, but verify. The quality gate for AI-generated Python code.
|
|
5
5
|
Project-URL: Homepage, https://github.com/themohitkhare/pycodegate
|
|
6
6
|
Project-URL: Repository, https://github.com/themohitkhare/pycodegate
|
|
@@ -53,7 +53,9 @@ AI coding agents ship code fast — but fast doesn't mean safe. Every `eval()` a
|
|
|
53
53
|
|
|
54
54
|
PyCodeGate detects your project's context: framework (Django, FastAPI, Flask), Python version, package manager (uv, poetry, pip), and test framework. That context drives which rules are active — Django projects get SQL injection checks, FastAPI projects get async-correctness checks, and so on.
|
|
55
55
|
|
|
56
|
-
It then runs
|
|
56
|
+
It then runs analysis passes **in parallel**: a lint pass across 9 categories (Security, Correctness, Complexity, Architecture, Performance, Structure, Imports, Dependencies, Dead Code), a dead-code pass powered by [Vulture](https://github.com/jendrikseipp/vulture) that finds unused functions, classes, imports, and variables, and an optional dependency-vulnerability pass via [pip-audit](https://github.com/pypa/pip-audit).
|
|
57
|
+
|
|
58
|
+
PyCodeGate is **precision-first**: it is meant to stay quiet on idiomatic, correct code so that a finding always means something. Test files only receive rules that make sense for tests (asserts, long fixtures, and long test functions are not flagged), and generated or non-source trees — `migrations`, `examples`, `docs`, and vendored directories — are skipped. The rule set is benchmarked against a corpus of respected PyPI packages (see [`benchmarks/`](benchmarks/)) to keep false positives down.
|
|
57
59
|
|
|
58
60
|
Findings are filtered through your configuration and scored using a **weighted category-budget system**. Each category has a maximum deduction budget proportional to its weight. Within a category the top 3 findings apply at full cost; additional findings apply diminishing returns (10% each), so fixing the worst issues always moves the needle. The final result is a **0–100 health score** with a label: Excellent (90+), Great (75–89), Needs work (50–74), or Critical (<50).
|
|
59
61
|
|
|
@@ -107,7 +109,7 @@ pycodegate . --json
|
|
|
107
109
|
|
|
108
110
|
```json
|
|
109
111
|
{
|
|
110
|
-
"version": "0.
|
|
112
|
+
"version": "0.3.0",
|
|
111
113
|
"path": ".",
|
|
112
114
|
"score": 87,
|
|
113
115
|
"label": "Great",
|
|
@@ -225,26 +227,33 @@ jobs:
|
|
|
225
227
|
| `--verbose` | off | Show file path and line number per finding |
|
|
226
228
|
| `--score` | off | Print only the numeric score and exit |
|
|
227
229
|
| `--json` | off | Emit structured JSON (for agents and CI) |
|
|
230
|
+
| `--sarif` | off | Emit SARIF 2.1.0 (for GitHub Code Scanning) |
|
|
228
231
|
| `--fix` | off | Run `ruff --fix` before scanning |
|
|
229
232
|
| `--diff TEXT` | — | Scan only files changed vs this base branch |
|
|
230
233
|
| `--fail-on [error\|warning\|none]` | `none` | Exit code 1 when findings at this level exist |
|
|
234
|
+
| `--min-score INT` | — | Exit code 1 when the score is below this threshold |
|
|
235
|
+
| `--profile [cli\|web\|library\|script]` | auto | Override the auto-detected project profile |
|
|
236
|
+
| `--badge` | off | Print a shields.io badge markdown snippet |
|
|
237
|
+
| `--ci` | off | Print a ready-to-use GitHub Actions workflow |
|
|
238
|
+
| `--pre-commit` | off | Install a git pre-commit hook |
|
|
231
239
|
| `-v, --version` | — | Show version and exit |
|
|
232
240
|
| `-h, --help` | — | Show help and exit |
|
|
233
241
|
|
|
234
242
|
## What It Checks
|
|
235
243
|
|
|
236
|
-
| Category | Weight |
|
|
237
|
-
|
|
238
|
-
| Security | 5 |
|
|
239
|
-
| Correctness | 4 |
|
|
240
|
-
| Complexity | 3 |
|
|
241
|
-
| Architecture | 3 |
|
|
242
|
-
| Performance | 2 |
|
|
243
|
-
| Structure | 2 |
|
|
244
|
-
| Imports | 1 |
|
|
245
|
-
|
|
|
244
|
+
| Category | Weight | What it catches |
|
|
245
|
+
|----------|--------|-----------------|
|
|
246
|
+
| Security | 5 | `eval`, `exec`, `pickle.load`, unsafe YAML, hardcoded secrets, weak hashes (excluding `usedforsecurity=False`), `os.system`, `shell=True`, `tempfile.mktemp` |
|
|
247
|
+
| Correctness | 4 | Mutable defaults, bare except, assert in production, bad `__init__` return |
|
|
248
|
+
| Complexity | 3 | Cyclomatic complexity > 15 (warning) or > 25 (error) |
|
|
249
|
+
| Architecture | 3 | Giant modules (>500 lines), deep nesting (>5), god functions (>50 lines), too many args (>7) |
|
|
250
|
+
| Performance | 2 | String concatenation in loops, star imports |
|
|
251
|
+
| Structure | 2 | Missing tests, README, LICENSE, linter/type-checker config, low type coverage |
|
|
252
|
+
| Imports | 1 | Circular imports between sibling modules |
|
|
253
|
+
| Dependencies | 1 | Known-vulnerable dependencies (via `pip-audit`, when installed) |
|
|
254
|
+
| Dead Code | 1 | Unused functions, classes, variables, and imports via Vulture |
|
|
246
255
|
|
|
247
|
-
Framework-specific rules (Django, FastAPI, Flask) are mapped into the Security or Correctness budget.
|
|
256
|
+
Weights are normalized to a 100-point deduction budget. Framework- and library-specific rules (Django, FastAPI, Flask, Pydantic, SQLAlchemy, Celery, requests, logging, pandas, pytest, NumPy) are mapped into the Security or Correctness budget.
|
|
248
257
|
|
|
249
258
|
## Scoring
|
|
250
259
|
|
|
@@ -281,8 +290,7 @@ files = ["tests/fixtures/**", "migrations/**", "scripts/**"]
|
|
|
281
290
|
"src/legacy/*.py" = ["high-complexity", "no-god-function"]
|
|
282
291
|
|
|
283
292
|
[scoring]
|
|
284
|
-
max-deduction
|
|
285
|
-
max-deduction.Dead Code = 0 # disable dead-code penalty entirely
|
|
293
|
+
max-deduction = { Security = 20, "Dead Code" = 0 } # 0 disables a category's penalty
|
|
286
294
|
```
|
|
287
295
|
|
|
288
296
|
Or use `pyproject.toml`:
|
|
@@ -306,26 +314,70 @@ files = ["tests/fixtures/**"]
|
|
|
306
314
|
|
|
307
315
|
If both files exist, `pycodegate.toml` takes precedence. CLI flags always override config values.
|
|
308
316
|
|
|
317
|
+
## Import boundaries
|
|
318
|
+
|
|
319
|
+
Turn architectural review feedback into a deterministic check. Import boundaries
|
|
320
|
+
are opt-in: forbid a module (and its submodules) in selected files and provide
|
|
321
|
+
actionable guidance for the developer or agent.
|
|
322
|
+
|
|
323
|
+
In `pycodegate.toml`:
|
|
324
|
+
|
|
325
|
+
```toml
|
|
326
|
+
[[import-boundaries]]
|
|
327
|
+
files = ["src/app/domain/*.py"]
|
|
328
|
+
forbidden = ["app.infrastructure", "requests", "fastapi"]
|
|
329
|
+
message = "Keep the domain independent of infrastructure; use a domain port."
|
|
330
|
+
|
|
331
|
+
[[import-boundaries]]
|
|
332
|
+
files = ["src/app/*.py"]
|
|
333
|
+
forbidden = ["app.legacy_client"]
|
|
334
|
+
message = "Use app.clients instead of adding new legacy dependencies."
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
For `pyproject.toml`, use `[[tool.pycodegate.import-boundaries]]` with the same
|
|
338
|
+
fields. `files` uses case-sensitive project-relative POSIX path globs; `*` can
|
|
339
|
+
span directories. `forbidden` contains literal dotted module prefixes, so
|
|
340
|
+
`requests` matches `requests.sessions` but not `requests_extra`. Both arrays
|
|
341
|
+
must contain at least one nonempty string. The optional `message` supplies the
|
|
342
|
+
diagnostic's remediation guidance.
|
|
343
|
+
|
|
344
|
+
Checks cover static `import` and `from ... import ...` statements, including
|
|
345
|
+
aliases, nested functions, and type-checking blocks. Relative imports are resolved
|
|
346
|
+
using the path under the project root, stripping a leading `src/` for conventional
|
|
347
|
+
src layouts. Custom import roots, runtime imports via `importlib` or `__import__`,
|
|
348
|
+
and re-export tracing are not supported. Importing a name from a package is
|
|
349
|
+
treated as importing that dotted name, so avoid forbidden prefixes that collide
|
|
350
|
+
with allowed attributes on the same package.
|
|
351
|
+
|
|
352
|
+
Violations emit `imports/boundary` errors with the source location and your
|
|
353
|
+
message in terminal, JSON, and SARIF output. Use
|
|
354
|
+
`pycodegate . --fail-on error` to enforce the boundary in CI. Existing rule and
|
|
355
|
+
per-file ignores apply; `--no-lint` disables boundary checks. Without configured
|
|
356
|
+
boundaries, scan behavior is unchanged. Static checks supplement tests and
|
|
357
|
+
runtime verification; a high score alone does not prove behavior is correct.
|
|
358
|
+
|
|
309
359
|
## Profiles
|
|
310
360
|
|
|
311
361
|
PyCodeGate auto-detects a project profile and adjusts rule weights accordingly. You can also set a profile explicitly in config (`profile = "library"`).
|
|
312
362
|
|
|
313
363
|
| Profile | Auto-detected when | Adjustments |
|
|
314
364
|
|---------|--------------------|-------------|
|
|
315
|
-
| `cli` | `[project.scripts]`
|
|
316
|
-
| `web` | Django / FastAPI / Flask
|
|
317
|
-
| `library` |
|
|
318
|
-
| `script` |
|
|
365
|
+
| `cli` | `[project.scripts]` or a CLI dependency (click, typer, …) | Security budget relaxed; `subprocess`/`shell` rules suppressed |
|
|
366
|
+
| `web` | Django / FastAPI / Flask / Starlette / aiohttp / … detected | Framework rules active |
|
|
367
|
+
| `library` | Has a `[build-system]` and no scripts | Default budgets |
|
|
368
|
+
| `script` | No package `__init__.py` and ≤ 5 top-level `.py` files | Structure budget relaxed; `no-tests`/`no-license` suppressed |
|
|
319
369
|
|
|
320
370
|
## Pre-commit Hook
|
|
321
371
|
|
|
322
|
-
|
|
372
|
+
`--pre-commit` installs a git `pre-commit` hook (one-time) that scores the project on
|
|
373
|
+
every commit and blocks the commit when the score falls below the threshold:
|
|
323
374
|
|
|
324
375
|
```bash
|
|
325
|
-
pycodegate . --pre-commit --
|
|
376
|
+
pycodegate . --pre-commit --min-score 80
|
|
326
377
|
```
|
|
327
378
|
|
|
328
|
-
|
|
379
|
+
If you use the [pre-commit framework](https://pre-commit.com), add it to
|
|
380
|
+
`.pre-commit-config.yaml` instead:
|
|
329
381
|
|
|
330
382
|
```yaml
|
|
331
383
|
repos:
|
|
@@ -333,7 +385,7 @@ repos:
|
|
|
333
385
|
hooks:
|
|
334
386
|
- id: pycodegate
|
|
335
387
|
name: PyCodeGate quality check
|
|
336
|
-
entry: pycodegate . --
|
|
388
|
+
entry: pycodegate . --fail-on error
|
|
337
389
|
language: system
|
|
338
390
|
types: [python]
|
|
339
391
|
pass_filenames: false
|
|
@@ -19,7 +19,9 @@ AI coding agents ship code fast — but fast doesn't mean safe. Every `eval()` a
|
|
|
19
19
|
|
|
20
20
|
PyCodeGate detects your project's context: framework (Django, FastAPI, Flask), Python version, package manager (uv, poetry, pip), and test framework. That context drives which rules are active — Django projects get SQL injection checks, FastAPI projects get async-correctness checks, and so on.
|
|
21
21
|
|
|
22
|
-
It then runs
|
|
22
|
+
It then runs analysis passes **in parallel**: a lint pass across 9 categories (Security, Correctness, Complexity, Architecture, Performance, Structure, Imports, Dependencies, Dead Code), a dead-code pass powered by [Vulture](https://github.com/jendrikseipp/vulture) that finds unused functions, classes, imports, and variables, and an optional dependency-vulnerability pass via [pip-audit](https://github.com/pypa/pip-audit).
|
|
23
|
+
|
|
24
|
+
PyCodeGate is **precision-first**: it is meant to stay quiet on idiomatic, correct code so that a finding always means something. Test files only receive rules that make sense for tests (asserts, long fixtures, and long test functions are not flagged), and generated or non-source trees — `migrations`, `examples`, `docs`, and vendored directories — are skipped. The rule set is benchmarked against a corpus of respected PyPI packages (see [`benchmarks/`](benchmarks/)) to keep false positives down.
|
|
23
25
|
|
|
24
26
|
Findings are filtered through your configuration and scored using a **weighted category-budget system**. Each category has a maximum deduction budget proportional to its weight. Within a category the top 3 findings apply at full cost; additional findings apply diminishing returns (10% each), so fixing the worst issues always moves the needle. The final result is a **0–100 health score** with a label: Excellent (90+), Great (75–89), Needs work (50–74), or Critical (<50).
|
|
25
27
|
|
|
@@ -73,7 +75,7 @@ pycodegate . --json
|
|
|
73
75
|
|
|
74
76
|
```json
|
|
75
77
|
{
|
|
76
|
-
"version": "0.
|
|
78
|
+
"version": "0.3.0",
|
|
77
79
|
"path": ".",
|
|
78
80
|
"score": 87,
|
|
79
81
|
"label": "Great",
|
|
@@ -191,26 +193,33 @@ jobs:
|
|
|
191
193
|
| `--verbose` | off | Show file path and line number per finding |
|
|
192
194
|
| `--score` | off | Print only the numeric score and exit |
|
|
193
195
|
| `--json` | off | Emit structured JSON (for agents and CI) |
|
|
196
|
+
| `--sarif` | off | Emit SARIF 2.1.0 (for GitHub Code Scanning) |
|
|
194
197
|
| `--fix` | off | Run `ruff --fix` before scanning |
|
|
195
198
|
| `--diff TEXT` | — | Scan only files changed vs this base branch |
|
|
196
199
|
| `--fail-on [error\|warning\|none]` | `none` | Exit code 1 when findings at this level exist |
|
|
200
|
+
| `--min-score INT` | — | Exit code 1 when the score is below this threshold |
|
|
201
|
+
| `--profile [cli\|web\|library\|script]` | auto | Override the auto-detected project profile |
|
|
202
|
+
| `--badge` | off | Print a shields.io badge markdown snippet |
|
|
203
|
+
| `--ci` | off | Print a ready-to-use GitHub Actions workflow |
|
|
204
|
+
| `--pre-commit` | off | Install a git pre-commit hook |
|
|
197
205
|
| `-v, --version` | — | Show version and exit |
|
|
198
206
|
| `-h, --help` | — | Show help and exit |
|
|
199
207
|
|
|
200
208
|
## What It Checks
|
|
201
209
|
|
|
202
|
-
| Category | Weight |
|
|
203
|
-
|
|
204
|
-
| Security | 5 |
|
|
205
|
-
| Correctness | 4 |
|
|
206
|
-
| Complexity | 3 |
|
|
207
|
-
| Architecture | 3 |
|
|
208
|
-
| Performance | 2 |
|
|
209
|
-
| Structure | 2 |
|
|
210
|
-
| Imports | 1 |
|
|
211
|
-
|
|
|
210
|
+
| Category | Weight | What it catches |
|
|
211
|
+
|----------|--------|-----------------|
|
|
212
|
+
| Security | 5 | `eval`, `exec`, `pickle.load`, unsafe YAML, hardcoded secrets, weak hashes (excluding `usedforsecurity=False`), `os.system`, `shell=True`, `tempfile.mktemp` |
|
|
213
|
+
| Correctness | 4 | Mutable defaults, bare except, assert in production, bad `__init__` return |
|
|
214
|
+
| Complexity | 3 | Cyclomatic complexity > 15 (warning) or > 25 (error) |
|
|
215
|
+
| Architecture | 3 | Giant modules (>500 lines), deep nesting (>5), god functions (>50 lines), too many args (>7) |
|
|
216
|
+
| Performance | 2 | String concatenation in loops, star imports |
|
|
217
|
+
| Structure | 2 | Missing tests, README, LICENSE, linter/type-checker config, low type coverage |
|
|
218
|
+
| Imports | 1 | Circular imports between sibling modules |
|
|
219
|
+
| Dependencies | 1 | Known-vulnerable dependencies (via `pip-audit`, when installed) |
|
|
220
|
+
| Dead Code | 1 | Unused functions, classes, variables, and imports via Vulture |
|
|
212
221
|
|
|
213
|
-
Framework-specific rules (Django, FastAPI, Flask) are mapped into the Security or Correctness budget.
|
|
222
|
+
Weights are normalized to a 100-point deduction budget. Framework- and library-specific rules (Django, FastAPI, Flask, Pydantic, SQLAlchemy, Celery, requests, logging, pandas, pytest, NumPy) are mapped into the Security or Correctness budget.
|
|
214
223
|
|
|
215
224
|
## Scoring
|
|
216
225
|
|
|
@@ -247,8 +256,7 @@ files = ["tests/fixtures/**", "migrations/**", "scripts/**"]
|
|
|
247
256
|
"src/legacy/*.py" = ["high-complexity", "no-god-function"]
|
|
248
257
|
|
|
249
258
|
[scoring]
|
|
250
|
-
max-deduction
|
|
251
|
-
max-deduction.Dead Code = 0 # disable dead-code penalty entirely
|
|
259
|
+
max-deduction = { Security = 20, "Dead Code" = 0 } # 0 disables a category's penalty
|
|
252
260
|
```
|
|
253
261
|
|
|
254
262
|
Or use `pyproject.toml`:
|
|
@@ -272,26 +280,70 @@ files = ["tests/fixtures/**"]
|
|
|
272
280
|
|
|
273
281
|
If both files exist, `pycodegate.toml` takes precedence. CLI flags always override config values.
|
|
274
282
|
|
|
283
|
+
## Import boundaries
|
|
284
|
+
|
|
285
|
+
Turn architectural review feedback into a deterministic check. Import boundaries
|
|
286
|
+
are opt-in: forbid a module (and its submodules) in selected files and provide
|
|
287
|
+
actionable guidance for the developer or agent.
|
|
288
|
+
|
|
289
|
+
In `pycodegate.toml`:
|
|
290
|
+
|
|
291
|
+
```toml
|
|
292
|
+
[[import-boundaries]]
|
|
293
|
+
files = ["src/app/domain/*.py"]
|
|
294
|
+
forbidden = ["app.infrastructure", "requests", "fastapi"]
|
|
295
|
+
message = "Keep the domain independent of infrastructure; use a domain port."
|
|
296
|
+
|
|
297
|
+
[[import-boundaries]]
|
|
298
|
+
files = ["src/app/*.py"]
|
|
299
|
+
forbidden = ["app.legacy_client"]
|
|
300
|
+
message = "Use app.clients instead of adding new legacy dependencies."
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
For `pyproject.toml`, use `[[tool.pycodegate.import-boundaries]]` with the same
|
|
304
|
+
fields. `files` uses case-sensitive project-relative POSIX path globs; `*` can
|
|
305
|
+
span directories. `forbidden` contains literal dotted module prefixes, so
|
|
306
|
+
`requests` matches `requests.sessions` but not `requests_extra`. Both arrays
|
|
307
|
+
must contain at least one nonempty string. The optional `message` supplies the
|
|
308
|
+
diagnostic's remediation guidance.
|
|
309
|
+
|
|
310
|
+
Checks cover static `import` and `from ... import ...` statements, including
|
|
311
|
+
aliases, nested functions, and type-checking blocks. Relative imports are resolved
|
|
312
|
+
using the path under the project root, stripping a leading `src/` for conventional
|
|
313
|
+
src layouts. Custom import roots, runtime imports via `importlib` or `__import__`,
|
|
314
|
+
and re-export tracing are not supported. Importing a name from a package is
|
|
315
|
+
treated as importing that dotted name, so avoid forbidden prefixes that collide
|
|
316
|
+
with allowed attributes on the same package.
|
|
317
|
+
|
|
318
|
+
Violations emit `imports/boundary` errors with the source location and your
|
|
319
|
+
message in terminal, JSON, and SARIF output. Use
|
|
320
|
+
`pycodegate . --fail-on error` to enforce the boundary in CI. Existing rule and
|
|
321
|
+
per-file ignores apply; `--no-lint` disables boundary checks. Without configured
|
|
322
|
+
boundaries, scan behavior is unchanged. Static checks supplement tests and
|
|
323
|
+
runtime verification; a high score alone does not prove behavior is correct.
|
|
324
|
+
|
|
275
325
|
## Profiles
|
|
276
326
|
|
|
277
327
|
PyCodeGate auto-detects a project profile and adjusts rule weights accordingly. You can also set a profile explicitly in config (`profile = "library"`).
|
|
278
328
|
|
|
279
329
|
| Profile | Auto-detected when | Adjustments |
|
|
280
330
|
|---------|--------------------|-------------|
|
|
281
|
-
| `cli` | `[project.scripts]`
|
|
282
|
-
| `web` | Django / FastAPI / Flask
|
|
283
|
-
| `library` |
|
|
284
|
-
| `script` |
|
|
331
|
+
| `cli` | `[project.scripts]` or a CLI dependency (click, typer, …) | Security budget relaxed; `subprocess`/`shell` rules suppressed |
|
|
332
|
+
| `web` | Django / FastAPI / Flask / Starlette / aiohttp / … detected | Framework rules active |
|
|
333
|
+
| `library` | Has a `[build-system]` and no scripts | Default budgets |
|
|
334
|
+
| `script` | No package `__init__.py` and ≤ 5 top-level `.py` files | Structure budget relaxed; `no-tests`/`no-license` suppressed |
|
|
285
335
|
|
|
286
336
|
## Pre-commit Hook
|
|
287
337
|
|
|
288
|
-
|
|
338
|
+
`--pre-commit` installs a git `pre-commit` hook (one-time) that scores the project on
|
|
339
|
+
every commit and blocks the commit when the score falls below the threshold:
|
|
289
340
|
|
|
290
341
|
```bash
|
|
291
|
-
pycodegate . --pre-commit --
|
|
342
|
+
pycodegate . --pre-commit --min-score 80
|
|
292
343
|
```
|
|
293
344
|
|
|
294
|
-
|
|
345
|
+
If you use the [pre-commit framework](https://pre-commit.com), add it to
|
|
346
|
+
`.pre-commit-config.yaml` instead:
|
|
295
347
|
|
|
296
348
|
```yaml
|
|
297
349
|
repos:
|
|
@@ -299,7 +351,7 @@ repos:
|
|
|
299
351
|
hooks:
|
|
300
352
|
- id: pycodegate
|
|
301
353
|
name: PyCodeGate quality check
|
|
302
|
-
entry: pycodegate . --
|
|
354
|
+
entry: pycodegate . --fail-on error
|
|
303
355
|
language: system
|
|
304
356
|
types: [python]
|
|
305
357
|
pass_filenames: false
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Precision benchmark
|
|
2
|
+
|
|
3
|
+
A quality gate is only useful if it stays quiet on good code. This benchmark runs
|
|
4
|
+
PyCodeGate against the source of widely used, idiomatic PyPI packages and reports
|
|
5
|
+
the findings per rule. A rule that fires heavily here is a false-positive suspect.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python benchmarks/run_corpus.py # default corpus, lint only
|
|
9
|
+
python benchmarks/run_corpus.py --with-dead-code
|
|
10
|
+
python benchmarks/run_corpus.py flask django # a custom package list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Source distributions are fetched from PyPI into `benchmarks/.corpus/` (git-ignored)
|
|
14
|
+
and cached between runs. No GitHub access is required.
|
|
15
|
+
|
|
16
|
+
## How to read the output
|
|
17
|
+
|
|
18
|
+
The per-package table shows the score and finding count; the tail aggregates
|
|
19
|
+
findings by rule across the whole corpus. When triaging a rule with a high count,
|
|
20
|
+
open a few of the flagged files — if the pattern is idiomatic and correct, the rule
|
|
21
|
+
needs to be narrowed, suppressed in the relevant context (e.g. test files), or
|
|
22
|
+
removed. Genuine issues (a 100-branch function, `verify=False`) should remain.
|
|
23
|
+
|
|
24
|
+
The precision fixes derived from this benchmark are locked in by
|
|
25
|
+
`tests/test_precision.py` and the per-rule unit tests under `tests/rules/`.
|
|
26
|
+
|
|
27
|
+
## Release validation
|
|
28
|
+
|
|
29
|
+
[0.5.0 validation](results/0.5.0.md) records the Python test matrix, packaging
|
|
30
|
+
checks, configured boundary checks, and an exact baseline comparison across the
|
|
31
|
+
29-package corpus. Machine-readable results are saved alongside the report.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
{
|
|
2
|
+
"release": "0.5.0",
|
|
3
|
+
"date": "2026-11-15",
|
|
4
|
+
"baseline_commit": "2a294b180daefe0511e253905b685de68d761175",
|
|
5
|
+
"candidate_code_commit": "8960979",
|
|
6
|
+
"packages": {
|
|
7
|
+
"requests": {
|
|
8
|
+
"files": 35,
|
|
9
|
+
"score": 68,
|
|
10
|
+
"findings": 169,
|
|
11
|
+
"matches_baseline": true,
|
|
12
|
+
"source_distribution": "requests-2.34.2"
|
|
13
|
+
},
|
|
14
|
+
"flask": {
|
|
15
|
+
"files": 65,
|
|
16
|
+
"score": 74,
|
|
17
|
+
"findings": 61,
|
|
18
|
+
"matches_baseline": true,
|
|
19
|
+
"source_distribution": "flask-3.1.3"
|
|
20
|
+
},
|
|
21
|
+
"click": {
|
|
22
|
+
"files": 76,
|
|
23
|
+
"score": 72,
|
|
24
|
+
"findings": 102,
|
|
25
|
+
"matches_baseline": true,
|
|
26
|
+
"source_distribution": "click-8.5.1"
|
|
27
|
+
},
|
|
28
|
+
"rich": {
|
|
29
|
+
"files": 100,
|
|
30
|
+
"score": 58,
|
|
31
|
+
"findings": 273,
|
|
32
|
+
"matches_baseline": true,
|
|
33
|
+
"source_distribution": "rich-15.0.0"
|
|
34
|
+
},
|
|
35
|
+
"httpx": {
|
|
36
|
+
"files": 60,
|
|
37
|
+
"score": 74,
|
|
38
|
+
"findings": 110,
|
|
39
|
+
"matches_baseline": true,
|
|
40
|
+
"source_distribution": "httpx-0.28.1"
|
|
41
|
+
},
|
|
42
|
+
"sqlalchemy": {
|
|
43
|
+
"files": 603,
|
|
44
|
+
"score": 22,
|
|
45
|
+
"findings": 2256,
|
|
46
|
+
"matches_baseline": true,
|
|
47
|
+
"source_distribution": "sqlalchemy-2.1.8"
|
|
48
|
+
},
|
|
49
|
+
"fastapi": {
|
|
50
|
+
"files": 738,
|
|
51
|
+
"score": 53,
|
|
52
|
+
"findings": 2383,
|
|
53
|
+
"matches_baseline": true,
|
|
54
|
+
"source_distribution": "fastapi-0.163.1"
|
|
55
|
+
},
|
|
56
|
+
"starlette": {
|
|
57
|
+
"files": 84,
|
|
58
|
+
"score": 71,
|
|
59
|
+
"findings": 162,
|
|
60
|
+
"matches_baseline": true,
|
|
61
|
+
"source_distribution": "starlette-1.10.0"
|
|
62
|
+
},
|
|
63
|
+
"pydantic": {
|
|
64
|
+
"files": 324,
|
|
65
|
+
"score": 35,
|
|
66
|
+
"findings": 720,
|
|
67
|
+
"matches_baseline": true,
|
|
68
|
+
"source_distribution": "pydantic-2.14.3"
|
|
69
|
+
},
|
|
70
|
+
"jinja2": {
|
|
71
|
+
"files": 49,
|
|
72
|
+
"score": 64,
|
|
73
|
+
"findings": 104,
|
|
74
|
+
"matches_baseline": true,
|
|
75
|
+
"source_distribution": "jinja2-3.1.6"
|
|
76
|
+
},
|
|
77
|
+
"werkzeug": {
|
|
78
|
+
"files": 84,
|
|
79
|
+
"score": 59,
|
|
80
|
+
"findings": 156,
|
|
81
|
+
"matches_baseline": true,
|
|
82
|
+
"source_distribution": "werkzeug-3.1.9"
|
|
83
|
+
},
|
|
84
|
+
"urllib3": {
|
|
85
|
+
"files": 80,
|
|
86
|
+
"score": 71,
|
|
87
|
+
"findings": 144,
|
|
88
|
+
"matches_baseline": true,
|
|
89
|
+
"source_distribution": "urllib3-2.8.0"
|
|
90
|
+
},
|
|
91
|
+
"django": {
|
|
92
|
+
"files": 2592,
|
|
93
|
+
"score": 33,
|
|
94
|
+
"findings": 969,
|
|
95
|
+
"matches_baseline": true,
|
|
96
|
+
"source_distribution": "django-6.1.3"
|
|
97
|
+
},
|
|
98
|
+
"celery": {
|
|
99
|
+
"files": 406,
|
|
100
|
+
"score": 37,
|
|
101
|
+
"findings": 472,
|
|
102
|
+
"matches_baseline": true,
|
|
103
|
+
"source_distribution": "celery-5.7.0"
|
|
104
|
+
},
|
|
105
|
+
"black": {
|
|
106
|
+
"files": 366,
|
|
107
|
+
"score": 35,
|
|
108
|
+
"findings": 392,
|
|
109
|
+
"matches_baseline": true,
|
|
110
|
+
"source_distribution": "black-26.10.1"
|
|
111
|
+
},
|
|
112
|
+
"isort": {
|
|
113
|
+
"files": 110,
|
|
114
|
+
"score": 70,
|
|
115
|
+
"findings": 144,
|
|
116
|
+
"matches_baseline": true,
|
|
117
|
+
"source_distribution": "isort-9.0.2"
|
|
118
|
+
},
|
|
119
|
+
"pytest": {
|
|
120
|
+
"files": 253,
|
|
121
|
+
"score": 35,
|
|
122
|
+
"findings": 1168,
|
|
123
|
+
"matches_baseline": true,
|
|
124
|
+
"source_distribution": "pytest-9.2.0"
|
|
125
|
+
},
|
|
126
|
+
"attrs": {
|
|
127
|
+
"files": 53,
|
|
128
|
+
"score": 77,
|
|
129
|
+
"findings": 54,
|
|
130
|
+
"matches_baseline": true,
|
|
131
|
+
"source_distribution": "attrs-26.1.0"
|
|
132
|
+
},
|
|
133
|
+
"typer": {
|
|
134
|
+
"files": 339,
|
|
135
|
+
"score": 66,
|
|
136
|
+
"findings": 155,
|
|
137
|
+
"matches_baseline": true,
|
|
138
|
+
"source_distribution": "typer-0.29.1"
|
|
139
|
+
},
|
|
140
|
+
"pyyaml": {
|
|
141
|
+
"files": 44,
|
|
142
|
+
"score": 58,
|
|
143
|
+
"findings": 127,
|
|
144
|
+
"matches_baseline": true,
|
|
145
|
+
"source_distribution": "pyyaml-6.0.3"
|
|
146
|
+
},
|
|
147
|
+
"redis": {
|
|
148
|
+
"files": 267,
|
|
149
|
+
"score": 43,
|
|
150
|
+
"findings": 1143,
|
|
151
|
+
"matches_baseline": true,
|
|
152
|
+
"source_distribution": "redis-8.1.0"
|
|
153
|
+
},
|
|
154
|
+
"scrapy": {
|
|
155
|
+
"files": 538,
|
|
156
|
+
"score": 45,
|
|
157
|
+
"findings": 417,
|
|
158
|
+
"matches_baseline": true,
|
|
159
|
+
"source_distribution": "scrapy-2.20.0"
|
|
160
|
+
},
|
|
161
|
+
"beautifulsoup4": {
|
|
162
|
+
"files": 38,
|
|
163
|
+
"score": 72,
|
|
164
|
+
"findings": 90,
|
|
165
|
+
"matches_baseline": true,
|
|
166
|
+
"source_distribution": "beautifulsoup4-4.15.0"
|
|
167
|
+
},
|
|
168
|
+
"loguru": {
|
|
169
|
+
"files": 19,
|
|
170
|
+
"score": 67,
|
|
171
|
+
"findings": 60,
|
|
172
|
+
"matches_baseline": true,
|
|
173
|
+
"source_distribution": "loguru-0.8.0"
|
|
174
|
+
},
|
|
175
|
+
"pandas": {
|
|
176
|
+
"files": 1448,
|
|
177
|
+
"score": 20,
|
|
178
|
+
"findings": 3210,
|
|
179
|
+
"matches_baseline": true,
|
|
180
|
+
"source_distribution": "pandas-3.1.1"
|
|
181
|
+
},
|
|
182
|
+
"numpy": {
|
|
183
|
+
"files": 1046,
|
|
184
|
+
"score": 22,
|
|
185
|
+
"findings": 2930,
|
|
186
|
+
"matches_baseline": true,
|
|
187
|
+
"source_distribution": "numpy-2.5.4"
|
|
188
|
+
},
|
|
189
|
+
"pillow": {
|
|
190
|
+
"files": 292,
|
|
191
|
+
"score": 45,
|
|
192
|
+
"findings": 666,
|
|
193
|
+
"matches_baseline": true,
|
|
194
|
+
"source_distribution": "pillow-13.0.0"
|
|
195
|
+
},
|
|
196
|
+
"tornado": {
|
|
197
|
+
"files": 77,
|
|
198
|
+
"score": 41,
|
|
199
|
+
"findings": 309,
|
|
200
|
+
"matches_baseline": true,
|
|
201
|
+
"source_distribution": "tornado-6.6.1"
|
|
202
|
+
},
|
|
203
|
+
"aiohttp": {
|
|
204
|
+
"files": 144,
|
|
205
|
+
"score": 44,
|
|
206
|
+
"findings": 430,
|
|
207
|
+
"matches_baseline": true,
|
|
208
|
+
"source_distribution": "aiohttp-3.14.5"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"files": 10330,
|
|
212
|
+
"diagnostic_regressions": 0,
|
|
213
|
+
"configured_boundary_checks": {
|
|
214
|
+
"requests": {
|
|
215
|
+
"forbidden_dependency": "urllib3",
|
|
216
|
+
"expected_violations": 28
|
|
217
|
+
},
|
|
218
|
+
"flask": {
|
|
219
|
+
"forbidden_dependency": "werkzeug",
|
|
220
|
+
"expected_violations": 79
|
|
221
|
+
},
|
|
222
|
+
"fastapi": {
|
|
223
|
+
"forbidden_dependency": "starlette",
|
|
224
|
+
"expected_violations": 115
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|