Skip to content

Instantly share code, notes, and snippets.

@dhellmann
Created September 23, 2025 21:08
Show Gist options
  • Save dhellmann/4b2bdc1012edce6870845641f87b10ae to your computer and use it in GitHub Desktop.
Save dhellmann/4b2bdc1012edce6870845641f87b10ae to your computer and use it in GitHub Desktop.

Update lint-requirements command logic

Exported on 9/23/2025 at 17:08:23 EDT from Cursor (1.6.45)


User

Update the lint-requirements command so that it resolves the valid input requirements to ensure we can find a matching version of the package. Use the same logic that is used in the bootstrap command to resolve the input toplevel requirements.


Cursor

I'll help you update the lint-requirements command to resolve valid input requirements using the same logic as the bootstrap command. Let me first examine the current implementation and understand how the bootstrap command handles requirement resolution.

Now let me examine the bootstrapper module to understand how the resolution works:

Let me get the complete _get_requirements_from_args function and also understand the context module:

Now let me examine the existing lint command to see if it uses WorkContext:

Now I understand the pattern. Let me analyze the resolution logic more closely and then implement the solution:

Now I have a good understanding of how the resolution works. Let me implement the solution:

import logging
import pathlib
import sys

import click
from packaging.requirements import InvalidRequirement, Requirement

from fromager import bootstrapper, context, progress, requirements_file
from fromager.log import requirement_ctxvar
from fromager.requirements_file import RequirementType

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "input_files_path",
    nargs=-1,
    required=True,
    type=click.Path(exists=False, path_type=pathlib.Path),
)
def lint_requirements(input_files_path: list[pathlib.Path]) -> None:
    """
    Command to lint the constraints.txt and requirements.txt files
    This command takes a single wildcard path string for constraints.txt and requirements.txt.
    It checks the formatting of these files and reports issues if found. Files with names that
    end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
    to contain extra dependencies.
    """

    if len(input_files_path) == 0:
        logger.error("no constraints.txt or requirements.txt found in given paths")
        sys.exit(1)

    flag = True

    for path in input_files_path:
        parsed_lines = requirements_file.parse_requirements_file(path)
        unique_entries: dict[str, Requirement] = {}
        for line in parsed_lines:
            try:
                requirement = Requirement(line)
                if requirement.name in unique_entries:
                    raise InvalidRequirement(
                        f"Duplicate entry, first found: {unique_entries[requirement.name]}"
                    )
                unique_entries[requirement.name] = requirement
                if requirement.extras and path.name.endswith("constraints.txt"):
                    raise InvalidRequirement(
                        "Constraints files cannot contain extra dependencies"
                    )
            except InvalidRequirement as err:
                logger.error(f"{path}: {line}: {err}")
                flag = False

    if not flag:
        sys.exit(1)
import logging
import pathlib
import sys

import click
from packaging.requirements import InvalidRequirement, Requirement

from fromager import bootstrapper, context, progress, requirements_file
from fromager.log import requirement_ctxvar
from fromager.requirements_file import RequirementType

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "input_files_path",
    nargs=-1,
    required=True,
    type=click.Path(exists=False, path_type=pathlib.Path),
)
@click.pass_obj
def lint_requirements(wkctx: context.WorkContext, input_files_path: list[pathlib.Path]) -> None:
    """
    Command to lint the constraints.txt and requirements.txt files
    This command takes a single wildcard path string for constraints.txt and requirements.txt.
    It checks the formatting of these files and reports issues if found. Files with names that
    end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
    to contain extra dependencies.
    """

    if len(input_files_path) == 0:
        logger.error("no constraints.txt or requirements.txt found in given paths")
        sys.exit(1)

    flag = True

    for path in input_files_path:
        parsed_lines = requirements_file.parse_requirements_file(path)
        unique_entries: dict[str, Requirement] = {}
        for line in parsed_lines:
            try:
                requirement = Requirement(line)
                if requirement.name in unique_entries:
                    raise InvalidRequirement(
                        f"Duplicate entry, first found: {unique_entries[requirement.name]}"
                    )
                unique_entries[requirement.name] = requirement
                if requirement.extras and path.name.endswith("constraints.txt"):
                    raise InvalidRequirement(
                        "Constraints files cannot contain extra dependencies"
                    )
            except InvalidRequirement as err:
                logger.error(f"{path}: {line}: {err}")
                flag = False

    if not flag:
        sys.exit(1)
import logging
import pathlib
import sys

import click
from packaging.requirements import InvalidRequirement, Requirement

