Python API¶
depkeeper is primarily a CLI. Its internals are importable and documented here for tools that need to embed dependency analysis.
Stability
Only the CLI surface is covered by a compatibility guarantee in 0.1.x. The Python API is documented but may change between minor versions. Pin the version if you depend on it.
Design rules you must follow¶
| Rule | Reason |
|---|---|
Everything network-facing is async. | All I/O is asyncio-based. Drive it with asyncio.run. |
HTTPClient must be used as an async context manager. | The underlying httpx.AsyncClient is created lazily and bound to the running loop. |
Construct one PyPIDataStore and share it. | This is what guarantees one HTTP request per package. |
VersionChecker and DependencyAnalyzer require a data store. | Passing None raises TypeError; they have no independent HTTP path. |
Canonicalise names with normalize_package_name. | Every cache, mapping and comparison in depkeeper is keyed on the PEP 503 form. |
DependencyAnalyzer mutates its input. | resolve_and_annotate_conflicts updates each Package in place. |
End-to-end example¶
import asyncio
from depkeeper.core import (
DependencyAnalyzer,
PyPIDataStore,
RequirementsParser,
VersionChecker,
)
from depkeeper.utils import HTTPClient
async def analyse(path: str):
parser = RequirementsParser()
requirements = parser.parse_file(path) # synchronous
async with HTTPClient() as http:
store = PyPIDataStore(http) # one store, shared
await store.prefetch_packages([r.name for r in requirements])
checker = VersionChecker(data_store=store)
packages = await checker.check_packages(requirements)
analyzer = DependencyAnalyzer(data_store=store)
result = await analyzer.resolve_and_annotate_conflicts(packages)
return packages, result
packages, result = asyncio.run(analyse("requirements.txt"))
for pkg in packages:
if pkg.has_update():
print(f"{pkg.name}: {pkg.current_version} -> {pkg.recommended_version}")
print(result.summary())
Rendering an update without writing¶
from depkeeper.core import RequirementsParser
parser = RequirementsParser()
req = parser.parse_line("celery[redis]>=5.0,<6.0 # queue", 1)
req.update_version("5.6.3")
# 'celery[redis]>=5.6.3,<6.0 # queue\n'
req.update_version("5.6.3", pin=True)
# 'celery[redis]==5.6.3 # queue\n'
Overriding network limits¶
from depkeeper.core import PyPIDataStore
from depkeeper.utils import HTTPClient
async with HTTPClient(timeout=10, max_retries=1, max_concurrency=4,
rate_limit_delay=0.25) as http:
store = PyPIDataStore(http, concurrent_limit=4)
...
This is the only supported way to change timeouts, retry counts or concurrency — the CLI exposes no flags for them.
Package exports¶
from depkeeper.core import (
RequirementsParser, PyPIDataStore, PyPIPackageData,
VersionChecker, DependencyAnalyzer, ResolutionResult,
)
from depkeeper.models import Package, Requirement, Conflict
from depkeeper.utils import (
HTTPClient, get_logger, setup_logging,
normalize_package_name, get_update_type,
retained_specs, rewrite_version_specs, specs_allow_version, specs_to_string,
safe_read_file, safe_write_file, create_timestamped_backup,
)
from depkeeper.exceptions import (
DepKeeperError, ParseError, ConfigError, FileOperationError, NetworkError, PyPIError,
)
Importing from depkeeper.core and depkeeper.utils rather than from the concrete modules keeps your code stable if the internal layout changes.
Core¶
RequirementsParser¶
RequirementsParser ¶
Stateful parser for pip-style requirements files.
Maintains two pieces of internal state across multiple parse_file calls:
- Include stack — tracks the chain of
-rdirectives to detect circular dependencies. - Constraint map — stores all requirements loaded via
-cdirectives; these are applied to matching package names during parsing.
Call reset to clear state before reusing the parser on an unrelated set of files.
Initialize the parser with empty state.
Source code in depkeeper/core/parser.py
Methods:¶
parse_file ¶
parse_file(
file_path: Union[str, Path],
is_constraint_file: bool = False,
_parent_directory_path: Optional[Path] = None,
) -> List[Requirement]
Parse a requirements file from disk.
Reads the file at file_path, processes all directives (-r, -c, -e, --hash), and returns a flat list of Requirement objects. If file_path is relative and _parent_directory_path is provided (internal use by -r), the path is resolved relative to the parent.
Circular include chains (A.txt includes B.txt which includes A.txt) are detected and raise ParseError.
| PARAMETER | DESCRIPTION |
|---|---|
file_path | Path to the requirements file (absolute or relative). TYPE: |
is_constraint_file | If TYPE: |
_parent_directory_path | Internal parameter used when resolving TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Requirement] | List of |
List[Requirement] | is_constraint_file is |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The file does not exist or cannot be read. |
ParseError | A circular include was detected or the file contains invalid syntax. |
Source code in depkeeper/core/parser.py
parse_string ¶
parse_string(
requirements_content: str,
source_file_path: Optional[str] = None,
is_constraint_file: bool = False,
_current_directory_path: Optional[Path] = None,
) -> List[Requirement]
Parse requirements from raw text content.
Splits requirements_content into lines and processes each via parse_line. Requirements loaded from -r includes are flattened into the result list.
| PARAMETER | DESCRIPTION |
|---|---|
requirements_content | Multi-line requirements text. TYPE: |
source_file_path | Optional file path for error messages (purely informational; does not affect parsing). TYPE: |
is_constraint_file | If TYPE: |
_current_directory_path | Internal parameter; the directory containing the "file" being parsed (used to resolve relative TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Requirement] | List of |
Example::
>>> content = """
... flask>=2.0
... # A comment
... requests>=2.25.0
... """
>>> parser = RequirementsParser()
>>> reqs = parser.parse_string(content)
>>> [r.name for r in reqs]
['flask', 'requests']
Source code in depkeeper/core/parser.py
| Python | |
|---|---|
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |
parse_line ¶
parse_line(
line_text: str,
line_number: int,
source_file_path: Optional[str] = None,
_current_directory_path: Optional[Path] = None,
) -> Optional[Union[Requirement, List[Requirement]]]
Parse a single line from a requirements file.
Handles all pip-supported line types:
- Blank lines and
#comments →None -r file.txt→List[Requirement](nested parse)-c file.txt→None(side-effect: populates constraints)- Pip global option lines (e.g.
--index-url ...) →None -e <url-or-path>→ editableRequirementpkg==1.0 --hash sha256:...→Requirementwith hashes- Standard PEP 508 specs →
Requirement
| PARAMETER | DESCRIPTION |
|---|---|
line_text | Raw line text (may include leading/trailing whitespace). TYPE: |
line_number | Line number (1-indexed) for error reporting. TYPE: |
source_file_path | Optional source file path for error messages. TYPE: |
_current_directory_path | Internal; directory of the file being parsed (used to resolve relative TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[Union[Requirement, List[Requirement]]] |
|
Optional[Union[Requirement, List[Requirement]]] |
|
Optional[Union[Requirement, List[Requirement]]] |
|
| RAISES | DESCRIPTION |
|---|---|
ParseError | The line contains invalid syntax or a directive that cannot be processed. |
Source code in depkeeper/core/parser.py
| Python | |
|---|---|
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 | |
get_constraints ¶
get_constraints() -> Dict[str, Requirement]
Return a copy of all constraint requirements loaded via -c.
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Requirement] | Dictionary mapping normalized package names to their constraint |
Dict[str, Requirement] |
|
Source code in depkeeper/core/parser.py
| Python | |
|---|---|
reset ¶
Clear all internal state (include stack and constraints).
Call this before reusing the parser on a new, unrelated set of files to prevent cross-contamination.
Source code in depkeeper/core/parser.py
PyPIDataStore¶
PyPIDataStore ¶
PyPIDataStore(
http_client: HTTPClient, concurrent_limit: int = 10
)
Async-safe, per-process cache for PyPI package metadata.
Each unique (normalized) package name triggers at most one concurrent HTTP request to /pypi/{pkg}/json. Two independent mechanisms cooperate:
- a per-key in-flight map coalesces callers that ask for the same package (or the same
name==versiondependency list) while a fetch is already running — the first caller performs the request and every later caller awaits its result; - a
asyncio.Semaphorecaps how many distinct fetches may be outbound at once.
A counting semaphore alone cannot deduplicate: it admits concurrent_limit coroutines simultaneously, so a re-check inside it is not mutually exclusive. Waiters coalesced by the in-flight map do not consume a semaphore slot.
Failures are never cached. Once a fetch fails, its in-flight entry is dropped so that a later call re-attempts the request (transient network errors must stay recoverable).
| PARAMETER | DESCRIPTION |
|---|---|
http_client | A pre-configured TYPE: |
concurrent_limit | Maximum number of PyPI fetches that may be in-flight at once. Defaults to TYPE: |
| RAISES | DESCRIPTION |
|---|---|
ValueError | concurrent_limit is less than |
Example::
async with HTTPClient() as client:
store = PyPIDataStore(client, concurrent_limit=5)
# warm the cache for several packages at once
await store.prefetch_packages(["flask", "click", "jinja2"])
# subsequent calls return instantly from cache
flask = await store.get_package_data("flask")
print(flask.latest_version)
Source code in depkeeper/core/data_store.py
Methods:¶
get_package_data async ¶
get_package_data(name: str) -> PyPIPackageData
Fetch (or return cached) metadata for name.
Cached entries are returned without awaiting anything. Otherwise the call is coalesced: the first caller starts the fetch and any concurrent caller for the same normalized name awaits that same fetch, so PyPI sees exactly one request.
| PARAMETER | DESCRIPTION |
|---|---|
name | PyPI package name (any casing / underscore style). TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
PyPIPackageData | A |
PyPIPackageData | JSON response. Every caller for the same normalized name |
PyPIPackageData | receives the same object. |
| RAISES | DESCRIPTION |
|---|---|
PyPIError | The package does not exist on PyPI or the API returned an unexpected status code. |
NetworkError | The request failed (timeout, rate limit, 5xx). |
Example::
>>> data = await store.get_package_data("Requests")
>>> data.name
'requests'
>>> data.latest_version
'2.31.0'
Source code in depkeeper/core/data_store.py
prefetch_packages async ¶
Concurrently warm the cache for a batch of packages.
Duplicate names (including different spellings that normalize to the same key, e.g. Flask and flask) are collapsed to a single fetch. Errors for individual packages are silenced so that one bad package name does not prevent the rest from being cached.
| PARAMETER | DESCRIPTION |
|---|---|
names | Package names to prefetch. TYPE: |
Example::
>>> await store.prefetch_packages(["numpy", "pandas", "numpy"])
# two fetches, not three; subsequent get_package_data calls
# for these return instantly
Source code in depkeeper/core/data_store.py
get_version_dependencies async ¶
Return the base dependencies for a specific version of name.
Resolution order (fastest first):
- Per-version dependency cache (
_version_deps_cache). - Already-populated fields inside the cached
PyPIPackageData(latest_dependenciesordependencies_cache). - A targeted
/pypi/{name}/{version}/jsonfetch, coalesced pername==versionkey and throttled by the semaphore.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name. TYPE: |
version | Exact version string, e.g. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[str] | List of PEP-508 dependency specifiers with extras and |
List[str] | environment markers stripped. |
Example::
>>> deps = await store.get_version_dependencies("flask", "2.3.0")
>>> deps
['Werkzeug>=2.0', 'Jinja2>=3.0', ...]
Source code in depkeeper/core/data_store.py
get_cached_package ¶
get_cached_package(name: str) -> Optional[PyPIPackageData]
Return cached data for name without triggering a fetch.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name (any casing / underscore style). TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[PyPIPackageData] | The cached |
Optional[PyPIPackageData] | package has not been fetched yet. |
Source code in depkeeper/core/data_store.py
get_versions ¶
Return cached stable versions for name (newest first).
Returns an empty list when name has not been fetched yet.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[str] | List of version strings, or |
Source code in depkeeper/core/data_store.py
is_python_compatible ¶
Check Python compatibility using only cached metadata.
Returns True when the package has not been fetched yet — the caller should call get_package_data first if a definitive answer is needed.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name. TYPE: |
version | Package version string. TYPE: |
python_version | Dot-separated Python version. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
bool | Compatibility flag (see |
Source code in depkeeper/core/data_store.py
get_current_python_version staticmethod ¶
Return the running interpreter's version as "major.minor.micro".
Example::
>>> PyPIDataStore.get_current_python_version()
'3.11.4'
Source code in depkeeper/core/data_store.py
| Python | |
|---|---|
PyPIPackageData¶
PyPIPackageData dataclass ¶
PyPIPackageData(
name: str,
latest_version: Optional[str] = None,
latest_requires_python: Optional[str] = None,
latest_dependencies: List[str] = list(),
all_versions: List[str] = list(),
parsed_versions: List[Tuple[str, Version]] = list(),
python_requirements: Dict[str, Optional[str]] = dict(),
releases: Dict[str, List[Dict[str, Any]]] = dict(),
dependencies_cache: Dict[str, List[str]] = dict(),
)
Immutable-by-convention snapshot of one PyPI package.
Populated once by PyPIDataStore._parse_package_data and then shared across every caller that requests the same package. All mutable collections use field(default_factory=…) so that each instance owns its own lists / dicts.
| ATTRIBUTE | DESCRIPTION |
|---|---|
name | Normalized package name (lower-case, hyphens). TYPE: |
latest_version | Version string reported by PyPI TYPE: |
latest_requires_python |
TYPE: |
latest_dependencies | Base (non-extra) deps of latest. TYPE: |
all_versions | Stable (non-pre-release) versions, newest first. TYPE: |
parsed_versions | Every version that could be parsed, as TYPE: |
python_requirements | Maps version string → its TYPE: |
releases | Raw TYPE: |
dependencies_cache | Lazily populated per-version dependency lists; seeded with latest on construction. TYPE: |
Methods:¶
get_versions_in_major ¶
Return stable versions that share a given major number.
Pre-releases and versions whose release tuple is empty are skipped.
| PARAMETER | DESCRIPTION |
|---|---|
major | The major version number to filter on (e.g. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[str] | Version strings in descending order (inherits the sort order |
List[str] | of |
Source code in depkeeper/core/data_store.py
is_python_compatible ¶
Check whether a package version supports a given Python version.
Returns True when the package omits requires_python or when parsing the specifier fails — matching pip's own permissive behavior.
| PARAMETER | DESCRIPTION |
|---|---|
version | Package version string, e.g. TYPE: |
python_version | Dot-separated Python version, e.g. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
bool |
|
bool |
|
bool | unparseable). |
Example::
>>> data.python_requirements["1.4.2"] = ">=3.7"
>>> data.is_python_compatible("1.4.2", "3.11.2")
True
>>> data.is_python_compatible("1.4.2", "2.7.18")
False
Source code in depkeeper/core/data_store.py
get_python_compatible_versions ¶
get_python_compatible_versions(
python_version: str, major: Optional[int] = None
) -> List[str]
Return stable versions compatible with python_version.
Optionally restrict results to a single major version. Versions are returned in descending order.
| PARAMETER | DESCRIPTION |
|---|---|
python_version | Dot-separated Python version to check against, e.g. TYPE: |
major | If provided, only versions with this major number are included. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[str] | Filtered, descending list of version strings. |
Source code in depkeeper/core/data_store.py
VersionChecker¶
VersionChecker ¶
VersionChecker(
data_store: PyPIDataStore,
infer_version_from_constraints: bool = True,
)
Async package version checker with strict major version boundaries.
Fetches metadata from PyPI (via the shared data store) and determines the highest Python-compatible version for each package, strictly respecting major-version boundaries when a current version is known. Unlike the base implementation, this checker will never recommend crossing a major version boundary, even if a newer major exists.
All network I/O is delegated to data_store, which guarantees that each unique package is fetched at most once.
| PARAMETER | DESCRIPTION |
|---|---|
data_store | Shared PyPI metadata cache. Required. TYPE: |
infer_version_from_constraints | When TYPE: |
| RAISES | DESCRIPTION |
|---|---|
TypeError | If data_store is |
Source code in depkeeper/core/checker.py
Methods:¶
get_package_info async ¶
get_package_info(
name: str,
current_version: Optional[str] = None,
constraints: Optional[Sequence[Tuple[str, str]]] = None,
) -> Package
Fetch metadata and compute a recommended version for name.
CRITICAL: Recommendations NEVER cross major version boundaries. If current_version is 2.x.x, the recommended version will be 2.y.z (never 3.0.0), even if 3.0.0 is the latest available version on PyPI.
Calls PyPIDataStore.get_package_data (which may trigger a network fetch or return cached data), then applies the strict major-boundary recommendation algorithm to choose the best upgrade target.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name (any casing / separator style). TYPE: |
current_version | The version currently installed (if known). When provided, the recommendation stays within the same major version. If no compatible version exists in that major, stays on current version rather than crossing the boundary. TYPE: |
constraints | Additional TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Package | A |
Package |
|
Package | data cannot be retrieved (missing package, unexpected status, |
Package | timeout, rate limiting) an unavailable stub is returned instead |
Package | — see |
Source code in depkeeper/core/checker.py
check_packages async ¶
check_packages(
requirements: List[Requirement],
) -> List[Package]
Check multiple packages concurrently.
For each requirement, extracts the current version (via extract_current_version) and calls get_package_info. Errors for individual packages are caught and replaced with unavailable stubs so that one bad package does not block the rest.
| PARAMETER | DESCRIPTION |
|---|---|
requirements | Parsed requirements from a requirements file. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Package] | List of |
Source code in depkeeper/core/checker.py
extract_current_version ¶
extract_current_version(req: Requirement) -> Optional[str]
Infer a "current" version from a requirement's version specifiers.
Heuristic:
- If the requirement has exactly one specifier and it is
==, return that version (pinned). - If
infer_version_from_constraintsisFalse, stop here. - Otherwise, scan for the first
>=,>, or~=specifier and return its version. This treats>=2.0as "currently on 2.0" for major-version boundary purposes.
| PARAMETER | DESCRIPTION |
|---|---|
req | A parsed TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[str] | The inferred version string, or |
Optional[str] | possible. |
Source code in depkeeper/core/checker.py
create_unavailable_package ¶
create_unavailable_package(
name: str, current_version: Optional[str]
) -> Package
Create a stub Package when PyPI data is unavailable.
Keeps the package in the result list (rather than dropping it) so the report still shows what is declared in the file, while the missing latest_version renders as an error row and no update is proposed.
| PARAMETER | DESCRIPTION |
|---|---|
name | Package name. TYPE: |
current_version | The version that was installed (if known). TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Package | A |
Package |
|
Source code in depkeeper/core/checker.py
DependencyAnalyzer¶
DependencyAnalyzer ¶
DependencyAnalyzer(
data_store: PyPIDataStore, concurrent_limit: int = 10
)
Detect and resolve version conflicts within major version boundaries.
Conflict resolution never moves a package into a different major version, so resolving one dependency's requirement cannot silently introduce a breaking change elsewhere.
The analyzer works exclusively through a PyPIDataStore instance, which guarantees that every /pypi/{pkg}/json call is made at most once. All public entry points are async.
| PARAMETER | DESCRIPTION |
|---|---|
data_store | Shared PyPI data store. Required — the class has no independent HTTP path. TYPE: |
concurrent_limit | Upper bound on in-flight PyPI fetches. Forwarded to the internal semaphore. Defaults to TYPE: |
| RAISES | DESCRIPTION |
|---|---|
TypeError | If data_store is |
Source code in depkeeper/core/dependency_analyzer.py
Methods:¶
resolve_and_annotate_conflicts async ¶
resolve_and_annotate_conflicts(
packages: List[Package],
) -> ResolutionResult
Resolve conflicts while strictly respecting major version boundaries.
Algorithm outline:
- Build an update set mapping each package name to its proposed version (
recommended_versionif available, otherwisecurrent_version). Recommended versions already respect major version boundaries. - Prefetch metadata for every package in one concurrent burst.
- Loop up to
_MAX_RESOLUTION_ITERATIONStimes:
a. Scan for cross-conflicts in the current update set. b. If none remain, stop — the set is self-consistent. c. Attempt resolution within major version boundaries only:
- Try to find a compatible source version within its current major
- If that fails, try to constrain the target within its current major
- If both fail, revert both packages to their current versions
d. Break early when no progress is made.
- For packages the loop could not fix, adopt the best version that satisfies every conflict at once, when one exists.
- Annotate each
Packagewith its final version and any conflicts still live against that final version. - Return a
ResolutionResultwith complete details.
Invariant: after this call, pkg.recommended_version equals result.resolved_versions[pkg.name].resolved for every package declared once. ResolutionResult is therefore the single source of truth for a singly-declared package — the version reported in the summary is always the version applied by depkeeper update.
Duplicate declarations (the same normalized package name appearing more than once in packages, e.g. the same distribution pulled in via two -r includes with different constraints) are each resolved independently: cross-package conflict detection still reasons about the name as a whole, but a name-level adjustment only reaches a given declaration's recommended_version when a real conflict was recorded for that name. Absent one, each declaration keeps the recommendation it already had. resolved_versions still holds one summary entry per name, so for a duplicated name it reports a single representative outcome — consult each Package.recommended_version directly for the authoritative per-declaration outcome.
| PARAMETER | DESCRIPTION |
|---|---|
packages | Mutable list of TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ResolutionResult |
|
ResolutionResult | package, conflict details, and resolution statistics. |
Source code in depkeeper/core/dependency_analyzer.py
| Python | |
|---|---|
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 | |
find_compatible_version ¶
find_compatible_version(
conflict_set: ConflictSet,
available_versions: List[str],
min_version: Optional[str] = None,
) -> Optional[str]
Pick the highest version from available_versions that satisfies every constraint in conflict_set and is at least min_version.
| PARAMETER | DESCRIPTION |
|---|---|
conflict_set | Aggregated conflicts for a single package. TYPE: |
available_versions | Candidate versions (any order; the conflict_set itself determines compatibility). Typically pre-filtered to only include versions within the current major. TYPE: |
min_version | If provided, discard any candidate that parses below this version. Typically the currently-installed version. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[str] | A compatible version string, or |
Optional[str] | passes all filters. |
Source code in depkeeper/core/dependency_analyzer.py
ResolutionResult¶
ResolutionResult dataclass ¶
ResolutionResult(
resolved_versions: Dict[str, PackageResolution],
total_packages: int,
packages_with_conflicts: int,
iterations_used: int,
converged: bool,
)
Complete result of dependency conflict resolution.
| ATTRIBUTE | DESCRIPTION |
|---|---|
resolved_versions | Map of package name → resolution details. TYPE: |
total_packages | Total number of packages analyzed. TYPE: |
packages_with_conflicts | Number of packages that have conflicts. TYPE: |
iterations_used | How many resolution iterations were performed. TYPE: |
converged | Whether resolution reached a stable state (True) or hit the iteration limit (False). TYPE: |
Methods:¶
get_changed_packages ¶
get_changed_packages() -> List[PackageResolution]
Return packages whose resolved version differs from the original.
get_conflicts ¶
get_conflicts() -> List[PackageResolution]
Return packages that had at least one conflict recorded.
summary ¶
Render a human-readable summary of the resolution run.
| RETURNS | DESCRIPTION |
|---|---|
str | Multi-line summary covering totals, convergence, conflicts and |
str | every version change. |
Source code in depkeeper/core/dependency_analyzer.py
PackageResolution¶
PackageResolution dataclass ¶
PackageResolution(
name: str,
original: Optional[str],
resolved: Optional[str],
status: ResolutionStatus,
conflicts: List[Conflict],
compatible_alternative: Optional[str] = None,
)
Resolution details for a single package.
| ATTRIBUTE | DESCRIPTION |
|---|---|
name | Package name (normalized). TYPE: |
original | Version that was initially proposed (from recommended_version or current_version). TYPE: |
resolved | Final version chosen after conflict resolution. This is the version that is applied — TYPE: |
status | Why this version was chosen. TYPE: |
conflicts | Every conflict recorded for this package during resolution, including ones a later iteration went on to resolve. TYPE: |
compatible_alternative | Advisory only. Best version satisfying all recorded conflicts at once, or None if no such version exists. It is adopted into TYPE: |
ResolutionStatus¶
ResolutionStatus ¶
Bases: Enum
Outcome of version resolution for a single package.
Models¶
Requirement¶
Requirement dataclass ¶
Requirement(
name: str,
specs: List[Tuple[str, str]] = list(),
extras: List[str] = list(),
markers: Optional[str] = None,
url: Optional[str] = None,
editable: bool = False,
hashes: List[str] = list(),
comment: Optional[str] = None,
line_number: int = 0,
raw_line: Optional[str] = None,
source_file: Optional[str] = None,
)
A single requirement line from a requirements file.
| ATTRIBUTE | DESCRIPTION |
|---|---|
name | Canonical package name. TYPE: |
specs | List of (operator, version) specifiers. TYPE: |
extras | Optional extras to install. TYPE: |
markers | Environment marker expression (PEP 508). TYPE: |
url | Direct URL or VCS source. TYPE: |
editable | Whether this is an editable install ( TYPE: |
hashes | Hash values used for verification. TYPE: |
comment | Inline comment without the TYPE: |
line_number | Original line number in the source file. TYPE: |
raw_line | Original unmodified line text. TYPE: |
source_file | Absolute path of the file this requirement was parsed from. Requirements pulled in via TYPE: |
Methods:¶
to_string ¶
Render the canonical requirements.txt representation.
Version specifiers are omitted when url is set, because a direct URL/VCS/local-path reference cannot carry a version specifier (doing so yields an uninstallable line).
| PARAMETER | DESCRIPTION |
|---|---|
include_hashes | Whether to include TYPE: |
include_comment | Whether to include inline comments. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | Formatted requirement string. |
Source code in depkeeper/models/requirement.py
update_version ¶
update_version(
new_version: str,
*,
pin: bool = False,
preserve_trailing_newline: bool = True,
allow_hash_removal: bool = False
) -> str
Return a requirement string updated to the given version.
By default only the specifiers that describe the currently selected version are rewritten. Upper bounds (<, <=), exclusions (!=) and wildcard bands (==2.*) are deliberate compatibility statements authored by the user and are preserved verbatim, so celery[redis]>=5.0,<6.0 updated to 5.5.3 becomes celery[redis]>=5.5.3,<6.0 rather than a hard == pin. Pass pin=True to opt into replacing every specifier with ==new_version.
Hashes are version-specific. Updating the version while silently removing --hash entries degrades integrity guarantees and can break hash-pinned installs (pip --require-hashes mode). To prevent silent security regressions, hashed requirements are rejected by default unless allow_hash_removal=True is explicitly provided by the caller.
| PARAMETER | DESCRIPTION |
|---|---|
new_version | Version string to apply. TYPE: |
pin | Replace all specifiers with an exact TYPE: |
preserve_trailing_newline | Ensure output ends with TYPE: |
allow_hash_removal | Allow updating requirements that include TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | Updated requirement string. |
| RAISES | DESCRIPTION |
|---|---|
ValueError | The requirement has one or more |
Source code in depkeeper/models/requirement.py
| Python | |
|---|---|
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
Package¶
Package dataclass ¶
Package(
name: str,
current_version: Optional[str] = None,
latest_version: Optional[str] = None,
recommended_version: Optional[str] = None,
metadata: Dict[str, Any] = dict(),
conflicts: List[Conflict] = list(),
_parsed_versions: Dict[str, Optional[Version]] = dict(),
)
A Python package with version and compatibility state.
| ATTRIBUTE | DESCRIPTION |
|---|---|
name | Normalized package name. TYPE: |
current_version | Installed or specified version. TYPE: |
latest_version | Latest known upstream version (informational only). TYPE: |
recommended_version | Best version considering constraints. TYPE: |
metadata | Arbitrary metadata (typically from PyPI). TYPE: |
conflicts | Dependency conflicts affecting this package. TYPE: |
Attributes¶
requires_downgrade property ¶
Whether the recommended version is lower than the current one.
True when conflict resolution or Python incompatibility forced the recommendation below the version declared in the requirements file.
Methods:¶
has_conflicts ¶
set_conflicts ¶
set_conflicts(
conflicts: List[Conflict],
*,
resolved_version: Optional[str] = None
) -> None
Record dependency conflicts and optionally override the recommendation.
| PARAMETER | DESCRIPTION |
|---|---|
conflicts | Detected conflicts affecting this package. TYPE: |
resolved_version | Version that resolves the conflicts. When given, it replaces TYPE: |
Source code in depkeeper/models/package.py
get_conflict_summary ¶
get_conflict_details ¶
has_update ¶
Return True when the recommended version is newer than current.
get_version_python_req ¶
Return the requires_python specifier for one version slot.
| PARAMETER | DESCRIPTION |
|---|---|
version_key | One of TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[str] | The specifier string, or |
Optional[str] | the slot has no metadata. |
Source code in depkeeper/models/package.py
get_status_summary ¶
Compute the high-level status used by line-based output.
The status ladder is ordered by severity: a missing recommendation means PyPI data was unavailable, and a required downgrade outranks a plain "outdated" because it signals an incompatible pin.
| RETURNS | DESCRIPTION |
|---|---|
str | Tuple of |
str | status is one of |
str |
|
Source code in depkeeper/models/package.py
to_json ¶
Serialize package state to a JSON-compatible dictionary.
Optional sections (versions, update_type, python_requirements, conflicts) are omitted when empty, so consumers must treat every key except name and status as optional.
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | JSON-safe package representation. |
Source code in depkeeper/models/package.py
render_python_compatibility ¶
Render Python compatibility as a multi-line cell for the table view.
| RETURNS | DESCRIPTION |
|---|---|
str | Newline-separated requirement lines, or a dimmed placeholder when |
str | no |
Source code in depkeeper/models/package.py
get_display_data ¶
Compute the derived values required for UI rendering.
Centralizing this keeps the renderers free of status logic, so table and simple output can never disagree about a package's state.
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | Dictionary of derived display properties. |
Source code in depkeeper/models/package.py
Conflict¶
Conflict dataclass ¶
Conflict(
source_package: str,
target_package: str,
required_spec: str,
conflicting_version: str,
source_version: Optional[str] = None,
)
A dependency conflict between two packages.
Frozen so a conflict can be hashed and deduplicated while the resolution loop accumulates findings across iterations.
| ATTRIBUTE | DESCRIPTION |
|---|---|
source_package | Package declaring the dependency. TYPE: |
target_package | Package being constrained. TYPE: |
required_spec | Version specifier required by the source package. TYPE: |
conflicting_version | Version that violates the requirement. TYPE: |
source_version | Version of the source package, if known. TYPE: |
Methods:¶
to_display_string ¶
Return a human-readable description of the conflict.
Source code in depkeeper/models/conflict.py
| Python | |
|---|---|
to_short_string ¶
to_json ¶
Return a JSON-serializable representation.
Source code in depkeeper/models/conflict.py
ConflictSet¶
ConflictSet dataclass ¶
ConflictSet(
package_name: str, conflicts: List[Conflict] = list()
)
Collection of conflicts affecting a single package.
| ATTRIBUTE | DESCRIPTION |
|---|---|
package_name | Name of the affected package. TYPE: |
conflicts | Conflicts associated with this package. TYPE: |
Methods:¶
has_conflicts ¶
get_max_compatible_version ¶
Return the highest version satisfying every recorded conflict.
All required_spec values are intersected into a single specifier set, so the answer is compatible with all conflicting dependents at once. Pre-releases are never returned.
| PARAMETER | DESCRIPTION |
|---|---|
available_versions | Candidate version strings, in any order. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[str] | The highest compatible version, or |
Optional[str] | a specifier is unparseable, or no candidate satisfies them all. |
Source code in depkeeper/models/conflict.py
Configuration¶
DepKeeperConfig dataclass ¶
DepKeeperConfig(
check_conflicts: bool = DEFAULT_CHECK_CONFLICTS,
strict_version_matching: bool = DEFAULT_STRICT_VERSION_MATCHING,
source_path: Optional[Path] = None,
)
Parsed and validated depkeeper configuration.
Contains settings from depkeeper.toml or pyproject.toml. All fields have defaults, so empty config files are valid.
| ATTRIBUTE | DESCRIPTION |
|---|---|
check_conflicts | Enable dependency conflict resolution. When TYPE: |
strict_version_matching | Only consider exact pins ( TYPE: |
source_path | Path to loaded config file, or TYPE: |
Methods:¶
to_log_dict ¶
Return configuration as dictionary for debug logging.
Excludes source_path metadata.
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | Dictionary of configuration option names to values. |
Source code in depkeeper/config.py
load_config ¶
load_config(
config_path: Optional[Path] = None,
) -> DepKeeperConfig
Load and validate depkeeper configuration.
Discovers config file (or uses provided path), parses and validates it. Returns config with defaults if no file found.
Handles both depkeeper.toml and pyproject.toml formats.
| PARAMETER | DESCRIPTION |
|---|---|
config_path | Explicit path to config file. If TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
DepKeeperConfig | Validated |
| RAISES | DESCRIPTION |
|---|---|
ConfigError | File cannot be parsed, has unknown keys, or invalid values. |
Source code in depkeeper/config.py
discover_config_file ¶
Find the configuration file to load.
Search order:
explicit_path(from--configorDEPKEEPER_CONFIG)depkeeper.tomlin current directorypyproject.tomlwith[tool.depkeeper]section in current directory
Validates pyproject.toml contains depkeeper section before using it.
| PARAMETER | DESCRIPTION |
|---|---|
explicit_path | Explicit config path. If provided, must exist. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[Path] | Resolved path to config file, or |
| RAISES | DESCRIPTION |
|---|---|
ConfigError | Explicit path provided but does not exist. |
Source code in depkeeper/config.py
Utilities¶
HTTP client¶
HTTPClient ¶
HTTPClient(
*,
timeout: int = DEFAULT_TIMEOUT,
max_retries: int = DEFAULT_MAX_RETRIES,
rate_limit_delay: float = 0.0,
verify_ssl: bool = True,
user_agent: Optional[str] = None,
max_concurrency: int = 10
)
Asynchronous HTTP client with retries, rate limiting, and concurrency control.
| PARAMETER | DESCRIPTION |
|---|---|
timeout | Request timeout in seconds. TYPE: |
max_retries | Maximum number of retry attempts. TYPE: |
rate_limit_delay | Minimum delay (seconds) between requests. TYPE: |
verify_ssl | Whether to verify SSL certificates. TYPE: |
user_agent | Custom User-Agent header value. TYPE: |
max_concurrency | Maximum number of concurrent requests. TYPE: |
Example
async with HTTPClient() as client: ... data = await client.get_json("https://pypi.org/pypi/requests/json")
Source code in depkeeper/utils/http.py
Methods:¶
close async ¶
get async ¶
Perform a GET request.
| PARAMETER | DESCRIPTION |
|---|---|
url | Target URL. TYPE: |
**kwargs | Forwarded to TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Response | The successful response. |
| RAISES | DESCRIPTION |
|---|---|
NetworkError | The request failed; see |
Source code in depkeeper/utils/http.py
post async ¶
Perform a POST request.
| PARAMETER | DESCRIPTION |
|---|---|
url | Target URL. TYPE: |
**kwargs | Forwarded to TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Response | The successful response. |
| RAISES | DESCRIPTION |
|---|---|
NetworkError | The request failed; see |
Source code in depkeeper/utils/http.py
get_json async ¶
Fetch a URL and decode the response as a JSON object.
| PARAMETER | DESCRIPTION |
|---|---|
url | Target URL. TYPE: |
**kwargs | Forwarded to TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | The decoded JSON object. |
| RAISES | DESCRIPTION |
|---|---|
NetworkError | The request failed, the body is not valid JSON, or the payload is a JSON value other than an object. |
Source code in depkeeper/utils/http.py
batch_get_json async ¶
batch_get_json(
urls: Iterable[str],
*,
progress_callback: Optional[
Callable[[int, int], None]
] = None
) -> Dict[str, Dict[str, Any]]
Fetch multiple JSON endpoints concurrently.
Individual failures are logged and reported as an empty dict rather than raised, so one bad URL cannot abort a batch.
| PARAMETER | DESCRIPTION |
|---|---|
urls | Iterable of URLs to fetch. TYPE: |
progress_callback | Optional callback invoked as TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Dict[str, Any]] | Mapping of URL to parsed JSON data. Failed requests map to |
Source code in depkeeper/utils/http.py
Version utilities¶
get_update_type ¶
Determine the semantic update type between two versions.
| PARAMETER | DESCRIPTION |
|---|---|
current_version | Currently installed version, or TYPE: |
target_version | Target version to compare against. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | One of: - |
Examples:
>>> get_update_type("1.0.0", "2.0.0")
'major'
>>> get_update_type(None, "1.0.0")
'new'
>>> get_update_type("1.2.3", "1.2.3")
'same'
Source code in depkeeper/utils/version_utils.py
retained_specs ¶
Return the specifiers that survive a version rewrite unchanged.
A version update only moves the floor of a requirement. Upper bounds (<, <=), exclusions (!=) and wildcard bands (==2.*) are deliberate compatibility statements and are carried through verbatim. Consequently any version depkeeper proposes must satisfy them.
| PARAMETER | DESCRIPTION |
|---|---|
specs | The requirement's declared specifier pairs. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Spec] | The subset of specs that |
Examples:
>>> retained_specs([(">=", "5.0"), ("<", "6.0")])
[('<', '6.0')]
>>> retained_specs([("==", "2.20.0")])
[]
>>> retained_specs([("==", "2.*")])
[('==', '2.*')]
Source code in depkeeper/utils/version_utils.py
rewrite_version_specs ¶
Rewrite a specifier set so it targets new_version.
Only the specifiers that describe the currently-selected version are changed:
>=and>become>=new_version(>is widened to>=so the newly selected version itself remains installable).~=keeps the compatible-release form and the author's chosen precision, e.g.~=2.0with2.3.3becomes~=2.3.- A non-wildcard
==/===pin is repinned to new_version. - Upper bounds, exclusions and wildcard bands are preserved verbatim.
A requirement with no specifiers gains an exact pin, matching the "add a version pin" behavior of the update command. A requirement that declares only upper bounds/exclusions gains an explicit >= floor so the selected version is actually recorded.
| PARAMETER | DESCRIPTION |
|---|---|
specs | The requirement's declared specifier pairs, in order. TYPE: |
new_version | The version to move the requirement to. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Spec] | A new list of specifier pairs. Duplicates introduced by the rewrite |
List[Spec] | (e.g. |
List[Spec] | while preserving order. |
Examples:
>>> rewrite_version_specs([(">=", "5.0"), ("<", "6.0")], "5.5.3")
[('>=', '5.5.3'), ('<', '6.0')]
>>> rewrite_version_specs([("~=", "2.0")], "2.3.3")
[('~=', '2.3')]
>>> rewrite_version_specs([], "1.0.0")
[('==', '1.0.0')]
Source code in depkeeper/utils/version_utils.py
specs_allow_version ¶
Check whether version satisfies every specifier in specs.
Mirrors pip's permissive behavior: an unparseable specifier or version is treated as "allowed" rather than silently discarding an update. Pre-releases are accepted so that an explicitly requested pre-release target is not rejected by SpecifierSet defaults.
| PARAMETER | DESCRIPTION |
|---|---|
specs | Specifier pairs to evaluate. TYPE: |
version | Candidate version string. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
bool |
|
bool | cannot be interpreted), |
Examples:
>>> specs_allow_version([("<", "3.0")], "2.9.0")
True
>>> specs_allow_version([("<", "3.0")], "3.1.0")
False
>>> specs_allow_version([], "1.0.0")
True
Source code in depkeeper/utils/version_utils.py
specs_to_string ¶
Render (operator, version) pairs as a PEP 440 specifier string.
| PARAMETER | DESCRIPTION |
|---|---|
specs | Specifier pairs in declaration order. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | Comma-joined specifier string, e.g. |
str | specs is empty. |
Examples:
>>> specs_to_string([(">=", "2.0"), ("<", "3.0")])
'>=2.0,<3.0'
>>> specs_to_string([])
''
Source code in depkeeper/utils/version_utils.py
is_lower_bound ¶
Return whether operator constrains only the floor of a range.
| PARAMETER | DESCRIPTION |
|---|---|
operator | A PEP 440 comparison operator. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
bool |
|
Examples:
Source code in depkeeper/utils/version_utils.py
Name canonicalisation¶
normalize_package_name ¶
Return the canonical PEP 503 form of a distribution name.
Runs of -, _ and . collapse to a single - and the result is lower-cased. The operation is idempotent, so it is safe to apply to a value that has already been normalized.
| PARAMETER | DESCRIPTION |
|---|---|
name | Raw distribution name in any casing / separator style. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | The canonical name. An empty string maps to an empty string. |
Example::
>>> normalize_package_name("Flask_Login")
'flask-login'
>>> normalize_package_name("zope.interface")
'zope-interface'
>>> normalize_package_name("My_Cool.Package")
'my-cool-package'
>>> normalize_package_name("requests")
'requests'
Source code in depkeeper/utils/naming.py
Filesystem utilities¶
Two backup layouts exist
create_timestamped_backup produces <stem>.<timestamp>_<uuid8>.backup<suffix> — this is what the CLI uses. create_backup produces <name><suffix>.<timestamp>_<uuid8>.backup, and restore_backup's target inference only understands that layout. Restore a CLI-produced backup by copying it manually.
safe_read_file ¶
safe_read_file(
file_path: PathLike,
*,
max_size: Optional[int] = MAX_FILE_SIZE,
encoding: str = DEFAULT_READ_ENCODING
) -> str
Safely read a text file with optional size limits.
The default encoding is utf-8-sig, which decodes plain UTF-8 exactly like utf-8 but also strips a leading byte order mark. Without this, the BOM survives as a \ufeff character at the start of the first line (str.strip() does not remove it) and corrupts the first token of the file. Pass encoding="utf-8" explicitly to retain the BOM.
| PARAMETER | DESCRIPTION |
|---|---|
file_path | Path to the file. TYPE: |
max_size | Maximum allowed file size in bytes (None disables limit). Measured in bytes on disk, so a BOM counts toward the limit. TYPE: |
encoding | Text encoding. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | File contents as a string, without a leading byte order mark. |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The file is missing, is not a regular file, exceeds max_size, or cannot be decoded. |
Source code in depkeeper/utils/filesystem.py
safe_write_file ¶
safe_write_file(
file_path: PathLike,
content: str,
*,
create_backup: bool = True,
encoding: str = DEFAULT_WRITE_ENCODING
) -> Optional[Path]
Safely write text to a file using atomic replacement.
The write is atomic: the content lands in a temporary file that is fsync-ed and then renamed over the destination, so the destination is never left truncated or half-written. Line endings in content are preserved exactly.
| PARAMETER | DESCRIPTION |
|---|---|
file_path | Destination path. TYPE: |
content | Text content to write. TYPE: |
create_backup | Whether to create a backup before writing. TYPE: |
encoding | Text encoding. Use TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Optional[Path] | Path to the created backup, if any. |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The write failed. Any backup taken beforehand is restored over the destination on a best-effort basis first. |
Source code in depkeeper/utils/filesystem.py
create_timestamped_backup ¶
Create a backup named <stem>.<timestamp>_<uuid>.backup<suffix>.
Unlike create_backup, the original suffix is kept last so the backup remains recognizable by extension (requirements.txt backs up to requirements.<timestamp>_<uuid>.backup.txt).
| PARAMETER | DESCRIPTION |
|---|---|
file_path | File to copy. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Path | Path to the new backup file. |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The source is missing or not a regular file, or the copy failed. |
Source code in depkeeper/utils/filesystem.py
create_backup ¶
Create a timestamped backup of an existing file.
| PARAMETER | DESCRIPTION |
|---|---|
file_path | File to copy. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Path | Path to the new backup file. |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The source does not exist, is not a regular file, or the copy failed. |
Source code in depkeeper/utils/filesystem.py
restore_backup ¶
Restore a file from a backup created by create_backup.
When target_path is omitted the original name is recovered from the backup name by dropping the .backup extension and the trailing .<timestamp>_<uuid> segment.
| PARAMETER | DESCRIPTION |
|---|---|
backup_path | Backup file to restore from. TYPE: |
target_path | Explicit destination. Required for backups whose name does not follow the generated convention. TYPE: |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The backup is missing, the destination cannot be inferred, or the copy failed. |
Source code in depkeeper/utils/filesystem.py
find_requirements_files ¶
Discover requirement files under a directory.
| PARAMETER | DESCRIPTION |
|---|---|
directory | Root to search. A non-directory yields an empty list. TYPE: |
recursive | Search subdirectories as well. When TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
List[Path] | Sorted, de-duplicated list of matching paths. |
Source code in depkeeper/utils/filesystem.py
validate_path ¶
Resolve a path and optionally confine it to a base directory.
Resolution is non-strict, so paths that do not exist yet are accepted. Passing base_dir turns this into a path-traversal guard for values that originate outside the process.
| PARAMETER | DESCRIPTION |
|---|---|
path | Path to resolve. TYPE: |
base_dir | When given, the resolved path must sit inside it. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Path | The absolute, resolved path. |
| RAISES | DESCRIPTION |
|---|---|
FileOperationError | The resolved path escapes base_dir. |
Source code in depkeeper/utils/filesystem.py
Console¶
Every helper accepts a stderr keyword. print_error defaults to stderr=True; the others default to stdout. Commands emitting machine-readable payloads must pass stderr=True for all status output.
print_success ¶
Print a success message.
| PARAMETER | DESCRIPTION |
|---|---|
message | Message body. TYPE: |
prefix | Label rendered before the message. TYPE: |
stderr | Write to stderr instead of stdout. Use this whenever stdout carries machine-readable output. TYPE: |
Source code in depkeeper/utils/console.py
print_warning ¶
Print a warning message.
| PARAMETER | DESCRIPTION |
|---|---|
message | Message body. TYPE: |
prefix | Label rendered before the message. TYPE: |
stderr | Write to stderr instead of stdout. Use this whenever stdout carries machine-readable output. TYPE: |
Source code in depkeeper/utils/console.py
print_error ¶
Print an error message.
Errors default to stderr so they never corrupt machine-readable stdout.
| PARAMETER | DESCRIPTION |
|---|---|
message | Message body. TYPE: |
prefix | Label rendered before the message. TYPE: |
stderr | Write to stderr (default) or stdout. TYPE: |
Source code in depkeeper/utils/console.py
print_table ¶
print_table(
data: List[Dict[str, Any]],
*,
headers: Optional[List[str]] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
column_styles: Optional[
Dict[str, Dict[str, Any]]
] = None,
row_styler: Optional[
Callable[[Dict[str, Any]], Optional[str]]
] = None,
show_row_lines: bool = False,
stderr: bool = False
) -> None
Render structured data as a Rich table.
| PARAMETER | DESCRIPTION |
|---|---|
data | List of row dictionaries. TYPE: |
headers | Column order. Defaults to keys of the first row. TYPE: |
title | Optional table title. TYPE: |
caption | Optional table caption. TYPE: |
column_styles | Per-column style configuration. TYPE: |
row_styler | Optional callback returning a row style. TYPE: |
show_row_lines | Whether to draw horizontal lines between rows. TYPE: |
stderr | Render to stderr instead of stdout. TYPE: |
Source code in depkeeper/utils/console.py
get_raw_console ¶
Return the underlying Rich Console instance.
| PARAMETER | DESCRIPTION |
|---|---|
stderr | Return the stderr-bound console instead of the stdout one. TYPE: |
reconfigure_console ¶
Discard the memoized stdout and stderr consoles.
Color support is probed once per stream when a console is first built, so call this after changing NO_COLOR or redirecting a stream at runtime (tests rely on it for isolation).
Source code in depkeeper/utils/console.py
colorize_update_type ¶
Wrap an update-type label in Rich markup colored by severity.
| PARAMETER | DESCRIPTION |
|---|---|
update_type | Update classification, e.g. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
str | Rich markup string, or update_type unchanged when the label has no |
str | assigned color. |
Source code in depkeeper/utils/console.py
confirm ¶
Prompt the user for a yes/no confirmation on stdout.
Input handling:
y/yes->Truen/no->False- empty or unrecognized input -> default
Ctrl+C/ EOF ->False
Unrecognized input falls back to default rather than re-prompting, so a non-interactive caller can never be trapped in a loop.
| PARAMETER | DESCRIPTION |
|---|---|
message | Prompt message shown to the user. TYPE: |
default | Choice used when the user presses Enter or types something unrecognized. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
bool |
|
Source code in depkeeper/utils/console.py
Logging¶
setup_logging reconfigures the process
It clears handlers on the shared depkeeper logger, installs its own, and sets propagate = False. Embedding depkeeper means its records will not reach your root logger unless you reconfigure afterwards. Tests must snapshot and restore that logger.
setup_logging ¶
setup_logging(
*,
level: int = INFO,
verbose: bool = False,
stream: Optional[IO[str]] = None
) -> None
Configure logging for the depkeeper logger hierarchy.
Safe to call multiple times: existing handlers are replaced under a process-wide lock rather than accumulated.
Side effects
Reconfigures the shared depkeeper logger for the whole process and sets propagate = False on it, so records no longer reach the root logger. Test suites that capture log output must snapshot and restore that logger's handlers, level and propagate flag.
| PARAMETER | DESCRIPTION |
|---|---|
level | Logging level (e.g., TYPE: |
verbose | Enable verbose formatting with timestamps and logger names. TYPE: |
stream | Output stream; defaults to TYPE: |
Source code in depkeeper/utils/logger.py
get_logger ¶
Return a logger within the depkeeper namespace.
Bare names are prefixed with depkeeper. so every logger inherits the configuration applied by setup_logging.
| PARAMETER | DESCRIPTION |
|---|---|
name | Logger name, e.g. TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Logger | A logger under the |
Logger | configured, a |
Logger | depkeeper as a library never emits "no handlers" warnings. |
Source code in depkeeper/utils/logger.py
disable_logging ¶
Silence all depkeeper logging output and reset the configured flag.
Source code in depkeeper/utils/logger.py
is_logging_configured ¶
Exceptions¶
DepKeeperError ¶
Bases: Exception
Base exception for all depkeeper errors.
All depkeeper-specific exceptions should inherit from this class. It supports structured metadata via details for richer error reporting and debugging.
| PARAMETER | DESCRIPTION |
|---|---|
message | Human-readable error message. TYPE: |
details | Optional structured metadata describing the error. TYPE: |
Source code in depkeeper/exceptions.py
ParseError ¶
ParseError(
message: str,
*,
line_number: Optional[int] = None,
line_content: Optional[str] = None,
file_path: Optional[str] = None
)
Bases: DepKeeperError
Raised when a requirements file cannot be parsed.
| PARAMETER | DESCRIPTION |
|---|---|
message | Error description. TYPE: |
line_number | Line number where parsing failed. TYPE: |
line_content | Raw content of the problematic line. TYPE: |
file_path | Path to the file being parsed. TYPE: |
Source code in depkeeper/exceptions.py
ConfigError ¶
ConfigError(
message: str,
*,
config_path: Optional[str] = None,
option: Optional[str] = None
)
Bases: DepKeeperError
Raised when a configuration file is invalid or cannot be loaded.
| PARAMETER | DESCRIPTION |
|---|---|
message | Human-readable description of the configuration problem. TYPE: |
config_path | Path to the configuration file that caused the error. TYPE: |
option | The specific configuration option that is invalid, if applicable. TYPE: |
Source code in depkeeper/exceptions.py
FileOperationError ¶
FileOperationError(
message: str,
*,
file_path: Optional[str] = None,
operation: Optional[str] = None,
original_error: Optional[Exception] = None
)
Bases: DepKeeperError
Raised when file system operations fail.
| PARAMETER | DESCRIPTION |
|---|---|
message | Error description. TYPE: |
file_path | Path to the file involved. TYPE: |
operation | Operation being performed (read/write/delete). TYPE: |
original_error | Original exception that triggered this error. TYPE: |
Source code in depkeeper/exceptions.py
NetworkError ¶
NetworkError(
message: str,
*,
url: Optional[str] = None,
status_code: Optional[int] = None,
response_body: Optional[str] = None
)
Bases: DepKeeperError
Raised when HTTP or network operations fail.
| PARAMETER | DESCRIPTION |
|---|---|
message | Error description. TYPE: |
url | URL being accessed. TYPE: |
status_code | HTTP status code, if available. TYPE: |
response_body | Raw response body, truncated for safety. TYPE: |
Source code in depkeeper/exceptions.py
PyPIError ¶
Bases: NetworkError
Raised for failures related to the PyPI API.
| PARAMETER | DESCRIPTION |
|---|---|
message | Error description. TYPE: |
package_name | Name of the package involved. TYPE: |
**kwargs | Additional arguments forwarded to TYPE: |
Source code in depkeeper/exceptions.py
Recipes¶
Just the recommendation for one package¶
import asyncio
from depkeeper.core import PyPIDataStore, VersionChecker
from depkeeper.utils import HTTPClient
async def recommend(name: str, current: str) -> str | None:
async with HTTPClient() as http:
checker = VersionChecker(data_store=PyPIDataStore(http))
pkg = await checker.get_package_info(name, current)
return pkg.recommended_version
print(asyncio.run(recommend("urllib3", "1.26.0"))) # '1.26.20'
Detect conflicts without touching any file¶
import asyncio
from depkeeper.core import DependencyAnalyzer, PyPIDataStore, VersionChecker
from depkeeper.models import Requirement
from depkeeper.utils import HTTPClient
async def conflicts(requirements: list[Requirement]):
async with HTTPClient() as http:
store = PyPIDataStore(http)
packages = await VersionChecker(data_store=store).check_packages(requirements)
result = await DependencyAnalyzer(data_store=store).resolve_and_annotate_conflicts(packages)
return result.get_conflicts()