From bdb7288f9d1b9ea32c3a735310bf52b36a48ec6e Mon Sep 17 00:00:00 2001 From: stonebig Date: Tue, 3 Jun 2025 21:26:09 +0200 Subject: [PATCH] -md markdown and -ws . options for comfort --- winpython/__init__.py | 2 +- winpython/packagemetadata.py | 14 ++----- winpython/wheelhouse.py | 81 ++---------------------------------- winpython/wppm.py | 24 +++++++---- 4 files changed, 24 insertions(+), 97 deletions(-) diff --git a/winpython/__init__.py b/winpython/__init__.py index d2fdc391..468eed57 100644 --- a/winpython/__init__.py +++ b/winpython/__init__.py @@ -28,6 +28,6 @@ OTHER DEALINGS IN THE SOFTWARE. """ -__version__ = '16.3.20250530' +__version__ = '16.4.20250603' __license__ = __doc__ __project_url__ = 'http://winpython.github.io/' diff --git a/winpython/packagemetadata.py b/winpython/packagemetadata.py index 469925d6..4e2a4989 100644 --- a/winpython/packagemetadata.py +++ b/winpython/packagemetadata.py @@ -14,7 +14,6 @@ from typing import Dict, List, Optional, Tuple from . import utils -from packaging.utils import canonicalize_name, parse_wheel_filename, parse_sdist_filename # --- Abstract metadata accessor --- class PackageMetadata: @@ -64,7 +63,7 @@ def extract_metadata_from_wheel(path: str) -> PackageMetadata: with zf.open(name) as f: # Parse metadata (simple parsing for Name, Version, Requires-Dist) return parse_metadata_file(f.read().decode()) - raise ValueError(f"No METADATA found in {path}") + raise ValueError(f"No METADATA found in {path}") def extract_metadata_from_sdist(path: str) -> PackageMetadata: import tarfile @@ -78,6 +77,8 @@ def extract_metadata_from_sdist(path: str) -> PackageMetadata: def parse_metadata_file(txt: str) -> PackageMetadata: name = version = summary = description = "" requires = [] + description_lines = [] + in_description = False for line in txt.splitlines(): if line.startswith('Name: '): name = line[6:].strip() @@ -86,15 +87,9 @@ def parse_metadata_file(txt: str) -> PackageMetadata: elif line.startswith('Summary: '): summary = description = line[9:].strip() elif line.startswith('Requires-Dist: '): - requires.append(line[14:].strip()) + requires.append(line[14:].strip()) return PackageMetadata(name, version, requires, summary, description, {'Name': name, "Summary": summary, "Description": description}) -# --- Main dependency tree logic --- - -def build_dependency_tree(pkgs: List[PackageMetadata]): - # Existing logic, but using our PackageMetadata objects - pass - def main(): if len(sys.argv) > 1: # Directory mode @@ -103,7 +98,6 @@ def main(): else: # Installed packages mode pkgs = get_installed_metadata() - build_dependency_tree(pkgs) if __name__ == "__main__": main() \ No newline at end of file diff --git a/winpython/wheelhouse.py b/winpython/wheelhouse.py index aa095af4..f500dc31 100644 --- a/winpython/wheelhouse.py +++ b/winpython/wheelhouse.py @@ -12,9 +12,7 @@ import shutil import subprocess from typing import Dict, List, Optional, Tuple -from email import message_from_bytes -from email.parser import BytesParser -from email.policy import default +from . import packagemetadata as pm from . import utils from packaging.utils import canonicalize_name, parse_wheel_filename, parse_sdist_filename @@ -192,83 +190,10 @@ def get_pylock_wheels(wheelhouse: Path, lockfile: Path, wheelorigin: Optional[Pa else: print(f"\n\n*** We can't install {filename} ! ***\n\n") -def extract_metadata_from_wheel(filepath: Path) -> Optional[Tuple[str, str, str]]: - "Extract package metadata from a .whl file and validate it matches the filename" - wheel_name = filepath.name - try: - name, version, build, tags = parse_wheel_filename(wheel_name) - filename_name = canonicalize_name(name) - filename_version = str(version) - except Exception as e: - print(f"❌ Could not parse filename: {wheel_name}", e) - return None - - with zipfile.ZipFile(filepath, 'r') as z: - # Locate *.dist-info/METADATA file inside but not in a vendored directory (flit-core) - for name in z.namelist(): - if name.endswith(r'.dist-info/METADATA') and name.split("/")[1] == "METADATA": - with z.open(name) as meta_file: - metadata = BytesParser(policy=default).parse(meta_file) - meta_name = canonicalize_name(str(metadata.get('Name', 'unknown'))) # Avoid Head type - meta_version = str(metadata.get('Version', 'unknown')) - summary = utils.sum_up(str(metadata.get('Summary', ''))) - # Assert consistency - if meta_name != filename_name or meta_version != filename_version: - print(f"⚠️ Mismatch in {wheel_name}: filename says {filename_name}=={filename_version}, " - f"but METADATA says {meta_name}=={meta_version}") - return None - return meta_name, meta_version , summary - return None - -def extract_metadata_from_sdist(filepath: Path) -> Optional[Tuple[str, str, str]]: - "get metadata from a tar.gz or .zip package" - open_func = tarfile.open if filepath.suffixes[-2:] == ['.tar', '.gz'] else zipfile.ZipFile - sdist_name = filepath.name - try: - name, version = parse_sdist_filename(sdist_name) - filename_name = canonicalize_name(name) - filename_version = str(version) - except Exception as e: - print(f"❌ Could not parse filename: {sdist_name}", e) - return None - - with open_func(filepath, 'r') as archive: - namelist = archive.getnames() if isinstance(archive, tarfile.TarFile) else archive.namelist() - for name in namelist: - if name.endswith('PKG-INFO'): - if isinstance(archive, tarfile.TarFile): - content = archive.extractfile(name) - else: - content = archive.open(name) - if content: - metadata = BytesParser(policy=default).parse(content) - meta_name = canonicalize_name(str(metadata.get('Name', 'unknown'))) # Avoid Head type - meta_version = str(metadata.get('Version', 'unknown')) - summary = utils.sum_up(str(metadata.get('Summary', ''))) - # Assert consistency - if meta_name != filename_name or meta_version != filename_version: - print(f"⚠️ Mismatch in {sdist_name}: filename says {filename_name}=={filename_version}, " - f"but METADATA says {meta_name}=={meta_version}") - return None - return meta_name, meta_version, summary - return None - def list_packages_with_metadata(directory: str) -> List[Tuple[str, str, str]]: "get metadata from a Wheelhouse directory" - results = [] - for file in os.listdir(directory): - path = Path(directory) / file - try: - if path.suffix == '.whl': - meta = extract_metadata_from_wheel(path) - elif path.suffix == '.zip' or path.name.endswith('.tar.gz'): - meta = extract_metadata_from_sdist(path) - else: - continue - if meta: - results.append(meta) - except OSError: #Exception as e: # need to see it - print(f"Skipping {file}: {e}") + packages = pm.get_directory_metadata(directory) + results = [ (p.name, p.version, p.summary) for p in packages] return results def main() -> None: diff --git a/winpython/wppm.py b/winpython/wppm.py index 5b83988c..01ed2c83 100644 --- a/winpython/wppm.py +++ b/winpython/wppm.py @@ -274,14 +274,15 @@ def main(test=False): # parser.add_argument( "--unregister_forall", action="store_true", help="un-Register distribution for all users") parser.add_argument("--fix", action="store_true", help="make WinPython fix") parser.add_argument("--movable", action="store_true", help="make WinPython movable") - parser.add_argument("-ws", dest="wheelsource", default=None, type=str, help="location to search wheels: wppm pylock.toml -ws source_of_wheels") - parser.add_argument("-wd", dest="wheeldrain" , default=None, type=str, help="location of found wheels: wppm pylock.toml -wd destination_of_wheels") - parser.add_argument("-ls", "--list", action="store_true", help="list installed packages matching the given [optional] package expression: wppm -ls, wppm -ls pand") - parser.add_argument("-lsa", dest="all", action="store_true",help=f"list details of package names matching given regular expression: wppm -lsa pandas -l1") - parser.add_argument("-p",dest="pipdown",action="store_true",help="show Package dependencies of the given package[option]: wppm -p pandas[test]") - parser.add_argument("-r", dest="pipup", action="store_true", help=f"show Reverse dependancies of the given package[option]: wppm -r pytest[test]") - parser.add_argument("-l", "--levels", type=int, default=2, help="show 'LEVELS' levels of dependencies (with -p, -r), default is 2: wppm -p pandas -l1") - parser.add_argument("-t", "--target", default=sys.prefix, help=f'path to target Python distribution (default: "{sys.prefix}")') + parser.add_argument("-ws", dest="wheelsource", default=None, type=str, help="wheels location, '.' = WheelHouse): wppm pylock.toml -ws source_of_wheels, wppm -ls -ws .") + parser.add_argument("-wd", dest="wheeldrain" , default=None, type=str, help="wheels destination: wppm pylock.toml -wd destination_of_wheels") + parser.add_argument("-ls", "--list", action="store_true", help="list installed packages matching [optional] expression: wppm -ls, wppm -ls pand") + parser.add_argument("-lsa", dest="all", action="store_true",help=f"list details of packages matching [optional] expression: wppm -lsa pandas -l1") + parser.add_argument("-md", dest="markdown", action="store_true",help=f"markdown summary if the installation") + parser.add_argument("-p",dest="pipdown",action="store_true",help="show Package dependencies of the given package[option], [.]=all: wppm -p pandas[.]") + parser.add_argument("-r", dest="pipup", action="store_true", help=f"show Reverse wppmdependancies of the given package[option]: wppm -r pytest[test]") + parser.add_argument("-l", dest="levels", type=int, default=2, help="show 'LEVELS' levels of dependencies (with -p, -r), default is 2: wppm -p pandas -l1") + parser.add_argument("-t", dest="target", default=sys.prefix, help=f'path to target Python distribution (default: "{sys.prefix}")') parser.add_argument("-i", "--install", action="store_true", help="install a given package wheel or pylock file (use pip for more features)") parser.add_argument("-u", "--uninstall", action="store_true", help="uninstall package (use pip for more features)") @@ -290,6 +291,10 @@ def main(test=False): targetpython = None if args.target and args.target != sys.prefix: targetpython = args.target if args.target.lower().endswith('.exe') else str(Path(args.target) / 'python.exe') + if args.wheelsource == ".": # play in default WheelHouse + if utils.is_python_distribution(args.target): + dist = Distribution(args.target) + args.wheelsource = dist.wheelhouse / 'included.wheels' if args.install and args.uninstall: raise RuntimeError("Incompatible arguments: --install and --uninstall") if args.registerWinPython and args.unregisterWinPython: @@ -360,6 +365,9 @@ def main(test=False): if args.movable: p = subprocess.Popen(["start", "cmd", "/k",dist.python_exe, "-c" , cmd_mov], shell = True, cwd=dist.target) sys.exit() + if args.markdown: + print(dist.generate_package_index_markdown()) + sys.exit() if not args.install and not args.uninstall and args.fname.endswith(".toml"): args.install = True # for Drag & Drop of .toml (and not wheel) if args.fname == "" or (not args.install and not args.uninstall):