Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[![Coverage](https://codecov.io/gh/rahulkaushal04/depkeeper/branch/main/graph/badge.svg)](https://codecov.io/gh/rahulkaushal04/depkeeper)
[![PyPI version](https://badge.fury.io/py/depkeeper.svg)](https://badge.fury.io/py/depkeeper)
[![Python versions](https://img.shields.io/pypi/pyversions/depkeeper.svg)](https://pypi.org/project/depkeeper/)
[![Downloads](https://static.pepy.tech/badge/depkeeper)](https://pepy.tech/project/depkeeper)
[![Downloads per month](https://static.pepy.tech/badge/depkeeper/month)](https://pepy.tech/project/depkeeper)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Modern, intelligent Python dependency management for `requirements.txt` files.
Expand Down
2 changes: 1 addition & 1 deletion depkeeper/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
from __future__ import annotations

#: Current depkeeper version (PEP 440 compliant).
__version__: str = "0.1.0.dev3"
__version__: str = "0.1.0"
26 changes: 19 additions & 7 deletions depkeeper/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""
Command-line interface for depkeeper.

This module defines the main Click entry point, global options, command
registration, and top-level error handling for the depkeeper CLI.
This module provides the main CLI entry point and handles global options,
configuration loading, and command registration.
"""

from __future__ import annotations
Expand All @@ -15,11 +15,12 @@

import click

from depkeeper.config import load_config
from depkeeper.__version__ import __version__
from depkeeper.context import DepKeeperContext
from depkeeper.exceptions import DepKeeperError
from depkeeper.utils.console import print_error, print_warning
from depkeeper.exceptions import ConfigError, DepKeeperError
from depkeeper.utils.logger import get_logger, setup_logging
from depkeeper.utils.console import print_error, print_warning

logger = get_logger("cli")

Expand Down Expand Up @@ -73,10 +74,19 @@ def cli(
"""
_configure_logging(verbose)

try:
loaded_config = load_config(config)
except ConfigError as exc:
print_error(str(exc))
raise SystemExit(1) from exc

depkeeper_ctx = DepKeeperContext()
depkeeper_ctx.config_path = config
depkeeper_ctx.verbose = verbose
depkeeper_ctx.config_path = config or (
loaded_config.source_path if loaded_config.source_path else None
)
depkeeper_ctx.color = color
depkeeper_ctx.verbose = verbose
depkeeper_ctx.config = loaded_config
ctx.obj = depkeeper_ctx

# Respect NO_COLOR for downstream libraries
Expand All @@ -86,7 +96,9 @@ def cli(
os.environ["NO_COLOR"] = "1"

logger.debug("depkeeper v%s", __version__)
logger.debug("Config path: %s", config)
logger.debug("Config path: %s", depkeeper_ctx.config_path)
if loaded_config.source_path:
logger.debug("Loaded configuration: %s", loaded_config.to_log_dict())
logger.debug("Verbosity: %s | Color: %s", verbose, color)


Expand Down
134 changes: 65 additions & 69 deletions depkeeper/commands/check.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,7 @@
"""Check command implementation for depkeeper.

Analyzes a ``requirements.txt`` file to identify packages with available
updates, dependency conflicts, and Python version compatibility issues.

The command orchestrates three core components:

1. **RequirementsParser** β€” parses the requirements file into structured
:class:`Requirement` objects.
2. **VersionChecker** β€” queries PyPI concurrently to fetch latest versions
and compute recommendations.
3. **DependencyAnalyzer** β€” cross-validates all recommended versions and
resolves conflicts through iterative downgrading/constraining.

All components share a single :class:`PyPIDataStore` instance to guarantee
that each package's metadata is fetched at most once per invocation.

Typical usage::

# Show all packages with available updates
$ depkeeper check requirements.txt --outdated-only

# Machine-readable JSON output
$ depkeeper check --format json > report.json

# Enable conflict resolution (adjusts recommendations to be mutually compatible)
$ depkeeper check --check-conflicts

# Strict mode: only use pinned versions, don't infer from constraints
$ depkeeper check --strict-version-matching
Analyzes requirements files to identify available updates, dependency
conflicts, and Python version compatibility issues.
"""

from __future__ import annotations
Expand All @@ -37,7 +11,7 @@
import click
import asyncio
from pathlib import Path
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional

from depkeeper.models import Package
from depkeeper.exceptions import DepKeeperError, ParseError
Expand Down Expand Up @@ -84,12 +58,12 @@
@click.option(
"--strict-version-matching",
is_flag=True,
default=None,
help="Only use exact version pins, don't infer from constraints.",
)
@click.option(
"--check-conflicts",
is_flag=True,
default=True,
"--check-conflicts/--no-check-conflicts",
default=None,
help="Check for dependency conflicts between packages.",
)
@pass_context
Expand All @@ -98,8 +72,8 @@ def check(
file: Path,
outdated_only: bool,
format: str,
strict_version_matching: bool,
check_conflicts: bool,
strict_version_matching: Optional[bool],
check_conflicts: Optional[bool],
) -> None:
"""Check requirements file for available updates.

Expand All @@ -108,16 +82,18 @@ def check(
Optionally performs dependency conflict analysis to ensure recommended
updates are compatible with each other.

When ``--check-conflicts`` is enabled, the command:
\b
When --check-conflicts is enabled (the default), the command:
1. Fetches initial recommendations for every package.
2. Cross-validates all recommendations to detect conflicts.
3. Iteratively adjusts versions until a conflict-free set is found.
4. Displays the final resolved versions along with any unresolved
conflicts.

1. Fetches initial recommendations for every package.
2. Cross-validates all recommendations to detect conflicts (package A's
dependency on B is incompatible with B's recommended version).
3. Iteratively adjusts versions (downgrading sources or constraining
targets) until a conflict-free set is found or the iteration limit
is reached.
4. Displays the final resolved versions along with any unresolved
conflicts.
Options not explicitly provided on the command line fall back to values
from the configuration file (depkeeper.toml or pyproject.toml), then
to built-in defaults.
\f

Args:
ctx: Depkeeper context with configuration and verbosity settings.
Expand All @@ -126,19 +102,22 @@ def check(
format: Output format (``table``, ``simple``, or ``json``).
strict_version_matching: Don't infer current versions from
constraints like ``>=2.0``; only use exact pins (``==``).
check_conflicts: Enable cross-package conflict resolution.
Falls back to the ``strict_version_matching`` config option.
check_conflicts: Enable cross-package conflict resolution. Falls
back to the ``check_conflicts`` config option.

Exits:
0 if all packages are up-to-date, 1 if updates are available or an
error occurred.

Example::

>>> # CLI invocation
$ depkeeper check requirements.txt --check-conflicts --format table
0 if the command completed successfully (whether or not updates
are available), 1 if an error occurred.
"""
cfg = ctx.config
if strict_version_matching is None:
strict_version_matching = cfg.strict_version_matching if cfg else False
if check_conflicts is None:
check_conflicts = cfg.check_conflicts if cfg else True

try:
has_updates = asyncio.run(
asyncio.run(
_check_async(
ctx,
file,
Expand All @@ -148,7 +127,7 @@ def check(
check_conflicts=check_conflicts,
)
)
sys.exit(1 if has_updates else 0)
sys.exit(0)

except DepKeeperError as e:
print_error(f"{e}")
Expand Down Expand Up @@ -195,7 +174,9 @@ async def _check_async(

Returns:
``True`` if any package has updates or unresolved conflicts,
``False`` if everything is up-to-date.
``False`` if everything is up-to-date. The return value is
used only for informational logging; it does not affect the
exit code (which is always 0 on success).

Raises:
DepKeeperError: Requirements file cannot be parsed or is malformed.
Expand Down Expand Up @@ -365,12 +346,18 @@ def _display_table(packages: List[Package]) -> None:

Example::

┏━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Status ┃ Package ┃ Current ┃ Latest ┃ Update Type ┃
┑━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
β”‚ βœ“ OK β”‚ requests β”‚ 2.31.0 β”‚ 2.31.0 β”‚ - β”‚
β”‚ ⬆ OUTDATED β”‚ flask β”‚ 2.0.0 β”‚ 3.0.0 β”‚ major β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Dependency Status

Status Package Current Latest Recommended Update Type Conflicts Python Support

βœ“ OK django 3.2.0 5.0.2 - - - Current: >=3.8
Latest: >=3.10

⬆ OUTDATED requests 2.28.0 2.32.0 2.32.0 minor - Current: >=3.7
Latest: >=3.8

⬆ OUTDATED flask 2.0.0 3.0.1 2.3.3 patch - Current: >=3.7
Latest: >=3.8
"""
data = [_create_table_row(pkg) for pkg in packages]

Expand Down Expand Up @@ -536,10 +523,12 @@ def _display_simple(packages: List[Package]) -> None:

Example::

[OUTDATED] flask 2.0.0 β†’ 3.0.0
⚠ Conflict: werkzeug requires >=2.0,<3
requests 2.28.0 β†’ 2.32.0 (recommended: 2.32.0)
Python: installed: >=3.7, latest: >=3.8
[OK] requests 2.31.0 β†’ 2.31.0
flask 2.0.0 β†’ 3.0.1 (recommended: 2.3.3)
Python: installed: >=3.7, latest: >=3.8, recommended: >=3.7
celery 5.3.0 β†’ 5.3.6
Python: installed: >=3.8, latest: >=3.8
"""
console = get_raw_console()

Expand Down Expand Up @@ -594,12 +583,19 @@ def _display_json(packages: List[Package]) -> None:

[
{
"name": "flask",
"current_version": "2.0.0",
"latest_version": "3.0.0",
"recommended_version": "2.3.3",
"conflicts": [...],
"metadata": {...}
"name": "requests",
"status": "outdated",
"versions": {
"current": "2.28.0",
"latest": "2.32.0",
"recommended": "2.32.0"
},
"update_type": "minor",
"python_requirements": {
"current": ">=3.7",
"latest": ">=3.8",
"recommended": ">=3.8"
}
},
...
]
Expand Down
Loading
Loading