from fromager import bootstrapper, context, progress, requirements_file
from fromager.log import requirement_ctxvar
from fromager.requirements_file import RequirementType

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "input_files_path",
    nargs=-1,
    required=True,
    type=click.Path(exists=False, path_type=pathlib.Path),
)
@click.pass_obj
def lint_requirements(wkctx: context.WorkContext, input_files_path: list[pathlib.Path]) -> None:
    """
    Command to lint the constraints.txt and requirements.txt files
    This command takes a single wildcard path string for constraints.txt and requirements.txt.
    It checks the formatting of these files and reports issues if found. Files with names that
    end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
    to contain extra dependencies. Additionally, it resolves valid input requirements to ensure
    we can find a matching version of each package.
    """

    if len(input_files_path) == 0:
        logger.error("no constraints.txt or requirements.txt found in given paths")
        sys.exit(1)

    flag = True

    # Create bootstrapper for requirement resolution
    bt = bootstrapper.Bootstrapper(
        ctx=wkctx,
        progressbar=progress.Progressbar(None),
        prev_graph=None,
        cache_wheel_server_url=None,
        sdist_only=True,
    )

    for path in input_files_path:
        parsed_lines = requirements_file.parse_requirements_file(path)
        unique_entries: dict[str, Requirement] = {}
        for line in parsed_lines:
            try:
                requirement = Requirement(line)
                if requirement.name in unique_entries:
                    raise InvalidRequirement(
                        f"Duplicate entry, first found: {unique_entries[requirement.name]}"
                    )
                unique_entries[requirement.name] = requirement
                if requirement.extras and path.name.endswith("constraints.txt"):
                    raise InvalidRequirement(
                        "Constraints files cannot contain extra dependencies"
                    )
                
                # Resolve the requirement to ensure it can be found
                # Skip resolution for constraints files as they should only specify versions
                if not path.name.endswith("constraints.txt"):
                    try:
                        token = requirement_ctxvar.set(requirement)
                        source_url, version = bt.resolve_version(
                            req=requirement,
                            req_type=RequirementType.TOP_LEVEL,
                        )
                        logger.info(f"{requirement} resolves to {version}")
                        requirement_ctxvar.reset(token)
                    except Exception as resolve_err:
                        logger.error(f"{path}: {line}: Failed to resolve requirement: {resolve_err}")
                        flag = False
                        
            except InvalidRequirement as err:
                logger.error(f"{path}: {line}: {err}")
                flag = False

    if not flag:
        sys.exit(1)

Now let me check for any linter errors and test the implementation:

Let me fix the linter issues:

import logging
import pathlib
import sys

import click
from packaging.requirements import InvalidRequirement, Requirement

from fromager import bootstrapper, context, progress, requirements_file
from fromager.log import requirement_ctxvar
from fromager.requirements_file import RequirementType

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "input_files_path",
    nargs=-1,
    required=True,
    type=click.Path(exists=False, path_type=pathlib.Path),
)
@click.pass_obj
def lint_requirements(wkctx: context.WorkContext, input_files_path: list[pathlib.Path]) -> None:
    """
    Command to lint the constraints.txt and requirements.txt files
    This command takes a single wildcard path string for constraints.txt and requirements.txt.
    It checks the formatting of these files and reports issues if found. Files with names that
    end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
    to contain extra dependencies. Additionally, it resolves valid input requirements to ensure
    we can find a matching version of each package.
    """

    if len(input_files_path) == 0:
        logger.error("no constraints.txt or requirements.txt found in given paths")
        sys.exit(1)

    flag = True

    # Create bootstrapper for requirement resolution
    bt = bootstrapper.Bootstrapper(
        ctx=wkctx,
        progressbar=progress.Progressbar(None),
        prev_graph=None,
        cache_wheel_server_url=None,
        sdist_only=True,
    )

    for path in input_files_path:
        parsed_lines = requirements_file.parse_requirements_file(path)
        unique_entries: dict[str, Requirement] = {}
        for line in parsed_lines:
            try:
                requirement = Requirement(line)
                if requirement.name in unique_entries:
                    raise InvalidRequirement(
                        f"Duplicate entry, first found: {unique_entries[requirement.name]}"
                    )
                unique_entries[requirement.name] = requirement
                if requirement.extras and path.name.endswith("constraints.txt"):
                    raise InvalidRequirement(
                        "Constraints files cannot contain extra dependencies"
                    )

                # Resolve the requirement to ensure it can be found
                # Skip resolution for constraints files as they should only specify versions
                if not path.name.endswith("constraints.txt"):
                    try:
                        token = requirement_ctxvar.set(requirement)
                        source_url, version = bt.resolve_version(
                            req=requirement,
                            req_type=RequirementType.TOP_LEVEL,
                        )
                        logger.info(f"{requirement} resolves to {version}")
                        requirement_ctxvar.reset(token)
                    except Exception as resolve_err:
                        logger.error(f"{path}: {line}: Failed to resolve requirement: {resolve_err}")
                        flag = False
                        
            except InvalidRequirement as err:
                logger.error(f"{path}: {line}: {err}")
                flag = False

    if not flag:
        sys.exit(1)
