Skip to content

Commit f320c3f

Browse files
authored
Merge pull request #1491 from stonebig/master
donw to one error of logic of Copilote
2 parents efcad06 + 41cc8de commit f320c3f

File tree

1 file changed

+30
-72
lines changed

1 file changed

+30
-72
lines changed

winpython/piptree.py

Lines changed: 30 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,30 @@
1010
import re
1111
import platform
1212
import os
13+
import logging
1314
from functools import lru_cache
1415
from collections import OrderedDict
1516
from typing import Dict, List, Optional, Tuple, Union
1617
from pip._vendor.packaging.markers import Marker
1718
from importlib.metadata import Distribution, distributions
1819
from pathlib import Path
1920

21+
logging.basicConfig(level=logging.INFO)
22+
logger = logging.getLogger(__name__)
23+
24+
class PipDataError(Exception):
25+
"""Custom exception for PipData related errors."""
26+
pass
2027

2128
def sum_up(text: str, max_length: int = 144, stop_at: str = ". ") -> str:
2229
"""Summarize text to fit within max_length, ending at last complete sentence."""
2330
summary = (text + os.linesep).splitlines()[0]
2431
if len(summary) <= max_length:
25-
return summary
32+
return summary
2633
if stop_at and stop_at in summary[:max_length]:
2734
return summary[:summary.rfind(stop_at, 0, max_length)] + stop_at.rstrip()
2835
return summary[:max_length].rstrip()
2936

30-
3137
class PipData:
3238
"""Manages package metadata and dependency relationships in a Python environment."""
3339

@@ -40,21 +46,19 @@ def __init__(self, target: Optional[str] = None):
4046
self.distro: Dict[str, Dict] = {}
4147
self.raw: Dict[str, Dict] = {}
4248
self.environment = self._get_environment()
43-
4449
try:
4550
packages = self._get_packages(target or sys.executable)
4651
self._process_packages(packages)
4752
self._populate_reverse_dependencies()
4853
except Exception as e:
49-
raise RuntimeError(f"Failed to initialize package data: {str(e)}") from e
50-
54+
raise PipDataError(f"Failed to initialize package data: {str(e)}") from e
5155

5256
@staticmethod
5357
@lru_cache(maxsize=None)
5458
def normalize(name: str) -> str:
5559
"""Normalize package name per PEP 503."""
5660
return re.sub(r"[-_.]+", "-", name).lower()
57-
61+
5862
def _get_environment(self) -> Dict[str, str]:
5963
"""Collect system and Python environment details."""
6064
return {
@@ -99,20 +103,10 @@ def _process_packages(self, packages: List[Distribution]) -> None:
99103
"provided": {'': None} # Placeholder for extras provided by this package
100104
}
101105
except Exception as e:
102-
print(f"Warning: Failed to process package {name}: {str(e)}", file=sys.stderr)
103-
106+
logger.warning(f"Failed to process package {name}: {str(e)}", exc_info=True)
104107

