Skip to content

Commit d757b47

Browse files
authored
Merge pull request winpython#1478 from stonebig/master
piptree.py typing with the help of Mistral 'Le Chat'
2 parents daa24ba + c7ab1c5 commit d757b47

File tree

3 files changed

+34
-36
lines changed

3 files changed

+34
-36
lines changed

winpython/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@
2828
OTHER DEALINGS IN THE SOFTWARE.
2929
"""
3030

31-
__version__ = '12.0.20250201'
31+
__version__ = '13.0.20250209'
3232
__license__ = __doc__
3333
__project_url__ = 'http://winpython.github.io/'

winpython/piptree.py

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
This script provides functionality to inspect and display package dependencies
3+
This script provides functionality to inspect and display package dependencies
44
in a Python environment, including both downward and upward dependencies.
55
Requires Python 3.8+ due to importlib.metadata.
66
"""
@@ -11,20 +11,19 @@
1111
import platform
1212
import os
1313
from collections import OrderedDict
14+
from typing import Dict, List, Optional, Tuple, Union
1415
from pip._vendor.packaging.markers import Marker, InvalidMarker
1516
from importlib.metadata import Distribution, distributions
1617
from pathlib import Path
1718

18-
19-
def normalize(name):
19+
def normalize(name: str) -> str:
2020
"""Normalize package name according to PEP 503."""
2121
return re.sub(r"[-_.]+", "-", name).lower()
2222

