diff --git a/pep517/_in_process.py b/pep517/_in_process.py index 499d9af..1589a6c 100644 --- a/pep517/_in_process.py +++ b/pep517/_in_process.py @@ -38,6 +38,10 @@ def __init__(self, message): self.message = message +class HookMissing(Exception): + """Raised if a hook is missing and we are not executing the fallback""" + + def contained_in(filename, directory): """Test if a file is located within the given directory.""" filename = os.path.normcase(os.path.abspath(filename)) @@ -87,15 +91,19 @@ def get_requires_for_build_wheel(config_settings): return hook(config_settings) -def prepare_metadata_for_build_wheel(metadata_directory, 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. + 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() return _get_wheel_metadata_from_wheel(backend, metadata_directory, config_settings) else: @@ -239,6 +247,8 @@ def main(): except GotUnsupportedOperation as e: json_out['unsupported'] = True json_out['traceback'] = e.traceback + except HookMissing: + json_out['hook_missing'] = True compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) diff --git a/pep517/wrappers.py b/pep517/wrappers.py index b2fc6a6..8d893fd 100644 --- a/pep517/wrappers.py +++ b/pep517/wrappers.py @@ -35,6 +35,13 @@ def __init__(self, backend_name, backend_path, message): self.message = message +class HookMissing(Exception): + """Will be raised on missing hooks.""" + def __init__(self, hook_name): + super(HookMissing, self).__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): @@ -134,18 +141,21 @@ def get_requires_for_build_wheel(self, config_settings=None): }) def prepare_metadata_for_build_wheel( - self, metadata_directory, config_settings=None): + self, metadata_directory, config_settings=None, + _allow_fallback=True): """Prepare a *.dist-info folder with metadata for this project. Returns the name of the newly created folder. If the build backend defines a hook with this name, it will be called in a subprocess. If not, the backend will be asked to build a wheel, - and the dist-info extracted from that. + and the dist-info extracted from that (unless _allow_fallback is + False). """ 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( @@ -237,6 +247,8 @@ def _call_hook(self, hook_name, kwargs): backend_path=self.backend_path, message=data.get('backend_error', '') ) + if data.get('hook_missing'): + raise HookMissing(hook_name) return data['return_val'] diff --git a/tests/test_hook_fallbacks.py b/tests/test_hook_fallbacks.py index d68f58c..0c1c064 100644 --- a/tests/test_hook_fallbacks.py +++ b/tests/test_hook_fallbacks.py @@ -1,9 +1,10 @@ from os.path import dirname, abspath, join as pjoin +import pytest import pytoml from testpath import modified_env, assert_isfile from testpath.tempdir import TemporaryDirectory -from pep517.wrappers import Pep517HookCaller +from pep517.wrappers import HookMissing, Pep517HookCaller SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples') BUILDSYS_PKGS = pjoin(SAMPLES_DIR, 'buildsys_pkgs') @@ -37,3 +38,18 @@ def test_prepare_metadata_for_build_wheel(): hooks.prepare_metadata_for_build_wheel(metadatadir, {}) assert_isfile(pjoin(metadatadir, 'pkg2-0.5.dist-info', 'METADATA')) + + +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)