Skip to content

Base

provide.foundation.tools.base

Base classes for tool management.

This module provides the foundation for tool managers, including the base manager class and metadata structures.

Classes

BaseToolManager

BaseToolManager(config: BaseConfig)

Bases: ABC

Abstract base class for tool managers.

Provides common functionality for downloading, verifying, and installing development tools. Subclasses must implement platform-specific logic.

Attributes:

Name Type Description
config

Configuration object.

tool_name str

Name of the tool being managed.

executable_name str

Name of the executable file.

supported_platforms list[str]

List of supported platforms.

Initialize the tool manager.

Parameters:

Name Type Description Default
config BaseConfig

Configuration object containing settings.

required
Source code in provide/foundation/tools/base.py
def __init__(self, config: BaseConfig) -> None:
    """Initialize the tool manager.

    Args:
        config: Configuration object containing settings.

    """
    if not self.tool_name:
        raise ToolError("Subclass must define tool_name")
    if not self.executable_name:
        raise ToolError("Subclass must define executable_name")

    self.config = config

    # Lazy-load components to avoid circular imports
    self._cache: ToolCache | None = None
    self._downloader: ToolDownloader | None = None
    self._verifier: ToolVerifier | None = None
    self._installer: ToolInstaller | None = None
    self._resolver: VersionResolver | None = None

    log.debug(f"Initialized {self.tool_name} manager")
Attributes
cache property
cache: ToolCache

Get or create cache instance.

downloader property
downloader: ToolDownloader

Get or create downloader instance.

installer property
installer: ToolInstaller

Get or create installer instance.

resolver property
resolver: VersionResolver

Get or create version resolver instance.

verifier property
verifier: ToolVerifier

Get or create verifier instance.

Functions
get_available_versions abstractmethod
get_available_versions() -> list[str]

Get list of available versions from upstream.

Returns:

Type Description
list[str]

List of version strings available for download.

Source code in provide/foundation/tools/base.py
@abstractmethod
def get_available_versions(self) -> list[str]:
    """Get list of available versions from upstream.

    Returns:
        List of version strings available for download.

    """
get_install_path
get_install_path(version: str) -> Path

Get the installation path for a version.

Parameters:

Name Type Description Default
version str

Version string.

required

Returns:

Type Description
Path

Path where the version is/will be installed.

Source code in provide/foundation/tools/base.py
def get_install_path(self, version: str) -> Path:
    """Get the installation path for a version.

    Args:
        version: Version string.

    Returns:
        Path where the version is/will be installed.

    """
    base_path = Path.home() / ".provide-foundation" / "tools" / self.tool_name / version
    return base_path
get_metadata abstractmethod
get_metadata(version: str) -> ToolMetadata

Get metadata for a specific version.

Parameters:

Name Type Description Default
version str

Version string to get metadata for.

required

Returns:

Type Description
ToolMetadata

ToolMetadata object with download URLs and checksums.

Source code in provide/foundation/tools/base.py
@abstractmethod
def get_metadata(self, version: str) -> ToolMetadata:
    """Get metadata for a specific version.

    Args:
        version: Version string to get metadata for.

    Returns:
        ToolMetadata object with download URLs and checksums.

    """
get_platform_info
get_platform_info() -> dict[str, str]

Get current platform information.

Returns:

Type Description
dict[str, str]

Dictionary with platform and arch keys.

Source code in provide/foundation/tools/base.py
def get_platform_info(self) -> dict[str, str]:
    """Get current platform information.

    Returns:
        Dictionary with platform and arch keys.

    """
    import platform

    system = platform.system().lower()
    if system == "darwin":
        system = "darwin"
    elif system == "linux":
        system = "linux"
    elif system == "windows":
        system = "windows"

    machine = platform.machine().lower()
    if machine in ["x86_64", "amd64"]:
        arch = "amd64"
    elif machine in ["aarch64", "arm64"]:
        arch = "arm64"
    else:
        arch = machine

    return {"platform": system, "arch": arch}
install async
install(
    version: str = "latest", force: bool = False
) -> Path

Install a specific version of the tool.

Parameters:

Name Type Description Default
version str

Version to install (default: "latest").

'latest'
force bool

Force reinstall even if cached.

False

Returns:

Type Description
Path

Path to the installed tool.

Raises:

Type Description
ToolInstallError

If installation fails.