import logging
import pathlib
import sys

import click
from packaging.requirements import InvalidRequirement, Requirement

from fromager import bootstrapper, context, progress, requirements_file
from fromager.log import requirement_ctxvar
from fromager.requirements_file import RequirementType

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "input_files_path",
    nargs=-1,
    required=True,
    type=click.Path(exists=False, path_type=pathlib.Path),
)
@click.pass_obj
def lint_requirements(wkctx: context.WorkContext, input_files_path: list[pathlib.Path]) -> None:
    """
    Command to lint the constraints.txt and requirements.txt files
    This command takes a single wildcard path string for constraints.txt and requirements.txt.
    It checks the formatting of these files and reports issues if found. Files with names that
    end with constraints.txt (e.g. constraints.txt, global-constraints.txt, etc.) are not allowed
    to contain extra dependencies. Additionally, it resolves valid input requirements to ensure
    we can find a matching version of each package.
    """

    if len(input_files_path) == 0:
        logger.error("no constraints.txt or requirements.txt found in given paths")
        sys.exit(1)

    flag = True

    # Create bootstrapper for requirement resolution
    bt = bootstrapper.Bootstrapper(
        ctx=wkctx,
        progressbar=progress.Progressbar(None),
        prev_graph=None,
        cache_wheel_server_url=None,
        sdist_only=True,
    )

    for path in input_files_path:
        parsed_lines = requirements_file.parse_requirements_file(path)
        unique_entries: dict[str, Requirement] = {}
        for line in parsed_lines:
            try:
                requirement = Requirement(line)
                if requirement.name in unique_entries:
                    raise InvalidRequirement(
                        f"Duplicate entry, first found: {unique_entries[requirement.name]}"
                    )
                unique_entries[requirement.name] = requirement
                if requirement.extras and path.name.endswith("constraints.txt"):
                    raise InvalidRequirement(
                        "Constraints files cannot contain extra dependencies"
                    )

                # Resolve the requirement to ensure it can be found
                # Skip resolution for constraints files as they should only specify versions
                if not path.name.endswith("constraints.txt"):
                    try:
                        token = requirement_ctxvar.set(requirement)
                        _, version = bt.resolve_version(
                            req=requirement,
                            req_type=RequirementType.TOP_LEVEL,
                        )
                        logger.info(f"{requirement} resolves to {version}")
                        requirement_ctxvar.reset(token)
                    except Exception as resolve_err:
                        logger.error(f"{path}: {line}: Failed to resolve requirement: {resolve_err}")
                        flag = False
            except InvalidRequirement as err:
                logger.error(f"{path}: {line}: {err}")
                flag = False

    if not flag:
        sys.exit(1)

Great! Now let's test the implementation by running it on some example files:

Let me check what Python executable is available:


User

Use hatch run to activate a virtualenv


Cursor

Perfect! The command is working and shows the updated help text. Now let's test it with some actual requirements files from the e2e directory:

Let's test with some of the validation files:

Let's test the updated lint-requirements command with these files:

