A low-level library for calling build-backends in pyproject.toml-based project
npx @tessl/cli install tessl/pypi-pyproject-hooks@1.2.0A low-level library for calling build-backends in pyproject.toml-based Python projects. This library provides the basic functionality to help write tooling that generates distribution files from Python projects, serving as the underlying piece for build frontends like pip, build, and other tools.
pip install pyproject-hooksfrom pyproject_hooks import BuildBackendHookCallerFor exception handling:
from pyproject_hooks import (
BackendUnavailable,
HookMissing,
UnsupportedOperation
)For subprocess runners:
from pyproject_hooks import (
default_subprocess_runner,
quiet_subprocess_runner
)from pyproject_hooks import BuildBackendHookCaller
import tempfile
# Initialize the hook caller for a project
hook_caller = BuildBackendHookCaller(
source_dir="/path/to/project",
build_backend="setuptools.build_meta"
)
# Get wheel build dependencies
wheel_deps = hook_caller.get_requires_for_build_wheel()
print(f"Wheel dependencies: {wheel_deps}")
# Build a wheel
with tempfile.TemporaryDirectory() as temp_dir:
wheel_filename = hook_caller.build_wheel(temp_dir)
print(f"Built wheel: {wheel_filename}")The library provides a subprocess-based isolation mechanism for calling build backends:
Main class for interfacing with build backends. Manages source directory, backend specification, and subprocess execution.
class BuildBackendHookCaller:
def __init__(
self,
source_dir: str,
build_backend: str,
backend_path: Optional[Sequence[str]] = None,
runner: Optional[SubprocessRunner] = None,
python_executable: Optional[str] = None,
) -> None:
"""
Initialize the hook caller.
Parameters:
- source_dir: The source directory to invoke the build backend for
- build_backend: The build backend spec (e.g., "setuptools.build_meta")
- backend_path: Additional path entries for the build backend spec
- runner: The subprocess runner to use (defaults to default_subprocess_runner)
- python_executable: The Python executable used to invoke the build backend
"""Temporarily override the default subprocess runner for specific operations.
def subprocess_runner(self, runner: SubprocessRunner) -> Iterator[None]:
"""
Context manager for temporarily overriding subprocess runner.
Parameters:
- runner: The new subprocess runner to use within the context
"""Operations for building wheels and managing wheel metadata.
def get_requires_for_build_wheel(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""
Get additional dependencies required for building a wheel.
Parameters:
- config_settings: The configuration settings for the build backend
Returns:
A list of PEP 508 dependency specifiers.
Note: Returns empty list if backend doesn't define this hook.
"""
def prepare_metadata_for_build_wheel(
self,
metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True,
) -> str:
"""
Prepare a *.dist-info folder with metadata for this project.
Parameters:
- metadata_directory: The directory to write the metadata to
- config_settings: The configuration settings for the build backend
- _allow_fallback: Whether to allow fallback to building wheel and extracting metadata
Returns:
Name of the newly created subfolder within metadata_directory containing the metadata.
Note: May fall back to building wheel and extracting metadata if hook not defined.
"""
def build_wheel(
self,
wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None,
) -> str:
"""
Build a wheel from this project.
Parameters:
- wheel_directory: The directory to write the wheel to
- config_settings: The configuration settings for the build backend
- metadata_directory: The directory to reuse existing metadata from
Returns:
The name of the newly created wheel within wheel_directory.
"""Operations for building editable wheels, supporting PEP 660 editable installs.
def get_requires_for_build_editable(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""
Get additional dependencies required for building an editable wheel.
Parameters:
- config_settings: The configuration settings for the build backend
Returns:
A list of PEP 508 dependency specifiers.
Note: Returns empty list if backend doesn't define this hook.
"""
def prepare_metadata_for_build_editable(
self,
metadata_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
_allow_fallback: bool = True,
) -> Optional[str]:
"""
Prepare a *.dist-info folder with metadata for editable installation.
Parameters:
- metadata_directory: The directory to write the metadata to
- config_settings: The configuration settings for the build backend
- _allow_fallback: Whether to allow fallback to building editable wheel and extracting metadata
Returns:
Name of the newly created subfolder within metadata_directory containing the metadata.
Note: May fall back to building editable wheel and extracting metadata if hook not defined.
"""
def build_editable(
self,
wheel_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
metadata_directory: Optional[str] = None,
) -> str:
"""
Build an editable wheel from this project.
Parameters:
- wheel_directory: The directory to write the wheel to
- config_settings: The configuration settings for the build backend
- metadata_directory: The directory to reuse existing metadata from
Returns:
The name of the newly created wheel within wheel_directory.
"""Operations for building source distributions (sdists).
def get_requires_for_build_sdist(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""
Get additional dependencies required for building an sdist.
Parameters:
- config_settings: The configuration settings for the build backend
Returns:
A list of PEP 508 dependency specifiers.
"""
def build_sdist(
self,
sdist_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
) -> str:
"""
Build an sdist from this project.
Parameters:
- sdist_directory: The directory to write the sdist to
- config_settings: The configuration settings for the build backend
Returns:
The name of the newly created sdist within sdist_directory.
"""Exception classes for handling various error conditions during build backend operations.
class BackendUnavailable(Exception):
"""
Raised when the backend cannot be imported in the hook process.
Attributes:
- backend_name: The name of the backend that failed to import
- backend_path: The backend path that was used
- traceback: The traceback from the import failure
"""
def __init__(
self,
traceback: str,
message: Optional[str] = None,
backend_name: Optional[str] = None,
backend_path: Optional[Sequence[str]] = None,
) -> None: ...
class HookMissing(Exception):
"""
Raised on missing hooks when a fallback cannot be used.
Attributes:
- hook_name: The name of the missing hook
"""
def __init__(self, hook_name: str) -> None: ...
class UnsupportedOperation(Exception):
"""
Raised by build_sdist if the backend indicates that it cannot perform the operation.
Attributes:
- traceback: The traceback from the backend failure
"""
def __init__(self, traceback: str) -> None: ...Functions for executing subprocess commands with different behaviors.
def default_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
"""
The default method of calling the wrapper subprocess.
Uses subprocess.check_call under the hood.
Parameters:
- cmd: The command to execute as a sequence of strings
- cwd: The working directory for the command
- extra_environ: Additional environment variables
"""
def quiet_subprocess_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
"""
Call the subprocess while suppressing output.
Uses subprocess.check_output under the hood.
Parameters:
- cmd: The command to execute as a sequence of strings
- cwd: The working directory for the command
- extra_environ: Additional environment variables
"""from typing import Protocol, Sequence, Optional, Mapping, Any
class SubprocessRunner(Protocol):
"""
Protocol for subprocess runner functions.
"""
def __call__(
self,
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None: ...# Deprecated alias for BackendUnavailable
BackendInvalid = BackendUnavailableNote: BackendInvalid is a deprecated alias for BackendUnavailable. Use BackendUnavailable instead.