python-qv 0.1.2__tar.gz → 0.1.3__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 (137) hide show
  1. {python_qv-0.1.2 → python_qv-0.1.3}/PKG-INFO +9 -2
  2. {python_qv-0.1.2 → python_qv-0.1.3}/README.md +8 -1
  3. {python_qv-0.1.2 → python_qv-0.1.3}/docs/cli_reference.md +25 -0
  4. python_qv-0.1.3/docs/rules.md +444 -0
  5. python_qv-0.1.3/examples/fastapi_issues/main.py +61 -0
  6. python_qv-0.1.3/examples/fastapi_issues/pyproject.toml +9 -0
  7. {python_qv-0.1.2 → python_qv-0.1.3}/pyproject.toml +1 -1
  8. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/__init__.py +1 -1
  9. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/cli/main.py +84 -0
  10. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/engine.py +4 -0
  11. python_qv-0.1.3/src/qv/frameworks/__init__.py +19 -0
  12. python_qv-0.1.3/src/qv/frameworks/base.py +25 -0
  13. python_qv-0.1.3/src/qv/frameworks/fastapi.py +2219 -0
  14. python_qv-0.1.3/src/qv/frameworks/sqlalchemy.py +1580 -0
  15. python_qv-0.1.3/src/qv/rules/registry.py +783 -0
  16. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_cli.py +50 -0
  17. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_examples.py +17 -0
  18. python_qv-0.1.3/tests/test_fastapi.py +960 -0
  19. python_qv-0.1.3/tests/test_sqlalchemy.py +636 -0
  20. {python_qv-0.1.2 → python_qv-0.1.3}/uv.lock +739 -737
  21. python_qv-0.1.2/docs/rules.md +0 -94
  22. python_qv-0.1.2/src/qv/frameworks/__init__.py +0 -1
  23. python_qv-0.1.2/src/qv/rules/registry.py +0 -169
  24. {python_qv-0.1.2 → python_qv-0.1.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  25. {python_qv-0.1.2 → python_qv-0.1.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  26. {python_qv-0.1.2 → python_qv-0.1.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  27. {python_qv-0.1.2 → python_qv-0.1.3}/.github/ISSUE_TEMPLATE/rule_proposal.yml +0 -0
  28. {python_qv-0.1.2 → python_qv-0.1.3}/.github/pull_request_template.md +0 -0
  29. {python_qv-0.1.2 → python_qv-0.1.3}/.github/workflows/ci.yml +0 -0
  30. {python_qv-0.1.2 → python_qv-0.1.3}/.github/workflows/docs.yml +0 -0
  31. {python_qv-0.1.2 → python_qv-0.1.3}/.github/workflows/publish.yml +0 -0
  32. {python_qv-0.1.2 → python_qv-0.1.3}/.gitignore +0 -0
  33. {python_qv-0.1.2 → python_qv-0.1.3}/.pre-commit-config.yaml +0 -0
  34. {python_qv-0.1.2 → python_qv-0.1.3}/.pre-commit-hooks.yaml +0 -0
  35. {python_qv-0.1.2 → python_qv-0.1.3}/.readthedocs.yaml +0 -0
  36. {python_qv-0.1.2 → python_qv-0.1.3}/CHANGELOG.md +0 -0
  37. {python_qv-0.1.2 → python_qv-0.1.3}/CONTRIBUTING.md +0 -0
  38. {python_qv-0.1.2 → python_qv-0.1.3}/SECURITY.md +0 -0
  39. {python_qv-0.1.2 → python_qv-0.1.3}/action.yml +0 -0
  40. {python_qv-0.1.2 → python_qv-0.1.3}/docs/architecture.md +0 -0
  41. {python_qv-0.1.2 → python_qv-0.1.3}/docs/assets/favicon.svg +0 -0
  42. {python_qv-0.1.2 → python_qv-0.1.3}/docs/assets/logo.svg +0 -0
  43. {python_qv-0.1.2 → python_qv-0.1.3}/docs/ci_integration.md +0 -0
  44. {python_qv-0.1.2 → python_qv-0.1.3}/docs/configuration.md +0 -0
  45. {python_qv-0.1.2 → python_qv-0.1.3}/docs/contributing.md +0 -0
  46. {python_qv-0.1.2 → python_qv-0.1.3}/docs/getting_started.md +0 -0
  47. {python_qv-0.1.2 → python_qv-0.1.3}/docs/html_report.md +0 -0
  48. {python_qv-0.1.2 → python_qv-0.1.3}/docs/index.md +0 -0
  49. {python_qv-0.1.2 → python_qv-0.1.3}/docs/pre_commit.md +0 -0
  50. {python_qv-0.1.2 → python_qv-0.1.3}/docs/remediation.md +0 -0
  51. {python_qv-0.1.2 → python_qv-0.1.3}/docs/requirements.txt +0 -0
  52. {python_qv-0.1.2 → python_qv-0.1.3}/docs/tree.md +0 -0
  53. {python_qv-0.1.2 → python_qv-0.1.3}/docs/tui.md +0 -0
  54. {python_qv-0.1.2 → python_qv-0.1.3}/examples/README.md +0 -0
  55. {python_qv-0.1.2 → python_qv-0.1.3}/examples/all_in_one_unhealthy/Dockerfile +0 -0
  56. {python_qv-0.1.2 → python_qv-0.1.3}/examples/all_in_one_unhealthy/cycle_a.py +0 -0
  57. {python_qv-0.1.2 → python_qv-0.1.3}/examples/all_in_one_unhealthy/cycle_b.py +0 -0
  58. {python_qv-0.1.2 → python_qv-0.1.3}/examples/all_in_one_unhealthy/main.py +0 -0
  59. {python_qv-0.1.2 → python_qv-0.1.3}/examples/all_in_one_unhealthy/pyproject.toml +0 -0
  60. {python_qv-0.1.2 → python_qv-0.1.3}/examples/circular_imports/module_a.py +0 -0
  61. {python_qv-0.1.2 → python_qv-0.1.3}/examples/circular_imports/module_b.py +0 -0
  62. {python_qv-0.1.2 → python_qv-0.1.3}/examples/circular_imports/pyproject.toml +0 -0
  63. {python_qv-0.1.2 → python_qv-0.1.3}/examples/dead_modules/active_feature.py +0 -0
  64. {python_qv-0.1.2 → python_qv-0.1.3}/examples/dead_modules/main.py +0 -0
  65. {python_qv-0.1.2 → python_qv-0.1.3}/examples/dead_modules/pyproject.toml +0 -0
  66. {python_qv-0.1.2 → python_qv-0.1.3}/examples/dead_modules/unused_legacy_module.py +0 -0
  67. {python_qv-0.1.2 → python_qv-0.1.3}/examples/deprecated_stdlib/legacy_app.py +0 -0
  68. {python_qv-0.1.2 → python_qv-0.1.3}/examples/deprecated_stdlib/pyproject.toml +0 -0
  69. {python_qv-0.1.2 → python_qv-0.1.3}/examples/environment_drift/.github/workflows/ci.yml +0 -0
  70. {python_qv-0.1.2 → python_qv-0.1.3}/examples/environment_drift/Dockerfile +0 -0
  71. {python_qv-0.1.2 → python_qv-0.1.3}/examples/environment_drift/pyproject.toml +0 -0
  72. {python_qv-0.1.2 → python_qv-0.1.3}/examples/missing_dependencies/main.py +0 -0
  73. {python_qv-0.1.2 → python_qv-0.1.3}/examples/missing_dependencies/pyproject.toml +0 -0
  74. {python_qv-0.1.2 → python_qv-0.1.3}/examples/missing_metadata/pyproject.toml +0 -0
  75. {python_qv-0.1.2 → python_qv-0.1.3}/examples/unresolved_imports/pyproject.toml +0 -0
  76. {python_qv-0.1.2 → python_qv-0.1.3}/examples/unresolved_imports/service.py +0 -0
  77. {python_qv-0.1.2 → python_qv-0.1.3}/examples/unused_dependencies/app.py +0 -0
  78. {python_qv-0.1.2 → python_qv-0.1.3}/examples/unused_dependencies/pyproject.toml +0 -0
  79. {python_qv-0.1.2 → python_qv-0.1.3}/examples/vulnerable_dependencies/app.py +0 -0
  80. {python_qv-0.1.2 → python_qv-0.1.3}/examples/vulnerable_dependencies/pyproject.toml +0 -0
  81. {python_qv-0.1.2 → python_qv-0.1.3}/examples/vulnerable_dependencies/requirements.txt +0 -0
  82. {python_qv-0.1.2 → python_qv-0.1.3}/mkdocs.yml +0 -0
  83. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/__main__.py +0 -0
  84. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/__init__.py +0 -0
  85. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/ci/__init__.py +0 -0
  86. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/dependencies/__init__.py +0 -0
  87. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/dependencies/analyzer.py +0 -0
  88. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/docker/__init__.py +0 -0
  89. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/environment/__init__.py +0 -0
  90. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/environment/drift.py +0 -0
  91. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/imports/__init__.py +0 -0
  92. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/imports/analyzer.py +0 -0
  93. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/packaging/__init__.py +0 -0
  94. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/packaging/analyzer.py +0 -0
  95. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/security/__init__.py +0 -0
  96. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/security/analyzer.py +0 -0
  97. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/security/lockfile_parser.py +0 -0
  98. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/analyzers/security/osv_client.py +0 -0
  99. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/cli/__init__.py +0 -0
  100. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/analyzer.py +0 -0
  101. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/config.py +0 -0
  102. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/context.py +0 -0
  103. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/models.py +0 -0
  104. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/core/project.py +0 -0
  105. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/integrations/__init__.py +0 -0
  106. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/remediation/__init__.py +0 -0
  107. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/remediation/engine.py +0 -0
  108. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/remediation/models.py +0 -0
  109. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/__init__.py +0 -0
  110. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/base.py +0 -0
  111. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/github_annotator.py +0 -0
  112. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/html_reporter.py +0 -0
  113. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/json_reporter.py +0 -0
  114. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/sarif.py +0 -0
  115. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/reporters/terminal.py +0 -0
  116. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/tui/__init__.py +0 -0
  117. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/tui/app.py +0 -0
  118. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/visualizers/__init__.py +0 -0
  119. {python_qv-0.1.2 → python_qv-0.1.3}/src/qv/visualizers/tree.py +0 -0
  120. {python_qv-0.1.2 → python_qv-0.1.3}/tests/analyzers/test_dependencies.py +0 -0
  121. {python_qv-0.1.2 → python_qv-0.1.3}/tests/analyzers/test_environment.py +0 -0
  122. {python_qv-0.1.2 → python_qv-0.1.3}/tests/analyzers/test_imports.py +0 -0
  123. {python_qv-0.1.2 → python_qv-0.1.3}/tests/analyzers/test_packaging.py +0 -0
  124. {python_qv-0.1.2 → python_qv-0.1.3}/tests/analyzers/test_security.py +0 -0
  125. {python_qv-0.1.2 → python_qv-0.1.3}/tests/conftest.py +0 -0
  126. {python_qv-0.1.2 → python_qv-0.1.3}/tests/reporters/test_github_annotator.py +0 -0
  127. {python_qv-0.1.2 → python_qv-0.1.3}/tests/reporters/test_html.py +0 -0
  128. {python_qv-0.1.2 → python_qv-0.1.3}/tests/reporters/test_reporters.py +0 -0
  129. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_config.py +0 -0
  130. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_lockfile_parser.py +0 -0
  131. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_models.py +0 -0
  132. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_pre_commit_hooks.py +0 -0
  133. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_remediation.py +0 -0
  134. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_rules.py +0 -0
  135. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_tree.py +0 -0
  136. {python_qv-0.1.2 → python_qv-0.1.3}/tests/test_tui.py +0 -0
  137. {python_qv-0.1.2 → python_qv-0.1.3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-qv
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Diagnose why a Python project is unhealthy — explain root cause and suggest safe fixes.
5
5
  Project-URL: Homepage, https://github.com/inzamol/qv
6
6
  Project-URL: Repository, https://github.com/inzamol/qv
@@ -199,8 +199,10 @@ Run `qv inspect` (or `qv ui`) for a full terminal dashboard:
199
199
  | | `IMP-002` | Unresolved relative or internal module imports | `ERROR` |
200
200
  | **Packaging** | `PKG-001` | Missing PEP 621 metadata (name, version, etc.) | `WARNING` |
201
201
  | | `PKG-002` | Invalid syntax or malformed keys in `pyproject.toml` | `ERROR` |
202
+ | **FastAPI Doctor** | `FAP-001`–`FAP-038` | Async blocking calls, CPU starvation in endpoints, insecure CORS, missing timeouts, lifecycle anti-patterns, Pydantic v2 migrations | `ERROR` / `WARNING` |
203
+ | **SQL & Database** | `SQL-001`–`SQL-030` | N+1 queries in loops, session leaks, SQL injection, sync DB in async loop, missing eager loading, pool starvation, 2.0 syntax | `ERROR` / `WARNING` |
202
204
 
203
- 👉 *See full explanations and remediation steps in the [Rules Catalog](docs/rules.md).*
205
+ 👉 *See all 80+ rules and remediation steps in the [Rules Catalog](docs/rules.md).*
204
206
 
205
207
  ---
206
208
 
@@ -331,6 +333,11 @@ qv environment
331
333
 
332
334
  # Check AST & imports only (circular import loops, unresolvable modules)
333
335
  qv architecture
336
+
337
+ # Check framework-specific issues (FastAPI, SQLAlchemy, SQLModel)
338
+ qv framework
339
+ qv framework --name fastapi
340
+ qv framework --name sqlalchemy
334
341
  ```
335
342
 
336
343
  ---
@@ -159,8 +159,10 @@ Run `qv inspect` (or `qv ui`) for a full terminal dashboard:
159
159
  | | `IMP-002` | Unresolved relative or internal module imports | `ERROR` |
160
160
  | **Packaging** | `PKG-001` | Missing PEP 621 metadata (name, version, etc.) | `WARNING` |
161
161
  | | `PKG-002` | Invalid syntax or malformed keys in `pyproject.toml` | `ERROR` |
162
+ | **FastAPI Doctor** | `FAP-001`–`FAP-038` | Async blocking calls, CPU starvation in endpoints, insecure CORS, missing timeouts, lifecycle anti-patterns, Pydantic v2 migrations | `ERROR` / `WARNING` |
163
+ | **SQL & Database** | `SQL-001`–`SQL-030` | N+1 queries in loops, session leaks, SQL injection, sync DB in async loop, missing eager loading, pool starvation, 2.0 syntax | `ERROR` / `WARNING` |
162
164
 
163
- 👉 *See full explanations and remediation steps in the [Rules Catalog](docs/rules.md).*
165
+ 👉 *See all 80+ rules and remediation steps in the [Rules Catalog](docs/rules.md).*
164
166
 
165
167
  ---
166
168
 
@@ -291,6 +293,11 @@ qv environment
291
293
 
292
294
  # Check AST & imports only (circular import loops, unresolvable modules)
293
295
  qv architecture
296
+
297
+ # Check framework-specific issues (FastAPI, SQLAlchemy, SQLModel)
298
+ qv framework
299
+ qv framework --name fastapi
300
+ qv framework --name sqlalchemy
294
301
  ```
295
302
 
296
303
  ---
@@ -194,3 +194,28 @@ Scans source code for circular imports and unresolved internal modules.
194
194
  ```bash
195
195
  qv architecture [PATH]
196
196
  ```
197
+
198
+ ### 8.4 `qv framework` (alias: `qv frameworks`)
199
+ Runs framework-specific diagnostic rules (e.g. FastAPI async blocking calls, SQLAlchemy N+1 queries, unclosed sessions, SQL injection, pool leaks).
200
+
201
+ ```bash
202
+ qv framework [PATH] [OPTIONS]
203
+ ```
204
+
205
+ Options:
206
+ - `-n, --name [fastapi|sqlalchemy|sql|all]`: Filter analysis to a specific framework (default: all detected frameworks).
207
+ - `--json`: Output framework scan results as structured JSON.
208
+ - `--sarif`: Output framework scan results in SARIF v2.1.0 format.
209
+
210
+ Example:
211
+ ```bash
212
+ # Scan current project for all framework issues
213
+ qv framework
214
+
215
+ # Scan specifically for FastAPI issues
216
+ qv framework --name fastapi
217
+
218
+ # Scan specifically for SQLAlchemy / SQL database issues
219
+ qv framework --name sqlalchemy
220
+ ```
221
+
@@ -0,0 +1,444 @@
1
+ # Diagnostic Rules Catalog
2
+
3
+ `qv` rule IDs are stable public identifiers. This catalog lists all supported diagnostic checks, their default severities, and remediation strategies.
4
+
5
+ ---
6
+
7
+ ## 1. Dependency Rules
8
+
9
+ ### 1.1 DEP-001: Dependency Constraint Conflict
10
+ - **Default Severity:** `ERROR`
11
+ - **Description:** Two or more declared or transitive dependencies have incompatible version constraints.
12
+ - **Remediation:** Upgrade the conflicting package or loosen the version pin to satisfy all requirements.
13
+
14
+ ### 1.2 DEP-002: Missing Dependency Declaration
15
+ - **Default Severity:** `ERROR`
16
+ - **Description:** A third-party package is imported in project source code but is not declared in `pyproject.toml` or requirements files.
17
+ - **Remediation:** Add the missing package to your project dependencies (`uv add <package>` or `pip install <package>`).
18
+
19
+ ### 1.3 DEP-003: Unused Declared Dependency
20
+ - **Default Severity:** `WARNING`
21
+ - **Description:** A package is declared as a direct dependency, but no import statements were detected across project source files.
22
+ - **Remediation:** Verify if this dependency is required at runtime or remove it to keep dependencies lean.
23
+
24
+ ### 1.4 DEP-004: Python Compatibility Mismatch
25
+ - **Default Severity:** `WARNING`
26
+ - **Description:** A package requires a Python version incompatible with the target project runtime.
27
+ - **Remediation:** Upgrade your target Python version or install a version of the package compatible with your environment.
28
+
29
+ ### 1.5 DEP-005: Installed/Declaration Mismatch
30
+ - **Default Severity:** `WARNING`
31
+ - **Description:** The package version installed in the virtualenv does not satisfy the constraint declared in your project manifest.
32
+ - **Remediation:** Synchronize your virtual environment using `uv sync`, `poetry install`, or `pip install -r requirements.txt`.
33
+
34
+ ### 1.6 DEP-006: Vulnerable Transitive Dependency
35
+ - **Default Severity:** `ERROR`
36
+ - **Description:** A security vulnerability has been identified in a direct or transitive dependency.
37
+ - **Remediation:** Update the vulnerable dependency or apply security patches.
38
+
39
+ ---
40
+
41
+ ## 2. Environment & Drift Rules
42
+
43
+ ### 2.1 ENV-001: Python Version Drift
44
+ - **Default Severity:** `WARNING`
45
+ - **Description:** The active Python interpreter version differs from the project's target runtime specification.
46
+ - **Remediation:** Rebuild your virtual environment using the configured target Python version.
47
+
48
+ ### 2.2 ENV-002: Docker Runtime Drift
49
+ - **Default Severity:** `WARNING`
50
+ - **Description:** The Python version specified in `Dockerfile` or `compose.yml` differs from the project's target runtime.
51
+ - **Remediation:** Update the base image tag in `Dockerfile` (e.g. `FROM python:3.12-slim`).
52
+
53
+ ### 2.3 ENV-003: CI Runtime Drift
54
+ - **Default Severity:** `WARNING`
55
+ - **Description:** CI workflow matrix does not test the Python versions declared in project support.
56
+ - **Remediation:** Align your CI test matrix with `pyproject.toml` supported versions.
57
+
58
+ ---
59
+
60
+ ## 3. Import & Architecture Rules
61
+
62
+ ### 3.1 IMP-001: Circular Import Detected
63
+ - **Default Severity:** `ERROR`
64
+ - **Description:** An import cycle exists between two or more local modules, risking runtime `ImportError` or partially initialized modules.
65
+ - **Remediation:** Break the cycle by refactoring shared logic into a separate module or using deferred imports inside functions.
66
+
67
+ ### 3.2 IMP-002: Unresolved Local Import
68
+ - **Default Severity:** `ERROR`
69
+ - **Description:** A local module imported in source code could not be resolved on the project's Python path.
70
+ - **Remediation:** Check the module name and ensure source directories are on the Python path or package root.
71
+
72
+ ### 3.3 IMP-003: Unused / Orphan Local Module
73
+ - **Default Severity:** `WARNING`
74
+ - **Description:** A Python source file exists in the project but is never imported or referenced by any other module, entry point, or test file.
75
+ - **Remediation:** Review if the module is dead/obsolete and can be safely removed, or export it in package `__init__.py`.
76
+
77
+ ### 3.4 IMP-004: Deprecated or Removed Standard Library Module
78
+ - **Default Severity:** `ERROR`
79
+ - **Description:** A standard library module imported in source code was deprecated or removed in modern Python 3.11-3.13 (PEP 594).
80
+ - **Remediation:** Replace the removed stdlib module with its modern replacement (e.g. `importlib` instead of `imp`, `subprocess` instead of `pipes`).
81
+
82
+ ---
83
+
84
+ ## 4. Packaging Rules
85
+
86
+ ### 4.1 PKG-001: Missing Package Metadata
87
+ - **Default Severity:** `WARNING`
88
+ - **Description:** Essential packaging metadata (such as project name, version, or description) is missing from `pyproject.toml`.
89
+ - **Remediation:** Provide standard PEP 621 fields under the `[project]` table.
90
+
91
+ ### 4.2 PKG-002: Invalid Project Configuration
92
+ - **Default Severity:** `ERROR`
93
+ - **Description:** `pyproject.toml` contains syntax errors or invalid configuration tables.
94
+ - **Remediation:** Fix TOML syntax and ensure configuration follows PEP 621 specifications.
95
+
96
+ ---
97
+
98
+ ## 5. FastAPI Framework Rules (`FAP-xxx`)
99
+
100
+ ### 5.1 FAP-001: Blocking Call or CPU-Bound Operation in Async Endpoint
101
+ - **Default Severity:** `ERROR`
102
+ - **Description:** Synchronous blocking operations (such as `time.sleep()`, synchronous `requests`, `urllib.request.urlopen()`, `subprocess.run()`, or synchronous database queries) or heavy CPU-bound hashing (`bcrypt.hashpw`, `passlib`, `hashlib.pbkdf2_hmac`) called inside an `async def` FastAPI route handler stall the entire asyncio event loop.
103
+ - **Remediation:** Use async non-blocking alternatives (e.g. `asyncio.sleep()`, `httpx.AsyncClient()`), delegate work to worker threads via `anyio.to_thread.run_sync()`, or declare the route as regular synchronous `def` so FastAPI executes it in a worker threadpool.
104
+
105
+ ### 5.2 FAP-002: Blocking Dependency in Async Path
106
+ - **Default Severity:** `WARNING`
107
+ - **Description:** An `async def` route handler depends on a dependency (`Depends(...)`) that executes synchronous blocking I/O calls or CPU-heavy computations.
108
+ - **Remediation:** Refactor the dependency function to use non-blocking async libraries or wrap blocking logic with a threadpool executor.
109
+
110
+ ### 5.3 FAP-003: Missing Response Model or Return Type Annotation
111
+ - **Default Severity:** `WARNING`
112
+ - **Description:** FastAPI route handler does not declare a `response_model` argument in its route decorator or provide a return type annotation, disabling OpenAPI schema generation and automated response serialization/filtering.
113
+ - **Remediation:** Add a return type annotation (`def get_item(...) -> ItemResponse:`) or set `response_model=ItemResponse` in the decorator.
114
+
115
+ ### 5.4 FAP-004: Insecure CORS or Debug Configuration
116
+ - **Default Severity:** `ERROR`
117
+ - **Description:** `CORSMiddleware` configured with wildcard `allow_origins=["*"]` and `allow_credentials=True`, insecure plaintext HTTP `tokenUrl` in `OAuth2PasswordBearer`, or FastAPI application instantiated with `debug=True`. Wildcard origins with credentials violate browser security policies and expose authentication state.
118
+ - **Remediation:** Specify explicit trusted origins when credentials are enabled, use HTTPS or relative paths for OAuth2 token URLs, and disable debug mode in production.
119
+
120
+ ### 5.5 FAP-005: Missing HTTP Client Timeout Configuration
121
+ - **Default Severity:** `WARNING`
122
+ - **Description:** HTTP client (such as `httpx.AsyncClient`, `httpx.Client`, or `aiohttp.ClientSession`) instantiated without an explicit timeout or with `timeout=None`.
123
+ - **Remediation:** Pass an explicit timeout parameter (e.g. `httpx.AsyncClient(timeout=10.0)`) to prevent hung connections and resource starvation.
124
+
125
+ ### 5.6 FAP-006: Route Path Parameter Template Mismatch
126
+ - **Default Severity:** `ERROR`
127
+ - **Description:** A path parameter defined in the route URL template (e.g. `@app.get("/users/{user_id}")`) does not match any parameter in the handler function signature, causing runtime 422 Unprocessable Entity errors.
128
+ - **Remediation:** Ensure all `{param}` placeholders in route URL decorators match parameter names in the endpoint function signature.
129
+
130
+ ### 5.7 FAP-007: Unsafe Yield Dependency Without Try/Finally
131
+ - **Default Severity:** `WARNING`
132
+ - **Description:** A generator dependency using `yield` does not wrap cleanup code in a `try...finally` block. If an exception occurs during request processing, teardown code after `yield` will not execute, leading to database connection or resource leaks.
133
+ - **Remediation:** Wrap the `yield` and teardown cleanup inside a `try...finally` block.
134
+
135
+ ### 5.8 FAP-008: Deprecated `@app.on_event` Lifecycle Hook
136
+ - **Default Severity:** `WARNING`
137
+ - **Description:** Using deprecated `@app.on_event("startup")` or `@app.on_event("shutdown")` event hooks instead of modern lifespan context managers.
138
+ - **Remediation:** Migrate lifecycle logic to `@asynccontextmanager async def lifespan(app: FastAPI)` and pass `lifespan` to `FastAPI(lifespan=lifespan)`.
139
+
140
+ ### 5.9 FAP-009: Untyped or Unvalidated Request Body
141
+ - **Default Severity:** `WARNING`
142
+ - **Description:** Route handler accepts an untyped parameter (such as `dict`, `Any`, or untyped `Body(...)`) instead of a strict Pydantic model, bypassing schema validation and API documentation.
143
+ - **Remediation:** Create a Pydantic `BaseModel` schema and use it as the type annotation for request payloads.
144
+
145
+ ### 5.10 FAP-010: Shadowed or Duplicate Route Endpoint
146
+ - **Default Severity:** `ERROR`
147
+ - **Description:** Multiple route handlers register the same HTTP method and path on the same router/app instance, causing one endpoint to silently shadow the other.
148
+ - **Remediation:** Ensure all routes have distinct paths or combine the logic into a single handler.
149
+
150
+ ### 5.11 FAP-011: Mutable Default Value in Route Parameter
151
+ - **Default Severity:** `WARNING`
152
+ - **Description:** Route handler parameter uses a mutable default value (such as `list`, `dict`, or `set`), which can leak state across concurrent requests.
153
+ - **Remediation:** Use `None` as the default value (e.g. `filters: list[str] | None = None`) or use `Field(default_factory=list)`.
154
+
155
+ ### 5.12 FAP-012: Invalid Exception Handler Signature
156
+ - **Default Severity:** `ERROR`
157
+ - **Description:** Custom `@app.exception_handler` function does not accept the required `(request: Request, exc: Exception)` signature, causing runtime crashes when exceptions occur.
158
+ - **Remediation:** Update the handler function signature to accept exactly `(request: Request, exc: Exception)`.
159
+
160
+ ### 5.13 FAP-013: Untracked `asyncio.create_task` in Route Handler
161
+ - **Default Severity:** `WARNING`
162
+ - **Description:** Spawning raw `asyncio.create_task()` directly inside route handlers without background task management, which can lead to unhandled crashes or premature task collection.
163
+ - **Remediation:** Use FastAPI's built-in `BackgroundTasks` (`background_tasks.add_task(...)`) for reliable request-scoped task execution.
164
+
165
+ ### 5.14 FAP-014: Potential Path Traversal in `FileResponse`
166
+ - **Default Severity:** `WARNING`
167
+ - **Description:** Passing an unsanitized path parameter or query string directly to `FileResponse()`, creating an arbitrary file read vulnerability.
168
+ - **Remediation:** Validate that the resolved file path is inside an allowed base directory using `path.is_relative_to(base_dir)`.
169
+
170
+ ### 5.15 FAP-015: Blocking File I/O in Async Endpoint
171
+ - **Default Severity:** `ERROR`
172
+ - **Description:** Synchronous `open()`, `.read()`, or `.write()` calls inside `async def` routes block the asyncio event loop.
173
+ - **Remediation:** Use `aiofiles`, `anyio.Path`, or run file operations in a synchronous `def` route.
174
+
175
+ ### 5.16 FAP-016: Sensitive Field Exposure in Schema
176
+ - **Default Severity:** `ERROR`
177
+ - **Description:** Pydantic model declares sensitive fields (e.g. `password`, `hashed_password`, `secret_key`) without `Field(exclude=True)` or output filtering.
178
+ - **Remediation:** Exclude sensitive fields from output schemas or mark them with `Field(exclude=True)`.
179
+
180
+ ### 5.17 FAP-017: Non-Standard HTTP Status Code on POST or DELETE
181
+ - **Default Severity:** `WARNING`
182
+ - **Description:** `@app.post(...)` or `@app.delete(...)` routes return `200 OK` by default instead of explicit REST status codes (`201 Created` or `204 No Content`).
183
+ - **Remediation:** Declare `status_code=status.HTTP_201_CREATED` or `status.HTTP_204_NO_CONTENT`.
184
+
185
+ ### 5.18 FAP-018: Global In-Memory State Mutation in Route Handler
186
+ - **Default Severity:** `ERROR`
187
+ - **Description:** Mutating module-level global variables or dictionaries inside route handlers without locks causes concurrency race conditions.
188
+ - **Remediation:** Persist shared state in a database/cache or guard operations with `asyncio.Lock`.
189
+
190
+ ### 5.19 FAP-019: Insecure Cookie Configuration
191
+ - **Default Severity:** `ERROR`
192
+ - **Description:** `response.set_cookie()` called with `httponly=False` or `secure=False`, leaving authentication cookies vulnerable to XSS and interception.
193
+ - **Remediation:** Always set `httponly=True` and `secure=True` for authentication and session cookies.
194
+
195
+ ### 5.20 FAP-020: Potential Open Redirect in `RedirectResponse`
196
+ - **Default Severity:** `WARNING`
197
+ - **Description:** Initializing `RedirectResponse` directly with user-supplied URL inputs without whitelist validation.
198
+ - **Remediation:** Verify that redirect URLs are relative paths or match an allowed domain whitelist.
199
+
200
+ ### 5.21 FAP-021: Router Included Without Tags or Prefix
201
+ - **Default Severity:** `WARNING`
202
+ - **Description:** `app.include_router()` called without `tags` or `prefix`, leading to disorganized OpenAPI documentation.
203
+ - **Remediation:** Specify `prefix` and `tags` when mounting routers.
204
+
205
+ ### 5.22 FAP-022: WebSocket Route Missing `await websocket.accept()`
206
+ - **Default Severity:** `ERROR`
207
+ - **Description:** WebSocket route handler attempts to process messages before completing the connection handshake with `await websocket.accept()`.
208
+ - **Remediation:** Call `await websocket.accept()` before reading or writing data.
209
+
210
+ ### 5.23 FAP-023: Deprecated Pydantic v1 `class Config`
211
+ - **Default Severity:** `WARNING`
212
+ - **Description:** Using legacy inner `class Config:` inside Pydantic models instead of modern `model_config = ConfigDict(...)`.
213
+ - **Remediation:** Migrate inner configuration to `model_config = ConfigDict(...)`.
214
+
215
+ ### 5.24 FAP-024: Redundant Duplicate Dependency Declaration
216
+ - **Default Severity:** `WARNING`
217
+ - **Description:** Multiple parameters in the same route handler declare identical `Depends(...)` targets.
218
+ - **Remediation:** Consolidate redundant dependencies into a single parameter.
219
+
220
+ ### 5.25 FAP-025: Returned HTTPException Instance Instead of Raise
221
+ - **Default Severity:** `ERROR`
222
+ - **Description:** Returning an `HTTPException` instance in a route handler returns a `200 OK` HTTP response with the serialized exception object instead of raising an error.
223
+ - **Remediation:** Use `raise HTTPException(...)` instead of `return HTTPException(...)`.
224
+
225
+ ### 5.26 FAP-026: Deprecated Pydantic v1 `@validator` or `@root_validator`
226
+ - **Default Severity:** `WARNING`
227
+ - **Description:** Using deprecated Pydantic v1 `@validator` or `@root_validator` decorators instead of modern Pydantic v2 `@field_validator` or `@model_validator`.
228
+ - **Remediation:** Migrate to `@field_validator` or `@model_validator` from `pydantic`.
229
+
230
+ ### 5.27 FAP-027: Dependency Parameter Missing Type Annotation
231
+ - **Default Severity:** `WARNING`
232
+ - **Description:** Route parameter initialized with `Depends(...)`, `Query(...)`, or `Path(...)` does not specify a type annotation, disabling schema generation and static type checking.
233
+ - **Remediation:** Add an explicit type annotation (e.g. `param: Type = Depends(...)` or `param: Annotated[Type, Depends(...)]`).
234
+
235
+ ### 5.28 FAP-028: Raw `json.loads` Called on Request Body
236
+ - **Default Severity:** `WARNING`
237
+ - **Description:** Calling `json.loads(await request.body())` is an anti-pattern when FastAPI/Starlette provide the optimized built-in `await request.json()` method.
238
+ - **Remediation:** Replace `json.loads(await request.body())` with `await request.json()`.
239
+
240
+ ### 5.29 FAP-029: StreamingResponse Initialized Without `media_type`
241
+ - **Default Severity:** `WARNING`
242
+ - **Description:** Instantiating `StreamingResponse` without specifying `media_type` can cause clients to misinterpret the streamed content format.
243
+ - **Remediation:** Pass an explicit `media_type` argument (e.g. `StreamingResponse(stream, media_type='application/json')`).
244
+
245
+ ### 5.30 FAP-030: Missing Route Summary or Docstring for OpenAPI Documentation
246
+ - **Default Severity:** `WARNING`
247
+ - **Description:** Public route endpoint does not provide a docstring, `summary`, or `description`, resulting in sparse and incomplete OpenAPI documentation.
248
+ - **Remediation:** Add a function docstring or pass `summary=...` in the route decorator.
249
+
250
+ ### 5.31 FAP-031: Prefer `typing.Annotated` Over Parameter Default Assignment
251
+ - **Default Severity:** `INFO`
252
+ - **Description:** Using default parameter assignments (e.g. `db: Session = Depends(...)`) can cause issues with type checkers and testing. Modern FastAPI strongly recommends `Annotated[Session, Depends(...)]`.
253
+ - **Remediation:** Refactor parameter to `param: Annotated[Type, Depends(...)]`.
254
+
255
+ ### 5.32 FAP-032: Redundant `jsonable_encoder` With `response_model`
256
+ - **Default Severity:** `WARNING`
257
+ - **Description:** Calling `jsonable_encoder()` in a route handler that already specifies `response_model` causes redundant double serialization, reducing throughput.
258
+ - **Remediation:** Return raw objects/dictionaries and allow FastAPI's `response_model` to handle serialization.
259
+
260
+ ### 5.33 FAP-033: Missing `from_attributes=True` in ORM Response Schema
261
+ - **Default Severity:** `WARNING`
262
+ - **Description:** Pydantic response models for ORM/database entities missing `model_config = ConfigDict(from_attributes=True)` trigger validation errors at runtime.
263
+ - **Remediation:** Add `model_config = ConfigDict(from_attributes=True)` to the schema.
264
+
265
+ ### 5.34 FAP-034: Missing `await` on `request.json()` or `request.body()`
266
+ - **Default Severity:** `ERROR`
267
+ - **Description:** Calling `request.json()` or `request.body()` without `await` inside an `async def` handler assigns an un-awaited coroutine object instead of the payload.
268
+ - **Remediation:** Add `await` before `request.json()` or `request.body()`.
269
+
270
+ ### 5.35 FAP-035: Raw `Exception` Raised Instead of `HTTPException`
271
+ - **Default Severity:** `WARNING`
272
+ - **Description:** Raising generic `Exception`, `ValueError`, or `RuntimeError` produces unhandled `500 Internal Server Error` responses instead of structured REST responses.
273
+ - **Remediation:** Raise `HTTPException(status_code=..., detail=...)`.
274
+
275
+ ### 5.36 FAP-036: Mutating `app.state` Inside Route Handler
276
+ - **Default Severity:** `WARNING`
277
+ - **Description:** Mutating `request.app.state` or `app.state` in per-request handlers introduces concurrency race conditions.
278
+ - **Remediation:** Initialize shared state during `lifespan` startup.
279
+
280
+ ### 5.37 FAP-037: `SecurityScopes` Declared With `Depends` Instead of `Security`
281
+ - **Default Severity:** `WARNING`
282
+ - **Description:** Declaring a `SecurityScopes` parameter with `Depends(...)` does not pass scopes. Use `Security(..., scopes=[...])`.
283
+ - **Remediation:** Use `Security(dependency, scopes=[...])`.
284
+
285
+ ### 5.38 FAP-038: Hardcoded HTTP Status Code Integer
286
+ - **Default Severity:** `INFO`
287
+ - **Description:** Using integer literals for `status_code` (e.g. `status_code=201`) reduces readability compared to `status.HTTP_201_CREATED`.
288
+ - **Remediation:** Import `status` from `fastapi` and use named constants (e.g. `status.HTTP_201_CREATED`).
289
+
290
+ ---
291
+
292
+ ## 6. SQLAlchemy & SQL Database Rules
293
+
294
+ ### 6.1 SQL-001: Potential N+1 Database Query in Loop
295
+ - **Default Severity:** `WARNING`
296
+ - **Description:** Executing queries (`session.execute`, `session.query`, `session.get`) inside `for` or `while` loops triggers N+1 database roundtrips.
297
+ - **Remediation:** Batch load records using `where(Model.id.in_(ids))` or configure eager loading with `joinedload`/`selectinload`.
298
+
299
+ ### 6.2 SQL-002: Session Instantiated Without Context Manager or Cleanup
300
+ - **Default Severity:** `WARNING`
301
+ - **Description:** Creating a `Session` or `SessionLocal()` without a `with` block or `try...finally: session.close()` leaks active database connections.
302
+ - **Remediation:** Use `with SessionLocal() as session:` or a dependency yield pattern.
303
+
304
+ ### 6.3 SQL-003: Synchronous DB Operation in Async Event Loop
305
+ - **Default Severity:** `ERROR`
306
+ - **Description:** Invoking synchronous `create_engine()` or synchronous session operations inside `async def` functions blocks the event loop.
307
+ - **Remediation:** Use `create_async_engine()` and `AsyncSession` with async drivers (e.g. `asyncpg`, `aiosqlite`).
308
+
309
+ ### 6.4 SQL-004: Raw SQL String Interpolation (SQL Injection Risk)
310
+ - **Default Severity:** `ERROR`
311
+ - **Description:** Formatting SQL query strings using f-strings, `%`, or `.format()` inside `text()` or `cursor.execute()` introduces critical SQL injection vulnerabilities.
312
+ - **Remediation:** Use bound parameters (`text("SELECT * FROM users WHERE id = :id"), {"id": user_val}`).
313
+
314
+ ### 6.5 SQL-005: Legacy SQLAlchemy 1.x `session.query()` Syntax
315
+ - **Default Severity:** `WARNING`
316
+ - **Description:** Using legacy `session.query(...)` in SQLAlchemy 2.0 projects instead of 2.0-style `select()` statements.
317
+ - **Remediation:** Migrate to `session.scalars(select(Model).where(...))`.
318
+
319
+ ### 6.6 SQL-006: Missing Relationship Eager Loading Strategy in Async Session
320
+ - **Default Severity:** `WARNING`
321
+ - **Description:** Accessing lazy-loaded ORM relationships in async sessions triggers `MissingGreenlet` errors at runtime.
322
+ - **Remediation:** Configure `lazy="selectin"` on relationships or apply `.options(selectinload(Model.relation))`.
323
+
324
+ ### 6.7 SQL-007: Uncommitted Transaction in Mutation Function
325
+ - **Default Severity:** `WARNING`
326
+ - **Description:** A function calls `session.add(...)` or `session.delete(...)` but never executes `session.commit()` or `with session.begin():`.
327
+ - **Remediation:** Call `session.commit()` or wrap in `with session.begin():`.
328
+
329
+ ### 6.8 SQL-008: `create_engine` Missing `pool_pre_ping` Connection Health Check
330
+ - **Default Severity:** `WARNING`
331
+ - **Description:** Database engines created without `pool_pre_ping=True` risk stale connection disconnects when idle connections are closed by servers/firewalls.
332
+ - **Remediation:** Add `pool_pre_ping=True` to `create_engine()` / `create_async_engine()`.
333
+
334
+ ### 6.9 SQL-009: `expire_on_commit=True` in `AsyncSession`
335
+ - **Default Severity:** `WARNING`
336
+ - **Description:** Leaving `expire_on_commit=True` in `AsyncSession` or `async_sessionmaker` causes `MissingGreenlet` errors when accessing committed model attributes.
337
+ - **Remediation:** Specify `expire_on_commit=False`.
338
+
339
+ ### 6.10 SQL-010: Hardcoded Database Credentials in Connection URL
340
+ - **Default Severity:** `ERROR`
341
+ - **Description:** Plaintext passwords and connection secrets are committed directly into Python source code.
342
+ - **Remediation:** Load database credentials from environment variables (`os.getenv("DATABASE_URL")`).
343
+
344
+ ### 6.11 SQL-011: Unbounded `SELECT` Query Without Limit or Pagination
345
+ - **Default Severity:** `WARNING`
346
+ - **Description:** Calling `.all()` on database queries without `.limit()` or pagination clauses risks out-of-memory crashes on large tables.
347
+ - **Remediation:** Add `.limit(PAGE_SIZE)` and pagination parameters.
348
+
349
+ ### 6.12 SQL-012: Relationship Cascade Delete Without ForeignKey `ondelete="CASCADE"`
350
+ - **Default Severity:** `INFO`
351
+ - **Description:** `cascade="all, delete-orphan"` on ORM relationships without database-level `ondelete="CASCADE"` on the foreign key forces slow Python-side row-by-row deletion.
352
+ - **Remediation:** Add `ondelete="CASCADE"` to the `ForeignKey` definition.
353
+
354
+ ### 6.13 SQL-013: Session Flush or Commit Called Inside Loop
355
+ - **Default Severity:** `WARNING`
356
+ - **Description:** Calling `session.flush()` or `session.commit()` inside tight loops creates excessive database network roundtrips.
357
+ - **Remediation:** Commit once after the loop or use bulk insert statements (`session.execute(insert(Model), batch)`).
358
+
359
+ ### 6.14 SQL-014: Deprecated `declarative_base()` Function
360
+ - **Default Severity:** `WARNING`
361
+ - **Description:** Using legacy `Base = declarative_base()` instead of modern SQLAlchemy 2.0 `class Base(DeclarativeBase): pass`.
362
+ - **Remediation:** Subclass `DeclarativeBase` from `sqlalchemy.orm`.
363
+
364
+ ### 6.15 SQL-015: SQLite Engine Missing `check_same_thread=False` in Multi-Threaded Application
365
+ - **Default Severity:** `WARNING`
366
+ - **Description:** Using SQLite engines across threads in web servers without `connect_args={"check_same_thread": False}` causes `ProgrammingError`.
367
+ - **Remediation:** Set `connect_args={"check_same_thread": False}` when instantiating SQLite engines.
368
+
369
+ ### 6.16 SQL-016: Missing Database Migration Configuration (Alembic)
370
+ - **Default Severity:** `INFO`
371
+ - **Description:** SQLAlchemy ORM models are declared in the codebase, but no Alembic migrations directory or `alembic.ini` file exists.
372
+ - **Remediation:** Initialize database migrations using `alembic init alembic`.
373
+
374
+ ### 6.17 SQL-017: `Mapped[...]` Attribute Missing `mapped_column()` in 2.0 Declarative Model
375
+ - **Default Severity:** `WARNING`
376
+ - **Description:** Using legacy `Column(...)` or untyped field definitions alongside `Mapped[...]` type annotations in SQLAlchemy 2.0 declarative models.
377
+ - **Remediation:** Replace `col: Mapped[int] = Column(Integer)` with `col: Mapped[int] = mapped_column()`.
378
+
379
+ ### 6.18 SQL-018: Excessive Connection Pool Size in Single Application Instance
380
+ - **Default Severity:** `WARNING`
381
+ - **Description:** Configuring `create_engine` with `pool_size` or `max_overflow` > 50 in a single application worker risks exhausting database `max_connections`.
382
+ - **Remediation:** Keep `pool_size` between 5 and 20 per worker and scale using an external connection pooler like pgBouncer.
383
+
384
+ ### 6.19 SQL-019: `NullPool` Configured in Persistent Web Application
385
+ - **Default Severity:** `WARNING`
386
+ - **Description:** Setting `poolclass=NullPool` in a persistent web server disables connection pooling, forcing a new TCP handshake and SSL negotiation on every request.
387
+ - **Remediation:** Use default `QueuePool` for persistent web applications; reserve `NullPool` for ephemeral AWS Lambda/serverless environments.
388
+
389
+ ### 6.20 SQL-020: Declarative ORM Model Missing Primary Key Definition
390
+ - **Default Severity:** `ERROR`
391
+ - **Description:** A concrete model inheriting from `Base` / `DeclarativeBase` does not declare a primary key column, causing runtime ORM identity map failures.
392
+ - **Remediation:** Mark at least one column with `primary_key=True` or `mapped_column(primary_key=True)`.
393
+
394
+ ### 6.21 SQL-021: Missing `session.rollback()` in Database Exception Handler
395
+ - **Default Severity:** `WARNING`
396
+ - **Description:** An `except` block catches errors around database operations but fails to call `session.rollback()`, leaving the session in an unusable invalid transaction state.
397
+ - **Remediation:** Add `session.rollback()` inside `except` handlers or wrap operations in `with session.begin():`.
398
+
399
+ ### 6.22 SQL-022: Large Binary or Heavy Text Column Without `deferred()` Strategy
400
+ - **Default Severity:** `INFO`
401
+ - **Description:** Model includes heavy `LargeBinary`, `BLOB`, or `BYTEA` columns that are eagerly loaded on every `SELECT *`, consuming excessive application memory.
402
+ - **Remediation:** Wrap the column in `deferred(Column(LargeBinary))` or use `.options(load_only(...))`.
403
+
404
+ ### 6.23 SQL-023: `ForeignKey` Column Defined Without `index=True`
405
+ - **Default Severity:** `WARNING`
406
+ - **Description:** Foreign key column created without an index, resulting in slow sequential table scans during `JOIN` queries, foreign key lookups, and cascade operations.
407
+ - **Remediation:** Add `index=True` to ForeignKey column definitions (e.g. `Column(Integer, ForeignKey("users.id"), index=True)`).
408
+
409
+ ### 6.24 SQL-024: Insecure Unencrypted Remote Database Connection URL
410
+ - **Default Severity:** `WARNING`
411
+ - **Description:** Remote database connection URL targeting external hosts does not specify SSL encryption parameters (`sslmode=require` or `ssl=true`).
412
+ - **Remediation:** Append `?sslmode=require` (PostgreSQL) or `?ssl=true` to remote connection strings.
413
+
414
+ ### 6.25 SQL-025: Deprecated `engine.execute()` or `engine.scalar()` Direct Call
415
+ - **Default Severity:** `ERROR`
416
+ - **Description:** Invoking `engine.execute(...)` or `engine.scalar(...)` directly is removed in SQLAlchemy 2.0.
417
+ - **Remediation:** Acquire an explicit connection: `with engine.connect() as conn: result = conn.execute(stmt)`.
418
+
419
+ ### 6.26 SQL-026: `AsyncSession` Instantiated Without Async Context Manager or `await close()`
420
+ - **Default Severity:** `WARNING`
421
+ - **Description:** `AsyncSession` created in an `async def` function without `async with` or explicit `await session.close()`, causing leaked database connections.
422
+ - **Remediation:** Use `async with AsyncSessionLocal() as session:` or ensure `await session.close()` is called in a `finally` block.
423
+
424
+ ### 6.27 SQL-027: Unsafe Concurrent Numeric Balance or Counter Update Without `with_for_update()`
425
+ - **Default Severity:** `WARNING`
426
+ - **Description:** Querying a record and mutating numeric balances or stock quantities (`balance -= amount`) without row-level locking causes lost-update race conditions.
427
+ - **Remediation:** Lock rows using `select(...).with_for_update()` or use atomic SQL increments `update(Account).values(balance=Account.balance - amount)`.
428
+
429
+ ### 6.28 SQL-028: Thread-Local `scoped_session` Used in Async Context
430
+ - **Default Severity:** `ERROR`
431
+ - **Description:** Using thread-local `scoped_session` in `asyncio` code causes concurrent coroutines to unsafely share session instances.
432
+ - **Remediation:** Use `async_scoped_session(..., scopefunc=asyncio.current_task)` or FastAPI per-request dependency injection.
433
+
434
+ ### 6.29 SQL-029: Declarative ORM Model Missing `__tablename__` Definition
435
+ - **Default Severity:** `ERROR`
436
+ - **Description:** Concrete declarative model class inheriting from `Base` does not declare `__tablename__` or `__table__`.
437
+ - **Remediation:** Define `__tablename__ = "table_name"` on the model (or set `__abstract__ = True` for reusable mixins).
438
+
439
+ ### 6.30 SQL-030: Direct DBAPI `raw_connection()` Used Without Cleanup
440
+ - **Default Severity:** `WARNING`
441
+ - **Description:** Calling `engine.raw_connection()` bypasses connection pool lifecycle management and leaks connections if not closed explicitly.
442
+ - **Remediation:** Wrap in `try...finally`: `raw_conn = engine.raw_connection(); try: ... finally: raw_conn.close()`.
443
+
444
+
@@ -0,0 +1,61 @@
1
+ import time
2
+
3
+ import httpx
4
+ import requests
5
+ from fastapi import Depends, FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from fastapi.security import OAuth2PasswordBearer
8
+
9
+ # FAP-004: debug=True
10
+ app = FastAPI(debug=True)
11
+
12
+ # FAP-004: Insecure plaintext HTTP OAuth2 token URL
13
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="http://auth.example.com/token")
14
+
15
+ # FAP-004: Insecure CORS wildcard with allow_credentials=True
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_credentials=True,
20
+ allow_methods=["*"],
21
+ allow_headers=["*"],
22
+ )
23
+
24
+
25
+ # FAP-007: Unsafe yield dependency without try...finally
26
+ def get_db_session():
27
+ db = {"connected": True}
28
+ yield db
29
+ # If request fails, this close() is never reached
30
+ db["connected"] = False
31
+
32
+
33
+ def blocking_sync_dependency():
34
+ time.sleep(1)
35
+ return {"user": "alice"}
36
+
37
+
38
+ # FAP-008: Deprecated on_event startup hook
39
+ @app.on_event("startup")
40
+ async def startup_event():
41
+ pass
42
+
43
+
44
+ # FAP-001: Blocking calls in async route
45
+ # FAP-002: Dependent on blocking_sync_dependency
46
+ # FAP-003: Missing response_model / return annotation
47
+ # FAP-006: Route template mismatch ({item_id} missing in function arguments)
48
+ @app.get("/items/{item_id}")
49
+ async def get_items(
50
+ auth=Depends(blocking_sync_dependency), # noqa: B008
51
+ db=Depends(get_db_session), # noqa: B008
52
+ ):
53
+ # Blocking calls in async def
54
+ time.sleep(2)
55
+ requests.get("https://api.example.com/data")
56
+
57
+ # FAP-005: Missing timeout on HTTP client
58
+ async with httpx.AsyncClient() as client:
59
+ resp = await client.get("https://httpbin.org/get")
60
+
61
+ return {"auth": auth, "data": resp.status_code}
@@ -0,0 +1,9 @@
1
+ [project]
2
+ name = "fastapi-demo-service"
3
+ version = "0.1.0"
4
+ description = "Sample FastAPI service demonstrating framework diagnostics"
5
+ dependencies = [
6
+ "fastapi>=0.110.0",
7
+ "httpx>=0.27.0",
8
+ "requests>=2.31.0",
9
+ ]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-qv"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Diagnose why a Python project is unhealthy — explain root cause and suggest safe fixes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """qv - Package-manager-agnostic diagnostic platform for Python projects."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.3"