Source code in provide/foundation/tools/base.py
async def install(self, version: str = "latest", force: bool = False) -> Path:
    """Install a specific version of the tool.

    Args:
        version: Version to install (default: "latest").
        force: Force reinstall even if cached.

    Returns:
        Path to the installed tool.

    Raises:
        ToolInstallError: If installation fails.

    """
    # Resolve version
    if version in ["latest", "stable", "dev"] or version.startswith(("~", "^")):
        version = self.resolve_version(version)

    # Check cache unless forced
    if not force and (cached_path := self.cache.get(self.tool_name, version)):
        log.info(f"Using cached {self.tool_name} {version}")
        return cached_path

    log.info(f"Installing {self.tool_name} {version}")

    # Get metadata
    metadata = self.get_metadata(version)
    if not metadata.download_url:
        raise ToolInstallError(f"No download URL for {self.tool_name} {version}")

    # Download to secure temporary directory
    from provide.foundation.file.temp import system_temp_dir

    download_path = system_temp_dir() / f"{self.tool_name}-{version}"
    artifact_path = await self.downloader.download_with_progress(
        metadata.download_url,
        download_path,
        metadata.checksum,
    )

    # Verify if checksum provided
    if metadata.checksum and not self.verifier.verify_checksum(artifact_path, metadata.checksum):
        artifact_path.unlink()
        raise ToolVerificationError(f"Checksum verification failed for {self.tool_name} {version}")

    # Install
    install_path = self.installer.install(artifact_path, metadata)

    # Cache the installation
    self.cache.store(self.tool_name, version, install_path)

    # Clean up download
    if artifact_path.exists():
        artifact_path.unlink()

    log.info(f"Successfully installed {self.tool_name} {version} to {install_path}")
    return install_path
is_installed
is_installed(version: str) -> bool

Check if a version is installed.

Parameters:

Name Type Description Default
version str

Version to check.

required

Returns:

Type Description
bool

True if installed, False otherwise.

Source code in provide/foundation/tools/base.py
def is_installed(self, version: str) -> bool:
    """Check if a version is installed.

    Args:
        version: Version to check.

    Returns:
        True if installed, False otherwise.

    """
    install_path = self.get_install_path(version)
    executable = install_path / "bin" / self.executable_name
    return executable.exists()
resolve_version
resolve_version(spec: str) -> str

Resolve a version specification to a concrete version.

Parameters:

Name Type Description Default
spec str

Version specification (e.g., "latest", "~1.5.0").

required

Returns:

Type Description
str

Concrete version string.

Raises:

Type Description
ToolNotFoundError

If version cannot be resolved.

Source code in provide/foundation/tools/base.py
def resolve_version(self, spec: str) -> str:
    """Resolve a version specification to a concrete version.

    Args:
        spec: Version specification (e.g., "latest", "~1.5.0").

    Returns:
        Concrete version string.

    Raises:
        ToolNotFoundError: If version cannot be resolved.

    """
    available = self.get_available_versions()
    if not available:
        raise ToolNotFoundError(f"No versions available for {self.tool_name}")

    resolved = self.resolver.resolve(spec, available)
    if not resolved:
        raise ToolNotFoundError(f"Cannot resolve version '{spec}' for {self.tool_name}")

    log.debug(f"Resolved {self.tool_name} version {spec} to {resolved}")
    return resolved
uninstall
uninstall(version: str) -> bool

Uninstall a specific version.

Parameters:

Name Type Description Default
version str

Version to uninstall.

required

Returns:

Type Description
bool

True if uninstalled, False if not found.

Source code in provide/foundation/tools/base.py
def uninstall(self, version: str) -> bool:
    """Uninstall a specific version.

    Args:
        version: Version to uninstall.

    Returns:
        True if uninstalled, False if not found.

    """
    # Invalidate cache
    self.cache.invalidate(self.tool_name, version)

    # Remove from filesystem
    install_path = self.get_install_path(version)
    if install_path.exists():
        import shutil

        shutil.rmtree(install_path)
        log.info(f"Uninstalled {self.tool_name} {version}")
        return True

    return False

ToolError

ToolError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: FoundationError

Base exception for tool-related errors.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)

ToolInstallError

ToolInstallError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: ToolError

Raised when tool installation fails.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)

ToolMetadata

Metadata about a tool version.

Attributes:

Name Type Description
name str

Tool name (e.g., "terraform").

version str

Version string (e.g., "1.5.0").

platform str

Platform identifier (e.g., "linux", "darwin").

arch str

Architecture (e.g., "amd64", "arm64").

checksum str | None

Optional checksum for verification.

signature str | None

Optional GPG/PGP signature.

download_url str | None

URL to download the tool.

checksum_url str | None

URL to download checksums file.

install_path Path | None

Where the tool is/will be installed.

env_vars dict[str, str]

Environment variables to set.

dependencies list[str]

Other tools this depends on.

executable_name str | None

Name of the executable file.

ToolNotFoundError

ToolNotFoundError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: ToolError

Raised when a tool or version cannot be found.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)

ToolVerificationError

ToolVerificationError(
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any
)

Bases: ToolError

Raised when tool verification fails.

Source code in provide/foundation/errors/base.py
def __init__(
    self,
    message: str,
    *,
    code: str | None = None,
    context: dict[str, Any] | None = None,
    cause: Exception | None = None,
    **extra_context: Any,
) -> None:
    self.message = message
    self.code = code or self._default_code()
    self.context = context or {}
    self.context.update(extra_context)
    self.cause = cause
    if cause:
        self.__cause__ = cause
    super().__init__(message)

Functions