Skip to content

Commit fbc603e

Browse files
authored
Merge pull request #1620 from stonebig/master
at last... a glitch-less wheelhouse report
2 parents f299e10 + de0423c commit fbc603e

File tree

4 files changed

+70
-38
lines changed

4 files changed

+70
-38
lines changed

winpython/piptree.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pip._vendor.packaging.markers import Marker
1818
from importlib.metadata import Distribution, distributions
1919
from pathlib import Path
20+
from winpython import utils
2021

2122
logging.basicConfig(level=logging.INFO)
2223
logger = logging.getLogger(__name__)
@@ -25,15 +26,6 @@ class PipDataError(Exception):
2526
"""Custom exception for PipData related errors."""
2627
pass
2728

28-
def sum_up(text: str, max_length: int = 144, stop_at: str = ". ") -> str:
29-
"""Summarize text to fit within max_length, ending at last complete sentence."""
30-
summary = (text + os.linesep).splitlines()[0]
31-
if len(summary) <= max_length:
32-
return summary
33-
if stop_at and stop_at in summary[:max_length]:
34-
return summary[:summary.rfind(stop_at, 0, max_length)] + stop_at.rstrip()
35-
return summary[:max_length].rstrip()
36-
3729
class PipData:
3830
"""Manages package metadata and dependency relationships in a Python environment."""
3931

@@ -287,5 +279,5 @@ def pip_list(self, full: bool = False, max_length: int = 144) -> List[Tuple[str,
287279
"""
288280
pkgs = sorted(self.distro.items())
289281
if full:
290-
return [(p, d["version"], sum_up(d["summary"], max_length)) for p, d in pkgs]
282+
return [(p, d["version"], utils.sum_up(d["summary"], max_length)) for p, d in pkgs]
291283
return [(p, d["version"]) for p, d in pkgs]

winpython/utils.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,14 @@ def onerror(function, path, excinfo):
7171
else:
7272
raise
7373

74-
def getFileProperties(fname):
75-
"""Read all properties of the given file return them as a dictionary."""
76-
import win32api
77-
prop_names = ('ProductName', 'ProductVersion', 'FileDescription', 'FileVersion')
78-
props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}
79-
80-
try:
81-
fixed_info = win32api.GetFileVersionInfo(fname, '\\')
82-
props['FixedFileInfo'] = fixed_info
83-
props['FileVersion'] = "{}.{}.{}.{}".format(
84-
fixed_info['FileVersionMS'] // 65536,
85-
fixed_info['FileVersionMS'] % 65536,
86-
fixed_info['FileVersionLS'] // 65536,
87-
fixed_info['FileVersionLS'] % 65536
88-
)
89-
lang, codepage = win32api.GetFileVersionInfo(fname, '\\VarFileInfo\\Translation')[0]
90-
props['StringFileInfo'] = {
91-
prop_name: win32api.GetFileVersionInfo(fname, f'\\StringFileInfo\\{lang:04X}{codepage:04X}\\{prop_name}')
92-
for prop_name in prop_names
93-
}
94-
except:
95-
pass
96-
97-
return props
74+
def sum_up(text: str, max_length: int = 144, stop_at: str = ". ") -> str:
75+
"""Summarize text to fit within max_length, ending at last complete sentence."""
76+
summary = (text + os.linesep).splitlines()[0].strip()
77+
if len(summary) <= max_length:
78+
return summary
79+
if stop_at and stop_at in summary[:max_length]:
80+
return summary[:summary.rfind(stop_at, 0, max_length)] + stop_at.strip()
81+
return summary[:max_length].strip()
9882

9983
def get_special_folder_path(path_name):
10084
"""Return special folder path."""

winpython/wheelhouse.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22
"""
33
WheelHouse.py - manage WinPython local WheelHouse.
44
"""
5-
5+
import os
6+
import re
7+
import tarfile
8+
import zipfile
69
import sys
710
from pathlib import Path
811
from collections import defaultdict
912
import shutil
1013
import subprocess
1114
from typing import Dict, List, Optional, Tuple
15+
from email import message_from_bytes
16+
from email.parser import BytesParser
17+
from email.policy import default
18+
from . import utils
19+
20+
from packaging.utils import canonicalize_name
1221

1322
# Use tomllib if available (Python 3.11+), otherwise fall back to tomli
1423
try:
@@ -183,6 +192,53 @@ def get_pylock_wheels(wheelhouse: Path, lockfile: Path, wheelorigin: Optional[Pa
183192
else:
184193
print(f"\n\n*** We can't install {filename} ! ***\n\n")
185194

195+
def extract_metadata_from_wheel(filepath: Path) -> Optional[Tuple[str, str, str]]:
196+
"get metadata from a wheel package"
197+
with zipfile.ZipFile(filepath, 'r') as z:
198+
# Locate *.dist-info/METADATA file inside but not in a vendored directory (flit-core)
199+
for name in z.namelist():
200+
if name.endswith(r'.dist-info/METADATA') and name.split("/")[1] == "METADATA":
201+
with z.open(name) as meta_file:
202+
metadata = BytesParser(policy=default).parse(meta_file)
203+
name = canonicalize_name(str(metadata.get('Name', 'unknown'))) # Avoid Head type
204+
version = str(metadata.get('Version', 'unknown'))
205+
summary = utils.sum_up(str(metadata.get('Summary', '')))
206+
return name, version, summary
207+
return None
208+
209+
def extract_metadata_from_sdist(filepath: Path) -> Optional[Tuple[str, str, str]]:
210+
"get metadata from a tar.gz or .zip package"
211+
open_func = tarfile.open if filepath.suffixes[-2:] == ['.tar', '.gz'] else zipfile.ZipFile
212+
with open_func(filepath, 'r') as archive:
213+
namelist = archive.getnames() if isinstance(archive, tarfile.TarFile) else archive.namelist()
214+
for name in namelist:
215+
if name.endswith('PKG-INFO'):
216+
content = archive.extractfile(name).read() if isinstance(archive, tarfile.TarFile) else archive.open(name).read()
217+
metadata = message_from_bytes(content)
218+
name = canonicalize_name(str(metadata.get('Name', 'unknown'))) # Avoid Head type
219+
version = str(metadata.get('Version', 'unknown'))
220+
summary = utils.sum_up(str(metadata.get('Summary', '')))
221+
return name, version, summary
222+
return None
223+
224+
def list_packages_with_metadata(directory: str) -> List[Tuple[str, str, str]]:
225+
"get metadata from a Wheelhouse directory"
226+
results = []
227+
for file in os.listdir(directory):
228+
path = Path(directory) / file
229+
try:
230+
if path.suffix == '.whl':
231+
meta = extract_metadata_from_wheel(path)
232+
elif path.suffix == '.zip' or path.name.endswith('.tar.gz'):
233+
meta = extract_metadata_from_sdist(path)
234+
else:
235+
continue
236+
if meta:
237+
results.append(meta)
238+
except OSError: #Exception as e: # need to see it
239+
print(f"Skipping {file}: {e}")
240+
return results
241+
186242
def main() -> None:
187243
"""Main entry point for the script."""
188244
if len(sys.argv) != 2:

winpython/wppm.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
from argparse import ArgumentParser, RawTextHelpFormatter
1717
from winpython import utils, piptree, associate
1818
from winpython import wheelhouse as wh
19+
from operator import itemgetter
1920
# Workaround for installing PyVISA on Windows from source:
2021
os.environ["HOME"] = os.environ["USERPROFILE"]
2122

2223
class Package:
2324
"""Standardize a Package from filename or pip list."""
2425
def __init__(self, fname: str, suggested_summary: str = None):
2526
self.fname = fname
26-
self.description = piptree.sum_up(suggested_summary) if suggested_summary else ""
27+
self.description = (utils.sum_up(suggested_summary) if suggested_summary else "").strip()
2728
self.name, self.version = fname, '?.?.?'
2829
if fname.lower().endswith((".zip", ".tar.gz", ".whl")):
2930
bname = Path(self.fname).name # e.g., "sqlite_bro-1.0.0..."
@@ -81,8 +82,7 @@ def get_wheelhouse_packages_markdown(self) -> str:
8182
if wheeldir.is_dir():
8283
package_lines = [
8384
f"[{name}](https://pypi.org/project/{name}) | {version} | {summary}"
84-
for name, version, summary in wh.list_packages_with_metadata(str(wheeldir))
85-
#for pkg in sorted(wh.list_packages_with_metadata(str(wheeldir)), key=lambda p: p.name.lower())
85+
for name, version, summary in sorted(wh.list_packages_with_metadata(str(wheeldir)), key=itemgetter(0 , 1)) # lambda p: p[0].lower())
8686
]
8787
return "\n".join(package_lines)
8888
return ""

0 commit comments

Comments
 (0)