python-vibe-guard 0.11.0__tar.gz → 0.12.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.
- {python_vibe_guard-0.11.0/python_vibe_guard.egg-info → python_vibe_guard-0.12.0}/PKG-INFO +58 -3
- python_vibe_guard-0.11.0/PKG-INFO → python_vibe_guard-0.12.0/README.md +56 -17
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyproject.toml +4 -1
- python_vibe_guard-0.11.0/README.md → python_vibe_guard-0.12.0/python_vibe_guard.egg-info/PKG-INFO +72 -2
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/python_vibe_guard.egg-info/SOURCES.txt +6 -1
- python_vibe_guard-0.12.0/python_vibe_guard.egg-info/requires.txt +3 -0
- python_vibe_guard-0.12.0/pyvibe/__init__.py +1 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/analyzer.py +97 -8
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/cli.py +77 -18
- python_vibe_guard-0.12.0/pyvibe/config.py +99 -0
- python_vibe_guard-0.12.0/pyvibe/suppressions.py +53 -0
- python_vibe_guard-0.12.0/tests/test_config.py +268 -0
- python_vibe_guard-0.12.0/tests/test_suppressions.py +158 -0
- python_vibe_guard-0.11.0/pyvibe/__init__.py +0 -1
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/python_vibe_guard.egg-info/dependency_links.txt +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/python_vibe_guard.egg-info/entry_points.txt +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/python_vibe_guard.egg-info/top_level.txt +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/__main__.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/autofix.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/baseline.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/diff.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/explain.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rule_docs.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/__init__.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/async_requests.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/async_sleep.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/asyncio_run.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/base.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/celery_time_limit.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/contextvar_cleanup.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/create_task_orphan.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/ensure_future_orphan.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/gather_no_return_exceptions.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/httpx_client_sync.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/httpx_sync.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/loop_run_until_complete.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/open_async.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/os_blocking.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/queue_put_nowait.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/retry_no_backoff.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/silent_except.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/sqlite_async.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/subprocess_async.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/threading_lock.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/rules/while_true_no_await.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/pyvibe/sarif.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/setup.cfg +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_baseline.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_diff.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_exclude.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_explain.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_rules.py +0 -0
- {python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/tests/test_sarif.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-vibe-guard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: async,linter,fastapi,asyncio,static-analysis
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
15
16
|
|
|
16
17
|
# python-vibe-guard
|
|
17
18
|
|
|
@@ -177,6 +178,9 @@ python -m pyvibe explain PYVIBE-002
|
|
|
177
178
|
python -m pyvibe baseline create src/ # snapshot current findings
|
|
178
179
|
python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
|
|
179
180
|
|
|
181
|
+
# Show suppressed findings (inline comments + pyvibe.toml) alongside the report
|
|
182
|
+
python -m pyvibe src/ --verbose
|
|
183
|
+
|
|
180
184
|
# Exit code: 0 = clean, 1 = violations found, 2 = path error
|
|
181
185
|
```
|
|
182
186
|
|
|
@@ -216,7 +220,7 @@ python -m pyvibe src/ --baseline # only reports findings NOT in the bas
|
|
|
216
220
|
Fix : Use `asyncio.Lock()` with `async with lock:` instead
|
|
217
221
|
|
|
218
222
|
─────────────────────────────────────────────
|
|
219
|
-
4
|
|
223
|
+
4 reported · 0 suppressed
|
|
220
224
|
```
|
|
221
225
|
|
|
222
226
|
`Suggested fix:` blocks are generated from the real code on the flagged line — they are
|
|
@@ -291,6 +295,57 @@ produces exit code `0`.
|
|
|
291
295
|
and reviewed like any other file; gitignore it if each contributor/CI run should
|
|
292
296
|
regenerate its own baseline instead.
|
|
293
297
|
|
|
298
|
+
### Suppressing findings
|
|
299
|
+
|
|
300
|
+
For one-off exceptions, use an inline comment right in the source:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
# pyvibe: ignore PYVIBE-008
|
|
304
|
+
conn = sqlite3.connect(...) # suppressed — standalone comment targets the next line
|
|
305
|
+
|
|
306
|
+
conn = sqlite3.connect(...) # pyvibe: ignore PYVIBE-008 # suppressed — trailing comment targets its own line
|
|
307
|
+
|
|
308
|
+
time.sleep(n) # pyvibe: ignore PYVIBE-001, PYVIBE-003 # multiple rules, comma-separated
|
|
309
|
+
|
|
310
|
+
# pyvibe: ignore-next-line PYVIBE-008
|
|
311
|
+
conn = sqlite3.connect(...) # suppressed — always targets the next line
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`pyvibe:` is case-insensitive. A directive with no recognizable `PYVIBE-XXX` id is ignored
|
|
315
|
+
(treated as a regular comment).
|
|
316
|
+
|
|
317
|
+
For project-wide rules, add a `pyvibe.toml` next to your code (python-vibe-guard walks up
|
|
318
|
+
from the scan target to find it, same convention as `pyproject.toml`):
|
|
319
|
+
|
|
320
|
+
```toml
|
|
321
|
+
[tool.pyvibe]
|
|
322
|
+
ignore = ["PYVIBE-019"]
|
|
323
|
+
exclude = ["tests/**", "examples/**", "docs/**"]
|
|
324
|
+
|
|
325
|
+
[tool.pyvibe.severity]
|
|
326
|
+
PYVIBE-008 = "warning"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
- `ignore`: rule IDs suppressed everywhere, project-wide.
|
|
330
|
+
- `exclude`: glob patterns (relative to the `pyvibe.toml` directory) for files/directories
|
|
331
|
+
to skip entirely.
|
|
332
|
+
- `[tool.pyvibe.severity]`: per-rule severity override (`"critical"` or `"warning"`),
|
|
333
|
+
applied after the built-in test-file downgrade.
|
|
334
|
+
|
|
335
|
+
Both mechanisms feed into the same summary line:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
4 reported · 2 suppressed
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Add `--verbose` to see exactly what was suppressed and why:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
Suppressed:
|
|
345
|
+
PYVIBE-008 app/db.py:42 (inline)
|
|
346
|
+
PYVIBE-019 legacy.py:81 (config)
|
|
347
|
+
```
|
|
348
|
+
|
|
294
349
|
---
|
|
295
350
|
|
|
296
351
|
## CI/CD integration
|
|
@@ -388,7 +443,7 @@ python -m pytest tests/ -v
|
|
|
388
443
|
python tests/test_rules.py
|
|
389
444
|
```
|
|
390
445
|
|
|
391
|
-
|
|
446
|
+
309 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, baseline mode, and suppressions (inline comments + pyvibe.toml) coverage.
|
|
392
447
|
|
|
393
448
|
---
|
|
394
449
|
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-vibe-guard
|
|
3
|
-
Version: 0.11.0
|
|
4
|
-
Summary: Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: async,linter,fastapi,asyncio,static-analysis
|
|
7
|
-
Classifier: Development Status :: 3 - Alpha
|
|
8
|
-
Classifier: Intended Audience :: Developers
|
|
9
|
-
Classifier: Topic :: Software Development :: Quality Assurance
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Requires-Python: >=3.10
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
|
|
16
1
|
# python-vibe-guard
|
|
17
2
|
|
|
18
3
|
[](https://github.com/Joaquinriosheredia/python-vibe-guard/actions/workflows/ci.yml)
|
|
@@ -177,6 +162,9 @@ python -m pyvibe explain PYVIBE-002
|
|
|
177
162
|
python -m pyvibe baseline create src/ # snapshot current findings
|
|
178
163
|
python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
|
|
179
164
|
|
|
165
|
+
# Show suppressed findings (inline comments + pyvibe.toml) alongside the report
|
|
166
|
+
python -m pyvibe src/ --verbose
|
|
167
|
+
|
|
180
168
|
# Exit code: 0 = clean, 1 = violations found, 2 = path error
|
|
181
169
|
```
|
|
182
170
|
|
|
@@ -216,7 +204,7 @@ python -m pyvibe src/ --baseline # only reports findings NOT in the bas
|
|
|
216
204
|
Fix : Use `asyncio.Lock()` with `async with lock:` instead
|
|
217
205
|
|
|
218
206
|
─────────────────────────────────────────────
|
|
219
|
-
4
|
|
207
|
+
4 reported · 0 suppressed
|
|
220
208
|
```
|
|
221
209
|
|
|
222
210
|
`Suggested fix:` blocks are generated from the real code on the flagged line — they are
|
|
@@ -291,6 +279,57 @@ produces exit code `0`.
|
|
|
291
279
|
and reviewed like any other file; gitignore it if each contributor/CI run should
|
|
292
280
|
regenerate its own baseline instead.
|
|
293
281
|
|
|
282
|
+
### Suppressing findings
|
|
283
|
+
|
|
284
|
+
For one-off exceptions, use an inline comment right in the source:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# pyvibe: ignore PYVIBE-008
|
|
288
|
+
conn = sqlite3.connect(...) # suppressed — standalone comment targets the next line
|
|
289
|
+
|
|
290
|
+
conn = sqlite3.connect(...) # pyvibe: ignore PYVIBE-008 # suppressed — trailing comment targets its own line
|
|
291
|
+
|
|
292
|
+
time.sleep(n) # pyvibe: ignore PYVIBE-001, PYVIBE-003 # multiple rules, comma-separated
|
|
293
|
+
|
|
294
|
+
# pyvibe: ignore-next-line PYVIBE-008
|
|
295
|
+
conn = sqlite3.connect(...) # suppressed — always targets the next line
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`pyvibe:` is case-insensitive. A directive with no recognizable `PYVIBE-XXX` id is ignored
|
|
299
|
+
(treated as a regular comment).
|
|
300
|
+
|
|
301
|
+
For project-wide rules, add a `pyvibe.toml` next to your code (python-vibe-guard walks up
|
|
302
|
+
from the scan target to find it, same convention as `pyproject.toml`):
|
|
303
|
+
|
|
304
|
+
```toml
|
|
305
|
+
[tool.pyvibe]
|
|
306
|
+
ignore = ["PYVIBE-019"]
|
|
307
|
+
exclude = ["tests/**", "examples/**", "docs/**"]
|
|
308
|
+
|
|
309
|
+
[tool.pyvibe.severity]
|
|
310
|
+
PYVIBE-008 = "warning"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
- `ignore`: rule IDs suppressed everywhere, project-wide.
|
|
314
|
+
- `exclude`: glob patterns (relative to the `pyvibe.toml` directory) for files/directories
|
|
315
|
+
to skip entirely.
|
|
316
|
+
- `[tool.pyvibe.severity]`: per-rule severity override (`"critical"` or `"warning"`),
|
|
317
|
+
applied after the built-in test-file downgrade.
|
|
318
|
+
|
|
319
|
+
Both mechanisms feed into the same summary line:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
4 reported · 2 suppressed
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Add `--verbose` to see exactly what was suppressed and why:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Suppressed:
|
|
329
|
+
PYVIBE-008 app/db.py:42 (inline)
|
|
330
|
+
PYVIBE-019 legacy.py:81 (config)
|
|
331
|
+
```
|
|
332
|
+
|
|
294
333
|
---
|
|
295
334
|
|
|
296
335
|
## CI/CD integration
|
|
@@ -388,7 +427,7 @@ python -m pytest tests/ -v
|
|
|
388
427
|
python tests/test_rules.py
|
|
389
428
|
```
|
|
390
429
|
|
|
391
|
-
|
|
430
|
+
309 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, baseline mode, and suppressions (inline comments + pyvibe.toml) coverage.
|
|
392
431
|
|
|
393
432
|
---
|
|
394
433
|
|
|
@@ -4,10 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-vibe-guard"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.12.0"
|
|
8
8
|
description = "Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"tomli>=2.0.1; python_version < '3.11'",
|
|
13
|
+
]
|
|
11
14
|
license = {text = "MIT"}
|
|
12
15
|
keywords = ["async", "linter", "fastapi", "asyncio", "static-analysis"]
|
|
13
16
|
classifiers = [
|
python_vibe_guard-0.11.0/README.md → python_vibe_guard-0.12.0/python_vibe_guard.egg-info/PKG-INFO
RENAMED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-vibe-guard
|
|
3
|
+
Version: 0.12.0
|
|
4
|
+
Summary: Runtime anti-pattern scanner for async Python — detects what AI-generated code gets wrong
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: async,linter,fastapi,asyncio,static-analysis
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
16
|
+
|
|
1
17
|
# python-vibe-guard
|
|
2
18
|
|
|
3
19
|
[](https://github.com/Joaquinriosheredia/python-vibe-guard/actions/workflows/ci.yml)
|
|
@@ -162,6 +178,9 @@ python -m pyvibe explain PYVIBE-002
|
|
|
162
178
|
python -m pyvibe baseline create src/ # snapshot current findings
|
|
163
179
|
python -m pyvibe src/ --baseline # only reports findings NOT in the baseline
|
|
164
180
|
|
|
181
|
+
# Show suppressed findings (inline comments + pyvibe.toml) alongside the report
|
|
182
|
+
python -m pyvibe src/ --verbose
|
|
183
|
+
|
|
165
184
|
# Exit code: 0 = clean, 1 = violations found, 2 = path error
|
|
166
185
|
```
|
|
167
186
|
|
|
@@ -201,7 +220,7 @@ python -m pyvibe src/ --baseline # only reports findings NOT in the bas
|
|
|
201
220
|
Fix : Use `asyncio.Lock()` with `async with lock:` instead
|
|
202
221
|
|
|
203
222
|
─────────────────────────────────────────────
|
|
204
|
-
4
|
|
223
|
+
4 reported · 0 suppressed
|
|
205
224
|
```
|
|
206
225
|
|
|
207
226
|
`Suggested fix:` blocks are generated from the real code on the flagged line — they are
|
|
@@ -276,6 +295,57 @@ produces exit code `0`.
|
|
|
276
295
|
and reviewed like any other file; gitignore it if each contributor/CI run should
|
|
277
296
|
regenerate its own baseline instead.
|
|
278
297
|
|
|
298
|
+
### Suppressing findings
|
|
299
|
+
|
|
300
|
+
For one-off exceptions, use an inline comment right in the source:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
# pyvibe: ignore PYVIBE-008
|
|
304
|
+
conn = sqlite3.connect(...) # suppressed — standalone comment targets the next line
|
|
305
|
+
|
|
306
|
+
conn = sqlite3.connect(...) # pyvibe: ignore PYVIBE-008 # suppressed — trailing comment targets its own line
|
|
307
|
+
|
|
308
|
+
time.sleep(n) # pyvibe: ignore PYVIBE-001, PYVIBE-003 # multiple rules, comma-separated
|
|
309
|
+
|
|
310
|
+
# pyvibe: ignore-next-line PYVIBE-008
|
|
311
|
+
conn = sqlite3.connect(...) # suppressed — always targets the next line
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`pyvibe:` is case-insensitive. A directive with no recognizable `PYVIBE-XXX` id is ignored
|
|
315
|
+
(treated as a regular comment).
|
|
316
|
+
|
|
317
|
+
For project-wide rules, add a `pyvibe.toml` next to your code (python-vibe-guard walks up
|
|
318
|
+
from the scan target to find it, same convention as `pyproject.toml`):
|
|
319
|
+
|
|
320
|
+
```toml
|
|
321
|
+
[tool.pyvibe]
|
|
322
|
+
ignore = ["PYVIBE-019"]
|
|
323
|
+
exclude = ["tests/**", "examples/**", "docs/**"]
|
|
324
|
+
|
|
325
|
+
[tool.pyvibe.severity]
|
|
326
|
+
PYVIBE-008 = "warning"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
- `ignore`: rule IDs suppressed everywhere, project-wide.
|
|
330
|
+
- `exclude`: glob patterns (relative to the `pyvibe.toml` directory) for files/directories
|
|
331
|
+
to skip entirely.
|
|
332
|
+
- `[tool.pyvibe.severity]`: per-rule severity override (`"critical"` or `"warning"`),
|
|
333
|
+
applied after the built-in test-file downgrade.
|
|
334
|
+
|
|
335
|
+
Both mechanisms feed into the same summary line:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
4 reported · 2 suppressed
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Add `--verbose` to see exactly what was suppressed and why:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
Suppressed:
|
|
345
|
+
PYVIBE-008 app/db.py:42 (inline)
|
|
346
|
+
PYVIBE-019 legacy.py:81 (config)
|
|
347
|
+
```
|
|
348
|
+
|
|
279
349
|
---
|
|
280
350
|
|
|
281
351
|
## CI/CD integration
|
|
@@ -373,7 +443,7 @@ python -m pytest tests/ -v
|
|
|
373
443
|
python tests/test_rules.py
|
|
374
444
|
```
|
|
375
445
|
|
|
376
|
-
|
|
446
|
+
309 tests: true positives + false-positive guards for every rule, plus SARIF output, `pyvibe explain`, `pyvibe review`, baseline mode, and suppressions (inline comments + pyvibe.toml) coverage.
|
|
377
447
|
|
|
378
448
|
---
|
|
379
449
|
|
{python_vibe_guard-0.11.0 → python_vibe_guard-0.12.0}/python_vibe_guard.egg-info/SOURCES.txt
RENAMED
|
@@ -4,6 +4,7 @@ python_vibe_guard.egg-info/PKG-INFO
|
|
|
4
4
|
python_vibe_guard.egg-info/SOURCES.txt
|
|
5
5
|
python_vibe_guard.egg-info/dependency_links.txt
|
|
6
6
|
python_vibe_guard.egg-info/entry_points.txt
|
|
7
|
+
python_vibe_guard.egg-info/requires.txt
|
|
7
8
|
python_vibe_guard.egg-info/top_level.txt
|
|
8
9
|
pyvibe/__init__.py
|
|
9
10
|
pyvibe/__main__.py
|
|
@@ -11,10 +12,12 @@ pyvibe/analyzer.py
|
|
|
11
12
|
pyvibe/autofix.py
|
|
12
13
|
pyvibe/baseline.py
|
|
13
14
|
pyvibe/cli.py
|
|
15
|
+
pyvibe/config.py
|
|
14
16
|
pyvibe/diff.py
|
|
15
17
|
pyvibe/explain.py
|
|
16
18
|
pyvibe/rule_docs.py
|
|
17
19
|
pyvibe/sarif.py
|
|
20
|
+
pyvibe/suppressions.py
|
|
18
21
|
pyvibe/rules/__init__.py
|
|
19
22
|
pyvibe/rules/async_requests.py
|
|
20
23
|
pyvibe/rules/async_sleep.py
|
|
@@ -38,8 +41,10 @@ pyvibe/rules/subprocess_async.py
|
|
|
38
41
|
pyvibe/rules/threading_lock.py
|
|
39
42
|
pyvibe/rules/while_true_no_await.py
|
|
40
43
|
tests/test_baseline.py
|
|
44
|
+
tests/test_config.py
|
|
41
45
|
tests/test_diff.py
|
|
42
46
|
tests/test_exclude.py
|
|
43
47
|
tests/test_explain.py
|
|
44
48
|
tests/test_rules.py
|
|
45
|
-
tests/test_sarif.py
|
|
49
|
+
tests/test_sarif.py
|
|
50
|
+
tests/test_suppressions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.12.0"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import FrozenSet, List, Optional
|
|
3
|
+
from typing import FrozenSet, List, Optional, Tuple
|
|
4
4
|
|
|
5
|
+
from pyvibe.config import PyvibeConfig, is_excluded
|
|
5
6
|
from pyvibe.rules.base import Violation
|
|
7
|
+
from pyvibe.suppressions import parse_inline_suppressions
|
|
6
8
|
from pyvibe.rules.async_sleep import AsyncSleepRule
|
|
7
9
|
from pyvibe.rules.async_requests import AsyncRequestsRule
|
|
8
10
|
from pyvibe.rules.asyncio_run import AsyncioRunRule
|
|
@@ -90,22 +92,29 @@ def _is_test_file(filepath: str) -> bool:
|
|
|
90
92
|
)
|
|
91
93
|
|
|
92
94
|
|
|
93
|
-
def
|
|
95
|
+
def analyze_source_full(
|
|
94
96
|
source: str,
|
|
95
97
|
filepath: str = "<string>",
|
|
96
98
|
*,
|
|
97
99
|
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
config: Optional[PyvibeConfig] = None,
|
|
101
|
+
) -> Tuple[List[Violation], List[Tuple[Violation, str]]]:
|
|
102
|
+
"""Parse source and run all rules. Returns (reported, suppressed) where
|
|
103
|
+
suppressed is a list of (Violation, reason) with reason "inline" (a
|
|
104
|
+
`# pyvibe: ignore ...` comment) or "config" (pyvibe.toml's [tool.pyvibe]
|
|
105
|
+
ignore list).
|
|
100
106
|
|
|
101
107
|
downgrade_in_tests: rule IDs whose severity is lowered to WARNING when
|
|
102
108
|
the file is detected as a test file. Pass frozenset() to disable all
|
|
103
109
|
downgrading, or ALL_RULE_IDS to downgrade every rule.
|
|
110
|
+
|
|
111
|
+
config: optional PyvibeConfig (see pyvibe/config.py) applying a global
|
|
112
|
+
ignore list and per-rule severity overrides on top of the above.
|
|
104
113
|
"""
|
|
105
114
|
try:
|
|
106
115
|
tree = ast.parse(source)
|
|
107
116
|
except SyntaxError:
|
|
108
|
-
return []
|
|
117
|
+
return [], []
|
|
109
118
|
|
|
110
119
|
source_lines = source.splitlines()
|
|
111
120
|
violations = []
|
|
@@ -124,8 +133,53 @@ def analyze_source(
|
|
|
124
133
|
if v.rule_id in downgrade_in_tests:
|
|
125
134
|
v.severity = "WARNING"
|
|
126
135
|
|
|
136
|
+
if config and config.severity:
|
|
137
|
+
for v in violations:
|
|
138
|
+
if v.rule_id in config.severity:
|
|
139
|
+
v.severity = config.severity[v.rule_id]
|
|
140
|
+
|
|
127
141
|
violations.sort(key=lambda v: v.line)
|
|
128
|
-
|
|
142
|
+
|
|
143
|
+
inline_suppressions = parse_inline_suppressions(source)
|
|
144
|
+
reported: List[Violation] = []
|
|
145
|
+
suppressed: List[Tuple[Violation, str]] = []
|
|
146
|
+
for v in violations:
|
|
147
|
+
if v.rule_id in inline_suppressions.get(v.line, frozenset()):
|
|
148
|
+
suppressed.append((v, "inline"))
|
|
149
|
+
elif config and v.rule_id in config.ignore:
|
|
150
|
+
suppressed.append((v, "config"))
|
|
151
|
+
else:
|
|
152
|
+
reported.append(v)
|
|
153
|
+
|
|
154
|
+
return reported, suppressed
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def analyze_source(
|
|
158
|
+
source: str,
|
|
159
|
+
filepath: str = "<string>",
|
|
160
|
+
*,
|
|
161
|
+
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
162
|
+
config: Optional[PyvibeConfig] = None,
|
|
163
|
+
) -> List[Violation]:
|
|
164
|
+
"""Parse source and run all rules. Returns the reported (non-suppressed)
|
|
165
|
+
violations — see analyze_source_full() for the suppressed ones too.
|
|
166
|
+
"""
|
|
167
|
+
reported, _ = analyze_source_full(
|
|
168
|
+
source, filepath, downgrade_in_tests=downgrade_in_tests, config=config
|
|
169
|
+
)
|
|
170
|
+
return reported
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def analyze_file_full(
|
|
174
|
+
path: Path,
|
|
175
|
+
*,
|
|
176
|
+
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
177
|
+
config: Optional[PyvibeConfig] = None,
|
|
178
|
+
) -> Tuple[List[Violation], List[Tuple[Violation, str]]]:
|
|
179
|
+
source = path.read_text(encoding="utf-8", errors="ignore")
|
|
180
|
+
return analyze_source_full(
|
|
181
|
+
source, filepath=str(path), downgrade_in_tests=downgrade_in_tests, config=config
|
|
182
|
+
)
|
|
129
183
|
|
|
130
184
|
|
|
131
185
|
def analyze_file(
|
|
@@ -133,12 +187,15 @@ def analyze_file(
|
|
|
133
187
|
*,
|
|
134
188
|
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
135
189
|
line_filter: Optional[FrozenSet[int]] = None,
|
|
190
|
+
config: Optional[PyvibeConfig] = None,
|
|
136
191
|
) -> List[Violation]:
|
|
137
192
|
"""line_filter: if given, only violations whose `line` is in this set
|
|
138
193
|
are returned (used by `pyvibe review` to report only diff-touched lines).
|
|
139
194
|
"""
|
|
140
195
|
source = path.read_text(encoding="utf-8", errors="ignore")
|
|
141
|
-
violations = analyze_source(
|
|
196
|
+
violations = analyze_source(
|
|
197
|
+
source, filepath=str(path), downgrade_in_tests=downgrade_in_tests, config=config
|
|
198
|
+
)
|
|
142
199
|
if line_filter is not None:
|
|
143
200
|
violations = [v for v in violations if v.line in line_filter]
|
|
144
201
|
return violations
|
|
@@ -156,23 +213,55 @@ def analyze_directory(
|
|
|
156
213
|
*,
|
|
157
214
|
skip_test_files: bool = False,
|
|
158
215
|
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
216
|
+
config: Optional[PyvibeConfig] = None,
|
|
159
217
|
) -> dict:
|
|
160
218
|
"""Walk directory and analyze all .py files. Returns {path: [violations]}.
|
|
161
219
|
|
|
162
220
|
Directories whose *name* appears in `exclude` are skipped entirely.
|
|
163
221
|
skip_test_files: if True, files matching test_*.py / *_test.py / tests/* are
|
|
164
222
|
omitted from results entirely rather than downgraded.
|
|
223
|
+
config: optional PyvibeConfig — files matching its exclude glob patterns
|
|
224
|
+
are also skipped entirely.
|
|
165
225
|
"""
|
|
166
226
|
results = {}
|
|
167
227
|
for py_file in _walk(root, exclude):
|
|
168
228
|
if skip_test_files and _is_test_file(str(py_file)):
|
|
169
229
|
continue
|
|
170
|
-
|
|
230
|
+
if config and is_excluded(py_file, config):
|
|
231
|
+
continue
|
|
232
|
+
violations = analyze_file(py_file, downgrade_in_tests=downgrade_in_tests, config=config)
|
|
171
233
|
if violations:
|
|
172
234
|
results[py_file] = violations
|
|
173
235
|
return results
|
|
174
236
|
|
|
175
237
|
|
|
238
|
+
def analyze_directory_full(
|
|
239
|
+
root: Path,
|
|
240
|
+
exclude: frozenset = DEFAULT_EXCLUDES,
|
|
241
|
+
*,
|
|
242
|
+
skip_test_files: bool = False,
|
|
243
|
+
downgrade_in_tests: FrozenSet[str] = TEST_FILE_DOWNGRADE,
|
|
244
|
+
config: Optional[PyvibeConfig] = None,
|
|
245
|
+
) -> Tuple[dict, List[Tuple[Path, Violation, str]]]:
|
|
246
|
+
"""Like analyze_directory(), but also returns the suppressed findings
|
|
247
|
+
(across every scanned file) as a flat list of (path, Violation, reason).
|
|
248
|
+
"""
|
|
249
|
+
results = {}
|
|
250
|
+
all_suppressed: List[Tuple[Path, Violation, str]] = []
|
|
251
|
+
for py_file in _walk(root, exclude):
|
|
252
|
+
if skip_test_files and _is_test_file(str(py_file)):
|
|
253
|
+
continue
|
|
254
|
+
if config and is_excluded(py_file, config):
|
|
255
|
+
continue
|
|
256
|
+
reported, suppressed = analyze_file_full(
|
|
257
|
+
py_file, downgrade_in_tests=downgrade_in_tests, config=config
|
|
258
|
+
)
|
|
259
|
+
if reported:
|
|
260
|
+
results[py_file] = reported
|
|
261
|
+
all_suppressed.extend((py_file, v, reason) for v, reason in suppressed)
|
|
262
|
+
return results, all_suppressed
|
|
263
|
+
|
|
264
|
+
|
|
176
265
|
def _walk(root: Path, exclude: frozenset):
|
|
177
266
|
"""Yield .py files under root, skipping any directory in exclude."""
|
|
178
267
|
for entry in sorted(root.iterdir()):
|