pax_global_header 0000666 0000000 0000000 00000000064 15252453774 0014530 g ustar 00root root 0000000 0000000 52 comment=184c9f56a87ede1276a5b33f5024204adb792cd2
pypa-pyproject-hooks-184c9f5/ 0000775 0000000 0000000 00000000000 15252453774 0016204 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/.bumpversion.cfg 0000664 0000000 0000000 00000000150 15252453774 0021310 0 ustar 00root root 0000000 0000000 [bumpversion]
current_version = 1.3.3
commit = True
[bumpversion:file:src/pyproject_hooks/__init__.py]
pypa-pyproject-hooks-184c9f5/.git-blame-ignore-revs 0000664 0000000 0000000 00000000051 15252453774 0022300 0 ustar 00root root 0000000 0000000 1731550536179b1289db4b8ae01d1b3673162aae
pypa-pyproject-hooks-184c9f5/.github/ 0000775 0000000 0000000 00000000000 15252453774 0017544 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/.github/dependabot.yml 0000664 0000000 0000000 00000000404 15252453774 0022372 0 ustar 00root root 0000000 0000000 version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
actions:
patterns:
- "*"
pypa-pyproject-hooks-184c9f5/.github/workflows/ 0000775 0000000 0000000 00000000000 15252453774 0021601 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/.github/workflows/ci.yml 0000664 0000000 0000000 00000004003 15252453774 0022714 0 ustar 00root root 0000000 0000000 name: CI
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
on: [push, pull_request]
env:
FORCE_COLOR: 1
permissions:
contents: read
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.x"
- run: pipx run nox -s lint
tests:
name: Tests (${{ matrix.os }}, ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
os: [Ubuntu, macOS, Windows]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: ${{ matrix.python-version }}-dev
cache: pip
cache-dependency-path: "dev-requirements.txt"
- run: pipx run nox -s test-${{ matrix.python-version }}
publish:
name: Publish release
runs-on: ubuntu-latest
needs: tests
environment: release
permissions:
id-token: write # Needed for trusted publishing
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.13"
- run: |
python -m pip install -r .github/workflows/publish-requirements.txt
python -m build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
pypa-pyproject-hooks-184c9f5/.github/workflows/publish-requirements.in 0000664 0000000 0000000 00000000033 15252453774 0026314 0 ustar 00root root 0000000 0000000 --only-binary :all:
build
pypa-pyproject-hooks-184c9f5/.github/workflows/publish-requirements.txt 0000664 0000000 0000000 00000001245 15252453774 0026533 0 ustar 00root root 0000000 0000000 #
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --generate-hashes --output-file=.github/workflows/publish-requirements.txt --pip-args='--only-binary :all:' .github/workflows/publish-requirements.in
#
--only-binary :all:
build==1.4.2 \
--hash=sha256:7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88
# via -r .github/workflows/publish-requirements.in
packaging==26.0 \
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
# via build
pyproject-hooks==1.2.0 \
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
# via build
pypa-pyproject-hooks-184c9f5/.gitignore 0000664 0000000 0000000 00000000131 15252453774 0020167 0 ustar 00root root 0000000 0000000 __pycache__/
*.pyc
/dist/
.nox
.pytest_cache
docs/_build/
*.egg-info/
.coverage
htmlcov/
pypa-pyproject-hooks-184c9f5/.pre-commit-config.yaml 0000664 0000000 0000000 00000001005 15252453774 0022461 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.2
hooks:
- id: ruff
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.1.1
hooks:
- id: mypy
exclude: tests/samples
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-case-conflict
- id: end-of-file-fixer
- id: trailing-whitespace
pypa-pyproject-hooks-184c9f5/.readthedocs.yml 0000664 0000000 0000000 00000000306 15252453774 0021271 0 ustar 00root root 0000000 0000000 version: 2
build:
os: ubuntu-22.04
tools:
python: "3.8"
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
pypa-pyproject-hooks-184c9f5/LICENSE 0000664 0000000 0000000 00000002071 15252453774 0017211 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pypa-pyproject-hooks-184c9f5/README.rst 0000664 0000000 0000000 00000001243 15252453774 0017673 0 ustar 00root root 0000000 0000000 ``pyproject-hooks``
===================
This is a low-level library for calling build-backends in ``pyproject.toml``-based project. It provides the basic functionality to help write tooling that generates distribution files from Python projects.
If you want a tool that builds Python packages, you'll want to use https://github.com/pypa/build instead. This is an underlying piece for `pip`, `build` and other "build frontends" use to call "build backends" within them.
You can read more in the `documentation `_.
Note: The ``pep517`` project has been replaced by this project (low level) and the ``build`` project (high level).
pypa-pyproject-hooks-184c9f5/dev-requirements.txt 0000664 0000000 0000000 00000000114 15252453774 0022240 0 ustar 00root root 0000000 0000000 pytest
flake8
testpath
setuptools>=30
tomli >=1.1.0 ; python_version<'3.11'
pypa-pyproject-hooks-184c9f5/docs/ 0000775 0000000 0000000 00000000000 15252453774 0017134 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/docs/changelog.rst 0000664 0000000 0000000 00000010747 15252453774 0021626 0 ustar 00root root 0000000 0000000 Changelog
=========
v1.3.3
------
- No code changes from 1.3. The git tags and the version number got out of sync
while fixing an issue with the Github actions workflow to publish the release.
1.3.3 exists only to get them back in sync without reassigning any tags.
v1.3
----
- Python 3.8 or above is now required (:pr:`214`).
- UserWarnings from the backend are now captured and re-emitted in the frontend
process as a new category ``BuildBackendWarning``, so that frontends can
control how they are shown to users (:pr:`213`).
- Show the traceback from the backend on a ``BackendUnavailable`` error (:pr:`219`).
- Fix the return type annotation for ``prepare_metadata_for_build_wheel``
(:pr:`209`).
- Fix removing the internal ``_in_process`` directory from ``sys.path`` where
the install location includes a symlink (:pr:`230`).
- Use ``os.path.commonpath`` instead of the deprecated ``commonprefix`` to
validate the location of in-tree backends.
- Fix in-tree backends in locations containing the path separator character -
``:`` on Posix platforms, ``;`` on Windows (:pr:`232`).
- Fix finding distribution metadata with ``importlib.metadata`` in the backend
when an in-tree backend is used (:pr:`212`).
- ``pyproject_hooks`` can be built from source using flit_core 4.x (:pr:`233`).
- Document that the ``hooks.subprocess_runner`` context manager API is not
thread-safe (:pr:227`)
v1.2
----
- Improve interoperability with ``importlib.metadata``, fixing a regression
in setuptools compatibility in 1.1 (PR :pr:`199`).
- Clean up the ``_in_process`` directory inside the package from ``sys.path``
before imporing the backend (PR :pr:`193`).
v1.1
----
- Add type annotations to the public API.
- More careful handling of the ``backend-path`` key from ``pyproject.toml``.
Previous versions would load the backend and then check that it was loaded
from the specified path; the new version only loads it from the specified path.
The ``BackendInvalid`` exception is now a synonym for :exc:`BackendUnavailable`,
and code should move to using the latter name.
v1.0
----
- Rename package to ``pyproject_hooks`` (from ``pep517``).
- Remove deprecated modules (``.build``, ``.check`` and ``.envbuild``).
Use the `build `_ project
instead for this higher-level functionality of setting up a temporary build
environment.
- Require Python 3.7 or above.
- Use ``tomllib`` from the standard library on Python 3.11. ``pyproject_hooks``
now has no external dependencies when installed in Python 3.11.
- Avoid chaining exceptions when using the fallback implementation for
:meth:`.prepare_metadata_for_build_wheel`.
- Fix propagating error message for :exc:`.BackendInvalid` errors.
v0.13
-----
- Remove support for end-of-life Pythons. Now requires Python3.6+.
- Remove support for ``toml`` package. Now requires ``tomli``.
- Rely on preferred "files" API on Python 3.9 and later (:issue:`140`).
v0.12
-----
- Add method for pip to check if build_editable hook is supported.
This is a private API for now.
v0.11.1
-------
- Fix DeprecationWarning in tomli.
v0.11
-----
- Support editable hooks (`PEP 660 `_).
- Use the TOML 1.0 compliant ``tomli`` parser module on Python 3.6 and above.
- Ensure TOML files are always read as UTF-8.
- Switch CI to Github actions.
v0.10
-----
- Avoid shadowing imports such as ``colorlog`` in the backend, by moving the
``_in_process.py`` script into a separate subpackage.
- Issue warnings when using the deprecated ``pep517.build`` and
``pep517.check`` modules at the command line. See the `PyPA build project
`_ for a replacement.
- Allow building with flit_core 3.x.
- Prefer the standard library ``unittest.mock`` to ``mock`` for tests on Python
3.6 and above.
v0.9.1
------
- Silence some static analysis warnings.
v0.9
-----
- Deprecated the higher level API which handles creating an environment and
installing build dependencies. This was not very complete, and the `PyPA build
project `_ is designed for this use case.
- New ``python_executable`` parameter for :class:`.BuildBackendHookCaller` to run hooks
with a different Python interpreter.
- Fix for locating the script to run in the subprocess in some scenarios.
- Fix example in README to get ``build-backend`` correctly.
- Created `documentation on Read the Docs
`__
- Various minor improvements to testing.
pypa-pyproject-hooks-184c9f5/docs/conf.py 0000664 0000000 0000000 00000003403 15252453774 0020433 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "pyproject-hooks"
copyright = "2020, Thomas Kluyver"
author = "Thomas Kluyver"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx_issues",
]
toc_object_entries_show_parents = "hide"
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_title = "pyproject-hooks"
html_theme = "furo"
# -- Options for autodoc -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration
autodoc_class_signature = "separated"
autodoc_member_order = "bysource"
autodoc_preserve_defaults = True
autodoc_typehints = "description"
autodoc_typehints_description_target = "documented_params"
# -- Options for intersphinx -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}
# -- Options for extlinks ----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html#configuration
issues_github_path = "pypa/pyproject-hooks"
pypa-pyproject-hooks-184c9f5/docs/index.rst 0000664 0000000 0000000 00000002426 15252453774 0021001 0 ustar 00root root 0000000 0000000 pyproject-hooks
===============
This is a low-level library for calling build-backends in ``pyproject.toml``-based project. It provides the basic functionality to help write tooling that generates distribution files from Python projects.
What this library provides
--------------------------
- Logic for calling build-backend hooks in a subprocess.
- Automated fallbacks for optional build-backend hooks.
- Control over how the subprocesses are run.
What this library does not provide
----------------------------------
- Environment management / isolation
It is the responsibility of the caller to setup an environment containing the build dependencies, and provide a ``python_executable`` to this library to use that environment. It is also the caller's responsibility to install any "additional" dependencies before calls to certain hooks.
- A command line interface
This is a low-level library that would simplify writing such an interface but providing a command line interface is out-of-scope for this project.
These roles are covered by tools like `build `_, which use this library.
.. toctree::
:caption: API reference
:hidden:
pyproject_hooks
.. toctree::
:caption: Project
:hidden:
changelog
release-process
pypa-pyproject-hooks-184c9f5/docs/pyproject_hooks.rst 0000664 0000000 0000000 00000003722 15252453774 0023114 0 ustar 00root root 0000000 0000000 ``pyproject_hooks``
===================
.. autoclass:: pyproject_hooks.BuildBackendHookCaller
:special-members: __init__
:members:
.. _Subprocess Runners:
Subprocess Runners
------------------
A subprocess runner is a function that is expected to execute the subprocess. They are typically used for controlling how output is presented from the subprocess.
The subprocess runners provided out-of-the-box with this library are:
.. autofunction:: pyproject_hooks.default_subprocess_runner(...)
.. autofunction:: pyproject_hooks.quiet_subprocess_runner(...)
Custom Subprocess Runners
^^^^^^^^^^^^^^^^^^^^^^^^^
It is possible to provide a custom subprocess runner, that behaves differently. The expected protocol for subprocess runners is as follows:
.. function:: subprocess_runner_protocol(cmd, cwd=None, extra_environ=None)
:noindex:
:param cmd: The command and arguments to execute, as would be passed to :func:`subprocess.run`.
:type cmd: typing.Sequence[str]
:param cwd: The working directory that must be used for the subprocess.
:type cwd: typing.Optional[str]
:param extra_environ: Mapping of environment variables (name to value) which must be set for the subprocess execution.
:type extra_environ: typing.Optional[typing.Mapping[str, str]]
:rtype: None
Since this codebase is currently Python 3.7-compatible, the type annotation for this protocol is only available to type checkers. To annotate a variable as a subprocess runner, you can do something along the lines of:
.. code-block:: python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pyproject_hooks import SubprocessRunner
# Example usage
def build(awesome_runner: "SubprocessRunner") -> None:
...
Exceptions
----------
Each exception has public attributes with the same name as their constructors.
.. autoexception:: pyproject_hooks.BackendUnavailable
.. autoexception:: pyproject_hooks.HookMissing
.. autoexception:: pyproject_hooks.UnsupportedOperation
pypa-pyproject-hooks-184c9f5/docs/release-process.rst 0000664 0000000 0000000 00000000605 15252453774 0022763 0 ustar 00root root 0000000 0000000 ===============
Release Process
===============
Actual mechanics of making the release:
- Update the changelog manually, and commit the changes.
- Install :pypi:`bump2version`::
pip install bump2version
- Run it to bump the version strings within the project, like::
bump2version minor
- Push the commit and tags to GitHub.
A GitHub action will upload the release to PyPI.
pypa-pyproject-hooks-184c9f5/docs/requirements.txt 0000664 0000000 0000000 00000000032 15252453774 0022413 0 ustar 00root root 0000000 0000000 furo
sphinx
sphinx-issues
pypa-pyproject-hooks-184c9f5/noxfile.py 0000664 0000000 0000000 00000002036 15252453774 0020223 0 ustar 00root root 0000000 0000000 """Automation using nox."""
import nox
nox.options.reuse_existing_virtualenvs = True
@nox.session(
python=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3"], venv_backend="venv"
)
def test(session: nox.Session) -> None:
session.install("-r", "dev-requirements.txt")
session.install(".")
session.run("pytest", *session.posargs)
@nox.session
def docs(session: nox.Session) -> None:
session.install("-e", ".")
session.install("-r", "docs/requirements.txt")
session.run(
"sphinx-build",
"-W",
"-d=docs/_build/doctrees/html",
"-b=dirhtml",
"docs/",
"docs/_build/html",
)
@nox.session
def lint(session: nox.Session) -> None:
session.install("pre-commit")
if session.posargs:
args = session.posargs + ["--all-files"]
else:
args = ["--all-files", "--show-diff-on-failure"]
session.run("pre-commit", "run", *args)
@nox.session
def release(session: nox.Session) -> None:
session.install("flit")
session.run("flit", "publish")
pypa-pyproject-hooks-184c9f5/pyproject.toml 0000664 0000000 0000000 00000001632 15252453774 0021122 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["flit_core >=3.11,<5"]
build-backend = "flit_core.buildapi"
[project]
name = "pyproject_hooks"
authors = [
{name = "Thomas Kluyver", email = "thomas@kluyver.me.uk"},
]
readme = "README.rst"
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.8"
dependencies = []
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
]
dynamic = ["version", "description"]
[project.urls]
Source = "https://github.com/pypa/pyproject-hooks"
Documentation = "https://pyproject-hooks.readthedocs.io/"
Changelog = "https://pyproject-hooks.readthedocs.io/en/latest/changelog.html"
[tool.flit.sdist]
include = [
"tests/",
"docs/",
"dev-requirements.txt",
"noxfile.py",
"pytest.ini",
]
exclude = [
"docs/_build"
]
[tool.ruff]
src = ["src", "tests"]
[tool.ruff.isort]
known-first-party = ["pyproject_hooks", "tests"]
pypa-pyproject-hooks-184c9f5/pytest.ini 0000664 0000000 0000000 00000000201 15252453774 0020226 0 ustar 00root root 0000000 0000000 [pytest]
addopts =
--strict-config
--strict-markers
xfail_strict = True
junit_family = xunit2
filterwarnings =
error
pypa-pyproject-hooks-184c9f5/src/ 0000775 0000000 0000000 00000000000 15252453774 0016773 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/ 0000775 0000000 0000000 00000000000 15252453774 0022215 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/__init__.py 0000664 0000000 0000000 00000001347 15252453774 0024333 0 ustar 00root root 0000000 0000000 """Wrappers to call pyproject.toml-based build backend hooks.
"""
from typing import TYPE_CHECKING
from ._impl import (
BuildBackendWarning,
BackendUnavailable,
BuildBackendHookCaller,
HookMissing,
UnsupportedOperation,
default_subprocess_runner,
quiet_subprocess_runner,
)
__version__ = "1.3.3"
__all__ = [
"BuildBackendWarning",
"BackendUnavailable",
"BackendInvalid",
"HookMissing",
"UnsupportedOperation",
"default_subprocess_runner",
"quiet_subprocess_runner",
"BuildBackendHookCaller",
]
BackendInvalid = BackendUnavailable # Deprecated alias, previously a separate exception
if TYPE_CHECKING:
from ._impl import SubprocessRunner
__all__ += ["SubprocessRunner"]
pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/_impl.py 0000664 0000000 0000000 00000036644 15252453774 0023704 0 ustar 00root root 0000000 0000000 import json
import os
import sys
import tempfile
import subprocess
from contextlib import contextmanager
from os.path import abspath
from os.path import join as pjoin
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Sequence
import warnings
from ._in_process import _in_proc_script_path
if TYPE_CHECKING:
from typing import Protocol
class SubprocessRunner(Protocol):
"""A protocol for the subprocess runner."""
def __call__(
self,
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
...
def write_json(obj: Mapping[str, Any], path: str, **kwargs) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, **kwargs)
def read_json(path: str) -> Mapping[str, Any]:
with open(path, encoding="utf-8") as f:
return json.load(f)
class BuildBackendWarning(UserWarning):
"""Will be emitted for every UserWarning emitted by the hook process."""
class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process."""
def __init__(
self,
traceback: str,
message: Optional[str] = None,
backend_name: Optional[str] = None,
backend_path: Optional[Sequence[str]] = None,
) -> None:
# Preserving arg order for the sake of API backward compatibility.
self.backend_name = backend_name
self.backend_path = backend_path
self.traceback = traceback or ""
super().__init__(
f"{message or 'Error while importing backend'}"
+ (f"\n\nTraceback:\n{self.traceback}" if self.traceback else "")
)
class HookMissing(Exception):
"""Will be raised on missing hooks (if a fallback can't be used)."""
def __init__(self, hook_name: str) -> None:
super().__init__(hook_name)
self.hook_name = hook_name
class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""
def __init__(self, traceback: str) -> None:
self.traceback = traceback
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.
This uses :func:`subprocess.run` under the hood.
"""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
subprocess.run(cmd, cwd=cwd, env=env, check=True)
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.
This uses :func:`subprocess.run` with `stdout=PIPE, stderr=STDOUT` under the hood.
"""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
# Capturing output in case it fails (available on the exception object)
subprocess.run(
cmd,
cwd=cwd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
)
def norm_and_check(source_tree: str, requested: str) -> str:
"""Normalise and check a backend path.
Ensure that the requested backend path is specified as a relative path,
and resolves to a location under the given source tree.
Return an absolute version of the requested path.
"""
if os.path.isabs(requested):
raise ValueError("paths must be relative")
abs_source = os.path.abspath(source_tree)
abs_requested = os.path.normpath(os.path.join(abs_source, requested))
norm_source = os.path.normcase(abs_source)
norm_requested = os.path.normcase(abs_requested)
if os.path.commonpath([norm_source, norm_requested]) != norm_source:
raise ValueError("paths must be inside source tree")
return abs_requested
class BuildBackendHookCaller:
"""A wrapper to call the build backend hooks for a source directory."""
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:
"""
:param source_dir: The source directory to invoke the build backend for
:param build_backend: The build backend spec
:param backend_path: Additional path entries for the build backend spec
:param runner: The :ref:`subprocess runner ` to use
:param python_executable:
The Python executable used to invoke the build backend
"""
if runner is None:
runner = default_subprocess_runner
self.source_dir = abspath(source_dir)
self.build_backend = build_backend
if backend_path:
backend_path = [norm_and_check(self.source_dir, p) for p in backend_path]
self.backend_path = backend_path
self._subprocess_runner = runner
if not python_executable:
python_executable = sys.executable
self.python_executable = python_executable
@contextmanager
def subprocess_runner(self, runner: "SubprocessRunner") -> Iterator[None]:
"""A context manager for temporarily overriding the default
:ref:`subprocess runner `.
:param runner: The new subprocess runner to use within the context.
.. warning::
This context manager temporarily mutates the hook caller instance and
is not thread-safe. Callers that need to run hooks concurrently
should create separate :class:`BuildBackendHookCaller` instances, or
provide the subprocess runner when constructing the caller.
.. code-block:: python
hook_caller = BuildBackendHookCaller(...)
with hook_caller.subprocess_runner(quiet_subprocess_runner):
...
"""
prev = self._subprocess_runner
self._subprocess_runner = runner
try:
yield
finally:
self._subprocess_runner = prev
def _supported_features(self) -> Sequence[str]:
"""Return the list of optional features supported by the backend."""
return self._call_hook("_supported_features", {})
def get_requires_for_build_wheel(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""Get additional dependencies required for building a wheel.
:param config_settings: The configuration settings for the build backend
:returns: A list of :pep:`dependency specifiers <508>`.
.. admonition:: Fallback
If the build backend does not defined a hook with this name, an
empty list will be returned.
"""
return self._call_hook(
"get_requires_for_build_wheel", {"config_settings": config_settings}
)
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.
:param metadata_directory: The directory to write the metadata to
:param config_settings: The configuration settings for the build backend
:param _allow_fallback:
Whether to allow the fallback to building a wheel and extracting
the metadata from it. Should be passed as a keyword argument only.
:returns: Name of the newly created subfolder within
``metadata_directory``, containing the metadata.
.. admonition:: Fallback
If the build backend does not define a hook with this name and
``_allow_fallback`` is truthy, the backend will be asked to build a
wheel via the ``build_wheel`` hook and the dist-info extracted from
that will be returned.
"""
return self._call_hook(
"prepare_metadata_for_build_wheel",
{
"metadata_directory": abspath(metadata_directory),
"config_settings": config_settings,
"_allow_fallback": _allow_fallback,
},
)
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.
:param wheel_directory: The directory to write the wheel to
:param config_settings: The configuration settings for the build backend
:param metadata_directory: The directory to reuse existing metadata from
:returns:
The name of the newly created wheel within ``wheel_directory``.
.. admonition:: Interaction with fallback
If the ``build_wheel`` hook was called in the fallback for
:meth:`prepare_metadata_for_build_wheel`, the build backend would
not be invoked. Instead, the previously built wheel will be copied
to ``wheel_directory`` and the name of that file will be returned.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
return self._call_hook(
"build_wheel",
{
"wheel_directory": abspath(wheel_directory),
"config_settings": config_settings,
"metadata_directory": metadata_directory,
},
)
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.
:param config_settings: The configuration settings for the build backend
:returns: A list of :pep:`dependency specifiers <508>`.
.. admonition:: Fallback
If the build backend does not defined a hook with this name, an
empty list will be returned.
"""
return self._call_hook(
"get_requires_for_build_editable", {"config_settings": config_settings}
)
def prepare_metadata_for_build_editable(
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.
:param metadata_directory: The directory to write the metadata to
:param config_settings: The configuration settings for the build backend
:param _allow_fallback:
Whether to allow the fallback to building a wheel and extracting
the metadata from it. Should be passed as a keyword argument only.
:returns: Name of the newly created subfolder within
``metadata_directory``, containing the metadata.
.. admonition:: Fallback
If the build backend does not define a hook with this name and
``_allow_fallback`` is truthy, the backend will be asked to build a
wheel via the ``build_editable`` hook and the dist-info
extracted from that will be returned.
"""
return self._call_hook(
"prepare_metadata_for_build_editable",
{
"metadata_directory": abspath(metadata_directory),
"config_settings": config_settings,
"_allow_fallback": _allow_fallback,
},
)
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.
:param wheel_directory: The directory to write the wheel to
:param config_settings: The configuration settings for the build backend
:param metadata_directory: The directory to reuse existing metadata from
:returns:
The name of the newly created wheel within ``wheel_directory``.
.. admonition:: Interaction with fallback
If the ``build_editable`` hook was called in the fallback for
:meth:`prepare_metadata_for_build_editable`, the build backend
would not be invoked. Instead, the previously built wheel will be
copied to ``wheel_directory`` and the name of that file will be
returned.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
return self._call_hook(
"build_editable",
{
"wheel_directory": abspath(wheel_directory),
"config_settings": config_settings,
"metadata_directory": metadata_directory,
},
)
def get_requires_for_build_sdist(
self,
config_settings: Optional[Mapping[str, Any]] = None,
) -> Sequence[str]:
"""Get additional dependencies required for building an sdist.
:returns: A list of :pep:`dependency specifiers <508>`.
"""
return self._call_hook(
"get_requires_for_build_sdist", {"config_settings": config_settings}
)
def build_sdist(
self,
sdist_directory: str,
config_settings: Optional[Mapping[str, Any]] = None,
) -> str:
"""Build an sdist from this project.
:returns:
The name of the newly created sdist within ``sdist_directory``.
"""
return self._call_hook(
"build_sdist",
{
"sdist_directory": abspath(sdist_directory),
"config_settings": config_settings,
},
)
def _call_hook(self, hook_name: str, kwargs: Mapping[str, Any]) -> Any:
extra_environ = {"_PYPROJECT_HOOKS_BUILD_BACKEND": self.build_backend}
if self.backend_path:
extra_environ["_PYPROJECT_HOOKS_BACKEND_PATH_JSON"] = json.dumps(
self.backend_path
)
with tempfile.TemporaryDirectory() as td:
hook_input = {"kwargs": kwargs}
write_json(hook_input, pjoin(td, "input.json"), indent=2)
# Run the hook in a subprocess
with _in_proc_script_path() as script:
python = self.python_executable
self._subprocess_runner(
[python, abspath(str(script)), hook_name, td],
cwd=self.source_dir,
extra_environ=extra_environ,
)
data = read_json(pjoin(td, "output.json"))
if data.get("unsupported"):
raise UnsupportedOperation(data.get("traceback", ""))
if data.get("no_backend"):
raise BackendUnavailable(
data.get("traceback", ""),
message=data.get("backend_error", ""),
backend_name=self.build_backend,
backend_path=self.backend_path,
)
if data.get("hook_missing"):
raise HookMissing(data.get("missing_hook_name") or hook_name)
for w in data.get("warnings", []):
warnings.warn_explicit(
message=w["message"],
category=BuildBackendWarning,
filename=w["filename"],
lineno=w["lineno"],
)
return data["return_val"]
pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/_in_process/ 0000775 0000000 0000000 00000000000 15252453774 0024520 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/_in_process/__init__.py 0000664 0000000 0000000 00000001055 15252453774 0026632 0 ustar 00root root 0000000 0000000 """This is a subpackage because the directory is on sys.path for _in_process.py
The subpackage should stay as empty as possible to avoid shadowing modules that
the backend might import.
"""
import importlib.resources as resources
try:
resources.files
except AttributeError:
# Python 3.8 compatibility
def _in_proc_script_path():
return resources.path(__package__, "_in_process.py")
else:
def _in_proc_script_path():
return resources.as_file(
resources.files(__package__).joinpath("_in_process.py")
)
pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/_in_process/_in_process.py 0000664 0000000 0000000 00000031070 15252453774 0027376 0 ustar 00root root 0000000 0000000 """This is invoked in a subprocess to call the build backend hooks.
It expects:
- Command line args: hook_name, control_dir
- Environment variables:
_PYPROJECT_HOOKS_BUILD_BACKEND=entry.point:spec
_PYPROJECT_HOOKS_BACKEND_PATH_JSON=paths (JSON encoded)
- control_dir/input.json:
- {"kwargs": {...}}
Results:
- control_dir/output.json
- {"return_val": ...}
"""
import json
import os
import os.path
import re
import shutil
import sys
import traceback
from glob import glob
from importlib import import_module
from importlib.machinery import PathFinder
from os.path import join as pjoin
import warnings
# This file is run as a script, and `import wrappers` is not zip-safe, so we
# include write_json() and read_json() from wrappers.py.
def write_json(obj, path, **kwargs):
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
class BackendUnavailable(Exception):
"""Raised if we cannot import the backend"""
def __init__(self, message, traceback=None):
super().__init__(message)
self.message = message
self.traceback = traceback
class HookMissing(Exception):
"""Raised if a hook is missing and we are not executing the fallback"""
def __init__(self, hook_name=None):
super().__init__(hook_name)
self.hook_name = hook_name
def _build_backend():
"""Find and load the build backend"""
backend_path_json = os.environ.get("_PYPROJECT_HOOKS_BACKEND_PATH_JSON")
ep = os.environ["_PYPROJECT_HOOKS_BUILD_BACKEND"]
mod_path, _, obj_path = ep.partition(":")
if backend_path_json:
extra_pathitems = json.loads(backend_path_json)
else:
extra_pathitems = []
if extra_pathitems:
# Ensure in-tree backend directories have the highest priority when importing.
sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path))
try:
obj = import_module(mod_path)
except ImportError:
msg = f"Cannot import {mod_path!r}"
raise BackendUnavailable(msg, traceback.format_exc())
if obj_path:
for path_part in obj_path.split("."):
obj = getattr(obj, path_part)
return obj
class _BackendPathFinder:
"""Implements the MetaPathFinder interface to locate modules in ``backend-path``.
Since the environment provided by the frontend can contain all sorts of
MetaPathFinders, the only way to ensure the backend is loaded from the
right place is to prepend our own.
"""
def __init__(self, backend_path, backend_module):
self.backend_path = backend_path
self.backend_module = backend_module
self.backend_parent, _, _ = backend_module.partition(".")
def find_spec(self, fullname, _path, _target=None):
if "." in fullname:
# Rely on importlib to find nested modules based on parent's path
return None
# Ignore other items in _path or sys.path and use backend_path instead:
spec = PathFinder.find_spec(fullname, path=self.backend_path)
if spec is None and fullname == self.backend_parent:
# According to the spec, the backend MUST be loaded from backend-path.
# Therefore, we can halt the import machinery and raise a clean error.
msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}"
raise BackendUnavailable(msg)
return spec
if sys.version_info >= (3, 8):
def find_distributions(self, context=None):
# Delayed import: Python 3.7 does not contain importlib.metadata
from importlib.metadata import DistributionFinder, MetadataPathFinder
name = context.name if context else None
context = DistributionFinder.Context(name=name, path=self.backend_path)
return MetadataPathFinder.find_distributions(context=context)
def _supported_features():
"""Return the list of options features supported by the backend.
Returns a list of strings.
The only possible value is 'build_editable'.
"""
backend = _build_backend()
features = []
if hasattr(backend, "build_editable"):
features.append("build_editable")
return features
def get_requires_for_build_wheel(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_wheel
except AttributeError:
return []
else:
return hook(config_settings)
def get_requires_for_build_editable(config_settings):
"""Invoke the optional get_requires_for_build_editable hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_editable
except AttributeError:
return []
else:
return hook(config_settings)
def prepare_metadata_for_build_wheel(
metadata_directory, config_settings, _allow_fallback
):
"""Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined,
unless _allow_fallback is False in which case HookMissing is raised.
"""
backend = _build_backend()
try:
hook = backend.prepare_metadata_for_build_wheel
except AttributeError:
if not _allow_fallback:
raise HookMissing()
else:
return hook(metadata_directory, config_settings)
# fallback to build_wheel outside the try block to avoid exception chaining
# which can be confusing to users and is not relevant
whl_basename = backend.build_wheel(metadata_directory, config_settings)
return _get_wheel_metadata_from_wheel(
whl_basename, metadata_directory, config_settings
)
def prepare_metadata_for_build_editable(
metadata_directory, config_settings, _allow_fallback
):
"""Invoke optional prepare_metadata_for_build_editable
Implements a fallback by building an editable wheel if the hook isn't
defined, unless _allow_fallback is False in which case HookMissing is
raised.
"""
backend = _build_backend()
try:
hook = backend.prepare_metadata_for_build_editable
except AttributeError:
if not _allow_fallback:
raise HookMissing()
try:
build_hook = backend.build_editable
except AttributeError:
raise HookMissing(hook_name="build_editable")
else:
whl_basename = build_hook(metadata_directory, config_settings)
return _get_wheel_metadata_from_wheel(
whl_basename, metadata_directory, config_settings
)
else:
return hook(metadata_directory, config_settings)
WHEEL_BUILT_MARKER = "PYPROJECT_HOOKS_ALREADY_BUILT_WHEEL"
def _dist_info_files(whl_zip):
"""Identify the .dist-info folder inside a wheel ZipFile."""
res = []
for path in whl_zip.namelist():
m = re.match(r"[^/\\]+-[^/\\]+\.dist-info/", path)
if m:
res.append(path)
if res:
return res
raise Exception("No .dist-info folder found in wheel")
def _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, config_settings):
"""Extract the metadata from a wheel.
Fallback for when the build backend does not
define the 'get_wheel_metadata' hook.
"""
from zipfile import ZipFile
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), "wb"):
pass # Touch marker file
whl_file = os.path.join(metadata_directory, whl_basename)
with ZipFile(whl_file) as zipf:
dist_info = _dist_info_files(zipf)
zipf.extractall(path=metadata_directory, members=dist_info)
return dist_info[0].split("/")[0]
def _find_already_built_wheel(metadata_directory):
"""Check for a wheel already built during the get_wheel_metadata hook."""
if not metadata_directory:
return None
metadata_parent = os.path.dirname(metadata_directory)
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
return None
whl_files = glob(os.path.join(metadata_parent, "*.whl"))
if not whl_files:
print("Found wheel built marker, but no .whl files")
return None
if len(whl_files) > 1:
print(
"Found multiple .whl files; unspecified behaviour. "
"Will call build_wheel."
)
return None
# Exactly one .whl file
return whl_files[0]
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
"""Invoke the mandatory build_wheel hook.
If a wheel was already built in the
prepare_metadata_for_build_wheel fallback, this
will copy it rather than rebuilding the wheel.
"""
prebuilt_whl = _find_already_built_wheel(metadata_directory)
if prebuilt_whl:
shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl)
return _build_backend().build_wheel(
wheel_directory, config_settings, metadata_directory
)
def build_editable(wheel_directory, config_settings, metadata_directory=None):
"""Invoke the optional build_editable hook.
If a wheel was already built in the
prepare_metadata_for_build_editable fallback, this
will copy it rather than rebuilding the wheel.
"""
backend = _build_backend()
try:
hook = backend.build_editable
except AttributeError:
raise HookMissing()
else:
prebuilt_whl = _find_already_built_wheel(metadata_directory)
if prebuilt_whl:
shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl)
return hook(wheel_directory, config_settings, metadata_directory)
def get_requires_for_build_sdist(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_sdist
except AttributeError:
return []
else:
return hook(config_settings)
class _DummyException(Exception):
"""Nothing should ever raise this exception"""
class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation"""
def __init__(self, traceback):
self.traceback = traceback
def build_sdist(sdist_directory, config_settings):
"""Invoke the mandatory build_sdist hook."""
backend = _build_backend()
try:
return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, "UnsupportedOperation", _DummyException):
raise GotUnsupportedOperation(traceback.format_exc())
HOOK_NAMES = {
"get_requires_for_build_wheel",
"prepare_metadata_for_build_wheel",
"build_wheel",
"get_requires_for_build_editable",
"prepare_metadata_for_build_editable",
"build_editable",
"get_requires_for_build_sdist",
"build_sdist",
"_supported_features",
}
def main():
if len(sys.argv) < 3:
sys.exit("Needs args: hook_name, control_dir")
hook_name = sys.argv[1]
control_dir = sys.argv[2]
if hook_name not in HOOK_NAMES:
sys.exit("Unknown hook: %s" % hook_name)
# Remove the parent directory from sys.path to avoid polluting the backend
# import namespace with this directory.
here = os.path.normcase(os.path.realpath(os.path.dirname(__file__)))
sys.path[:] = [
path for path in sys.path if os.path.normcase(os.path.realpath(path)) != here
]
hook = globals()[hook_name]
hook_input = read_json(pjoin(control_dir, "input.json"))
with warnings.catch_warnings(record=True) as captured_warnings:
json_out = {"unsupported": False, "return_val": None}
try:
json_out["return_val"] = hook(**hook_input["kwargs"])
except BackendUnavailable as e:
json_out["no_backend"] = True
json_out["traceback"] = e.traceback
json_out["backend_error"] = e.message
except GotUnsupportedOperation as e:
json_out["unsupported"] = True
json_out["traceback"] = e.traceback
except HookMissing as e:
json_out["hook_missing"] = True
json_out["missing_hook_name"] = e.hook_name or hook_name
json_out["warnings"] = [
{
"message": str(w.message),
"filename": w.filename,
"lineno": w.lineno,
}
for w in captured_warnings
if isinstance(w.category, type) and issubclass(w.category, UserWarning)
]
write_json(json_out, pjoin(control_dir, "output.json"), indent=2)
if __name__ == "__main__":
main()
pypa-pyproject-hooks-184c9f5/src/pyproject_hooks/py.typed 0000664 0000000 0000000 00000000000 15252453774 0023702 0 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/ 0000775 0000000 0000000 00000000000 15252453774 0017346 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/__init__.py 0000664 0000000 0000000 00000000000 15252453774 0021445 0 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/compat.py 0000664 0000000 0000000 00000000171 15252453774 0021202 0 ustar 00root root 0000000 0000000 __all__ = ("tomllib",)
import sys
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
pypa-pyproject-hooks-184c9f5/tests/samples/ 0000775 0000000 0000000 00000000000 15252453774 0021012 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/ 0000775 0000000 0000000 00000000000 15252453774 0023674 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/buildsys.py 0000664 0000000 0000000 00000003623 15252453774 0026110 0 ustar 00root root 0000000 0000000 """This is a very stupid backend for testing purposes.
Don't use this for any real code.
"""
import shutil
import tarfile
from glob import glob
from os.path import join as pjoin
from zipfile import ZipFile
def get_requires_for_build_wheel(config_settings):
return ["wheelwright"]
def get_requires_for_build_editable(config_settings):
return ["wheelwright", "editables"]
def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
for distinfo in glob("*.dist-info"):
shutil.copytree(distinfo, pjoin(metadata_directory, distinfo))
prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel
def prepare_build_wheel_files(build_directory, config_settings):
shutil.copy("pyproject.toml", build_directory)
for pyfile in glob("*.py"):
shutil.copy(pyfile, build_directory)
for distinfo in glob("*.dist-info"):
shutil.copytree(distinfo, pjoin(build_directory, distinfo))
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
whl_file = "pkg1-0.5-py2.py3-none-any.whl"
with ZipFile(pjoin(wheel_directory, whl_file), "w") as zf:
for pyfile in glob("*.py"):
zf.write(pyfile)
for metadata in glob("*.dist-info/*"):
zf.write(metadata)
return whl_file
build_editable = build_wheel
def get_requires_for_build_sdist(config_settings):
return ["frog"]
class UnsupportedOperation(Exception):
pass
def build_sdist(sdist_directory, config_settings):
if config_settings.get("test_unsupported", False):
raise UnsupportedOperation
target = "pkg1-0.5.tar.gz"
with tarfile.open(
pjoin(sdist_directory, target), "w:gz", format=tarfile.PAX_FORMAT
) as tf:
def _add(relpath):
tf.add(relpath, arcname="pkg1-0.5/" + relpath)
_add("pyproject.toml")
for pyfile in glob("*.py"):
_add(pyfile)
return target
pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/buildsys_minimal.py 0000664 0000000 0000000 00000001755 15252453774 0027622 0 ustar 00root root 0000000 0000000 """Test backend defining only the mandatory hooks.
Don't use this for any real code.
"""
import tarfile
from glob import glob
from os.path import join as pjoin
from zipfile import ZipFile
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
whl_file = "pkg2-0.5-py2.py3-none-any.whl"
with ZipFile(pjoin(wheel_directory, whl_file), "w") as zf:
for pyfile in glob("*.py"):
zf.write(pyfile)
for metadata in glob("*.dist-info/*"):
zf.write(metadata)
return whl_file
def build_sdist(sdist_directory, config_settings):
target = "pkg2-0.5.tar.gz"
with tarfile.open(
pjoin(sdist_directory, target), "w:gz", format=tarfile.PAX_FORMAT
) as tf:
def _add(relpath):
tf.add(relpath, arcname="pkg2-0.5/" + relpath)
_add("pyproject.toml")
for pyfile in glob("*.py"):
_add(pyfile)
for distinfo in glob("*.dist-info"):
_add(distinfo)
return target
pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/buildsys_minimal_editable.py 0000664 0000000 0000000 00000000134 15252453774 0031441 0 ustar 00root root 0000000 0000000 from buildsys_minimal import build_sdist, build_wheel # noqa
build_editable = build_wheel
pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/buildsys_warnings.py 0000664 0000000 0000000 00000000322 15252453774 0030011 0 ustar 00root root 0000000 0000000 """Test "backend" defining nothing other than a hook that logs a warning.
"""
import warnings
def get_requires_for_build_wheel(config_settings):
warnings.warn("this is my example warning")
return []
pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/nested/ 0000775 0000000 0000000 00000000000 15252453774 0025156 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/buildsys_pkgs/nested/buildsys.py 0000664 0000000 0000000 00000000047 15252453774 0027367 0 ustar 00root root 0000000 0000000 from ..buildsys import * # noqa: F403
pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/ 0000775 0000000 0000000 00000000000 15252453774 0025416 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/backend/ 0000775 0000000 0000000 00000000000 15252453774 0027005 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/backend/_pkg-0.0.1.dist-info/ 0000775 0000000 0000000 00000000000 15252453774 0032252 5 ustar 00root root 0000000 0000000 METADATA 0000664 0000000 0000000 00000000032 15252453774 0033271 0 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/backend/_pkg-0.0.1.dist-info Name: _pkg
Version: 0.0.1
pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/backend/intree_backend.py 0000664 0000000 0000000 00000000300 15252453774 0032305 0 ustar 00root root 0000000 0000000 from importlib.metadata import distribution
def get_requires_for_build_sdist(config_settings):
dist = distribution("setuptools") # not present in backend-path
return [dist.version]
pypa-pyproject-hooks-184c9f5/tests/samples/distribution_mismatch/pyproject.toml 0000664 0000000 0000000 00000000113 15252453774 0030325 0 ustar 00root root 0000000 0000000 [build-system]
build-backend = 'intree_backend'
backend-path = ['backend']
pypa-pyproject-hooks-184c9f5/tests/samples/path-pollution/ 0000775 0000000 0000000 00000000000 15252453774 0023771 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/path-pollution/pyproject.toml 0000664 0000000 0000000 00000000132 15252453774 0026701 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
pypa-pyproject-hooks-184c9f5/tests/samples/path-pollution/setup.py 0000664 0000000 0000000 00000000365 15252453774 0025507 0 ustar 00root root 0000000 0000000 import json
import sys
from os import environ, path
from setuptools import setup
captured_sys_path = sys.path
out = path.join(environ["TEST_POLLUTION_OUTDIR"], "out.json")
with open(out, "w") as f:
json.dump(captured_sys_path, f)
setup()
pypa-pyproject-hooks-184c9f5/tests/samples/pkg-with-warnings/ 0000775 0000000 0000000 00000000000 15252453774 0024372 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg-with-warnings/pyproject.toml 0000664 0000000 0000000 00000000116 15252453774 0027304 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["eg_buildsys"]
build-backend = "buildsys_warnings"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/ 0000775 0000000 0000000 00000000000 15252453774 0021654 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1-0.5.dist-info/ 0000775 0000000 0000000 00000000000 15252453774 0024711 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1-0.5.dist-info/LICENSE 0000664 0000000 0000000 00000002071 15252453774 0025716 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1-0.5.dist-info/METADATA 0000664 0000000 0000000 00000000375 15252453774 0026021 0 ustar 00root root 0000000 0000000 Metadata-Version: 1.2
Name: pkg1
Version: 0.5
Summary: Sample package for tests
Home-page: https://github.com/takluyver/pep517
License: UNKNOWN
Author: Thomas Kluyver
Author-email: thomas@kluyver.me.uk
Classifier: License :: OSI Approved :: MIT License
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1-0.5.dist-info/RECORD 0000664 0000000 0000000 00000000520 15252453774 0025607 0 ustar 00root root 0000000 0000000 pkg1.py,sha256=ZawKBtrxtdGEheOCWvwzGZsO8Q1OSzEzecGNsRz-ekc,52
pkg1-0.5.dist-info/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081
pkg1-0.5.dist-info/WHEEL,sha256=jxKvNaDKHDacpaLi69-vnLKkBSynwBzmMS82pipt1T0,100
pkg1-0.5.dist-info/METADATA,sha256=GDliGDwDPM11hoO79KhjyJuFgcm-TOj30gewsPNjkHw,251
pkg1-0.5.dist-info/RECORD,,
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1-0.5.dist-info/WHEEL 0000664 0000000 0000000 00000000145 15252453774 0025500 0 ustar 00root root 0000000 0000000 Wheel-Version: 1.0
Generator: buildsys 0.1
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pkg1.py 0000664 0000000 0000000 00000000064 15252453774 0023070 0 ustar 00root root 0000000 0000000 """Sample package for tests"""
__version__ = "0.5"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg1/pyproject.toml 0000664 0000000 0000000 00000000253 15252453774 0024570 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["eg_buildsys"]
build-backend = "buildsys"
[project]
description = "Factory ⸻ A code generator 🏭"
maintainers = [{name = "Łukasz Langa"}]
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/ 0000775 0000000 0000000 00000000000 15252453774 0021655 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2-0.5.dist-info/ 0000775 0000000 0000000 00000000000 15252453774 0024713 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2-0.5.dist-info/LICENSE 0000664 0000000 0000000 00000002071 15252453774 0025720 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2-0.5.dist-info/METADATA 0000664 0000000 0000000 00000000373 15252453774 0026021 0 ustar 00root root 0000000 0000000 Metadata-Version: 1.2
Name: pkg2
Version: 0.5
Summary: Sample package for tests
Home-page: https://github.com/takluyver/pkg2
License: UNKNOWN
Author: Thomas Kluyver
Author-email: thomas@kluyver.me.uk
Classifier: License :: OSI Approved :: MIT License
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2-0.5.dist-info/RECORD 0000664 0000000 0000000 00000000520 15252453774 0025611 0 ustar 00root root 0000000 0000000 pkg2.py,sha256=ZawKBtrxtdGEheOCWvwzGZsO8Q1OSzEzecGNsRz-ekc,52
pkg2-0.5.dist-info/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081
pkg2-0.5.dist-info/WHEEL,sha256=jxKvNaDKHDacpaLi69-vnLKkBSynwBzmMS82pipt1T0,100
pkg2-0.5.dist-info/METADATA,sha256=4zQxJqc4Rvnlf5Y-seXnRx8g-1FK-sjTuS0A1KP0ajk,251
pkg2-0.5.dist-info/RECORD,,
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2-0.5.dist-info/WHEEL 0000664 0000000 0000000 00000000144 15252453774 0025501 0 ustar 00root root 0000000 0000000 Wheel-Version: 1.0
Generator: flit 0.11.3
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pkg2.py 0000664 0000000 0000000 00000000064 15252453774 0023072 0 ustar 00root root 0000000 0000000 """Sample package for tests"""
__version__ = "0.5"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg2/pyproject.toml 0000664 0000000 0000000 00000000100 15252453774 0024560 0 ustar 00root root 0000000 0000000 [build-system]
requires = []
build-backend = "buildsys_minimal"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/ 0000775 0000000 0000000 00000000000 15252453774 0021656 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3-0.5.dist-info/ 0000775 0000000 0000000 00000000000 15252453774 0024715 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3-0.5.dist-info/LICENSE 0000664 0000000 0000000 00000002071 15252453774 0025722 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2017 Thomas Kluyver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3-0.5.dist-info/METADATA 0000664 0000000 0000000 00000000373 15252453774 0026023 0 ustar 00root root 0000000 0000000 Metadata-Version: 1.2
Name: pkg3
Version: 0.5
Summary: Sample package for tests
Home-page: https://github.com/takluyver/pkg3
License: UNKNOWN
Author: Thomas Kluyver
Author-email: thomas@kluyver.me.uk
Classifier: License :: OSI Approved :: MIT License
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3-0.5.dist-info/RECORD 0000664 0000000 0000000 00000000520 15252453774 0025613 0 ustar 00root root 0000000 0000000 pkg3.py,sha256=ZawKBtrxtdGEheOCWvwzGZsO8Q1OSzEzecGNsRz-ekc,52
pkg3-0.5.dist-info/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081
pkg3-0.5.dist-info/WHEEL,sha256=jxKvNaDKHDacpaLi69-vnLKkBSynwBzmMS82pipt1T0,100
pkg3-0.5.dist-info/METADATA,sha256=4zQxJqc4Rvnlf5Y-seXnRx8g-1FK-sjTuS0A1KP0ajk,251
pkg3-0.5.dist-info/RECORD,,
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3-0.5.dist-info/WHEEL 0000664 0000000 0000000 00000000144 15252453774 0025503 0 ustar 00root root 0000000 0000000 Wheel-Version: 1.0
Generator: flit 0.11.3
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pkg3.py 0000664 0000000 0000000 00000000064 15252453774 0023074 0 ustar 00root root 0000000 0000000 """Sample package for tests"""
__version__ = "0.5"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg3/pyproject.toml 0000664 0000000 0000000 00000000111 15252453774 0024563 0 ustar 00root root 0000000 0000000 [build-system]
requires = []
build-backend = "buildsys_minimal_editable"
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree/ 0000775 0000000 0000000 00000000000 15252453774 0023141 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree/backend/ 0000775 0000000 0000000 00000000000 15252453774 0024530 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree/backend/intree_backend.py 0000664 0000000 0000000 00000000130 15252453774 0030031 0 ustar 00root root 0000000 0000000 def get_requires_for_build_sdist(config_settings):
return ["intree_backend_called"]
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree/pyproject.toml 0000664 0000000 0000000 00000000113 15252453774 0026050 0 ustar 00root root 0000000 0000000 [build-system]
build-backend = 'intree_backend'
backend-path = ['backend']
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/ 0000775 0000000 0000000 00000000000 15252453774 0025001 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/backend/ 0000775 0000000 0000000 00000000000 15252453774 0026370 5 ustar 00root root 0000000 0000000 _test_bootstrap-0.0.1.dist-info/ 0000775 0000000 0000000 00000000000 15252453774 0034051 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/backend METADATA 0000664 0000000 0000000 00000000045 15252453774 0035153 0 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/backend/_test_bootstrap-0.0.1.dist-info Name: _test_bootstrap
Version: 0.0.1
entry_points.txt 0000664 0000000 0000000 00000000061 15252453774 0037344 0 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/backend/_test_bootstrap-0.0.1.dist-info [_test_backend.importlib_metadata]
hello = world
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/backend/intree_backend.py 0000664 0000000 0000000 00000000372 15252453774 0031701 0 ustar 00root root 0000000 0000000 from importlib.metadata import distribution
def get_requires_for_build_sdist(config_settings):
dist = distribution("_test_bootstrap") # discovered in backend-path
ep = next(iter(dist.entry_points))
return [ep.group, ep.name, ep.value]
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_intree_metadata/pyproject.toml 0000664 0000000 0000000 00000000113 15252453774 0027710 0 ustar 00root root 0000000 0000000 [build-system]
build-backend = 'intree_backend'
backend-path = ['backend']
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/ 0000775 0000000 0000000 00000000000 15252453774 0024503 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/backend/ 0000775 0000000 0000000 00000000000 15252453774 0026072 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/backend/intree_backend.py 0000664 0000000 0000000 00000000255 15252453774 0031403 0 ustar 00root root 0000000 0000000 # PathFinder.find_spec only take into consideration the last segment
# of the module name (not the full name).
raise Exception("This isn't the backend you are looking for")
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/backend/nested/ 0000775 0000000 0000000 00000000000 15252453774 0027354 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/backend/nested/intree_backend.py 0000664 0000000 0000000 00000000130 15252453774 0032655 0 ustar 00root root 0000000 0000000 def get_requires_for_build_sdist(config_settings):
return ["intree_backend_called"]
pypa-pyproject-hooks-184c9f5/tests/samples/pkg_nested_intree/pyproject.toml 0000664 0000000 0000000 00000000122 15252453774 0027412 0 ustar 00root root 0000000 0000000 [build-system]
build-backend = 'nested.intree_backend'
backend-path = ['backend']
pypa-pyproject-hooks-184c9f5/tests/samples/setup-py/ 0000775 0000000 0000000 00000000000 15252453774 0022600 5 ustar 00root root 0000000 0000000 pypa-pyproject-hooks-184c9f5/tests/samples/setup-py/pyproject.toml 0000664 0000000 0000000 00000000132 15252453774 0025510 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
pypa-pyproject-hooks-184c9f5/tests/samples/setup-py/setup.py 0000664 0000000 0000000 00000000143 15252453774 0024310 0 ustar 00root root 0000000 0000000 from setuptools import setup
setup(
name="pep517-test-setup-py-support",
version="1.0",
)
pypa-pyproject-hooks-184c9f5/tests/test_call_hooks.py 0000664 0000000 0000000 00000020552 15252453774 0023101 0 ustar 00root root 0000000 0000000 from contextlib import contextmanager
import json
import os
import tarfile
import zipfile
from os.path import abspath, dirname
from os.path import join as pjoin
from unittest.mock import Mock
import pytest
from testpath import assert_isfile, modified_env
from testpath.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
import pyproject_hooks._impl as impl
from pyproject_hooks import (
BackendUnavailable,
BuildBackendWarning,
BuildBackendHookCaller,
UnsupportedOperation,
default_subprocess_runner,
)
from pyproject_hooks._in_process import _in_proc_script_path as in_proc_script_path
from tests.compat import tomllib
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), "samples")
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, "buildsys_pkgs")
def get_hooks(pkg, **kwargs):
source_dir = pjoin(SAMPLES_DIR, pkg)
with open(pjoin(source_dir, "pyproject.toml"), "rb") as f:
data = tomllib.load(f)
return BuildBackendHookCaller(
source_dir, data["build-system"]["build-backend"], **kwargs
)
def test_missing_backend_gives_exception():
hooks = get_hooks("pkg1")
with modified_env({"PYTHONPATH": ""}):
msg = "Cannot import 'buildsys'"
with pytest.raises(BackendUnavailable, match=msg) as exc:
hooks.get_requires_for_build_wheel({})
assert exc.value.backend_name == "buildsys"
def test_get_requires_for_build_wheel():
hooks = get_hooks("pkg1")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_wheel({})
assert res == ["wheelwright"]
def test_get_requires_for_build_editable():
hooks = get_hooks("pkg1")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_editable({})
assert res == ["wheelwright", "editables"]
def test_get_requires_for_build_sdist():
hooks = get_hooks("pkg1")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_sdist({})
assert res == ["frog"]
def test_prepare_metadata_for_build_wheel():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
hooks.prepare_metadata_for_build_wheel(metadatadir, {})
assert_isfile(pjoin(metadatadir, "pkg1-0.5.dist-info", "METADATA"))
def test_prepare_metadata_for_build_editable():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
hooks.prepare_metadata_for_build_editable(metadatadir, {})
assert_isfile(pjoin(metadatadir, "pkg1-0.5.dist-info", "METADATA"))
def test_build_wheel():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as builddir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
whl_file = hooks.build_wheel(builddir, {})
assert whl_file.endswith(".whl")
assert os.sep not in whl_file
whl_file = pjoin(builddir, whl_file)
assert_isfile(whl_file)
assert zipfile.is_zipfile(whl_file)
def test_build_editable():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as builddir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
whl_file = hooks.build_editable(builddir, {})
assert whl_file.endswith(".whl")
assert os.sep not in whl_file
whl_file = pjoin(builddir, whl_file)
assert_isfile(whl_file)
assert zipfile.is_zipfile(whl_file)
def test_build_wheel_relpath():
hooks = get_hooks("pkg1")
with TemporaryWorkingDirectory() as builddir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
whl_file = hooks.build_wheel(".", {})
assert whl_file.endswith(".whl")
assert os.sep not in whl_file
whl_file = pjoin(builddir, whl_file)
assert_isfile(whl_file)
assert zipfile.is_zipfile(whl_file)
def test_build_sdist():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as sdistdir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
sdist = hooks.build_sdist(sdistdir, {})
assert sdist.endswith(".tar.gz")
assert os.sep not in sdist
sdist = pjoin(sdistdir, sdist)
assert_isfile(sdist)
assert tarfile.is_tarfile(sdist)
with tarfile.open(sdist) as tf:
contents = tf.getnames()
assert "pkg1-0.5/pyproject.toml" in contents
def test_build_sdist_unsupported():
hooks = get_hooks("pkg1")
with TemporaryDirectory() as sdistdir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
with pytest.raises(UnsupportedOperation):
hooks.build_sdist(sdistdir, {"test_unsupported": True})
def test_runner_replaced_on_exception(monkeypatch):
monkeypatch.setenv("PYTHONPATH", BUILDSYS_PKGS)
runner = Mock(wraps=default_subprocess_runner)
hooks = get_hooks("pkg1", runner=runner)
hooks.get_requires_for_build_wheel()
runner.assert_called_once()
runner.reset_mock()
runner2 = Mock(wraps=default_subprocess_runner)
try:
with hooks.subprocess_runner(runner2):
hooks.get_requires_for_build_wheel()
runner2.assert_called_once()
runner2.reset_mock()
raise RuntimeError()
except RuntimeError:
pass
hooks.get_requires_for_build_wheel()
runner.assert_called_once()
def test_custom_python_executable(monkeypatch, tmpdir):
monkeypatch.setenv("PYTHONPATH", BUILDSYS_PKGS)
runner = Mock(autospec=default_subprocess_runner)
hooks = get_hooks("pkg1", runner=runner, python_executable="some-python")
with hooks.subprocess_runner(runner):
with pytest.raises(FileNotFoundError):
# output.json is missing because we didn't actually run the hook
hooks.get_requires_for_build_wheel()
runner.assert_called_once()
assert runner.call_args[0][0][0] == "some-python"
def test_path_pollution():
hooks = get_hooks("path-pollution")
with TemporaryDirectory() as outdir:
with modified_env(
{
"PYTHONPATH": BUILDSYS_PKGS,
"TEST_POLLUTION_OUTDIR": outdir,
}
):
hooks.get_requires_for_build_wheel({})
with open(pjoin(outdir, "out.json")) as f:
captured_sys_path = json.load(f)
with in_proc_script_path() as path:
assert os.path.dirname(path) not in captured_sys_path
assert captured_sys_path[0] == BUILDSYS_PKGS
def test_path_pollution_symlinked_in_process(monkeypatch):
hooks = get_hooks("path-pollution")
with TemporaryDirectory() as link_root:
with in_proc_script_path() as path:
link_parent = pjoin(link_root, "alias")
os.symlink(os.path.dirname(path), link_parent)
@contextmanager
def symlinked_in_proc_script_path():
yield pjoin(link_parent, os.path.basename(path))
monkeypatch.setattr(impl, "_in_proc_script_path", symlinked_in_proc_script_path)
with TemporaryDirectory() as outdir:
with modified_env(
{
"PYTHONPATH": BUILDSYS_PKGS,
"TEST_POLLUTION_OUTDIR": outdir,
}
):
hooks.get_requires_for_build_wheel({})
with open(pjoin(outdir, "out.json")) as f:
captured_sys_path = json.load(f)
assert link_parent not in captured_sys_path
assert os.path.realpath(link_parent) not in captured_sys_path
assert captured_sys_path[0] == BUILDSYS_PKGS
def test_setup_py():
hooks = get_hooks("setup-py")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = set(hooks.get_requires_for_build_wheel({}))
# Depending on the version of setuptools, it may be both, just wheel, or neither
assert res.issubset({"setuptools", "wheel"})
@pytest.mark.parametrize(
("pkg", "expected"),
[
("pkg1", ["build_editable"]),
("pkg2", []),
("pkg3", ["build_editable"]),
],
)
def test__supported_features(pkg, expected):
hooks = get_hooks(pkg)
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks._supported_features()
assert res == expected
def test_warnings():
hooks = get_hooks("pkg-with-warnings")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
with pytest.warns(BuildBackendWarning, match="this is my example warning"):
hooks.get_requires_for_build_wheel({})
pypa-pyproject-hooks-184c9f5/tests/test_hook_fallbacks.py 0000664 0000000 0000000 00000006727 15252453774 0023735 0 ustar 00root root 0000000 0000000 from os.path import abspath, dirname
from os.path import join as pjoin
import pytest
from testpath import assert_isfile, modified_env
from testpath.tempdir import TemporaryDirectory
from pyproject_hooks import BuildBackendHookCaller, HookMissing
from tests.compat import tomllib
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), "samples")
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, "buildsys_pkgs")
def get_hooks(pkg):
source_dir = pjoin(SAMPLES_DIR, pkg)
with open(pjoin(source_dir, "pyproject.toml"), "rb") as f:
data = tomllib.load(f)
return BuildBackendHookCaller(source_dir, data["build-system"]["build-backend"])
def test_get_requires_for_build_wheel():
hooks = get_hooks("pkg2")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_wheel({})
assert res == []
def test_get_requires_for_build_editable():
hooks = get_hooks("pkg2")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_editable({})
assert res == []
def test_get_requires_for_build_sdist():
hooks = get_hooks("pkg2")
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_sdist({})
assert res == []
def test_prepare_metadata_for_build_wheel():
hooks = get_hooks("pkg2")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
hooks.prepare_metadata_for_build_wheel(metadatadir, {})
assert_isfile(pjoin(metadatadir, "pkg2-0.5.dist-info", "METADATA"))
def test_prepare_metadata_for_build_editable():
hooks = get_hooks("pkg3")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
hooks.prepare_metadata_for_build_editable(metadatadir, {})
assert_isfile(pjoin(metadatadir, "pkg3-0.5.dist-info", "METADATA"))
def test_prepare_metadata_for_build_editable_missing_build_editable():
hooks = get_hooks("pkg2")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
# pkg2's build system does not have build_editable
with pytest.raises(HookMissing) as exc_info:
hooks.prepare_metadata_for_build_editable(metadatadir, {})
e = exc_info.value
assert "build_editable" == e.hook_name
assert "build_editable" == str(e)
def test_prepare_metadata_for_build_wheel_no_fallback():
hooks = get_hooks("pkg2")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
with pytest.raises(HookMissing) as exc_info:
hooks.prepare_metadata_for_build_wheel(
metadatadir, {}, _allow_fallback=False
)
e = exc_info.value
assert "prepare_metadata_for_build_wheel" == e.hook_name
assert "prepare_metadata_for_build_wheel" in str(e)
def test_prepare_metadata_for_build_editable_no_fallback():
hooks = get_hooks("pkg2")
with TemporaryDirectory() as metadatadir:
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
with pytest.raises(HookMissing) as exc_info:
hooks.prepare_metadata_for_build_editable(
metadatadir, {}, _allow_fallback=False
)
e = exc_info.value
assert "prepare_metadata_for_build_editable" == e.hook_name
assert "prepare_metadata_for_build_editable" in str(e)
pypa-pyproject-hooks-184c9f5/tests/test_inplace_hooks.py 0000664 0000000 0000000 00000011522 15252453774 0023576 0 ustar 00root root 0000000 0000000 from inspect import cleandoc
from os import pathsep
from os.path import abspath, dirname
from os.path import join as pjoin
from pathlib import Path
from shutil import copytree
import pytest
from testpath import modified_env
from testpath.tempdir import TemporaryDirectory
from pyproject_hooks import BackendUnavailable, BuildBackendHookCaller
from tests.compat import tomllib
SAMPLES_DIR = pjoin(dirname(abspath(__file__)), "samples")
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, "buildsys_pkgs")
SOURCE_DIR = pjoin(SAMPLES_DIR, "pkg1")
def get_hooks(pkg, backend=None, path=None):
source_dir = pjoin(SAMPLES_DIR, pkg)
with open(pjoin(source_dir, "pyproject.toml"), "rb") as f:
data = tomllib.load(f)
if backend is None:
backend = data["build-system"]["build-backend"]
if path is None:
path = data["build-system"]["backend-path"]
return BuildBackendHookCaller(source_dir, backend, path)
@pytest.mark.parametrize(
"backend_path",
[
[".", "subdir"],
["../pkg1", "subdir/.."],
],
)
def test_backend_path_within_tree(backend_path):
BuildBackendHookCaller(SOURCE_DIR, "dummy", backend_path)
@pytest.mark.parametrize(
"backend_path",
[
[SOURCE_DIR],
[".", ".."],
["subdir/../.."],
["/"],
],
)
def test_backend_out_of_tree(backend_path):
# TODO: Do we want to insist on ValueError, or invent another exception?
with pytest.raises(Exception):
BuildBackendHookCaller(SOURCE_DIR, "dummy", backend_path)
@pytest.mark.parametrize("example", ("pkg_intree", "pkg_nested_intree"))
def test_intree_backend(example):
hooks = get_hooks(example)
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
res = hooks.get_requires_for_build_sdist({})
assert res == ["intree_backend_called"]
def test_intree_backend_path_containing_path_separator(tmp_path):
source_dir = tmp_path / f"project{pathsep}{pathsep}source"
copytree(Path(SAMPLES_DIR, "pkg_intree"), source_dir)
hooks = BuildBackendHookCaller(source_dir, "intree_backend", ["backend"])
assert hooks.get_requires_for_build_sdist({}) == ["intree_backend_called"]
@pytest.mark.parametrize("backend", ("buildsys", "nested.buildsys"))
def test_intree_backend_not_in_path(backend):
hooks = get_hooks("pkg_intree", backend=backend)
with modified_env({"PYTHONPATH": BUILDSYS_PKGS}):
msg = f"Cannot find module {backend!r} in .*pkg_intree.*backend"
with pytest.raises(BackendUnavailable, match=msg):
hooks.get_requires_for_build_sdist({})
def test_intree_backend_loaded_from_correct_backend_path():
"""
PEP 517 establishes that the backend code should be loaded from ``backend-path``,
and recognizes that not always the environment isolation is perfect
(e.g. it explicitly mentions ``--system-site-packages``).
Therefore, even in a situation where a third-party ``MetaPathFinder`` has
precedence over ``importlib.machinery.PathFinder``, the backend should
still be loaded from ``backend-path``.
"""
hooks = get_hooks("pkg_intree", backend="intree_backend")
with TemporaryDirectory() as tmp:
invalid = Path(tmp, ".invalid", "intree_backend.py")
invalid.parent.mkdir()
invalid.write_text("raise ImportError('Do not import')", encoding="utf-8")
install_finder_with_sitecustomize(tmp, {"intree_backend": str(invalid)})
with modified_env({"PYTHONPATH": tmp}): # Override `sitecustomize`.
res = hooks.get_requires_for_build_sdist({})
assert res == ["intree_backend_called"]
def test_intree_backend_importlib_metadata_interoperation():
pytest.importorskip("importlib.metadata")
hooks = get_hooks("pkg_intree_metadata", backend="intree_backend")
assert hooks.get_requires_for_build_sdist({}) == [
"_test_backend.importlib_metadata",
"hello",
"world",
]
def test_distribution_mismatch():
pytest.importorskip("importlib.metadata")
hooks = get_hooks("distribution_mismatch")
assert hooks.get_requires_for_build_sdist({}) != ["0.0.1"]
def install_finder_with_sitecustomize(directory, mapping):
finder = f"""
import sys
from importlib.util import spec_from_file_location
MAPPING = {mapping!r}
class _Finder: # MetaPathFinder
@classmethod
def find_spec(cls, fullname, path=None, target=None):
if fullname in MAPPING:
return spec_from_file_location(fullname, MAPPING[fullname])
def install():
if not any(finder == _Finder for finder in sys.meta_path):
sys.meta_path.insert(0, _Finder)
"""
sitecustomize = "import _test_finder_; _test_finder_.install()"
Path(directory, "_test_finder_.py").write_text(cleandoc(finder), encoding="utf-8")
Path(directory, "sitecustomize.py").write_text(sitecustomize, encoding="utf-8")