-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored the lock file parsing and vulnerability downloading into s…
…eparate modules
- Loading branch information
1 parent
1b335b9
commit 49e0c50
Showing
4 changed files
with
92 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from uv_secure.package_info.lock_file_parser import parse_uv_lock_file | ||
from uv_secure.package_info.vulnerability_downloader import download_vulnerabilities | ||
|
||
|
||
__all__ = ["download_vulnerabilities", "parse_uv_lock_file"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from pathlib import Path | ||
import sys | ||
|
||
from pydantic import BaseModel | ||
|
||
|
||
# Conditional import for toml | ||
if sys.version_info >= (3, 11): | ||
import tomllib as toml | ||
else: | ||
import tomli as toml | ||
|
||
|
||
class Dependency(BaseModel): | ||
name: str | ||
version: str | ||
|
||
|
||
def parse_uv_lock_file(file_path: Path) -> list[Dependency]: | ||
"""Parses a uv.lock TOML file and extracts package PyPi dependencies""" | ||
with file_path.open("rb") as f: | ||
data = toml.load(f) | ||
|
||
package_data = data.get("package", []) | ||
return [ | ||
Dependency(name=package["name"], version=package["version"]) | ||
for package in package_data | ||
if package.get("source", {}).get("registry") == "https://pypi.org/simple" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import asyncio | ||
import re | ||
from typing import Optional | ||
|
||
import httpx | ||
from pydantic import BaseModel | ||
import typer | ||
|
||
from uv_secure.package_info.lock_file_parser import Dependency | ||
|
||
|
||
class Vulnerability(BaseModel): | ||
id: str | ||
details: str | ||
fixed_in: Optional[list[str]] = None | ||
aliases: Optional[list[str]] = None | ||
link: Optional[str] = None | ||
source: Optional[str] = None | ||
summary: Optional[str] = None | ||
withdrawn: Optional[str] = None | ||
|
||
|
||
def _canonicalize_name(name: str) -> str: | ||
"""Converts a package name to its canonical form for PyPI URLs""" | ||
return re.sub(r"[_.]+", "-", name).lower() | ||
|
||
|
||
async def _download_package_vulnerabilities( | ||
client: httpx.AsyncClient, dependency: Dependency | ||
) -> tuple[Dependency, list[Vulnerability]]: | ||
"""Queries the PyPi JSON API for vulnerabilities of a given dependency.""" | ||
canonical_name = _canonicalize_name(dependency.name) | ||
url = f"https://pypi.org/pypi/{canonical_name}/{dependency.version}/json" | ||
try: | ||
response = await client.get(url) | ||
if response.status_code == 200: | ||
data = response.json() | ||
vulnerabilities = [ | ||
Vulnerability(**v) for v in data.get("vulnerabilities", []) | ||
] | ||
return dependency, vulnerabilities | ||
typer.echo( | ||
f"Warning: Could not fetch data for {dependency.name}=={dependency.version}" | ||
) | ||
except httpx.RequestError as e: | ||
typer.echo(f"Error fetching {dependency.name}=={dependency.version}: {e}") | ||
return dependency, [] | ||
|
||
|
||
async def download_vulnerabilities( | ||
dependencies: list[Dependency], | ||
) -> list[tuple[Dependency, list[Vulnerability]]]: | ||
"""Fetch vulnerabilities for all dependencies concurrently.""" | ||
async with httpx.AsyncClient(timeout=10) as client: | ||
tasks = [_download_package_vulnerabilities(client, dep) for dep in dependencies] | ||
return await asyncio.gather(*tasks) |