#!/usr/bin/env python3 """ Compile any `requirements.in` files into a list of exact versions in `requirements.txt`. * If you pass the `--upgrade` flag, it will upgrade all the requirements to the latest version. * If you pass `--upgrade-package `, it will upgrade only that specific package. """ import argparse import shlex import subprocess from pathlib import Path def compile_requirements_file( in_file: str, *, upgrade: bool, upgrade_packages: list[str] | None, no_cache: bool ) -> None: """ Compile a single requirements file. """ assert in_file.endswith(".in") txt_file = in_file.replace(".in", ".txt") assert in_file != txt_file # If this `.in` file doesn't exist. if not Path(in_file).exists(): return # Construct the `uv pip compile` command. cmd = [ "uv", "pip", "compile", in_file, f"--output-file={txt_file}", # # Exclude dependencies which are less than 7 days old; this is # a mitigation against supply chain attacks and installing # recently-published malicious code on my computer. "--exclude-newer=P7D", # # Allow installing new versions of my own packages "--exclude-newer-package", "alexwlchan-chives=false", ] if upgrade: cmd.append("--upgrade") if upgrade_packages: for pkg in upgrade_packages: cmd.extend(["--upgrade-package", pkg]) cmd.extend(["--exclude-newer-package", f"{pkg}=false"]) if no_cache: cmd.append("--no-cache") # Actually run the command, and print a debug entry for it. # # `uv pip compile` prints the generated `requirements.txt` file to # stdout, so we pipe that to /dev/null -- we're writing it to a file. subprocess.check_call( ["/Users/alexwlchan/repos/scripts/debug/print_info", f"-> {shlex.join(cmd)}"] ) subprocess.check_call(cmd, stdout=subprocess.DEVNULL) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Compile requirements.in files into requirements.txt" ) parser.add_argument( "--upgrade", action="store_true", help="Upgrade all packages to their latest versions", ) parser.add_argument( "--upgrade-package", action="append", dest="upgrade_packages", metavar="PACKAGE", help="Upgrade a specific package (can be passed multiple times)", ) parser.add_argument( "--no-cache", action="store_true", help="Disable caching in uv", ) return parser.parse_args() if __name__ == "__main__": args = parse_args() for f in ["requirements.in", "dev_requirements.in"]: compile_requirements_file( f, upgrade=args.upgrade, upgrade_packages=args.upgrade_packages, no_cache=args.no_cache, )