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.

Easy Python 3.9+ Aug 9, 2026 Automation & scripting 14 views 0 copies

Python code

45 lines
Python 3.9+
import 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

stdout
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

  1. Use `requirements-parser` library to parse and modify requirement files more robustly.
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Automation & scripting

Related tutorials and quizzes for this topic.