python-qv 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-qv
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -8,6 +8,7 @@ Project-URL: Documentation, https://inzamol.github.io/qv/
8
8
  Project-URL: Issues, https://github.com/inzamol/qv/issues
9
9
  Author: Inzamul Hoque
10
10
  License-Expression: MIT
11
+ License-File: LICENSE
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: License :: OSI Approved :: MIT License
@@ -49,7 +50,7 @@ Description-Content-Type: text/markdown
49
50
  [![CI Status](https://github.com/inzamol/qv/actions/workflows/ci.yml/badge.svg)](https://github.com/inzamol/qv/actions/workflows/ci.yml)
50
51
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
51
52
 
52
- [Installation](#-installation) • [Quick Start](#-quick-start) • [Interactive TUI](#-interactive-tui-explorer) • [Features](#-what-it-detects) • [CLI Commands](#-cli-commands) • [CI/CD Integration](#-cicd-integration) • [Documentation](docs/getting_started.md)
53
+ [Installation](#-installation) • [Quick Start](#-quick-start) • [Interactive TUI](#-interactive-tui-explorer) • [Features](#-what-it-detects) • [CLI Commands](#-cli-commands) • [CI/CD Integration](#-cicd-integration) • [Documentation](https://inzamol.github.io/qv/)
53
54
 
54
55
  </div>
55
56
 
@@ -199,8 +200,10 @@ Run `qv inspect` (or `qv ui`) for a full terminal dashboard:
199
200
  | | `IMP-002` | Unresolved relative or internal module imports | `ERROR` |
200
201
  | **Packaging** | `PKG-001` | Missing PEP 621 metadata (name, version, etc.) | `WARNING` |
201
202
  | | `PKG-002` | Invalid syntax or malformed keys in `pyproject.toml` | `ERROR` |
203
+ | **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` |
204
+ | **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
205
 
203
- 👉 *See full explanations and remediation steps in the [Rules Catalog](docs/rules.md).*
206
+ 👉 *See all 80+ rules and remediation steps in the [Rules Catalog](docs/rules.md).*
204
207
 
205
208
  ---
206
209
 
@@ -331,6 +334,11 @@ qv environment
331
334
 
332
335
  # Check AST & imports only (circular import loops, unresolvable modules)
333
336
  qv architecture
337
+
338
+ # Check framework-specific issues (FastAPI, SQLAlchemy, SQLModel)
339
+ qv framework
340
+ qv framework --name fastapi
341
+ qv framework --name sqlalchemy
334
342
  ```
335
343
 
336
344
  ---
@@ -477,13 +485,14 @@ repos:
477
485
  ---
478
486
 
479
487
  ## 📚 Complete Documentation
480
-
481
- - 🚀 [Getting Started Guide](docs/getting_started.md)
482
- - 📖 [CLI Reference](docs/cli_reference.md)
483
- - 📋 [Diagnostic Rules Catalog](docs/rules.md)
484
- - ⚙️ [Configuration Guide](docs/configuration.md)
485
- - 🤖 [CI/CD & SARIF Integration](docs/ci_integration.md)
486
- - 🤝 [Contributing Guidelines](docs/contributing.md)
488
+ Online documentation is available at **[inzamol.github.io/qv](https://inzamol.github.io/qv/)**.
489
+
490
+ - 🚀 [Getting Started Guide](https://inzamol.github.io/qv/getting_started/)
491
+ - 📖 [CLI Reference](https://inzamol.github.io/qv/cli_reference/)
492
+ - 📋 [Diagnostic Rules Catalog](https://inzamol.github.io/qv/rules/)
493
+ - ⚙️ [Configuration Guide](https://inzamol.github.io/qv/configuration/)
494
+ - 🤖 [CI/CD & SARIF Integration](https://inzamol.github.io/qv/ci_integration/)
495
+ - 🤝 [Contributing Guidelines](https://inzamol.github.io/qv/contributing/)
487
496
 
488
497
  ---
489
498
 
@@ -1,4 +1,4 @@
1
- qv/__init__.py,sha256=ytQrd73EtfTFFFPArsLrgOJNI9NNyUwp_B8_rlbd-Fg,100
1
+ qv/__init__.py,sha256=XbmMZXW29dUN4PoZL_Q2RaC2sEqJ3Uk7VeM2iw3ZNpo,100
2
2
  qv/__main__.py,sha256=tG1eQDuox_m4xSLYLEC8KXwxKhL-IJno7CsCgdrw9Bs,112
3
3
  qv/analyzers/__init__.py,sha256=_d-1_NpNFMDu1cT-SqTKRBBeipboWZlRt5zZ0tsVPk0,25
4
4
  qv/analyzers/ci/__init__.py,sha256=W0Kr4IJBwjysEJPY-D3wNeDMWVFOFQzZt71LIzRDGfY,20
@@ -16,14 +16,17 @@ qv/analyzers/security/analyzer.py,sha256=DQAm3kex4v63ta8mFeI0gs4OCDvQYPfECLv1vza
16
16
  qv/analyzers/security/lockfile_parser.py,sha256=XkrJ-bwkv82wIojFsyzkqe4flCJWIliS1DEJNj9E59M,6565
17
17
  qv/analyzers/security/osv_client.py,sha256=Y9s8VeY3dSpUd-VBqOvdSSB8KgBzeti1K8niJY2G8IM,4961
18
18
  qv/cli/__init__.py,sha256=EUnPKm0L_oVbU1jAO_Icle4TXMCVGpd_aFTQ4iaueik,88
19
- qv/cli/main.py,sha256=aiDn3sjPVijPrNrW5YqyFBsO2KEL5h-V4rXhnNzzNP0,18806
19
+ qv/cli/main.py,sha256=s4QT4HEkDroVZhuzdKokGzGImiEqqNOSscZIAKZswZM,21839
20
20
  qv/core/analyzer.py,sha256=FPiEjghPbi-RVCOxODE_BRlkvtMsnrgbakhq7-YORuc,503
21
21
  qv/core/config.py,sha256=0ARPWbLCV_RJhJZOVomldnuztxeLhkQPPkoiOrTO9Jw,3792
22
22
  qv/core/context.py,sha256=kAI_QENd6c9kRiIE5Ejt2Um4MZsQ3TL9ubkl3w5AJF0,2575
23
- qv/core/engine.py,sha256=foFddOmcSFuLWqmqYdcXWC-6vh_27IsgUF2l7XJrBJ4,4496
23
+ qv/core/engine.py,sha256=xYpM4JKHKE5s1npD__R2Fm7JxAZ203AazMcF91hXEt4,4675
24
24
  qv/core/models.py,sha256=aQQTmyaBJsdof7oh8Onu2C2DLqQA6YbpLxQ0zB5utXw,4096
25
25
  qv/core/project.py,sha256=pHpDbjhYs_8iZiIluXUcj_zwTZPwbKHyXDIWfTxgCFM,18218
26
- qv/frameworks/__init__.py,sha256=5XnOOPd2Kyb9uG3Z7MjqZO1emDZs5F1sCAmI4yjWMPI,25
26
+ qv/frameworks/__init__.py,sha256=VRsvKo42xj0OAz4e-bkIZC3fp-MW7NYmeMdUQKK_WZA,459
27
+ qv/frameworks/base.py,sha256=4ue1HyXWXEtPF04u_AmUsrBY3xNJNlE6CQzXjUD8Mpc,691
28
+ qv/frameworks/fastapi.py,sha256=uhn8F0DUdi02Y073oSyltHvkVC3JbRp6Q4HrbMW5cXU,118523
29
+ qv/frameworks/sqlalchemy.py,sha256=aZF7yYgdBzeqrrmurzT-5X-K311OypFBuZyHDl61cHM,78946
27
30
  qv/integrations/__init__.py,sha256=xBWG_BBOAn4l43MnjXmhbu4qjiMdYq32pVjmDO0Q8iM,37
28
31
  qv/remediation/__init__.py,sha256=oHIxQraTgQVQUZ_kIorPR4t-YDVe7ygpn_dqXq_dmIU,252
29
32
  qv/remediation/engine.py,sha256=QkmrHqAyV7Zf1Z0OtWvxySJlzjUxKAmz63bJPiWHlsE,12741
@@ -35,12 +38,13 @@ qv/reporters/html_reporter.py,sha256=WZHYu_ND1GVNUHjgUK4ts97KPp1DqbkoYp3_-eOgnoU
35
38
  qv/reporters/json_reporter.py,sha256=RRzEZAMpOw8HGEVbYV8n_PCJ0PrIvFlOgAGptUxvtvY,446
36
39
  qv/reporters/sarif.py,sha256=CZEtjmqB7sNCc4qZJBLtm_7cT_gYa8frI1jl9JHzuyU,3062
37
40
  qv/reporters/terminal.py,sha256=H5c5-HMKSZ2PIHx-ZE4XA5bhMH826JHzBav0EKFqU44,4549
38
- qv/rules/registry.py,sha256=sz-S59_AAE0dzTe4wb77q2xymwQra0mXmEDrXf-FuMo,8596
41
+ qv/rules/registry.py,sha256=8Uru3E-ljz3Z_rzfczUO1g5mb9OHY2ax665d9RoonQw,50143
39
42
  qv/tui/__init__.py,sha256=otEuHr1bZ6rRMF2WkgPmv6z2Ws1cmnvFqr9Semhe_6A,100
40
43
  qv/tui/app.py,sha256=Db08g5SMYsLZZ4ncrh_9ElIw_TyiRoh5gDiGkoF6mPI,11841
41
44
  qv/visualizers/__init__.py,sha256=0kMeoXfdUJalMeZms5EwrFEYMpXmx30dJxITr9ahLCU,132
42
45
  qv/visualizers/tree.py,sha256=Jf7w4HZzvxvmCIiGea1PzaUoCKN2ByAVdM8vXLHId-c,8043
43
- python_qv-0.1.2.dist-info/METADATA,sha256=P7c1fy7JlOGs60A338evg0PvW6EkUwBxhJAcwW_cuPU,16415
44
- python_qv-0.1.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
45
- python_qv-0.1.2.dist-info/entry_points.txt,sha256=y0aZqfDPndkJ2yXxBM2RLL1qriH72zWEKSj4nJtc2q4,94
46
- python_qv-0.1.2.dist-info/RECORD,,
46
+ python_qv-0.1.4.dist-info/METADATA,sha256=Ym3L7sA8JMuq5KCOVdA1oLe96DcEGOpPiLdBelT6sko,17206
47
+ python_qv-0.1.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
48
+ python_qv-0.1.4.dist-info/entry_points.txt,sha256=y0aZqfDPndkJ2yXxBM2RLL1qriH72zWEKSj4nJtc2q4,94
49
+ python_qv-0.1.4.dist-info/licenses/LICENSE,sha256=fNrhekSMdWyK_2XdiJjrAYoeGfyLdNMZ9hgW65gWzYs,1075
50
+ python_qv-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Inzamul Hoque
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
qv/__init__.py CHANGED
@@ -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.4"
qv/cli/main.py CHANGED
@@ -18,6 +18,7 @@ from qv.core.config import QvConfig
18
18
  from qv.core.engine import AnalysisEngine
19
19
  from qv.core.models import Severity
20
20
  from qv.core.project import ProjectDiscovery
21
+ from qv.frameworks import AVAILABLE_FRAMEWORK_ANALYZERS
21
22
  from qv.remediation.engine import RemediationEngine
22
23
  from qv.reporters.github_annotator import GitHubAnnotator
23
24
  from qv.reporters.html_reporter import HtmlReporter
@@ -527,6 +528,89 @@ def graph_cmd(
527
528
  )
528
529
 
529
530
 
531
+ @cli.command("framework")
532
+ @click.argument(
533
+ "path",
534
+ default=".",
535
+ type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
536
+ )
537
+ @click.option(
538
+ "--name",
539
+ "-n",
540
+ type=str,
541
+ default=None,
542
+ help="Filter by framework plugin name (e.g. 'fastapi', 'sqlalchemy', 'sql').",
543
+ )
544
+ @click.option("--json", "as_json", is_flag=True, help="Output JSON results.")
545
+ @click.option("--sarif", "as_sarif", is_flag=True, help="Output SARIF results.")
546
+ def framework_cmd(path: Path, name: str | None, as_json: bool, as_sarif: bool) -> None:
547
+ """Run framework-specific doctors (FastAPI, SQLAlchemy, Celery, Django)."""
548
+ try:
549
+ pyproject_path = path / "pyproject.toml"
550
+ config = QvConfig.from_pyproject(pyproject_path if pyproject_path.exists() else None)
551
+ discovery = ProjectDiscovery(root=path, config=config)
552
+ context = discovery.discover_context()
553
+
554
+ # Select framework analyzers
555
+ analyzers_to_run = []
556
+ for cls in AVAILABLE_FRAMEWORK_ANALYZERS:
557
+ inst = cls()
558
+ if name:
559
+ target_name = name.lower()
560
+ if target_name in (inst.id.lower(), inst.name.lower()) or (
561
+ target_name == "sql" and inst.id == "sqlalchemy"
562
+ ):
563
+ analyzers_to_run.append(inst)
564
+ else:
565
+ analyzers_to_run.append(inst)
566
+
567
+ if not analyzers_to_run:
568
+ console.print(
569
+ f"[bold red]Unknown framework '{name}'. Available: fastapi, sqlalchemy[/bold red]"
570
+ )
571
+ sys.exit(2)
572
+
573
+ engine = AnalysisEngine(config=config, analyzers=analyzers_to_run)
574
+ result = engine.run(context)
575
+
576
+ if as_json:
577
+ reporter = JsonReporter()
578
+ click.echo(reporter.render(result))
579
+ elif as_sarif:
580
+ reporter = SarifReporter()
581
+ click.echo(reporter.render(result))
582
+ else:
583
+ TerminalReporter(console=console).print_result(result)
584
+
585
+ if result.has_blocking_errors:
586
+ sys.exit(1)
587
+ sys.exit(0)
588
+ except click.ClickException:
589
+ raise
590
+ except SystemExit:
591
+ raise
592
+ except Exception as e:
593
+ console.print(f"[bold red]Error running framework analysis: {e}[/bold red]")
594
+ sys.exit(3)
595
+
596
+
597
+ @cli.command("frameworks")
598
+ @click.argument(
599
+ "path",
600
+ default=".",
601
+ type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
602
+ )
603
+ @click.option("--name", "-n", type=str, default=None, help="Filter by framework plugin name.")
604
+ @click.option("--json", "as_json", is_flag=True, help="Output JSON results.")
605
+ @click.option("--sarif", "as_sarif", is_flag=True, help="Output SARIF results.")
606
+ @click.pass_context
607
+ def frameworks_cmd(
608
+ ctx: click.Context, path: Path, name: str | None, as_json: bool, as_sarif: bool
609
+ ) -> None:
610
+ """Alias for 'qv framework' command."""
611
+ ctx.invoke(framework_cmd, path=path, name=name, as_json=as_json, as_sarif=as_sarif)
612
+
613
+
530
614
  @cli.command("inspect")
531
615
  @click.argument(
532
616
  "path",
qv/core/engine.py CHANGED
@@ -13,6 +13,8 @@ from qv.core.analyzer import Analyzer
13
13
  from qv.core.config import QvConfig
14
14
  from qv.core.context import ProjectContext
15
15
  from qv.core.models import Diagnostic, RootCause, ScanResult, Suggestion
16
+ from qv.frameworks.fastapi import FastApiAnalyzer
17
+ from qv.frameworks.sqlalchemy import SqlAlchemyAnalyzer
16
18
 
17
19
 
18
20
  class AnalysisEngine:
@@ -33,6 +35,8 @@ class AnalysisEngine:
33
35
  DependencyAnalyzer(),
34
36
  ImportAnalyzer(),
35
37
  SecurityAnalyzer(),
38
+ FastApiAnalyzer(),
39
+ SqlAlchemyAnalyzer(),
36
40
  ]
37
41
 
38
42
  def run(self, context: ProjectContext) -> ScanResult:
qv/frameworks/__init__.py CHANGED
@@ -1 +1,19 @@
1
- """Framework plugins."""
1
+ """Framework analyzers for qv."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from qv.frameworks.base import FrameworkPlugin
6
+ from qv.frameworks.fastapi import FastApiAnalyzer
7
+ from qv.frameworks.sqlalchemy import SqlAlchemyAnalyzer
8
+
9
+ AVAILABLE_FRAMEWORK_ANALYZERS: list[type[FrameworkPlugin]] = [
10
+ FastApiAnalyzer,
11
+ SqlAlchemyAnalyzer,
12
+ ]
13
+
14
+ __all__ = [
15
+ "FrameworkPlugin",
16
+ "FastApiAnalyzer",
17
+ "SqlAlchemyAnalyzer",
18
+ "AVAILABLE_FRAMEWORK_ANALYZERS",
19
+ ]
qv/frameworks/base.py ADDED
@@ -0,0 +1,25 @@
1
+ """Base protocol and registry for framework-specific analyzers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol, runtime_checkable
6
+
7
+ from qv.core.context import ProjectContext
8
+ from qv.core.models import Diagnostic
9
+
10
+
11
+ @runtime_checkable
12
+ class FrameworkPlugin(Protocol):
13
+ """Protocol for framework-specific diagnostic plugins."""
14
+
15
+ id: str
16
+ name: str
17
+ description: str
18
+
19
+ def detect(self, context: ProjectContext) -> bool:
20
+ """Detect if this framework is actively used in the project."""
21
+ ...
22
+
23
+ def analyze(self, context: ProjectContext) -> list[Diagnostic]:
24
+ """Analyze the project context for framework-specific issues."""
25
+ ...