105108
def _get_requires(self, package: Distribution) -> List[Dict[str, str]]:
106-
"""
107-
Extract and normalize requirements for a package.
108-
109-
This method parses the requirements of a package and normalizes them
110-
into a list of dictionaries. Each dictionary contains the required
111-
package key, version, extra, and marker (if any).
112-
113-
:param package: The Distribution object to extract requirements from
114-
:return: List of dictionaries containing normalized requirements
115-
"""
109+
"""Extract and normalize requirements for a package."""
116110
requires = []
117111
replacements = str.maketrans({" ": " ", "[": "", "]": "", "'": "", '"': ""})
118112
further_replacements = [
@@ -155,11 +149,7 @@ def _get_provides(self, package: Distribution) -> Dict[str, None]:
155149
return provides
156150

157151
def _populate_reverse_dependencies(self) -> None:
158-
"""Populate reverse dependencies.
159-
160-
It iterates over the requirements of each package
161-
and adds the package as a reverse dependency to the required packages.
162-
"""
152+
"""Populate reverse dependencies."""
163153
for pkg_key, pkg_data in self.distro.items():
164154
for req in pkg_data["requires_dist"]:
165155
target_key = req["req_key"]
@@ -173,32 +163,16 @@ def _populate_reverse_dependencies(self) -> None:
173163
self.distro[target_key]["reverse_dependencies"].append(rev_dep)
174164

175165
def _get_dependency_tree(self, package_name: str, extra: str = "", version_req: str = "", depth: int = 20, path: Optional[List[str]] = None, verbose: bool = False, upward: bool = False) -> List[List[str]]:
176-
"""
177-
Recursive function to build dependency tree.
178-
179-
This method builds a dependency tree for the specified package. It can
180-
build the tree for downward dependencies (default) or upward dependencies
181-
(if upward is True). The tree is built recursively up to the specified
182-
depth.
183-
184-
:param package_name: The name of the package to build the tree for
185-
:param extra: The extra to include in the dependency tree
186-
:param version_req: The version requirement for the package
187-
:param depth: The maximum depth of the dependency tree
188-
:param path: The current path in the dependency tree (used for cycle detection)
189-
:param verbose: Whether to include verbose output in the tree
190-
:param upward: Whether to build the tree for upward dependencies
191-
:return: List of lists containing the dependency tree
192-
"""
166+
"""Recursive function to build dependency tree."""
193167
path = path or []
194168
extras = extra.split(",")
195169
pkg_key = self.normalize(package_name)
196170
ret_all = []
197171

198172
full_name = f"{package_name}[{extra}]" if extra else package_name
199173
if full_name in path:
200-
print(f"Cycle detected: {' -> '.join(path + [full_name])}")
201-
return [] # Return empty list to avoid further recursion
174+
logger.warning(f"Cycle detected: {' -> '.join(path + [full_name])}")
175+
return []
202176

203177
pkg_data = self.distro[pkg_key]
204178
if pkg_data and len(path) <= depth:
@@ -215,19 +189,22 @@ def _get_dependency_tree(self, package_name: str, extra: str = "", version_req:
215189
next_path = path + [base_name]
216190
if upward:
217191
up_req = (dependency.get("req_marker", "").split('extra == ')+[""])[1].strip("'\"")
218-
# avoids circular links on dask[array]
219192
if dependency["req_key"] in self.distro and dependency["req_key"]+"["+up_req+"]" not in path:
220193
# upward dependancy taken if:
221194
# - if extra "" demanded, and no marker from upward package: like pandas[] ==> numpy
222195
# - or the extra is in the upward package, like pandas[test] ==> pytest, for 'test' extra
223196
# - or an extra "array" is demanded, and indeed in the req_extra list: array,dataframe,diagnostics,distributer
224-
if (not dependency.get("req_marker") and extra ==""
225-
) or ("req_marker" in dependency and extra==up_req and dependency["req_key"]!=pkg_key and Marker(dependency["req_marker"]).evaluate(environment=environment)
226-
) or ("req_marker" in dependency and extra!="" and extra+',' in dependency["req_extra"]+',' and Marker(dependency["req_marker"]).evaluate(environment=environment|{"extra": up_req})
227-
):
197+
if (not dependency.get("req_marker") and extra == "") or \
198+
("req_marker" in dependency and extra == up_req and \
199+
dependency["req_key"] != pkg_key and \
200+
Marker(dependency["req_marker"]).evaluate(environment=environment)) or \
201+
("req_marker" in dependency and extra != "" and \
202+
extra + ',' in dependency["req_extra"] + ',' and \
203+
Marker(dependency["req_marker"]).evaluate(environment=environment | {"extra": up_req})):
204+
# IA risk error: # dask[array] go upwards as dask[dataframe], so {"extra": up_req} , not {"extra": extra}
228205
ret += self._get_dependency_tree(
229206
dependency["req_key"],
230-
up_req, # dask[array] going upwards continues as dask[dataframe]
207+
up_req,
231208
f"[requires: {package_name}"
232209
+ (f"[{dependency['req_extra']}]" if dependency["req_extra"] != "" else "")
233210
+ f'{dependency["req_version"]}]',
@@ -251,16 +228,7 @@ def _get_dependency_tree(self, package_name: str, extra: str = "", version_req:
251228
return ret_all
252229

253230
def down(self, pp: str = "", extra: str = "", depth: int = 20, indent: int = 5, version_req: str = "", verbose: bool = False) -> str:
254-
"""
255-
Generate downward dependency tree as formatted string.
256-
257-
:param pp: The package name or "." to print dependencies for all packages
258-
:param extra: The extra to include in the dependency tree
259-
:param depth: The maximum depth of the dependency tree
260-
:param indent: The indentation level for the JSON output
261-
:param version_req: The version requirement for the package
262-
:param verbose: Whether to include verbose output in the tree
263-
"""
231+
"""Generate downward dependency tree as formatted string."""
264232
if pp == ".":
265233
results = [self.down(p, extra, depth, indent, version_req, verbose=verbose) for p in sorted(self.distro)]
266234
return '\n'.join(filter(None, results))
@@ -270,27 +238,17 @@ def down(self, pp: str = "", extra: str = "", depth: int = 20, indent: int = 5,
270238
results = [self.down(pp, one_extra, depth, indent, version_req, verbose=verbose)
271239
for one_extra in sorted(self.distro[pp]["provides"])]
272240
return '\n'.join(filter(None, results))
273-
return "" # Handle cases where extra is "." and package_name is not found.
241+
return ""
274242

275243
if pp not in self.distro:
276-
return "" # Handle cases where package_name is not found.
244+
return ""
277245

278246
rawtext = json.dumps(self._get_dependency_tree(pp, extra, version_req, depth, verbose=verbose), indent=indent)
279247
lines = [l for l in rawtext.split("\n") if len(l.strip()) > 2]
280248
return "\n".join(lines).replace('"', "")
281249

282-
def up(self, pp: str, extra: str = "", depth: int = 20, indent: int = 5,
283-
version_req: str = "", verbose: bool = False) -> str:
284-
"""
285-
Generate upward dependency tree as formatted string.
286-
287-
:param pp: The package name
288-
:param extra: The extra to include in the dependency tree
289-
:param depth: The maximum depth of the dependency tree
290-
:param indent: The indentation level for the JSON output
291-
:param version_req: The version requirement for the package
292-
:param verbose: Whether to include verbose output in the tree
293-
"""
250+
def up(self, pp: str, extra: str = "", depth: int = 20, indent: int = 5, version_req: str = "", verbose: bool = False) -> str:
251+
"""Generate upward dependency tree as formatted string."""
294252
if pp == ".":
295253
results = [self.up(p, extra, depth, indent, version_req, verbose) for p in sorted(self.distro)]
296254
return '\n'.join(filter(None, results))

0 commit comments

Comments
 (0)