././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030488.3506298 pyproject_hooks-1.0.0/.bumpversion.cfg0000644000000000000000000000016314336661130014773 0ustar00[bumpversion] current_version = 1.0.0 commit = True tag = True [bumpversion:file:src/pyproject_hooks/__init__.py] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/.github/workflows/tests.yml0000644000000000000000000000322614336660263017156 0ustar00name: Tests on: [push, pull_request] env: FORCE_COLOR: 1 jobs: static-checks: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.x" - name: Install Tox run: | pip install tox tox-venv - name: Run tests run: tox -e isort,flake8 tests: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }}-dev cache: pip cache-dependency-path: "dev-requirements.txt" - name: Install Tox run: | pip install tox tox-venv - name: Run tests run: tox -e python publish: runs-on: ubuntu-latest needs: tests if: ${{ startsWith(github.ref, 'refs/tags/') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" cache: pip cache-dependency-path: "tox.ini" - name: Install Tox run: | pip install tox tox-venv - name: Publish to PyPI run: tox -e release env: FLIT_USERNAME: __token__ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }} TOX_TESTENV_PASSENV: "FLIT_USERNAME FLIT_PASSWORD" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/.gitignore0000644000000000000000000000013014336660263013653 0ustar00__pycache__/ *.pyc /dist/ .tox .pytest_cache doc/_build/ *.egg-info/ .coverage htmlcov/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/.readthedocs.yml0000644000000000000000000000022014336660263014751 0ustar00version: 2 sphinx: configuration: docs/conf.py python: install: - requirements: docs/requirements.txt - method: pip path: . ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/LICENSE0000644000000000000000000000207114336660263012676 0ustar00The 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/README.rst0000644000000000000000000000124314336660263013360 0ustar00``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). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/dev-requirements.txt0000644000000000000000000000010414336660263015724 0ustar00pytest flake8 testpath setuptools>=30 tomli ; python_version<'3.11' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/changelog.rst0000644000000000000000000000470114336660263015304 0ustar00Changelog ========= 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 (#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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/conf.py0000644000000000000000000000323514336660263014123 0ustar00# 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.ext.extlinks", ] 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_member_order = "bysource" autodoc_typehints = "description" # -- 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 extlinks = { "pypi": ("https://pypi.org/project/%s", "%s"), } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/index.rst0000644000000000000000000000242614336660263014466 0ustar00pyproject-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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/pyproject_hooks.rst0000644000000000000000000000301214336660263016571 0ustar00``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, extra_environ) :noindex: :param cmd: The command and arguments to execute, as would be passed to :func:`subprocess.run`. :type cmd: list[str] :param cwd: The working directory that must be used for the subprocess. :type cwd: str :param extra_environ: Mapping of environment variables (name to value) which must be set for the subprocess execution. :type extra_environ: dict[str, str] :rtype: None Exceptions ---------- Each exception has public attributes with the same name as their constructors. .. autoexception:: pyproject_hooks.BackendInvalid .. autoexception:: pyproject_hooks.BackendUnavailable .. autoexception:: pyproject_hooks.HookMissing .. autoexception:: pyproject_hooks.UnsupportedOperation ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/release-process.rst0000644000000000000000000000060514336660263016450 0ustar00=============== 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/docs/requirements.txt0000644000000000000000000000001414336660263016100 0ustar00furo sphinx ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/pyproject.toml0000644000000000000000000000134514336660263014610 0ustar00[build-system] requires = ["flit_core >=3.2,<4"] build-backend = "flit_core.buildapi" [project] name = "pyproject_hooks" authors = [ {name = "Thomas Kluyver", email = "thomas@kluyver.me.uk"}, ] readme = "README.rst" requires-python = ">=3.7" dependencies = [ "tomli >=1.1.0 ; python_version<'3.11'", ] classifiers = [ "License :: OSI Approved :: MIT License", "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.isort] profile = "black" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3036077 pyproject_hooks-1.0.0/pytest.ini0000644000000000000000000000020114336660263013713 0ustar00[pytest] addopts = --strict-config --strict-markers xfail_strict = True junit_family = xunit2 filterwarnings = error ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030488.3506298 pyproject_hooks-1.0.0/src/pyproject_hooks/__init__.py0000644000000000000000000000075314336661130020012 0ustar00"""Wrappers to call pyproject.toml-based build backend hooks. """ from ._impl import ( BackendInvalid, BackendUnavailable, BuildBackendHookCaller, HookMissing, UnsupportedOperation, default_subprocess_runner, quiet_subprocess_runner, ) __version__ = '1.0.0' __all__ = [ 'BackendUnavailable', 'BackendInvalid', 'HookMissing', 'UnsupportedOperation', 'default_subprocess_runner', 'quiet_subprocess_runner', 'BuildBackendHookCaller', ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/src/pyproject_hooks/_compat.py0000644000000000000000000000017114336660263017675 0ustar00__all__ = ("tomllib",) import sys if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/src/pyproject_hooks/_impl.py0000644000000000000000000002722014336660263017357 0ustar00import json import os import sys import tempfile from contextlib import contextmanager from os.path import abspath from os.path import join as pjoin from subprocess import STDOUT, check_call, check_output from ._in_process import _in_proc_script_path 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): """Will be raised if the backend cannot be imported in the hook process.""" def __init__(self, traceback): self.traceback = traceback class BackendInvalid(Exception): """Will be raised if the backend is invalid.""" def __init__(self, backend_name, backend_path, message): super().__init__(message) self.backend_name = backend_name self.backend_path = backend_path class HookMissing(Exception): """Will be raised on missing hooks (if a fallback can't be used).""" def __init__(self, hook_name): 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): self.traceback = traceback def default_subprocess_runner(cmd, cwd=None, extra_environ=None): """The default method of calling the wrapper subprocess. This uses :func:`subprocess.check_call` under the hood. """ env = os.environ.copy() if extra_environ: env.update(extra_environ) check_call(cmd, cwd=cwd, env=env) def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): """Call the subprocess while suppressing output. This uses :func:`subprocess.check_output` under the hood. """ env = os.environ.copy() if extra_environ: env.update(extra_environ) check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) def norm_and_check(source_tree, requested): """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)) # We have to use commonprefix for Python 2.7 compatibility. So we # normalise case to avoid problems because commonprefix is a character # based comparison :-( norm_source = os.path.normcase(abs_source) norm_requested = os.path.normcase(abs_requested) if os.path.commonprefix([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, build_backend, backend_path=None, runner=None, python_executable=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): """A context manager for temporarily overriding the default :ref:`subprocess runner `. .. 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): """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=None): """Get additional dependencies required for building a wheel. :returns: A list of :pep:`dependency specifiers <508>`. :rtype: list[str] .. 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, config_settings=None, _allow_fallback=True): """Prepare a ``*.dist-info`` folder with metadata for this project. :returns: Name of the newly created subfolder within ``metadata_directory``, containing the metadata. :rtype: str .. 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, config_settings=None, metadata_directory=None): """Build a wheel from this project. :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=None): """Get additional dependencies required for building an editable wheel. :returns: A list of :pep:`dependency specifiers <508>`. :rtype: list[str] .. 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, config_settings=None, _allow_fallback=True): """Prepare a ``*.dist-info`` folder with metadata for this project. :returns: Name of the newly created subfolder within ``metadata_directory``, containing the metadata. :rtype: str .. 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, config_settings=None, metadata_directory=None): """Build an editable wheel from this project. :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=None): """Get additional dependencies required for building an sdist. :returns: A list of :pep:`dependency specifiers <508>`. :rtype: list[str] """ return self._call_hook('get_requires_for_build_sdist', { 'config_settings': config_settings }) def build_sdist(self, sdist_directory, config_settings=None): """Build an sdist from this project. :returns: The name of the newly created sdist within ``wheel_directory``. """ return self._call_hook('build_sdist', { 'sdist_directory': abspath(sdist_directory), 'config_settings': config_settings, }) def _call_hook(self, hook_name, kwargs): extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend} if self.backend_path: backend_path = os.pathsep.join(self.backend_path) extra_environ['PEP517_BACKEND_PATH'] = 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', '')) if data.get('backend_invalid'): raise BackendInvalid( backend_name=self.build_backend, backend_path=self.backend_path, message=data.get('backend_error', '') ) if data.get('hook_missing'): raise HookMissing(data.get('missing_hook_name') or hook_name) return data['return_val'] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/src/pyproject_hooks/_in_process/__init__.py0000644000000000000000000000104214336660263022313 0ustar00"""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')) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/src/pyproject_hooks/_in_process/_in_process.py0000644000000000000000000002525714336660263023075 0ustar00"""This is invoked in a subprocess to call the build backend hooks. It expects: - Command line args: hook_name, control_dir - Environment variables: PEP517_BUILD_BACKEND=entry.point:spec PEP517_BACKEND_PATH=paths (separated with os.pathsep) - 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 os.path import join as pjoin # 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, traceback): self.traceback = traceback class BackendInvalid(Exception): """Raised if the backend is invalid""" def __init__(self, message): self.message = message 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 contained_in(filename, directory): """Test if a file is located within the given directory.""" filename = os.path.normcase(os.path.abspath(filename)) directory = os.path.normcase(os.path.abspath(directory)) return os.path.commonprefix([filename, directory]) == directory def _build_backend(): """Find and load the build backend""" # Add in-tree backend directories to the front of sys.path. backend_path = os.environ.get('PEP517_BACKEND_PATH') if backend_path: extra_pathitems = backend_path.split(os.pathsep) sys.path[:0] = extra_pathitems ep = os.environ['PEP517_BUILD_BACKEND'] mod_path, _, obj_path = ep.partition(':') try: obj = import_module(mod_path) except ImportError: raise BackendUnavailable(traceback.format_exc()) if backend_path: if not any( contained_in(obj.__file__, path) for path in extra_pathitems ): raise BackendInvalid("Backend was not loaded from backend-path") if obj_path: for path_part in obj_path.split('.'): obj = getattr(obj, path_part) return obj 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 = 'PEP517_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) hook = globals()[hook_name] hook_input = read_json(pjoin(control_dir, 'input.json')) 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 except BackendInvalid as e: json_out['backend_invalid'] = True 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 write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/tests/__init__.py0000644000000000000000000000000014336660263015132 0ustar00././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/tests/samples/buildsys_pkgs/buildsys.py0000644000000000000000000000363214336660263021575 0ustar00"""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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/tests/samples/buildsys_pkgs/buildsys_minimal.py0000644000000000000000000000176414336660263023307 0ustar00"""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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3046076 pyproject_hooks-1.0.0/tests/samples/buildsys_pkgs/buildsys_minimal_editable.py0000644000000000000000000000013414336660263025126 0ustar00from buildsys_minimal import build_sdist, build_wheel # noqa build_editable = build_wheel ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pkg1-0.5.dist-info/LICENSE0000644000000000000000000000207114336660263021403 0ustar00The 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pkg1-0.5.dist-info/METADATA0000644000000000000000000000037514336660263021506 0ustar00Metadata-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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pkg1-0.5.dist-info/RECORD0000644000000000000000000000052014336660263021274 0ustar00pkg1.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,, ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pkg1-0.5.dist-info/WHEEL0000644000000000000000000000014514336660263021165 0ustar00Wheel-Version: 1.0 Generator: buildsys 0.1 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pkg1.py0000644000000000000000000000006414336660263016555 0ustar00"""Sample package for tests""" __version__ = '0.5' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg1/pyproject.toml0000644000000000000000000000025314336660263020255 0ustar00[build-system] requires = ["eg_buildsys"] build-backend = "buildsys" [project] description = "Factory ⸻ A code generator 🏭" maintainers = [{name = "Łukasz Langa"}] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pkg2-0.5.dist-info/LICENSE0000644000000000000000000000207114336660263021405 0ustar00The 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pkg2-0.5.dist-info/METADATA0000644000000000000000000000037314336660263021506 0ustar00Metadata-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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pkg2-0.5.dist-info/RECORD0000644000000000000000000000052014336660263021276 0ustar00pkg2.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,, ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pkg2-0.5.dist-info/WHEEL0000644000000000000000000000014414336660263021166 0ustar00Wheel-Version: 1.0 Generator: flit 0.11.3 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pkg2.py0000644000000000000000000000006414336660263016557 0ustar00"""Sample package for tests""" __version__ = '0.5' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3056076 pyproject_hooks-1.0.0/tests/samples/pkg2/pyproject.toml0000644000000000000000000000010014336660263020245 0ustar00[build-system] requires = [] build-backend = "buildsys_minimal" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pkg3-0.5.dist-info/LICENSE0000644000000000000000000000207114336660263021407 0ustar00The 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. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pkg3-0.5.dist-info/METADATA0000644000000000000000000000037314336660263021510 0ustar00Metadata-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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pkg3-0.5.dist-info/RECORD0000644000000000000000000000052014336660263021300 0ustar00pkg3.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,, ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pkg3-0.5.dist-info/WHEEL0000644000000000000000000000014414336660263021170 0ustar00Wheel-Version: 1.0 Generator: flit 0.11.3 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pkg3.py0000644000000000000000000000006414336660263016561 0ustar00"""Sample package for tests""" __version__ = '0.5' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg3/pyproject.toml0000644000000000000000000000011114336660263020250 0ustar00[build-system] requires = [] build-backend = "buildsys_minimal_editable" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg_intree/backend/intree_backend.py0000644000000000000000000000013014336660263023516 0ustar00def get_requires_for_build_sdist(config_settings): return ["intree_backend_called"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/pkg_intree/pyproject.toml0000644000000000000000000000011214336660263021534 0ustar00[build-system] build-backend = 'intree_backend' backend-path = ['backend']././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/setup-py/pyproject.toml0000644000000000000000000000013214336660263021175 0ustar00[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3066077 pyproject_hooks-1.0.0/tests/samples/setup-py/setup.py0000644000000000000000000000014214336660263017774 0ustar00from setuptools import setup setup( name='pep517-test-setup-py-support', version='1.0' ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tests/samples/test-for-issue-104/pyproject.toml0000644000000000000000000000013214336660263022602 0ustar00[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tests/samples/test-for-issue-104/setup.py0000644000000000000000000000037114336660263021405 0ustar00import json import sys from os import environ, listdir, path from setuptools import setup children = listdir(sys.path[0]) out = path.join(environ['PEP517_ISSUE104_OUTDIR'], 'out.json') with open(out, 'w') as f: json.dump(children, f) setup() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tests/test_call_hooks.py0000644000000000000000000001534014336660263016565 0ustar00import 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 from pyproject_hooks import ( BackendUnavailable, BuildBackendHookCaller, UnsupportedOperation, default_subprocess_runner, ) from pyproject_hooks._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': ''}): with pytest.raises(BackendUnavailable): hooks.get_requires_for_build_wheel({}) 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_issue_104(): hooks = get_hooks('test-for-issue-104') with TemporaryDirectory() as outdir: with modified_env({ 'PYTHONPATH': BUILDSYS_PKGS, 'PEP517_ISSUE104_OUTDIR': outdir, }): hooks.get_requires_for_build_wheel({}) with open(pjoin(outdir, 'out.json')) as f: children = json.load(f) assert set(children) <= { '__init__.py', '__init__.pyc', '_in_process.py', '_in_process.pyc', '__pycache__', } def test_setup_py(): hooks = get_hooks('setup-py') with modified_env({'PYTHONPATH': BUILDSYS_PKGS}): res = hooks.get_requires_for_build_wheel({}) # Some versions of setuptools list setuptools itself here res = [x for x in res if x != 'setuptools'] assert res == ['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 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tests/test_hook_fallbacks.py0000644000000000000000000000674214336660263017417 0ustar00from 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 pyproject_hooks._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) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tests/test_inplace_hooks.py0000644000000000000000000000337614336660263017273 0ustar00from os.path import abspath, dirname from os.path import join as pjoin import pytest from testpath import modified_env from pyproject_hooks import BackendInvalid, BuildBackendHookCaller from pyproject_hooks._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) def test_intree_backend(): hooks = get_hooks('pkg_intree') with modified_env({'PYTHONPATH': BUILDSYS_PKGS}): res = hooks.get_requires_for_build_sdist({}) assert res == ["intree_backend_called"] def test_intree_backend_not_in_path(): hooks = get_hooks('pkg_intree', backend='buildsys') with modified_env({'PYTHONPATH': BUILDSYS_PKGS}): with pytest.raises(BackendInvalid): hooks.get_requires_for_build_sdist({}) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1669030067.3076077 pyproject_hooks-1.0.0/tox.ini0000644000000000000000000000061414336660263013205 0ustar00[tox] envlist = py37, py38, py39, py310, py311, pypy3, isort isolated_build = true [testenv] passenv = FORCE_COLOR deps = -rdev-requirements.txt commands = pytest [] [testenv:isort] deps = isort commands = python -m isort --check --diff {toxinidir} [testenv:flake8] deps = flake8 commands = flake8 src [testenv:release] skip_install = True deps = flit commands = python -m flit publish pyproject_hooks-1.0.0/PKG-INFO0000644000000000000000000000247500000000000012726 0ustar00Metadata-Version: 2.1 Name: pyproject_hooks Version: 1.0.0 Summary: Wrappers to call pyproject.toml-based build backend hooks. Author-email: Thomas Kluyver Requires-Python: >=3.7 Description-Content-Type: text/x-rst Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Dist: tomli >=1.1.0 ; python_version<'3.11' Project-URL: Changelog, https://pyproject-hooks.readthedocs.io/en/latest/changelog.html Project-URL: Documentation, https://pyproject-hooks.readthedocs.io/ Project-URL: Source, https://github.com/pypa/pyproject-hooks ``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).