mafw.devtools.toolchain
Toolchain management package for MAFw development tools.
This package provides the abstract interface, data models, helpers, and
registry for centralized management of all development tools used by MAFw.
Each tool implements the ToolChainTool contract, enabling uniform
bootstrapping, version checking, updating, and configuration verification.
The ensure_devtools_available() guard is performed at the CLI entry-point
level rather than at package import time, allowing individual modules to be
imported and tested independently.
- class mafw.devtools.toolchain.HostTool[source]
Bases:
ToolChainTool,ABCIntermediate base for tools installed system-wide via pipx.
Concrete subclasses need only define
pipx_package_nameas a class-level attribute. This base class provides working default implementations of all abstract methods fromToolChainTool.Subclasses may override individual methods for tool-specific behaviour (e.g.
UvTooloverridesupdate()andverify()to additionally synchronize pyproject.toml).- _ensure_pipx_available() str[source]
Locate the pipx executable on the system PATH.
- Returns:
Absolute path to the pipx executable.
- Raises:
DevtoolsError – If pipx is not found on the system PATH.
- _get_pipx_version() Version | None[source]
Parse the installed version from
pipx list --jsonoutput.Queries pipx for its list of managed packages and extracts the version string for
pipx_package_name.- Returns:
The installed
Version, orNoneif the package is not present in pipx.- Raises:
DevtoolsError – If the pipx command fails or the JSON output has an unexpected structure.
- bootstrap() bool[source]
Install the tool via
pipx install.- Returns:
Trueif installation was performed successfully.- Raises:
DevtoolsError – If pipx is not available or installation fails.
- detect_current_version() Version | None[source]
Detect the installed version from pipx metadata.
Runs
pipx list --jsonand parses the JSON output to extract the version of this package.- Returns:
The installed
Version, orNoneif the package is not installed via pipx.- Raises:
DevtoolsError – If pipx is not available or the JSON output cannot be parsed.
- detect_latest_version() Version[source]
Query PyPI for the latest stable release.
- Returns:
The latest available
Version.- Raises:
DevtoolsError – If the PyPI query fails.
- update() bool[source]
Upgrade the tool via
pipx upgrade.Compares the installed version before and after the upgrade to determine whether a change occurred.
- Returns:
Trueif the version changed,Falseif the tool was already at the latest version.- Raises:
DevtoolsError – If pipx is not available or the upgrade fails.
- verify() list[Issue][source]
Verify configuration consistency.
Host tools that do not modify repository files have no cross-file consistency checks. Override in subclasses that need verification (e.g. uv checks its pyproject.toml specifier).
- Returns:
An empty list (no issues to report).
- property category: Literal['host', 'project']
Tool category:
'host'(pipx-managed).
- property name: str
Human-readable tool identifier.
Defaults to
pipx_package_name. Override in subclasses that need a different display name.
- abstract property pipx_package_name: str
The package name as known to pipx (e.g.
'hatch','uv').
- class mafw.devtools.toolchain.Issue(tool_name: str, description: str)[source]
Bases:
objectA configuration inconsistency detected during verification.
Each instance captures a single problem found by a tool’s
ToolChainTool.verify()method.- Parameters:
tool_name – Name of the tool that reported the issue.
description – Human-readable explanation of the inconsistency.
- class mafw.devtools.toolchain.ProjectTool(project_root: Path | None = None)[source]
Bases:
ToolChainTool,ABCIntermediate base for tools managed via pyproject.toml.
Concrete subclasses need only define
package_nameandsection_pathas class-level attributes. The base class provides working default implementations of all abstract methods fromToolChainTool.Subclasses override only what differs (e.g. custom update logic for ruff,
post_updatehooks for pytest/mypy/sphinx/pre-commit).- Parameters:
project_root (Path | None) – Path to the project root directory containing
pyproject.toml. Defaults to the current working directory.
- static _parse_resolution_failure(stderr: str) str | None[source]
Extract the conflicting package name from resolver error output.
Parses hatch/uv resolver error messages to identify which package caused a dependency resolution failure. Returns the package name if found, or
Noneif the error cannot be parsed.Handles common patterns such as:
"Because only <package><=X.Y is available ...""Because <package>>=X.Y depends on ...""package <package> has no version that satisfies ...""Could not find a version that satisfies the requirement <package>""No matching distribution found for <package>"
Package names may contain letters, digits, hyphens, underscores, and dots (e.g.
sphinxcontrib-external-links,ruamel.yaml).- Parameters:
stderr – The standard error output from a failed resolver command.
- Returns:
The conflicting package name, or
Noneif not parseable.- Return type:
str | None
- _get_highest_python_version() str[source]
Return the highest supported Python version (cached after first call).
On the first invocation, reads
tool.mafw.supported-pythonfrom pyproject.toml and caches the result. Subsequent calls return the cached value without re-reading the file, avoiding repeated TOML parsing across multiple tools.- Returns:
The highest supported Python version as a dotted string (e.g.
"3.14").- Return type:
str
- Raises:
DevtoolsError – If
tool.mafw.supported-pythonis missing or empty in pyproject.toml.
- _get_modifier() PyprojectModifier[source]
Create a
PyprojectModifierfor this project’s pyproject.toml.- Returns:
A fresh modifier instance (not yet loaded).
- Return type:
- _recreate_environment(env_name: str, py_version: str | None = None) None[source]
Validate dependency resolution via a temporary clone environment.
Instead of removing and recreating the real environment (which may be the active environment running
devtools), this method creates a temporary clone environment in pyproject.toml, attempts to create it viahatch env create, and removes it afterwards. If the clone creation fails due to a dependency resolution conflict, the pyproject.toml is reverted (removing the clone declaration) and a descriptive error is raised.The temporary environment inherits the
templateof the target environment, so it resolves the same dependency graph. Its name is_toolchain_verifyto avoid collisions with real environments.- Parameters:
env_name – The hatch environment base name to validate (e.g.
'dev','types','hatch-test').py_version – Python version for matrix slot targeting (e.g.
'3.14'). When provided, the clone is created for that specific Python version.
- Raises:
DevtoolsError – If the temporary environment creation fails (indicating a resolution conflict with the updated dependencies).
- _revert() None[source]
Restore pyproject.toml to the content saved before
update().If no content was saved (update was not called or already reverted), this method is a no-op.
- bootstrap() bool[source]
Project tools cannot be bootstrapped independently.
They are installed automatically when syncing the appropriate Hatch environment.
- Raises:
DevtoolsError – Always, since project tools do not support standalone bootstrapping.
- detect_current_version() Version | None[source]
Parse the
>=lower bound from pyproject.toml.Reads the dependency specifier for
package_namefrom the section atsection_pathand extracts the version from its>=constraint usingLOWER_BOUND_RE.- Returns:
The currently configured lower-bound version, or
Noneif the dependency cannot be found or lacks a>=specifier.- Return type:
Version | None
- detect_latest_version() Version[source]
Query PyPI for the latest stable release.
Delegates to
fetch_latest_version()usingpackage_name.- Returns:
The latest non-pre-release version on PyPI.
- Return type:
Version
- Raises:
DevtoolsError – If the PyPI query fails.
- update() bool[source]
Update the lower bound in pyproject.toml.
Compares the current lower bound with the latest PyPI version and updates the specifier if necessary. Saves the original file content before modification so that subclasses with
post_update()hooks can revert on failure via_revert().- Returns:
Trueif the version was changed,Falseif already up to date.- Raises:
DevtoolsError – If the update process fails.
- verify() list[Issue][source]
Return an empty list — default for single-location tools.
Most project tools are referenced in a single pyproject.toml section, so there is no cross-file consistency to check. Tools with multiple locations (e.g. ruff) override this method.
- Returns:
An empty list of issues.
- Return type:
list[Issue]
- property category: Literal['host', 'project']
Tool category.
- Returns:
"project"— this tool is managed via pyproject.toml.- Return type:
Category
- abstract property env_name: str
Hatch environment base name where this tool lives.
This is the environment that gets recreated (via a temporary clone) during post-update validation. Examples:
'dev','types','hatch-test'.
- property name: str
Human-readable tool identifier.
Defaults to
package_name. Override in subclasses that need a different display name.
- abstract property package_name: str
PyPI package name (e.g.
'pytest','git-cliff').
- abstract property section_path: str
Dot-separated TOML path to the dependency array.
Examples:
'project.optional-dependencies.dev','tool.hatch.envs.types.extra-dependencies'.
- class mafw.devtools.toolchain.ToolChainTool[source]
Bases:
ABCAbstract interface that every managed development tool must implement.
Concrete subclasses represent individual tools (e.g. ruff, pytest, hatch) and provide the logic for bootstrapping, version detection, updating, and configuration verification.
- abstractmethod bootstrap() bool[source]
Install the tool if not present.
- Returns:
Trueif installation was performed.- Raises:
Exception – If the tool is already installed or installation fails.
- abstractmethod detect_current_version() Version | None[source]
Return the currently installed or configured version.
- Returns:
The current
Version, orNoneif the tool is not installed.
- abstractmethod detect_latest_version() Version[source]
Return the latest available version from PyPI or the canonical source.
- Returns:
The latest
Version.- Raises:
Exception – If the version cannot be determined (e.g. network error).
- post_update() None[source]
Hook executed after a successful update.
Override in subclasses to run validation (e.g. test suites, type checks) after the tool’s version has been bumped. The default implementation is a no-op.
- abstractmethod update() bool[source]
Update the tool to its latest version.
- Returns:
Trueif a configuration change was made,Falseif already up to date.- Raises:
Exception – If the update process fails.
- abstractmethod verify() list[Issue][source]
Check configuration consistency and return any detected issues.
- Returns:
A list of
Issueobjects describing inconsistencies, or an empty list when the tool’s configuration is consistent.
- abstract property category: Literal['host', 'project']
Tool category:
'host'(pipx-managed) or'project'(pyproject-managed).
- abstract property name: str
Human-readable tool identifier (lowercase, e.g.
'ruff').
- class mafw.devtools.toolchain.ToolCheckResult(tool_name: str, category: str, current_version: Version | None, latest_version: Version | None, current_error: str | None = None, latest_error: str | None = None)[source]
Bases:
objectResult of checking a single tool’s version status.
Captures the current and latest versions along with any errors encountered during detection.
- Parameters:
tool_name – Name of the tool that was checked.
category – The tool’s category (
"host"or"project").current_version – Currently installed/configured version, or
Noneif detection failed.latest_version – Latest available version from the canonical source, or
Noneif detection failed.current_error – Error message if current version detection failed.
latest_error – Error message if latest version detection failed.
- property in_sync: bool
Tool is in sync when both versions are known and equal.
Returns
Falseif either version detection encountered an error or if the two versions differ.
- class mafw.devtools.toolchain.ToolRegistry[source]
Bases:
objectCentral registry that maintains all registered ToolChainTool instances.
Tools are stored in insertion order and indexed by name for O(1) lookup. Duplicate names are rejected to ensure each tool has a unique identifier within the registry.
Usage:
registry = ToolRegistry() registry.register(my_tool) all_tools = registry.all() project_tools = registry.filter_by_category('project') specific = registry.get('ruff')
- all() list[ToolChainTool][source]
Return all registered tools in insertion order.
- Returns:
A new list containing all tools in the order they were registered.
- Return type:
list[toolchain.ToolChainTool]
- filter_by_category(category: Literal['host', 'project']) list[ToolChainTool][source]
Return tools matching the given category, preserving insertion order.
- Parameters:
category – The category to filter by (
"host"or"project").- Returns:
A list of matching tools, or an empty list if none match.
- Return type:
list[toolchain.ToolChainTool]
- get(name: str) ToolChainTool[source]
Retrieve a tool by its exact name (case-sensitive).
- Parameters:
name – The tool name to look up.
- Returns:
The matching
toolchain.ToolChainToolinstance.- Return type:
- Raises:
KeyError – If no tool with the given name is registered.
- register(tool: ToolChainTool) None[source]
Add a tool to the registry.
The tool is appended to the internal list and indexed by name. If a tool with the same name already exists, a
ValueErroris raised and the registry state remains unchanged.- Parameters:
tool – The tool instance to register.
- Raises:
ValueError – If a tool with the same name is already registered.
- class mafw.devtools.toolchain.ToolUpdateResult(tool_name: str, updated: bool, error: str | None = None, hook_error: str | None = None, revert_failed: bool = False)[source]
Bases:
objectResult of processing a single tool during the update command.
Aggregates the outcome of calling
ToolChainTool.update()and the optionalToolChainTool.post_update()hook.- Parameters:
tool_name – Name of the tool that was processed.
updated – Whether the tool’s configuration was changed.
error – Error message if the update method raised an exception.
hook_error – Error message if the post_update hook failed.
revert_failed – Whether the rollback attempt after a hook failure also failed.
Modules
Abstract base class and data models for the toolchain management system. |
|
Default tool registry factory. |
|
Tool filtering utility for the toolchain command group. |
|
Pre-commit configuration modifier for toolchain management. |
|
PyPI version query utility for toolchain management. |
|
Pyproject.toml modifier for toolchain dependency management. |
|
Central registry for ToolChainTool instances. |
|
Concrete tool implementations for the toolchain management system. |