23-
24-
def sum_up(text, max_length=144, stop_at=". "):
23+
def sum_up(text: str, max_length: int = 144, stop_at: str = ". ") -> str:
2524
"""
2625
Summarize text to fit within max_length characters, ending at the last complete sentence if possible.
27-
26+
2827
:param text: The text to summarize
2928
:param max_length: Maximum length for summary
3029
:param stop_at: String to stop summarization at
@@ -37,14 +36,14 @@ def sum_up(text, max_length=144, stop_at=". "):
3736
summary = summary[:max_length]
3837
return summary
3938

40-
class pipdata:
39+
class PipData:
4140
"""
4241
Wrapper around Distribution.discover() or Distribution.distributions() to manage package metadata.
4342
"""
4443

45-
def __init__(self, target=None):
46-
self.distro = {}
47-
self.raw = {}
44+
def __init__(self, target: Optional[str] = None):
45+
self.distro: Dict[str, Dict] = {}
46+
self.raw: Dict[str, Dict] = {}
4847
self.environment = self._get_environment()
4948

5049
search_path = target or sys.executable
@@ -57,18 +56,18 @@ def __init__(self, target=None):
5756
for package in packages:
5857
self._process_package(package)
5958

60-
# On a second pass, complement dependancies in reverse mode with 'wanted-per':
59+
# On a second pass, complement dependencies in reverse mode with 'wanted-per':
6160
self._populate_reverse_dependencies()
6261

63-
def _get_environment(self):
62+
def _get_environment(self) -> Dict[str, str]:
6463
"""
6564
Collect environment details for dependency evaluation.
66-
65+
6766
:return: Dictionary containing system and Python environment information
6867
"""
6968
return {
7069
"implementation_name": sys.implementation.name,
71-
"implementation_version": "{0.major}.{0.minor}.{0.micro}".format(sys.implementation.version),
70+
"implementation_version": f"{sys.implementation.version.major}.{sys.implementation.version.minor}.{sys.implementation.version.micro}",
7271
"os_name": os.name,
7372
"platform_machine": platform.machine(),
7473
"platform_release": platform.release(),
@@ -80,7 +79,7 @@ def _get_environment(self):
8079
"sys_platform": sys.platform,
8180
}
8281

83-
def _process_package(self, package):
82+
def _process_package(self, package: Distribution) -> None:
8483
"""Process package metadata and store it in the distro dictionary."""
8584
meta = package.metadata
8685
name = meta['Name']
@@ -99,7 +98,7 @@ def _process_package(self, package):
9998
"provided": {'': None} # Placeholder for extras provided by this package
10099
}
101100

102-
def _get_requires(self, package):
101+
def _get_requires(self, package: Distribution) -> List[Dict[str, str]]:
103102
"""Extract and normalize requirements for a package."""
104103
# requires = list of dict with 1 level need downward
105104
# req_key = package_key requires
@@ -136,7 +135,7 @@ def _get_requires(self, package):
136135
requires.append(req_add)
137136
return requires
138137

139-
def _get_provides(self, package):
138+
def _get_provides(self, package: Distribution) -> Dict[str, None]:
140139
"""Get the list of extras provided by this package."""
141140
provides = {'': None}
142141
if package.requires:
@@ -147,7 +146,7 @@ def _get_provides(self, package):
147146
provides[req_marker.split('extra == ')[1].translate(remove_list)] = None
148147
return provides
149148

150-
def _populate_reverse_dependencies(self):
149+
def _populate_reverse_dependencies(self) -> None:
151150
"""Add reverse dependencies to each package."""
152151
# - get all downward links in 'requires_dist' of each package
153152
# - feed the required packages 'reverse_dependencies' as a reverse dict of dict
@@ -167,11 +166,11 @@ def _populate_reverse_dependencies(self):
167166
if "req_marker" in requirement:
168167
want_add["req_marker"] = requirement["req_marker"]
169168
if 'extra == ' in requirement["req_marker"]:
170-
remove_list = {ord("'"):None, ord('"'):None}
169+
remove_list = {ord("'"): None, ord('"'): None}
171170
self.distro[requirement["req_key"]]["provided"][requirement["req_marker"].split('extra == ')[1].translate(remove_list)] = None
172171
self.distro[requirement["req_key"]]["reverse_dependencies"].append(want_add)
173172

174-
def _get_dependency_tree(self, package_name, extra="", version_req="", depth=20, path=None, verbose=False, upward=False):
173+
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]]:
175174
"""Recursive function to build dependency tree."""
176175
path = path or []
177176
extras = extra.split(",")
@@ -235,7 +234,7 @@ def _get_dependency_tree(self, package_name, extra="", version_req="", depth=20,
235234
ret_all.append(ret)
236235
return ret_all
237236

238-
def down(self, pp="", extra="", depth=20, indent=5, version_req="", verbose=False):
237+
def down(self, pp: str = "", extra: str = "", depth: int = 20, indent: int = 5, version_req: str = "", verbose: bool = False) -> str:
239238
"""Print the downward requirements for the package or all packages."""
240239
if pp == ".":
241240
results = [self.down(one_pp, extra, depth, indent, version_req, verbose=verbose) for one_pp in sorted(self.distro)]
@@ -255,9 +254,8 @@ def down(self, pp="", extra="", depth=20, indent=5, version_req="", verbose=Fals
255254
lines = [l for l in rawtext.split("\n") if len(l.strip()) > 2]
256255
return "\n".join(lines).replace('"', "")
257256

258-
def up(self, pp, extra="", depth=20, indent=5, version_req="", verbose=False):
257+
def up(self, pp: str, extra: str = "", depth: int = 20, indent: int = 5, version_req: str = "", verbose: bool = False) -> str:
259258
"""Print the upward needs for the package."""
260-
261259
if pp == ".":
262260
results = [self.up(one_pp, extra, depth, indent, version_req, verbose) for one_pp in sorted(self.distro)]
263261
return '\n'.join(filter(None, results))
@@ -274,22 +272,22 @@ def up(self, pp, extra="", depth=20, indent=5, version_req="", verbose=False):
274272

275273
rawtext = json.dumps(self._get_dependency_tree(pp, extra, version_req, depth, verbose=verbose, upward=True), indent=indent)
276274
lines = [l for l in rawtext.split("\n") if len(l.strip()) > 2]
277-
return '\n'.join(filter(None, lines)).replace('"', "")
275+
return "\n".join(filter(None, lines)).replace('"', "")
278276

279-
def description(self, pp):
277+
def description(self, pp: str) -> None:
280278
"""Return description of the package."""
281279
if pp in self.distro:
282280
return print("\n".join(self.distro[pp]["description"].split(r"\n")))
283281

284282
def summary(self, pp):
285283
"""Return summary of the package."""
286284
if pp in self.distro:
287-
return self.distro[pp]["summary"]
285+
return self.distro[pp]["summary"]
286+
return ""
288287

289-
def pip_list(self, full=False, max_length=144):
288+
def pip_list(self, full: bool = False, max_length: int = 144) -> List[Tuple[str, Union[str, Tuple[str, str]]]]:
290289
"""List installed packages similar to pip list."""
291290
if full:
292-
return [(p, self.distro[p]["version"], sum_up(self.distro[p]["summary"]), max_length) for p in sorted(self.distro)]
291+
return [(p, self.distro[p]["version"], sum_up(self.distro[p]["summary"], max_length)) for p in sorted(self.distro)]
293292
else:
294293
return [(p, sum_up(self.distro[p]["version"], max_length)) for p in sorted(self.distro)]
295-

winpython/wppm.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ def get_installed_packages(self, update=False):
148148
# Include package installed via pip (not via WPPM)
149149
wppm = []
150150
if str(Path(sys.executable).parent) == self.target:
151-
self.pip = piptree.pipdata()
151+
self.pip = piptree.PipData()
152152
else:
153-
self.pip = piptree.pipdata(utils.get_python_executable(self.target))
153+
self.pip = piptree.PipData(utils.get_python_executable(self.target))
154154
pip_list = self.pip.pip_list()
155155

156156
# create pip package list
@@ -571,25 +571,25 @@ def main(test=False):
571571
if args.registerWinPython and args.unregisterWinPython:
572572
raise RuntimeError("Incompatible arguments: --install and --uninstall")
573573
if args.pipdown:
574-
pip = piptree.pipdata(targetpython)
574+
pip = piptree.PipData(targetpython)
575575
pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
576576
print(pip.down(pack, extra, args.levels, verbose=args.verbose))
577577
sys.exit()
578578
elif args.pipup:
579-
pip = piptree.pipdata(targetpython)
579+
pip = piptree.PipData(targetpython)
580580
pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
581581
print(pip.up(pack, extra, args.levels, verbose=args.verbose))
582582
sys.exit()
583583
elif args.list:
584-
pip = piptree.pipdata(targetpython)
584+
pip = piptree.PipData(targetpython)
585585
todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0])) ]
586586
titles = [['Package', 'Version', 'Summary'],['_' * max(x, 6) for x in utils.columns_width(todo)]]
587587
listed = utils.formatted_list(titles + todo, max_width=70)
588588
for p in listed:
589589
print(*p)
590590
sys.exit()
591591
elif args.all:
592-
pip = piptree.pipdata(targetpython)
592+
pip = piptree.PipData(targetpython)
593593
todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0])) ]
594594
for l in todo:
595595
# print(pip.distro[l[0]])

0 commit comments

Comments
 (0)