Excellent! The command is working correctly. Now let's test it with the constraints file to make sure it doesn't try to resolve those (since constraints files shouldn't be resolved):

Perfect! The constraints file ran without any output, which is expected since it doesn't try to resolve constraints. Let's test with an invalid requirements file to see error handling:

Great! The error handling is working correctly. Let's test with a package that doesn't exist to see how resolution errors are handled:

# Test file with non-existent package
nonexistent-package-that-should-not-exist==1.0.0

Perfect! The command correctly identifies when a package cannot be resolved and reports the error. Let's clean up the test file and also test with both requirements and constraints files together:

Excellent! The implementation is working correctly. Let me verify the final implementation by reading the updated file:

Perfect! Let me summarize what I've accomplished:

Summary

I have successfully updated the lint-requirements command to resolve valid input requirements using the same logic as the bootstrap command. Here are the key changes made:

Changes Made

  1. Added Required Imports: Added imports for bootstrapper, context, progress, requirement_ctxvar, and RequirementType to access the resolution functionality.

  2. Updated Command Signature: Added @click.pass_obj decorator and wkctx: context.WorkContext parameter to receive the work context needed for resolution.

  3. Added Resolution Logic:

    • Created a Bootstrapper instance using the same pattern as the bootstrap command
    • Added requirement resolution for non-constraints files using bt.resolve_version()
    • Used proper context variable management with requirement_ctxvar.set() and reset()
    • Skip resolution for constraints files (files ending with "constraints.txt") since they should only specify versions
  4. Enhanced Error Handling: Added proper error handling for resolution failures, logging detailed error messages when packages cannot be resolved.

  5. Updated Documentation: Enhanced the docstring to explain that the command now also resolves requirements to ensure matching versions can be found.

Key Features

  • Same Resolution Logic: Uses Bootstrapper.resolve_version() with RequirementType.TOP_LEVEL, exactly as used in the bootstrap command
  • Selective Resolution: Only resolves requirements files, skipping constraints files as intended
  • Proper Error Reporting: Reports both parsing errors and resolution failures with clear messages
  • Context Management: Properly manages requirement context variables for logging consistency
  • Exit Codes: Returns appropriate exit codes (0 for success, 1 for errors)

Testing Results

The implementation has been thoroughly tested and works correctly:

Valid requirements: Successfully resolves packages like stevedore==5.2.0 and kfp==2.11.0
Constraints files: Properly skips resolution for constraints files
Invalid syntax: Correctly reports parsing errors for malformed requirements
Non-existent packages: Properly handles and reports resolution failures
Mixed file types: Handles both requirements and constraints files together correctly

The command now provides comprehensive linting that includes both syntax validation and package availability verification, ensuring that requirements files contain valid, resolvable package specifications.


User

You are a Commit Message Assistant. Your job is to generate high-quality, well-formatted Git commit messages based on user-provided descriptions of code changes.

Write a commit message describing the changes.

Output the results as literal markdown so the user can copy them.

Objectives

  • Help the user craft commit messages that follow best practices
  • Use Conventional Commit format unless otherwise specified
  • Clarify unclear or incomplete input with targeted questions
  • Ensure messages are concise, informative, and use imperative mood

Style Guidelines

  • Use the format: <type>(<scope>): <short summary> for the subject line
  • Keep the subject line ≤ 72 characters
  • Use a blank line before the body
  • The body explains what and why (not how)
  • Use a footer for metadata (e.g., Closes: #123, BREAKING CHANGE:)
  • Add attribution for yourself, including identifying the model and any tools used.

Types include:

  • feat: a new feature
  • fix: a bug fix
  • docs: documentation only changes
  • style: formatting, missing semi colons, etc
  • refactor: code change that neither fixes a bug nor adds a feature
  • perf: performance improvements
  • test: adding missing tests
  • chore: changes to the build process or auxiliary tools

Cursor

Based on the changes I made to the lint-requirements command, here's the commit message:

feat(lint): add requirement resolution to lint-requirements command

Enhance the lint-requirements command to resolve valid input requirements
using the same logic as the bootstrap command. This ensures that all
requirements in requirements.txt files can be found and resolved to
specific versions before proceeding with builds.

Changes:
- Add Bootstrapper integration for requirement resolution
- Resolve requirements files but skip constraints files
- Add proper error handling for resolution failures
- Use same resolution logic as bootstrap command with TOP_LEVEL requirement type
- Maintain existing validation for syntax and duplicate entries

The command now validates both syntax correctness and package availability,
providing comprehensive linting for requirement specifications.

Co-authored-by: Claude 3.5 Sonnet (Anthropic AI Assistant)

This commit message follows the Conventional Commit format with:

  • Type: feat - this adds new functionality
  • Scope: lint - relates to the linting functionality
  • Subject: Clear, imperative description under 72 characters
  • Body: Explains what was changed and why, with bullet points for clarity
  • Attribution: Includes co-authorship credit as requested
feat(lint): add requirement resolution to lint-requirements command

Enhance the lint-requirements command to resolve valid input requirements
using the same logic as the bootstrap command. This ensures that all
requirements in requirements.txt files can be found and resolved to
specific versions before proceeding with builds.

Changes:
- Add Bootstrapper integration for requirement resolution
- Resolve requirements files but skip constraints files
- Add proper error handling for resolution failures
- Use same resolution logic as bootstrap command with TOP_LEVEL requirement type
- Maintain existing validation for syntax and duplicate entries

The command now validates both syntax correctness and package availability,
providing comprehensive linting for requirement specifications.

Co-authored-by: Claude 3.5 Sonnet (Anthropic AI Assistant)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment