Skip to content

Dependency Lister API Reference

dependency_lister

Dependency Listing Utilities.

This module provides functions to identify and list project dependencies from various common package manager files. It supports multiple programming languages and package managers, outputting structured Markdown suitable for inclusion in context bundles.

Core functionalities: - Parsing Python dependencies from pyproject.toml (Poetry and PEP 621) - Parsing Python dependencies from requirements.txt files - Parsing Node.js dependencies from package.json - Extensible design for future language/package manager support - Structured Markdown output with grouping by language/file - Graceful handling of missing or malformed files

Classes

DependencyInfo(name, version=None, extras=None, group='main')

Represents information about a single dependency.

Attributes
name: The name of the dependency
version: The version constraint/specification (optional)
extras: List of optional extras/features (optional)
group: Dependency group (e.g., 'dev', 'test', 'main')
Source code in src/codebrief/tools/dependency_lister.py
def __init__(
    self,
    name: str,
    version: Optional[str] = None,
    extras: Optional[list[str]] = None,
    group: str = "main",
):
    """Initialize a DependencyInfo object."""
    self.name = name
    self.version = version
    self.extras = extras or []
    self.group = group
Functions
__str__()

Return the string representation of the dependency.

Source code in src/codebrief/tools/dependency_lister.py
def __str__(self) -> str:
    """Return the string representation of the dependency."""
    result = self.name
    if self.extras:
        result += f"[{','.join(self.extras)}]"
    if self.version:
        result += f" {self.version}"
    return result
__repr__()

Return the detailed string representation of the object.

Source code in src/codebrief/tools/dependency_lister.py
def __repr__(self) -> str:
    """Return the detailed string representation of the object."""
    return (
        f"DependencyInfo(name='{self.name}', version='{self.version}', "
        f"extras={self.extras}, group='{self.group}')"
    )

PackageManagerParser(file_path)

Base class for package manager parsers.

Source code in src/codebrief/tools/dependency_lister.py
def __init__(self, file_path: Path):
    """Initialize the parser with a file path."""
    self.file_path = file_path
    self.language = "Unknown"
    self.package_manager = "Unknown"
Functions
can_parse()

Check if this parser can handle the given file.

Source code in src/codebrief/tools/dependency_lister.py
def can_parse(self) -> bool:
    """Check if this parser can handle the given file."""
    return self.file_path.exists()
parse()

Parse the file and extract dependency information.

Source code in src/codebrief/tools/dependency_lister.py
def parse(self) -> list[DependencyInfo]:
    """Parse the file and extract dependency information."""
    raise NotImplementedError("Subclasses must implement parse()")

PyProjectTomlParser(file_path)

Bases: PackageManagerParser

Parser for Python pyproject.toml files (Poetry and PEP 621).

Source code in src/codebrief/tools/dependency_lister.py
def __init__(self, file_path: Path):
    """Initialize the pyproject.toml parser."""
    super().__init__(file_path)
    self.language = "Python"
    self.package_manager = "pyproject.toml"
Functions
can_parse()

Check if this is a valid pyproject.toml file.

Source code in src/codebrief/tools/dependency_lister.py
def can_parse(self) -> bool:
    """Check if this is a valid pyproject.toml file."""
    if not self.file_path.exists():
        return False

    # Check if we have a TOML parser available
    if not tomllib:
        console.print(
            "[yellow]Warning: No TOML parser available for "
            "pyproject.toml parsing[/yellow]"
        )
        return False

    return True
parse()

Parse pyproject.toml file.

Source code in src/codebrief/tools/dependency_lister.py
def parse(self) -> list[DependencyInfo]:
    """Parse pyproject.toml file."""
    if not self.can_parse():
        return []

    data = self._load_toml()
    if not data:
        return []

    deps = []

    # Try Poetry format first
    if "tool" in data and "poetry" in data["tool"]:
        self.package_manager = "Poetry (pyproject.toml)"
        deps.extend(self._parse_poetry_dependencies(data))

    # Try PEP 621 format
    if "project" in data:
        if deps:  # If we already found Poetry deps, this might be a mixed file
            self.package_manager = "Poetry + PEP 621 (pyproject.toml)"
        else:
            self.package_manager = "PEP 621 (pyproject.toml)"
        deps.extend(self._parse_pep621_dependencies(data))

    return deps

RequirementsTxtParser(file_path)

Bases: PackageManagerParser

Parser for Python requirements.txt files.

Source code in src/codebrief/tools/dependency_lister.py
def __init__(self, file_path: Path):
    """Initialize the requirements.txt parser."""
    super().__init__(file_path)
    self.language = "Python"
    self.package_manager = f"requirements.txt ({file_path.name})"
Functions
parse()

Parse requirements.txt file.

Source code in src/codebrief/tools/dependency_lister.py
def parse(self) -> list[DependencyInfo]:
    """Parse requirements.txt file."""
    if not self.can_parse():
        return []

    deps = []
    group = self._determine_group_from_filename()
    try:
        with self.file_path.open("r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or line.startswith("-"):
                    continue
                name, version, extras = self._parse_requirement_string(line)
                deps.append(
                    DependencyInfo(
                        name=name, version=version, extras=extras, group=group
                    )
                )
    except Exception as e:
        console.print(
            f"[yellow]Warning: Failed to parse {self.file_path}: {e}[/yellow]"
        )

    return deps

PackageJsonParser(file_path)

Bases: PackageManagerParser

Parser for Node.js package.json files.

Source code in src/codebrief/tools/dependency_lister.py
def __init__(self, file_path: Path):
    """Initialize the package.json parser."""
    super().__init__(file_path)
    self.language = "Node.js"
    self.package_manager = "npm/yarn (package.json)"
Functions
parse()

Parse package.json file.

Source code in src/codebrief/tools/dependency_lister.py
def parse(self) -> list[DependencyInfo]:
    """Parse package.json file."""
    if not self.can_parse():
        return []

    deps = []
    try:
        with self.file_path.open("r", encoding="utf-8") as f:
            data = json.load(f)

        # Parse main dependencies
        dependencies = data.get("dependencies", {})
        for name, version in dependencies.items():
            deps.append(DependencyInfo(name=name, version=version, group="main"))

        # Parse dev dependencies
        dev_dependencies = data.get("devDependencies", {})
        for name, version in dev_dependencies.items():
            deps.append(DependencyInfo(name=name, version=version, group="dev"))

        # Parse peer dependencies
        peer_dependencies = data.get("peerDependencies", {})
        for name, version in peer_dependencies.items():
            deps.append(DependencyInfo(name=name, version=version, group="peer"))

        # Parse optional dependencies
        optional_dependencies = data.get("optionalDependencies", {})
        for name, version in optional_dependencies.items():
            deps.append(
                DependencyInfo(name=name, version=version, group="optional")
            )

    except Exception as e:
        console.print(
            f"[yellow]Warning: Failed to parse {self.file_path}: {e}[/yellow]"
        )
        return []

    return deps

Functions

discover_dependency_files(project_path)

Discover all supported dependency files in the project.

Source code in src/codebrief/tools/dependency_lister.py
def discover_dependency_files(project_path: Path) -> list[Path]:
    """Discover all supported dependency files in the project."""
    supported_files = [
        "pyproject.toml",
        "requirements.txt",
        "requirements-dev.txt",
        "dev-requirements.txt",
        "requirements_dev.txt",
        "test-requirements.txt",
        "requirements-test.txt",
        "requirements_test.txt",
        "package.json",
    ]
    found_files = []
    for p in project_path.rglob("*"):
        if p.name in supported_files:
            # Exclude files in common virtual environment folders
            if ".venv" not in p.parts and "venv" not in p.parts:
                found_files.append(p)
    return found_files

create_parser(file_path)

Create a parser instance for a given file path.

Source code in src/codebrief/tools/dependency_lister.py
def create_parser(file_path: Path) -> Optional[PackageManagerParser]:
    """Create a parser instance for a given file path."""
    name = file_path.name
    if name == "pyproject.toml":
        return PyProjectTomlParser(file_path)
    if name.endswith(".txt") and "requirements" in name:
        return RequirementsTxtParser(file_path)
    if name == "package.json":
        return PackageJsonParser(file_path)
    return None

format_dependencies_as_markdown(dependency_data)

Format the collected dependency data into a Markdown string.

Source code in src/codebrief/tools/dependency_lister.py
def format_dependencies_as_markdown(
    dependency_data: dict[str, dict[str, dict[str, list[DependencyInfo]]]],
) -> str:
    """Format the collected dependency data into a Markdown string."""
    md_content = ["# Project Dependencies"]
    for lang, managers in sorted(dependency_data.items()):
        md_content.append(f"\n## {lang}\n")
        for manager, groups in sorted(managers.items()):
            md_content.append(f"### {manager}\n")
            for group, deps in sorted(groups.items()):
                if deps:
                    md_content.append(f"#### {group.capitalize()} Dependencies\n")
                    for dep in sorted(deps, key=lambda d: d.name):
                        md_content.append(f"- `{dep}`")
                    md_content.append("")
    return "\n".join(md_content)

list_dependencies(project_path, output_file)

Main logic function for listing project dependencies.

Parameters:

NameTypeDescriptionDefault
project_pathPath

Root directory to scan for dependency files.

required
output_fileOptional[Path]

Optional path to save the output. If None, returns string.

required

Returns:

TypeDescription
Optional[str]

Markdown string if no output file specified, None otherwise.

Source code in src/codebrief/tools/dependency_lister.py
def list_dependencies(project_path: Path, output_file: Optional[Path]) -> Optional[str]:
    """Main logic function for listing project dependencies.

    Args:
        project_path: Root directory to scan for dependency files.
        output_file: Optional path to save the output. If None, returns string.

    Returns:
        Markdown string if no output file specified, None otherwise.
    """
    console.print(f"Scanning for dependency files in: {project_path}")
    files_to_parse = discover_dependency_files(project_path)

    if not files_to_parse:
        raise FileNotFoundError("No supported dependency files found in the project.")

    all_deps: dict[str, dict[str, dict[str, list[DependencyInfo]]]] = {}

    for file_path in files_to_parse:
        parser = create_parser(file_path)
        if parser and parser.can_parse():
            console.print(
                f"  -> Parsing [green]{file_path.relative_to(project_path)}[/green]..."
            )
            deps = parser.parse()
            if not deps:
                continue

            lang = parser.language
            manager = parser.package_manager
            if lang not in all_deps:
                all_deps[lang] = {}
            if manager not in all_deps[lang]:
                all_deps[lang][manager] = {}

            for dep in deps:
                if dep.group not in all_deps[lang][manager]:
                    all_deps[lang][manager][dep.group] = []
                all_deps[lang][manager][dep.group].append(dep)

    # Reformat the dictionary for the markdown formatter
    # The all_deps structure is: lang -> manager -> group -> list[DependencyInfo]
    # We need to pass this directly to format_dependencies_as_markdown
    markdown_output = format_dependencies_as_markdown(all_deps)

    if output_file:
        try:
            output_file.write_text(markdown_output, encoding="utf-8")
            console.print(
                f"\n[bold green]✓ Dependencies saved to {output_file}[/bold green]"
            )
        except Exception as e:
            console.print(f"[bold red]Error saving to {output_file}: {e}[/bold red]")
            raise typer.Exit(code=1)
        return None
    else:
        # Return the markdown output for the main command to handle
        return markdown_output

Overview

The dependency lister tool analyzes project dependency files across multiple programming languages and package managers, generating comprehensive dependency reports in Markdown format.

Supported Languages & Package Managers

Python

  • pyproject.toml - Poetry and PEP 621 format
  • requirements.txt files and variants
  • setup.py (partial support)

Node.js

  • package.json - Dependencies and devDependencies
  • package-lock.json (metadata only)
  • yarn.lock (planned)

Future Support

  • Java: Maven (pom.xml), Gradle (build.gradle)
  • Ruby: Gemfile, Gemfile.lock
  • Go: go.mod, go.sum
  • Rust: Cargo.toml, Cargo.lock
  • PHP: composer.json, composer.lock
  • C#: .csproj, packages.config

Key Features

Multi-Language Analysis

  • Automatic detection of dependency files
  • Language-specific parsing logic
  • Unified output format across languages

Dependency Grouping

  • Main/production dependencies
  • Development dependencies
  • Optional dependencies
  • Peer dependencies (Node.js)
  • Build dependencies

Metadata Extraction

  • Version constraints and ranges
  • Dependency descriptions
  • License information (when available)
  • Repository URLs

Output Formatting

  • Clean Markdown tables
  • Hierarchical organization by language
  • Version information preservation
  • Group-based categorization

Usage Examples

Basic Usage

from pathlib import Path
from codebrief.tools.dependency_lister import list_dependencies_logic

# Analyze current directory
list_dependencies_logic(
    project_path=Path("."),
    actual_output_path=None  # Print to console
)

# Save to file
list_dependencies_logic(
    project_path=Path("/path/to/project"),
    actual_output_path=Path("dependencies.md")
)

Programmatic Access

from codebrief.tools.dependency_lister import (
    find_dependency_files,
    parse_python_dependencies,
    parse_nodejs_dependencies
)

# Find all dependency files
project_path = Path(".")
dependency_files = find_dependency_files(project_path)

# Parse specific files
for file_path in dependency_files:
    if file_path.name == "pyproject.toml":
        deps = parse_python_dependencies(file_path)
    elif file_path.name == "package.json":
        deps = parse_nodejs_dependencies(file_path)

Architecture

Extensible Design

The dependency lister uses a plugin-like architecture that makes it easy to add support for new languages:

# Each language has dedicated parser functions
def parse_python_dependencies(file_path: Path) -> DependencyInfo:
    """Parse Python dependency files."""
    # Language-specific parsing logic

def parse_nodejs_dependencies(file_path: Path) -> DependencyInfo:
    """Parse Node.js dependency files."""
    # Language-specific parsing logic

Data Structures

The module uses well-defined data structures for consistency:

  • DependencyInfo: Container for all dependency data
  • DependencyGroup: Categorized dependencies (main, dev, etc.)
  • Dependency: Individual dependency with version info

Error Handling

Robust error handling ensures graceful operation:

  • Invalid file formats are skipped with warnings
  • Missing files don't halt processing
  • Malformed version specs are preserved as-is
  • Network-dependent operations are avoided

Output Format

The dependency lister generates structured Markdown output:

# Project Dependencies

## Python Dependencies

### Main Dependencies (pyproject.toml)
- typer: ^0.9.0
- rich: ^13.0.0
- pathspec: ^0.12.1

### Development Dependencies (pyproject.toml)
- pytest: ^7.0.0
- mypy: ^1.0.0
- ruff: ^0.1.0

## Node.js Dependencies

### Dependencies (package.json)
- express: ^4.18.0
- lodash: ^4.17.21

### Development Dependencies (package.json)
- typescript: ^4.9.0
- jest: ^29.0.0

Configuration Integration

The dependency lister integrates with codebrief's configuration system:

[tool.codebrief]
default_output_filename_deps = "docs/dependencies.md"

Performance Considerations

  • Fast File Discovery: Uses efficient file system traversal
  • Lazy Parsing: Only parses files when needed
  • Memory Efficient: Streams output for large dependency lists
  • Caching: Future versions will cache parsed results

Testing

The module includes comprehensive tests covering:

  • All supported file formats
  • Edge cases and error conditions
  • Multi-language projects
  • Various dependency configurations
  • Output formatting validation

Future Enhancements

Version 1.1

  • License detection and reporting
  • Security vulnerability scanning
  • Dependency graph visualization
  • Outdated dependency detection

Version 1.2

  • Dependency analysis and recommendations
  • Conflict detection
  • Size analysis for JavaScript packages
  • Performance impact assessment

Error Handling Patterns

The dependency lister uses consistent error handling:

try:
    # Parse dependency file
    dependencies = parse_file(file_path)
except FileNotFoundError:
    console.print(f"[yellow]Warning: File not found: {file_path}[/yellow]")
    continue
except ParseError as e:
    console.print(f"[yellow]Warning: Could not parse {file_path}: {e}[/yellow]")
    continue

Integration Examples

CI/CD Integration

# GitHub Actions
- name: Generate dependency report
  run: poetry run codebrief deps --output artifacts/dependencies.md

- name: Check for security issues
  run: |
    poetry run codebrief deps --output deps.md
    # Use output for security scanning

Development Workflow

# Regular dependency analysis
codebrief deps --output docs/dependencies.md

# Include in project documentation
cat docs/dependencies.md >> docs/project-overview.md

# Compare dependencies between branches
git checkout main
codebrief deps --output main-deps.md
git checkout feature-branch
codebrief deps --output feature-deps.md
diff main-deps.md feature-deps.md

For more examples and advanced usage, see the Tutorials and User Guide.