pyproject-api
pyproject-api is a Python library that provides an abstract API for interacting with `pyproject.toml`-based projects. It standardizes the communication with various build backends (like setuptools, Hatchling, Flit) as defined by PEP 517 and PEP 660, allowing tools to build, inspect, and manage Python packages without direct knowledge of the underlying build system. The library is actively maintained by the tox-dev team and is currently at version 1.10.0, with regular releases to support new Python versions and address issues.
Warnings
- breaking Python 3.9 support was dropped in version 1.10.0. Python 3.8 support was dropped in version 1.9.0. The library now requires Python 3.10 or newer.
- gotcha pyproject-api provides an API to interact with build backends, but it does not include the build backends themselves. You must ensure that the build backend specified in `pyproject.toml` (e.g., `setuptools`, `hatchling`, `flit_core`) is correctly declared in `build-system.requires` and is installable/provisionable within the environment where `pyproject-api` operates.
- gotcha Misconfigured or invalid `pyproject.toml` files can lead to obscure errors, as the issues might originate from the build backend and be re-reported through `pyproject-api` without clear context on the initial problem within the TOML structure itself.
Install
-
pip install pyproject-api
Imports
- Frontend
from pyproject_api import Frontend
- BackendFailed
from pyproject_api import BackendFailed
Quickstart
import sys
from pathlib import Path
import tempfile
import shutil
# Create a temporary project directory
temp_dir = Path(tempfile.mkdtemp())
project_root = temp_dir / "my_project"
project_root.mkdir()
# Create a minimal pyproject.toml
pyproject_toml_content = '''
[build-system]
requires = ["my-dummy-backend"]
build-backend = "my_dummy_backend:MyDummyBackend"
[project]
name = "my-example-package"
version = "0.0.1"
'''
(project_root / "pyproject.toml").write_text(pyproject_toml_content)
# Create a dummy build backend module (my_dummy_backend.py)
backend_module_content = '''
from pathlib import Path
class MyDummyBackend:
def __init__(self, directory): # pyproject-api passes the project directory
self.directory = Path(directory)
self.name = "my-example-package"
def get_requires_for_build_wheel(self, config_settings=None):
return []
def build_wheel(self, wheel_directory, config_settings=None, metadata_directory=None):
# Simulate building a wheel file
wheel_name = f"{self.name}-0.0.1-py3-none-any.whl"
(Path(wheel_directory) / wheel_name).touch()
return wheel_name
'''
(project_root / "my_dummy_backend.py").write_text(backend_module_content)
from pyproject_api import Frontend, BackendFailed
print(f"Created temporary project at: {project_root}")
try:
# Initialize the Frontend to interact with our dummy project
# backend_paths is crucial to make 'my_dummy_backend' importable
frontend = Frontend(
root=project_root,
backend_paths=[project_root],
backend_module="my_dummy_backend",
backend_obj="MyDummyBackend", # The class name to instantiate
requires=[], # Our dummy backend doesn't have build requirements itself
reuse_backend=False
)
# Get build requirements (should be empty for our dummy backend)
build_requirements = frontend.get_requires_for_build_wheel()
print(f"Build requirements reported by backend: {build_requirements.requires}")
# Build a wheel
build_dir = temp_dir / "dist"
build_dir.mkdir()
print(f"Attempting to build wheel into: {build_dir}")
wheel_result = frontend.build_wheel(build_dir)
print(f"Successfully built wheel: {wheel_result.path.name}")
print(f"Wheel file location: {wheel_result.path}")
except BackendFailed as e:
print(f"Backend operation failed: {e.exc_msg}")
print(f"Backend stdout: {e.out}")
print(f"Backend stderr: {e.err}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
finally:
# Clean up the temporary directory
if temp_dir.exists():
shutil.rmtree(temp_dir)
print(f"Cleaned up temporary directory: {temp_dir}")