mafw.devtools.dependencies
Dependency management utilities for MAFw development tools.
This package provides business logic for compiling, freezing, unfreezing, comparing, and auditing project dependencies.
Functions
Compile dependency lockfiles and collect the resolved versions they report. |
|
|
Commit the unfreezing of dependency upper bounds to |
|
Compare two package dictionaries and classify differences. |
|
Compile a dependency lockfile for a specific CPython version. |
|
Build the Python selector used by |
|
Compute an upper bound for a dependency based on PEP 440 compatible-release philosophy. |
Ensure the current working directory is the MAFw project root. |
|
|
Freeze dependencies by adding computed upper bounds in |
|
Freeze dependencies in a |
|
Add a computed upper bound to a requirement string, if eligible. |
Read pyproject.toml and extract dependencies with their expected lower bounds. |
|
|
Parse a pylock TOML file into a dictionary keyed by lowercase package name. |
|
Parse a |
|
Parse a Python version string in |
Read the supported CPython versions from |
|
Extract supported CPython versions from a parsed |
|
|
Build the inclusive list of Python versions between two bounds. |
|
Read resolved dependency versions from a compiled lockfile payload. |
|
Render comparison results as a JSON document string. |
|
Render comparison results as a markdown document string. |
|
Render comparison results to the terminal using Rich panels and tables. |
|
Determine the effective output format based on CLI arguments. |
|
Run pip-audit on a requirements file and save the output. |
Unfreeze dependencies by removing computed upper bounds in |
|
|
Unfreeze dependencies in a |
|
Remove a computed upper bound from a requirement string, if it matches the computed rule. |
|
Update the requirements RST files and README.rst. |
|
Verify that the specified environment has the expected lowest dependency versions. |
Classes
|
A single dependency change between reference and latest lockfiles. |
|
Comparison result for a single Python version. |
- class mafw.devtools.dependencies.PackageChange(package_name: str, change_type: Literal['ADDED', 'REMOVED', 'UPDATED'], reference_version: str | None, new_version: str | None)
Bases:
objectA single dependency change between reference and latest lockfiles.
Each instance represents one package that was added, removed, or updated between the reference and the latest resolved dependency set.
- Parameters:
package_name – Normalized (lowercase) package name.
change_type – Classification of the change (ADDED, REMOVED, UPDATED).
reference_version – Version in the reference file,
Nonefor ADDED entries.new_version – Version in the latest file,
Nonefor REMOVED entries.
- class mafw.devtools.dependencies.VersionComparisonResult(python_version: str, changes: list[~mafw.devtools.dependencies.PackageChange] = <factory>)
Bases:
objectComparison result for a single Python version.
Holds all detected dependency changes for a given Python interpreter version.
- Parameters:
python_version – Python version string (e.g.
"3.12").changes – Sorted list of package changes for this version.
- property has_changes: bool
Return
Trueif there is at least one dependency change.
- mafw.devtools.dependencies.collect_compiled_dependency_versions(python_versions: list[str]) dict[str, Version]
Compile dependency lockfiles and collect the resolved versions they report.
The command mirrors the dependency verification workflow by invoking
uv pip compilefor each supported CPython version. A temporary lockfile is generated for each version and removed afterwards.- Parameters:
python_versions (list[str]) – Supported Python versions to compile.
- Returns:
Mapping of package name to highest resolved version across the compiled lockfiles.
- Return type:
dict[str, Version]
- mafw.devtools.dependencies.commit_dependency_unfreeze(dry_run: bool) None
Commit the unfreezing of dependency upper bounds to
main.- Parameters:
dry_run (bool) – Whether command execution is disabled.
- mafw.devtools.dependencies.compare_packages(latest_packages: dict[str, dict[str, Any]], reference_packages: dict[str, dict[str, Any]]) list[PackageChange]
Compare two package dictionaries and classify differences.
Both dictionaries are expected to be keyed by lowercase package name. The function classifies each difference into one of three categories:
ADDED: package present in latest but absent from reference.
REMOVED: package present in reference but absent from latest.
UPDATED: package present in both but with a different version or marker.
- Parameters:
latest_packages (dict[str, dict[str, Any]]) – Packages from the freshly compiled lockfile, keyed by lowercase name.
reference_packages (dict[str, dict[str, Any]]) – Packages from the reference lockfile, keyed by lowercase name.
- Returns:
List of
PackageChangeentries sorted alphabetically by package name.- Return type:
list[PackageChange]
- mafw.devtools.dependencies.compile_dependency_lockfile(python_version: str, pylock_file: Path, extras: list[str], resolution: str | None = None, output_format: str | None = None, with_hashes: bool = False) None
Compile a dependency lockfile for a specific CPython version.
- Parameters:
python_version (str) – CPython version used for the
uv pip compilerun.pylock_file (Path) – Output path for the generated lockfile.
extras (list[str]) – Project extras requested during compilation.
resolution (str | None) – Optional UV resolution strategy (e.g. ‘lowest-direct’, ‘highest’).
output_format (str | None) – Optional output format (e.g. ‘requirements.txt’).
with_hashes (bool) – Whether to generate hashes for the compiled requirements.
- mafw.devtools.dependencies.compile_python_selector(python_version: str) str
Build the Python selector used by
uvfor dependency compilation.For Python 3.14 and newer, request the GIL-enabled variant explicitly so free-threaded interpreters do not leak into dependency resolution.
This distinction is still needed because of the psycopg
- Parameters:
python_version (str) – Base Python version in
major.minorform.- Returns:
Python selector string passed to
uv.- Return type:
str
- mafw.devtools.dependencies.compute_upper_bound(lower_bound: str) str
Compute an upper bound for a dependency based on PEP 440 compatible-release philosophy.
The rule implemented here is purposely conservative and mirrors the intent of compatible release clauses while remaining explicit:
For major-versioned releases (
X.*withX > 0), freeze to<(X + 1).For
0.*releases, freeze to<0.(minor + 1)(rolling compatibility during pre-1.0).
- Parameters:
lower_bound (str) – Version string used as the starting point for the freeze rule.
- Returns:
Upper-bound version string without operator.
- Return type:
str
- Raises:
DevtoolsError – If the version cannot be parsed.
- mafw.devtools.dependencies.ensure_mafw_project_root() list[str]
Ensure the current working directory is the MAFw project root.
- Returns:
Validated supported Python versions from
tool.mafw.supported-python.- Return type:
list[str]
- Raises:
DevtoolsError – If
pyproject.tomlis missing or does not identify MAFw.
- mafw.devtools.dependencies.freeze_dependencies(*, dry_run: bool) str
Freeze dependencies by adding computed upper bounds in
pyproject.toml.- Parameters:
dry_run (bool) – Whether command execution is disabled.
- Returns:
Original
pyproject.tomlcontent captured before freeze.- Return type:
str
- mafw.devtools.dependencies.freeze_pyproject_toml(toml_text: str, *, resolved_versions: dict[str, Version] | None = None, doc: TOMLDocument | None = None) tuple[str, list[str]]
Freeze dependencies in a
pyproject.tomlpayload by adding upper bounds.The TOML structure is preserved via tomlkit; only dependency strings may be normalized.
When
resolved_versionsis provided, the function uses those compiled versions as the basis for upper-bound computation. Otherwise the declared lower bounds are used as a fallback.- Parameters:
toml_text (str) – Raw TOML file content.
resolved_versions (dict[str, Version] | None) – Optional mapping of dependency names to resolved versions.
doc (tomlkit.TOMLDocument | None) – Optional pre-parsed TOML document to reuse when available.
- Returns:
Updated TOML plus warnings.
- Return type:
tuple[str, list[str]]
- Raises:
DevtoolsError – If TOML parsing fails.
- mafw.devtools.dependencies.freeze_requirement(requirement_text: str, *, resolved_version: str | None = None) tuple[str, list[str]]
Add a computed upper bound to a requirement string, if eligible.
The function is intentionally conservative:
URL-based requirements are skipped (cannot be version constrained).
Requirements already containing
<,<=,~=,==or===are skipped.Requirements without a lower bound emit a warning and are left unchanged.
When a resolved version is provided, the upper bound is computed from that version instead of the declared lower bound.
- Parameters:
requirement_text (str) – Raw PEP 508 requirement string.
- Returns:
Updated requirement text plus warnings.
- Return type:
tuple[str, list[str]]
- Raises:
DevtoolsError – If parsing fails.
- mafw.devtools.dependencies.get_expected_lower_bounds() dict[str, Requirement]
Read pyproject.toml and extract dependencies with their expected lower bounds.
- Returns:
A dictionary mapping lowercase package names to their parsed Requirement objects.
- Return type:
dict[str, Requirement]
- Raises:
DevtoolsError – If the pyproject.toml cannot be parsed.
- mafw.devtools.dependencies.load_pylock_packages(pylock_path: Path) dict[str, dict[str, Any]]
Parse a pylock TOML file into a dictionary keyed by lowercase package name.
Each value in the returned dictionary contains at minimum the
name,version, and optionallymarkerfields from the original TOML entry.- Parameters:
pylock_path (Path) – Path to the pylock TOML file.
- Returns:
Dictionary mapping lowercase package names to their package metadata.
- Return type:
dict[str, dict[str, Any]]
- Raises:
FileNotFoundError – If pylock_path does not exist.
tomllib.TOMLDecodeError – If the file is not valid TOML.
- mafw.devtools.dependencies.load_pyproject_doc(toml_text: str) TOMLDocument
Parse a
pyproject.tomlpayload once and return the TOML document.- Parameters:
toml_text (str) – Raw TOML payload.
- Returns:
Parsed TOML document.
- Return type:
tomlkit.TOMLDocument
- Raises:
DevtoolsError – If the TOML payload cannot be parsed.
- mafw.devtools.dependencies.parse_python_version(version: str) tuple[int, int]
Parse a Python version string in
major.minorform.- Parameters:
version (str) – Python version string.
- Returns:
Major/minor version tuple.
- Return type:
tuple[int, int]
- Raises:
DevtoolsError – If the version is invalid.
- mafw.devtools.dependencies.project_python_versions() list[str]
Read the supported CPython versions from
tool.mafw.supported-pythonlist in pyproject.toml.The field is expected to be a list of strings representing CPython major.minor versions. The helper validates each entry and returns a sorted, de-duplicated list so downstream callers have deterministic ordering.
- Returns:
Sorted list of supported CPython versions.
- Return type:
list[str]
- Raises:
DevtoolsError – If
pyproject.tomlcannot be parsed or the field contains unsupported values.
- mafw.devtools.dependencies.project_python_versions_from_doc(doc: TOMLDocument) list[str]
Extract supported CPython versions from a parsed
pyproject.tomldocument.- Parameters:
doc (tomlkit.TOMLDocument) – Parsed TOML document.
- Returns:
Sorted list of supported CPython versions.
- Return type:
list[str]
- Raises:
DevtoolsError – If the
tool.mafw.supported-pythonfield is invalid.
- mafw.devtools.dependencies.python_versions_between(min_python_ver: str, max_python_ver: str, supported_versions: list[str]) list[str]
Build the inclusive list of Python versions between two bounds.
The returned versions must also be present in
supported_versions.- Parameters:
min_python_ver (str) – Minimum Python version.
max_python_ver (str) – Maximum Python version.
supported_versions (list[str]) – List of supported Python versions from project metadata.
- Returns:
Ordered list of version strings.
- Return type:
list[str]
- mafw.devtools.dependencies.read_compiled_dependency_versions(pylock_text: str) dict[str, Version]
Read resolved dependency versions from a compiled lockfile payload.
The returned mapping stores the highest resolved version seen for each dependency name, normalized to lowercase for stable lookups.
- Parameters:
pylock_text (str) – Raw
pylock.pyX.Y.tomlcontent.- Returns:
Mapping of package name to highest resolved version.
- Return type:
dict[str, Version]
- Raises:
DevtoolsError – If the TOML payload cannot be parsed.
- mafw.devtools.dependencies.render_comparison_json(results: list[VersionComparisonResult]) str
Render comparison results as a JSON document string.
Produces a JSON object with:
timestamp: ISO 8601 generation time.python_versions: ordered list of all Python versions tested.results: dictionary keyed by Python version, each value being a list of change entries (empty list when no differences exist for that version).
Each change entry contains
package_name,change_type,reference_version(null for ADDED), andnew_version(null for REMOVED).- Parameters:
results (list[VersionComparisonResult]) – Comparison results for each Python version.
- Returns:
Formatted JSON string with 2-space indentation.
- Return type:
str
- mafw.devtools.dependencies.render_comparison_markdown(results: list[VersionComparisonResult]) str
Render comparison results as a markdown document string.
Produces a structured markdown report with a title, an ISO 8601 timestamp, and per-version sections containing ADDED, REMOVED, and UPDATED tables. Versions with no dependency changes are omitted from the output.
- Parameters:
results (list[VersionComparisonResult]) – List of comparison results, one per Python version.
- Returns:
Complete markdown document as a string.
- Return type:
str
- mafw.devtools.dependencies.render_comparison_rich(results: list[VersionComparisonResult], console: Console) None
Render comparison results to the terminal using Rich panels and tables.
Displays a title panel, followed by a section per Python version containing ADDED, REMOVED, and UPDATED subsections in that fixed order. Within each subsection, entries are sorted alphabetically by package name.
When no changes are detected across all Python versions, a single informational message is printed instead.
- Parameters:
results (list[VersionComparisonResult]) – Comparison results for each Python version.
console (Console) – Rich Console instance for output.
- mafw.devtools.dependencies.resolve_output_format(explicit_format: str | None, output_file: Path | None) str
Determine the effective output format based on CLI arguments.
The resolution follows a priority order that avoids ambiguity between an explicitly requested format and the file extension of the output path:
If output_file has a recognized extension and explicit_format is set:
Match (e.g.,
json+.json): return the format.Conflict (e.g.,
json+.md): raiseclick.UsageError.
If output_file has a recognized extension and explicit_format is
None: infer the format from the extension.If output_file has an unrecognized extension and explicit_format is set: return the explicit format.
Otherwise: return
"markdown"as the default.
- Parameters:
explicit_format (str | None) – The value of
--formatif explicitly provided by the user, orNonewhen omitted.output_file (Path | None) – The value of
--output-file, orNonewhen omitted.
- Returns:
The resolved format string (
"markdown"or"json").- Return type:
str
- Raises:
click.UsageError – When explicit_format conflicts with the format inferred from the file extension.
- mafw.devtools.dependencies.run_pip_audit(req_file: Path, output_file: Path, output_format: str) CompletedProcess[Any]
Run pip-audit on a requirements file and save the output.
- Parameters:
req_file (Path) – Requirements file to audit.
output_file (Path) – Path to the output report.
output_format (str) – Format of the report (e.g. ‘markdown’, ‘json’).
- Returns:
Completed process produced by the command execution.
- Return type:
subprocess.CompletedProcess[Any]
- mafw.devtools.dependencies.unfreeze_dependencies(original_pyproject_toml: str, *, dry_run: bool) None
Unfreeze dependencies by removing computed upper bounds in
pyproject.toml.- Parameters:
dry_run (bool) – Whether command execution is disabled.
- mafw.devtools.dependencies.unfreeze_pyproject_toml(toml_text: str, *, baseline_toml_text: str | None = None, doc: TOMLDocument | None = None, baseline_doc: TOMLDocument | None = None) tuple[str, list[str]]
Unfreeze dependencies in a
pyproject.tomlpayload by removing computed upper bounds.Only upper bounds matching the computed rule are removed; existing manual constraints remain.
- Parameters:
toml_text (str) – Raw TOML file content.
baseline_toml_text (str | None) – Optional original TOML text captured before freezing. When provided, unfreezing is computed against the baseline to avoid altering dependencies that were already frozen before release.
doc (tomlkit.TOMLDocument | None) – Optional pre-parsed TOML document to reuse when available.
baseline_doc (tomlkit.TOMLDocument | None) – Optional pre-parsed baseline TOML document to reuse when available.
- Returns:
Updated TOML plus warnings.
- Return type:
tuple[str, list[str]]
- Raises:
DevtoolsError – If TOML parsing fails.
- mafw.devtools.dependencies.unfreeze_requirement(requirement_text: str) tuple[str, list[str]]
Remove a computed upper bound from a requirement string, if it matches the computed rule.
Only the auto-generated upper bound
<upperis removed; existing manual upper bounds are preserved.- Parameters:
requirement_text (str) – Raw PEP 508 requirement string.
- Returns:
Updated requirement text plus warnings.
- Return type:
tuple[str, list[str]]
- Raises:
DevtoolsError – If parsing fails.
- mafw.devtools.dependencies.update_requirements_and_readme(*, dry_run: bool) None
Update the requirements RST files and README.rst.
- Parameters:
dry_run (bool) – Whether command execution is disabled.
- mafw.devtools.dependencies.verify_lowest_resolution(env_name: str, python_version: str, expected_bounds: dict[str, Requirement]) None
Verify that the specified environment has the expected lowest dependency versions.
This function executes uv pip list in the given environment, parses the output, and compares installed versions against the expected lower bounds extracted from pyproject.toml.
- Parameters:
env_name (str) – The name of the environment (e.g., ‘hatch-test’, ‘types’).
python_version (str) – The Python version string (e.g., ‘3.11’).
expected_bounds (dict[str, Requirement]) – Mapping of package names to their Requirement objects.
- Raises:
DevtoolsError – If a required dependency is missing.
Modules
Dependency auditing utilities for MAFw. |
|
Dependency comparison engine for MAFw lockfiles. |
|
Dependency compilation utilities for MAFw. |
|
Dependency freezing and unfreezing utilities for MAFw. |
|
Dependency resolution verification utilities for MAFw. |