fix: resolve all lint issues and fix failing tests
- Fix blank line whitespace issues (W293) using ruff --unsafe-fixes - Reformat code using ruff format for consistent styling - Fix analyze_package_quality function to return list[Message] instead of string - Add missing 'assessment' keyword to package analysis template - Update tests to use real prompt functions instead of mocks for structure validation - Fix import ordering in test files - All 64 tests now pass with 47% code coverage Signed-off-by: longhao <hal.long@outlook.com>
This commit is contained in:
parent
d63ef02ef3
commit
a28d999958
18 changed files with 554 additions and 390 deletions
|
|
@ -28,7 +28,7 @@ class DependencyResolver:
|
|||
python_version: str | None = None,
|
||||
include_extras: list[str] | None = None,
|
||||
include_dev: bool = False,
|
||||
max_depth: int | None = None
|
||||
max_depth: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Resolve all dependencies for a package recursively.
|
||||
|
||||
|
|
@ -48,7 +48,9 @@ class DependencyResolver:
|
|||
max_depth = max_depth or self.max_depth
|
||||
include_extras = include_extras or []
|
||||
|
||||
logger.info(f"Resolving dependencies for {package_name} (Python {python_version})")
|
||||
logger.info(
|
||||
f"Resolving dependencies for {package_name} (Python {python_version})"
|
||||
)
|
||||
|
||||
# Track visited packages to avoid circular dependencies
|
||||
visited: set[str] = set()
|
||||
|
|
@ -63,13 +65,15 @@ class DependencyResolver:
|
|||
visited=visited,
|
||||
dependency_tree=dependency_tree,
|
||||
current_depth=0,
|
||||
max_depth=max_depth
|
||||
max_depth=max_depth,
|
||||
)
|
||||
|
||||
# Check if main package was resolved
|
||||
normalized_name = package_name.lower().replace("_", "-")
|
||||
if normalized_name not in dependency_tree:
|
||||
raise PackageNotFoundError(f"Package '{package_name}' not found on PyPI")
|
||||
raise PackageNotFoundError(
|
||||
f"Package '{package_name}' not found on PyPI"
|
||||
)
|
||||
|
||||
# Generate summary
|
||||
summary = self._generate_dependency_summary(dependency_tree)
|
||||
|
|
@ -80,13 +84,15 @@ class DependencyResolver:
|
|||
"include_extras": include_extras,
|
||||
"include_dev": include_dev,
|
||||
"dependency_tree": dependency_tree,
|
||||
"summary": summary
|
||||
"summary": summary,
|
||||
}
|
||||
|
||||
except PyPIError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error resolving dependencies for {package_name}: {e}")
|
||||
logger.error(
|
||||
f"Unexpected error resolving dependencies for {package_name}: {e}"
|
||||
)
|
||||
raise NetworkError(f"Failed to resolve dependencies: {e}", e) from e
|
||||
|
||||
async def _resolve_recursive(
|
||||
|
|
@ -98,7 +104,7 @@ class DependencyResolver:
|
|||
visited: set[str],
|
||||
dependency_tree: dict[str, Any],
|
||||
current_depth: int,
|
||||
max_depth: int
|
||||
max_depth: int,
|
||||
) -> None:
|
||||
"""Recursively resolve dependencies."""
|
||||
|
||||
|
|
@ -138,11 +144,13 @@ class DependencyResolver:
|
|||
"requires_python": info.get("requires_python", ""),
|
||||
"dependencies": {
|
||||
"runtime": [str(req) for req in categorized["runtime"]],
|
||||
"development": [str(req) for req in categorized["development"]] if include_dev else [],
|
||||
"extras": {}
|
||||
"development": [str(req) for req in categorized["development"]]
|
||||
if include_dev
|
||||
else [],
|
||||
"extras": {},
|
||||
},
|
||||
"depth": current_depth,
|
||||
"children": {}
|
||||
"children": {},
|
||||
}
|
||||
|
||||
# Add requested extras
|
||||
|
|
@ -177,12 +185,14 @@ class DependencyResolver:
|
|||
visited=visited,
|
||||
dependency_tree=dependency_tree,
|
||||
current_depth=current_depth + 1,
|
||||
max_depth=max_depth
|
||||
max_depth=max_depth,
|
||||
)
|
||||
|
||||
# Add to children if resolved
|
||||
if dep_name.lower() in dependency_tree:
|
||||
package_info["children"][dep_name.lower()] = dependency_tree[dep_name.lower()]
|
||||
package_info["children"][dep_name.lower()] = dependency_tree[
|
||||
dep_name.lower()
|
||||
]
|
||||
|
||||
except PackageNotFoundError:
|
||||
logger.warning(f"Package {package_name} not found, skipping")
|
||||
|
|
@ -190,7 +200,9 @@ class DependencyResolver:
|
|||
logger.error(f"Error resolving {package_name}: {e}")
|
||||
# Continue with other dependencies
|
||||
|
||||
def _generate_dependency_summary(self, dependency_tree: dict[str, Any]) -> dict[str, Any]:
|
||||
def _generate_dependency_summary(
|
||||
self, dependency_tree: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Generate summary statistics for the dependency tree."""
|
||||
|
||||
total_packages = len(dependency_tree)
|
||||
|
|
@ -214,7 +226,7 @@ class DependencyResolver:
|
|||
"total_development_dependencies": total_dev_deps,
|
||||
"total_extra_dependencies": total_extra_deps,
|
||||
"max_depth": max_depth,
|
||||
"package_list": list(dependency_tree.keys())
|
||||
"package_list": list(dependency_tree.keys()),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -223,7 +235,7 @@ async def resolve_package_dependencies(
|
|||
python_version: str | None = None,
|
||||
include_extras: list[str] | None = None,
|
||||
include_dev: bool = False,
|
||||
max_depth: int = 5
|
||||
max_depth: int = 5,
|
||||
) -> dict[str, Any]:
|
||||
"""Resolve package dependencies with comprehensive analysis.
|
||||
|
||||
|
|
@ -242,5 +254,5 @@ async def resolve_package_dependencies(
|
|||
package_name=package_name,
|
||||
python_version=python_version,
|
||||
include_extras=include_extras,
|
||||
include_dev=include_dev
|
||||
include_dev=include_dev,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ async def get_package_download_stats(
|
|||
|
||||
# Get basic package info for metadata
|
||||
try:
|
||||
package_info = await pypi_client.get_package_info(package_name, use_cache)
|
||||
package_info = await pypi_client.get_package_info(
|
||||
package_name, use_cache
|
||||
)
|
||||
package_metadata = {
|
||||
"name": package_info.get("info", {}).get("name", package_name),
|
||||
"version": package_info.get("info", {}).get("version", "unknown"),
|
||||
|
|
@ -48,10 +50,14 @@ async def get_package_download_stats(
|
|||
"author": package_info.get("info", {}).get("author", ""),
|
||||
"home_page": package_info.get("info", {}).get("home_page", ""),
|
||||
"project_url": package_info.get("info", {}).get("project_url", ""),
|
||||
"project_urls": package_info.get("info", {}).get("project_urls", {}),
|
||||
"project_urls": package_info.get("info", {}).get(
|
||||
"project_urls", {}
|
||||
),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not fetch package metadata for {package_name}: {e}")
|
||||
logger.warning(
|
||||
f"Could not fetch package metadata for {package_name}: {e}"
|
||||
)
|
||||
package_metadata = {"name": package_name}
|
||||
|
||||
# Extract download data
|
||||
|
|
@ -143,10 +149,26 @@ async def get_top_packages_by_downloads(
|
|||
"""
|
||||
# Known popular packages (this would ideally come from an API)
|
||||
popular_packages = [
|
||||
"boto3", "urllib3", "requests", "certifi", "charset-normalizer",
|
||||
"idna", "setuptools", "python-dateutil", "six", "botocore",
|
||||
"typing-extensions", "packaging", "numpy", "pip", "pyyaml",
|
||||
"cryptography", "click", "jinja2", "markupsafe", "wheel"
|
||||
"boto3",
|
||||
"urllib3",
|
||||
"requests",
|
||||
"certifi",
|
||||
"charset-normalizer",
|
||||
"idna",
|
||||
"setuptools",
|
||||
"python-dateutil",
|
||||
"six",
|
||||
"botocore",
|
||||
"typing-extensions",
|
||||
"packaging",
|
||||
"numpy",
|
||||
"pip",
|
||||
"pyyaml",
|
||||
"cryptography",
|
||||
"click",
|
||||
"jinja2",
|
||||
"markupsafe",
|
||||
"wheel",
|
||||
]
|
||||
|
||||
async with PyPIStatsClient() as stats_client:
|
||||
|
|
@ -163,12 +185,14 @@ async def get_top_packages_by_downloads(
|
|||
download_data = stats.get("data", {})
|
||||
download_count = _extract_download_count(download_data, period)
|
||||
|
||||
top_packages.append({
|
||||
"rank": i + 1,
|
||||
"package": package_name,
|
||||
"downloads": download_count,
|
||||
"period": period,
|
||||
})
|
||||
top_packages.append(
|
||||
{
|
||||
"rank": i + 1,
|
||||
"package": package_name,
|
||||
"downloads": download_count,
|
||||
"period": period,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get stats for {package_name}: {e}")
|
||||
|
|
@ -221,7 +245,9 @@ def _analyze_download_stats(download_data: dict[str, Any]) -> dict[str, Any]:
|
|||
analysis["periods_available"].append(period)
|
||||
analysis["total_downloads"] += count
|
||||
|
||||
if analysis["highest_period"] is None or count > download_data.get(analysis["highest_period"], 0):
|
||||
if analysis["highest_period"] is None or count > download_data.get(
|
||||
analysis["highest_period"], 0
|
||||
):
|
||||
analysis["highest_period"] = period
|
||||
|
||||
# Calculate growth indicators
|
||||
|
|
@ -230,15 +256,21 @@ def _analyze_download_stats(download_data: dict[str, Any]) -> dict[str, Any]:
|
|||
last_month = download_data.get("last_month", 0)
|
||||
|
||||
if last_day and last_week:
|
||||
analysis["growth_indicators"]["daily_vs_weekly"] = round(last_day * 7 / last_week, 2)
|
||||
analysis["growth_indicators"]["daily_vs_weekly"] = round(
|
||||
last_day * 7 / last_week, 2
|
||||
)
|
||||
|
||||
if last_week and last_month:
|
||||
analysis["growth_indicators"]["weekly_vs_monthly"] = round(last_week * 4 / last_month, 2)
|
||||
analysis["growth_indicators"]["weekly_vs_monthly"] = round(
|
||||
last_week * 4 / last_month, 2
|
||||
)
|
||||
|
||||
return analysis
|
||||
|
||||
|
||||
def _analyze_download_trends(time_series_data: list[dict], include_mirrors: bool) -> dict[str, Any]:
|
||||
def _analyze_download_trends(
|
||||
time_series_data: list[dict], include_mirrors: bool
|
||||
) -> dict[str, Any]:
|
||||
"""Analyze download trends from time series data.
|
||||
|
||||
Args:
|
||||
|
|
@ -263,8 +295,7 @@ def _analyze_download_trends(time_series_data: list[dict], include_mirrors: bool
|
|||
# Filter data based on mirror preference
|
||||
category_filter = "with_mirrors" if include_mirrors else "without_mirrors"
|
||||
filtered_data = [
|
||||
item for item in time_series_data
|
||||
if item.get("category") == category_filter
|
||||
item for item in time_series_data if item.get("category") == category_filter
|
||||
]
|
||||
|
||||
if not filtered_data:
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class PackageDownloader:
|
|||
include_dev: bool = False,
|
||||
prefer_wheel: bool = True,
|
||||
verify_checksums: bool = True,
|
||||
max_depth: int = 5
|
||||
max_depth: int = 5,
|
||||
) -> dict[str, Any]:
|
||||
"""Download a package and all its dependencies.
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ class PackageDownloader:
|
|||
python_version=python_version,
|
||||
include_extras=include_extras,
|
||||
include_dev=include_dev,
|
||||
max_depth=max_depth
|
||||
max_depth=max_depth,
|
||||
)
|
||||
|
||||
dependency_tree = resolution_result["dependency_tree"]
|
||||
|
|
@ -78,19 +78,18 @@ class PackageDownloader:
|
|||
version=pkg_info["version"],
|
||||
python_version=python_version,
|
||||
prefer_wheel=prefer_wheel,
|
||||
verify_checksums=verify_checksums
|
||||
verify_checksums=verify_checksums,
|
||||
)
|
||||
download_results[pkg_name] = result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to download {pkg_name}: {e}")
|
||||
failed_downloads.append({
|
||||
"package": pkg_name,
|
||||
"error": str(e)
|
||||
})
|
||||
failed_downloads.append({"package": pkg_name, "error": str(e)})
|
||||
|
||||
# Generate summary
|
||||
summary = self._generate_download_summary(download_results, failed_downloads)
|
||||
summary = self._generate_download_summary(
|
||||
download_results, failed_downloads
|
||||
)
|
||||
|
||||
return {
|
||||
"package_name": package_name,
|
||||
|
|
@ -99,7 +98,7 @@ class PackageDownloader:
|
|||
"resolution_result": resolution_result,
|
||||
"download_results": download_results,
|
||||
"failed_downloads": failed_downloads,
|
||||
"summary": summary
|
||||
"summary": summary,
|
||||
}
|
||||
|
||||
except PyPIError:
|
||||
|
|
@ -114,7 +113,7 @@ class PackageDownloader:
|
|||
version: str | None = None,
|
||||
python_version: str | None = None,
|
||||
prefer_wheel: bool = True,
|
||||
verify_checksums: bool = True
|
||||
verify_checksums: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Download a single package."""
|
||||
|
||||
|
|
@ -129,12 +128,16 @@ class PackageDownloader:
|
|||
# Determine version to download
|
||||
target_version = version or info.get("version")
|
||||
if not target_version or target_version not in releases:
|
||||
raise PackageNotFoundError(f"Version {target_version} not found for {package_name}")
|
||||
raise PackageNotFoundError(
|
||||
f"Version {target_version} not found for {package_name}"
|
||||
)
|
||||
|
||||
# Get release files
|
||||
release_files = releases[target_version]
|
||||
if not release_files:
|
||||
raise PackageNotFoundError(f"No files found for {package_name} {target_version}")
|
||||
raise PackageNotFoundError(
|
||||
f"No files found for {package_name} {target_version}"
|
||||
)
|
||||
|
||||
# Select best file to download
|
||||
selected_file = self._select_best_file(
|
||||
|
|
@ -142,25 +145,25 @@ class PackageDownloader:
|
|||
)
|
||||
|
||||
if not selected_file:
|
||||
raise PackageNotFoundError(f"No suitable file found for {package_name} {target_version}")
|
||||
raise PackageNotFoundError(
|
||||
f"No suitable file found for {package_name} {target_version}"
|
||||
)
|
||||
|
||||
# Download the file
|
||||
download_result = await self._download_file(
|
||||
selected_file, verify_checksums
|
||||
)
|
||||
download_result = await self._download_file(selected_file, verify_checksums)
|
||||
|
||||
return {
|
||||
"package_name": package_name,
|
||||
"version": target_version,
|
||||
"file_info": selected_file,
|
||||
"download_result": download_result
|
||||
"download_result": download_result,
|
||||
}
|
||||
|
||||
def _select_best_file(
|
||||
self,
|
||||
release_files: list[dict[str, Any]],
|
||||
python_version: str | None = None,
|
||||
prefer_wheel: bool = True
|
||||
prefer_wheel: bool = True,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Select the best file to download from available release files."""
|
||||
|
||||
|
|
@ -172,7 +175,9 @@ class PackageDownloader:
|
|||
if prefer_wheel and wheels:
|
||||
# Try to find compatible wheel
|
||||
if python_version:
|
||||
compatible_wheels = self._filter_compatible_wheels(wheels, python_version)
|
||||
compatible_wheels = self._filter_compatible_wheels(
|
||||
wheels, python_version
|
||||
)
|
||||
if compatible_wheels:
|
||||
return compatible_wheels[0]
|
||||
|
||||
|
|
@ -187,9 +192,7 @@ class PackageDownloader:
|
|||
return release_files[0] if release_files else None
|
||||
|
||||
def _filter_compatible_wheels(
|
||||
self,
|
||||
wheels: list[dict[str, Any]],
|
||||
python_version: str
|
||||
self, wheels: list[dict[str, Any]], python_version: str
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Filter wheels compatible with the specified Python version."""
|
||||
|
||||
|
|
@ -204,18 +207,18 @@ class PackageDownloader:
|
|||
filename = wheel.get("filename", "")
|
||||
|
||||
# Check for Python version in filename
|
||||
if (f"py{major_minor_nodot}" in filename or
|
||||
f"cp{major_minor_nodot}" in filename or
|
||||
"py3" in filename or
|
||||
"py2.py3" in filename):
|
||||
if (
|
||||
f"py{major_minor_nodot}" in filename
|
||||
or f"cp{major_minor_nodot}" in filename
|
||||
or "py3" in filename
|
||||
or "py2.py3" in filename
|
||||
):
|
||||
compatible.append(wheel)
|
||||
|
||||
return compatible
|
||||
|
||||
async def _download_file(
|
||||
self,
|
||||
file_info: dict[str, Any],
|
||||
verify_checksums: bool = True
|
||||
self, file_info: dict[str, Any], verify_checksums: bool = True
|
||||
) -> dict[str, Any]:
|
||||
"""Download a single file."""
|
||||
|
||||
|
|
@ -265,13 +268,11 @@ class PackageDownloader:
|
|||
"file_path": str(file_path),
|
||||
"downloaded_size": downloaded_size,
|
||||
"verification": verification_result,
|
||||
"success": True
|
||||
"success": True,
|
||||
}
|
||||
|
||||
def _generate_download_summary(
|
||||
self,
|
||||
download_results: dict[str, Any],
|
||||
failed_downloads: list[dict[str, Any]]
|
||||
self, download_results: dict[str, Any], failed_downloads: list[dict[str, Any]]
|
||||
) -> dict[str, Any]:
|
||||
"""Generate download summary statistics."""
|
||||
|
||||
|
|
@ -288,8 +289,11 @@ class PackageDownloader:
|
|||
"failed_downloads": failed_count,
|
||||
"total_downloaded_size": total_size,
|
||||
"download_directory": str(self.download_dir),
|
||||
"success_rate": successful_downloads / (successful_downloads + failed_count) * 100
|
||||
if (successful_downloads + failed_count) > 0 else 0
|
||||
"success_rate": successful_downloads
|
||||
/ (successful_downloads + failed_count)
|
||||
* 100
|
||||
if (successful_downloads + failed_count) > 0
|
||||
else 0,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -301,7 +305,7 @@ async def download_package_with_dependencies(
|
|||
include_dev: bool = False,
|
||||
prefer_wheel: bool = True,
|
||||
verify_checksums: bool = True,
|
||||
max_depth: int = 5
|
||||
max_depth: int = 5,
|
||||
) -> dict[str, Any]:
|
||||
"""Download a package and its dependencies to local directory.
|
||||
|
||||
|
|
@ -326,5 +330,5 @@ async def download_package_with_dependencies(
|
|||
include_dev=include_dev,
|
||||
prefer_wheel=prefer_wheel,
|
||||
verify_checksums=verify_checksums,
|
||||
max_depth=max_depth
|
||||
max_depth=max_depth,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue