Pin Python package versions in requirements.txt
Pin package versions in requirements.txt-style text by adding ==version when no specifier is present, while preserving existing version constraints and comments.
Python code
45 linesimport re
from pathlib import Path
def pin_versions(requirements_text: str) -> str:
"""
Pin package versions in requirements.txt-style text.
Adds ==version if no version specifier is present.
Keeps existing specifiers (>=, <=, ~=, etc.) unchanged.
"""
lines = requirements_text.strip().splitlines()
pinned_lines = []
for line in lines:
line = line.strip()
if not line or line.startswith("#"):
pinned_lines.append(line)
continue
# Skip lines with extras, URLs, or already pinned versions
if "==" in line or any(op in line for op in [">=", "<=", "~=", ">", "<"]):
pinned_lines.append(line)
continue
# Extract package name and version
match = re.match(r"^([A-Za-z0-9_.\-]+)\s*(?:\[.*?\])?\s*(.*)$", line)
if match:
package = match.group(1)
pinned_lines.append(f"{package}=={match.group(2) or '0.0.0'}")
else:
pinned_lines.append(line)
return "\n".join(pinned_lines)
if __name__ == "__main__":
requirements = """
requests
flask>=2.0
numpy==1.24.3
pandas
# comment line
"""
result = pin_versions(requirements)
print(result)
Output
requests==0.0.0
flask>=2.0
numpy==1.24.3
pandas==0.0.0
# comment line
How it works
The function splits input text into lines and processes each non-comment, non-empty line. A regex captures the package name and any optional extras, ignoring version specifiers already present (>=, <=, ~=, etc.) to avoid over-pinning. If a package has no version, it appends ==0.0.0 as a placeholder. Comment lines and blank lines are preserved untouched, and the result is joined back into a single string. This approach keeps existing constraints intact while ensuring every dependency has an explicit pin for reproducible builds.
Common mistakes
- Using ==0.0.0 as a placeholder instead of a real version, which may fail installation.
- Not accounting for package names with hyphens or underscores in the regex.
- Accidentally modifying comment lines or lines with version specifiers.
- Forgetting to trim whitespace from lines, leading to inconsistent output.
Variations
- Use `requirements-parser` library to parse and modify requirement files more robustly.
- Instead of placeholder 0.0.0, query PyPI via `pip index` to fetch current versions automatically.
Real-world use cases
- Freezing dependency versions before a production deployment for consistency.
- Automating requirements.txt cleanup in CI pipelines to enforce pinned versions.
- Migrating a legacy project to fully pinned dependencies for security audits.
Sponsored
More from Automation & scripting
- Aggregate Log Errors Count by Hour in Python easy
- Automate Tweeting New Blog Posts in Python easy
- Automatically Clean Temporary Files from Applications Using Python medium
- Automatically Download the Latest Software Release from GitHub with Python medium
- Automatically Generate Charts from CSV Files with One Command medium
- Automatically Generate Hardware Inventory Reports in Python easy
Keep learning
Related tutorials and quizzes for this topic.