mafw.devtools.toolchain.base
Abstract base class and data models for the toolchain management system.
This module defines the ToolChainTool interface that every managed
development tool must implement, the ProjectTool intermediate base
class for tools managed via pyproject.toml, the HostTool
intermediate base class for pipx-managed host tools, and supporting data
models used by the CLI commands to aggregate and report results.
Module Attributes
Regex to extract the version portion from a |
|
Tool category discriminator. |
Classes
|
Intermediate base for tools installed system-wide via pipx. |
|
A configuration inconsistency detected during verification. |
|
Intermediate base for tools managed via pyproject.toml. |
Abstract interface that every managed development tool must implement. |
|
|
Result of checking a single tool's version status. |
|
Result of processing a single tool during the update command. |
- class mafw.devtools.toolchain.base.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.base.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.base.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.base.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.base.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.base.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.
- mafw.devtools.toolchain.base.Category
Tool category discriminator.
"host": tools installed system-wide via pipx (e.g. hatch, uv)."project": tools whose version is managed through pyproject.toml.
alias of
Literal[‘host’, ‘project’]
- mafw.devtools.toolchain.base.LOWER_BOUND_RE: Final[Pattern[str]] = re.compile('>=\\s*([A-Za-z0-9.*!+]+)')
Regex to extract the version portion from a
>=specifier.Shared across all project tools that parse pyproject.toml dependency specifiers. Defined here to avoid duplication in each tool module.