Skip to main content

python/pip_compile

1#!/usr/bin/env python3
2"""
3Compile any `requirements.in` files into a list of exact versions
4in `requirements.txt`.
6* If you pass the `--upgrade` flag, it will upgrade all the requirements
7 to the latest version.
8* If you pass `--upgrade-package <package>`, it will upgrade only that
9 specific package.
11"""
13import argparse
14import shlex
15import subprocess
16from pathlib import Path
19def compile_requirements_file(
20 in_file: str, *, upgrade: bool, upgrade_packages: list[str] | None, no_cache: bool
21) -> None:
22 """
23 Compile a single requirements file.
24 """
25 assert in_file.endswith(".in")
26 txt_file = in_file.replace(".in", ".txt")
27 assert in_file != txt_file
29 # If this `.in` file doesn't exist.
30 if not Path(in_file).exists():
31 return
33 # Construct the `uv pip compile` command.
34 cmd = [
35 "uv",
36 "pip",
37 "compile",
38 in_file,
39 f"--output-file={txt_file}",
40 #
41 # Exclude dependencies which are less than 7 days old; this is
42 # a mitigation against supply chain attacks and installing
43 # recently-published malicious code on my computer.
44 "--exclude-newer=P7D",
45 #
46 # Allow installing new versions of my own packages
47 "--exclude-newer-package",
48 "alexwlchan-chives=false",
49 ]
51 if upgrade:
52 cmd.append("--upgrade")
54 if upgrade_packages:
55 for pkg in upgrade_packages:
56 cmd.extend(["--upgrade-package", pkg])
57 cmd.extend(["--exclude-newer-package", f"{pkg}=false"])
59 if no_cache:
60 cmd.append("--no-cache")
62 # Actually run the command, and print a debug entry for it.
63 #
64 # `uv pip compile` prints the generated `requirements.txt` file to
65 # stdout, so we pipe that to /dev/null -- we're writing it to a file.
66 subprocess.check_call(
67 ["/Users/alexwlchan/repos/scripts/debug/print_info", f"-> {shlex.join(cmd)}"]
68 )
69 subprocess.check_call(cmd, stdout=subprocess.DEVNULL)
72def parse_args() -> argparse.Namespace:
73 parser = argparse.ArgumentParser(
74 description="Compile requirements.in files into requirements.txt"
75 )
76 parser.add_argument(
77 "--upgrade",
78 action="store_true",
79 help="Upgrade all packages to their latest versions",
80 )
81 parser.add_argument(
82 "--upgrade-package",
83 action="append",
84 dest="upgrade_packages",
85 metavar="PACKAGE",
86 help="Upgrade a specific package (can be passed multiple times)",
87 )
88 parser.add_argument(
89 "--no-cache",
90 action="store_true",
91 help="Disable caching in uv",
92 )
93 return parser.parse_args()
96if __name__ == "__main__":
97 args = parse_args()
99 for f in ["requirements.in", "dev_requirements.in"]:
100 compile_requirements_file(
101 f,
102 upgrade=args.upgrade,
103 upgrade_packages=args.upgrade_packages,
104 no_cache=args.no_cache,
105 )