# SPDX-FileCopyrightText: 2026 Bart Broere # SPDX-License-Identifier: GPL-3.0-or-later """Make sure every Python distribution in an environment ships its license text. Most wheels include their license files in ``*.dist-info``. For the ones that do not, copy its license files to ``/-/``, taking them from ``//`` when present and otherwise from the source distribution on PyPI. Run with the interpreter of the environment to inspect: ``/opt/pgadmin/bin/python collect-python-licenses.py ``. Only the environment's own site-packages are inspected, not the system packages a ``--system-site-packages`` venv can see: those come with Debian copyright files. """ import io import json import re import sys import sysconfig import tarfile import urllib.request import zipfile from importlib import metadata from pathlib import Path LICENSE_FILE = re.compile(r"(^|/)(LICEN[CS]E|COPYING|NOTICE|AUTHORS)[^/]*$", re.IGNORECASE) def has_license_file(dist): return any(LICENSE_FILE.search(str(f)) for f in dist.files or []) def sdist_members(name, version): with urllib.request.urlopen(f"https://pypi.org/pypi/{name}/{version}/json", timeout=60) as r: urls = json.load(r)["urls"] sdist = next((u for u in urls if u["packagetype"] == "sdist"), None) if sdist is None: return {} with urllib.request.urlopen(sdist["url"], timeout=120) as r: data = r.read() found = {} if sdist["filename"].endswith(".zip"): with zipfile.ZipFile(io.BytesIO(data)) as z: for n in z.namelist(): if n.count("/") == 1 and LICENSE_FILE.search(n): found[n.split("/", 1)[1]] = z.read(n) else: with tarfile.open(fileobj=io.BytesIO(data)) as t: for m in t.getmembers(): if m.isfile() and m.name.count("/") == 1 and LICENSE_FILE.search(m.name): found[m.name.split("/", 1)[1]] = t.extractfile(m).read() return found def vendored_files(vendored, name): directory = vendored / name if not directory.is_dir(): return {} return {f.name: f.read_bytes() for f in directory.iterdir() if f.is_file()} def main(output, vendored): output, vendored = Path(output), Path(vendored) missing = [] site_packages = sysconfig.get_paths()["purelib"] for dist in sorted(metadata.distributions(path=[site_packages]), key=lambda d: d.metadata["Name"].lower()): if has_license_file(dist): continue name, version = dist.metadata["Name"], dist.version files = vendored_files(vendored, name) if not files: try: files = sdist_members(name, version) except Exception as e: # noqa: BLE001 - report and carry on print(f"{name} {version}: could not fetch sdist: {e}", file=sys.stderr) if not files: missing.append(f"{name} {version}") continue target = output / f"{name}-{version}" target.mkdir(parents=True, exist_ok=True) for filename, content in files.items(): (target / filename).write_bytes(content) print(f"{name} {version}: added {', '.join(files)}") for item in missing: print(f"WARNING: no license file found for {item}", file=sys.stderr) if __name__ == "__main__": main(sys.argv[1], sys.argv[2])