FixMyEnv / src /upgrade_advisor /agents /tools /parse_response.py
Geevarghese George
fix imports
d306ba7
import logging
from src.upgrade_advisor.schema import (
PackageInfoSchema,
PackageReleaseSchema,
PackageSearchResponseSchema,
PackageVersionResponseSchema,
ResolvedDep,
ResolveResult,
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
def parse_response_pypi_search(
data: dict, cutoff: int = 10
) -> PackageSearchResponseSchema:
"""
Parse the JSON response from the PyPI search API into a Pydantic model.
Args:
data (dict): The JSON response from the PyPI search API.
cutoff (int): The maximum number of releases to include in the response. Defaults to 10.
Returns:
PackageSearchResponseSchema: A Pydantic model containing metadata about the package.
"""
info = data.get("info", {})
releases = dict(list(data.get("releases", {}).items())[-cutoff:])
last_serial = data.get("last_serial", 0)
# create the info
info = PackageInfoSchema(**info)
# create the releases
parsed_releases = {}
for version, release_list in releases.items():
# the whl and tar.gz contain the same info and are placed in a list
try:
release = release_list[0]
except IndexError:
release = {}
parsed_release_list = PackageReleaseSchema(version=version, **release)
parsed_releases[version] = parsed_release_list
# create the final response model
parsed_response = PackageSearchResponseSchema(
info=info,
releases=parsed_releases,
last_serial=last_serial,
)
return parsed_response
def parse_response_version_search(
data: dict, cutoff: int = 10
) -> PackageVersionResponseSchema:
"""
Parse the JSON response from the PyPI version search API.
Args:
data (dict): The JSON response from the PyPI version search API.
cutoff (int): The maximum number of URLs to include in the response from the end of the list. Defaults to the last 10.
Returns:
PackageVersionResponseSchema: A Pydantic model containing metadata about the specific version of the package.
"""
info = data.get("info", {})
urls = data.get("urls", [])[-cutoff:] # get only the last `cutoff` entries
last_serial = data.get("last_serial", 0)
# create the info
info = PackageInfoSchema(**info)
# create the urls
parsed_urls = []
for url_info in urls:
parsed_url = PackageReleaseSchema(
version=url_info.get("version", ""), # this is empty in the url info
**url_info,
)
parsed_urls.append(parsed_url)
parsed_response = PackageVersionResponseSchema(
info=info,
urls=parsed_urls,
last_serial=last_serial,
)
return parsed_response
def parse_resolved_deps(data: str) -> ResolveResult:
"""
Parse the resolved dependencies from the output string of a uv resolution command.
Args:
data (str): The output string containing resolved dependencies.
Returns:
ResolveResult: A ResolveResult model containing resolved dependencies.
"""
resolved_deps = []
current_dep = None
lines = data.splitlines()
for line_number, line in enumerate(lines):
# skip 2 lines
if line_number < 2:
continue
# one liner:
# # via pydantic
# could also be:
# # via
# # pydantic=1.10.7
# # packaging=23.1
# if begins with ascii character
if not line.lower().strip().startswith("#"):
direct_dep = line.strip()
# pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'
direct_dep_metainfo = direct_dep.rsplit(";", 1)
direct_dep = direct_dep_metainfo[0].strip().split("==")
direct_dep_name = direct_dep[0] if len(direct_dep) > 0 else direct_dep
direct_dep_version = direct_dep[1] if len(direct_dep) > 1 else ""
logger.info(
f"Parsed direct dependency: {direct_dep_name}=={direct_dep_version}"
)
resolved_dep = ResolvedDep(
name=direct_dep_name,
version=direct_dep_version,
via=[], # will be filled later
metainfo=direct_dep_metainfo[1].strip()
if len(direct_dep_metainfo) > 1
else "",
)
resolved_deps.append(resolved_dep)
current_dep = direct_dep_name
continue
else:
if line.replace(" #", "").strip() == "via":
continue
indirect_dep = line.replace(" #", "").replace("via", "").strip()
if current_dep is not None:
resolved_deps[-1].update_indirect_dep(indirect_dep)
logger.info(
f"Updated indirect dependency for {current_dep}: {indirect_dep}"
)
logger.info(f"Total resolved dependencies parsed: {len(resolved_deps)}")
logger.debug(f"Resolved dependencies details: {resolved_deps}")
return ResolveResult(deps={dep.name: dep for dep in resolved_deps})