diff --git a/docs/source/get_started/config.md b/docs/source/get_started/config.md index 50db0b2..b162c9c 100644 --- a/docs/source/get_started/config.md +++ b/docs/source/get_started/config.md @@ -21,6 +21,7 @@ Or with all options given: ensured-targets = ["foo/generated.txt"] skip-if-exists = ["foo/generated.txt"] install-pre-commit-hook = true + optional-editable-build = true [tool.hatch.build.hooks.jupyter-builder.build-kwargs] build_cmd = "build:src" @@ -67,6 +68,12 @@ You can also use `editable-build-kwargs` if the parameters should differ in editable mode. If only the build command is different, you can use `editable_build_cmd` in `build-kwargs` instead. +### optional-editable-build + +The optional `optional-editable-build` parameter can be set to `true` to +show a warning instead of erroring if the build fails in editable mode. +This can be used when build artifacts are optional for local development. + ### install-pre-commit-hook The optional `install-pre-commit-hook` boolean causes a `pre-commit` hook to be installed during an editable install. diff --git a/hatch_jupyter_builder/plugin.py b/hatch_jupyter_builder/plugin.py index 173f903..1a900d7 100644 --- a/hatch_jupyter_builder/plugin.py +++ b/hatch_jupyter_builder/plugin.py @@ -1,5 +1,6 @@ import os import typing as t +import warnings from dataclasses import dataclass, field, fields from hatchling.builders.hooks.plugin.interface import BuildHookInterface @@ -24,6 +25,7 @@ class JupyterBuildConfig: editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict) ensured_targets: t.List[str] = field(default_factory=list) skip_if_exists: t.List[str] = field(default_factory=list) + optional_editable_build: str = "" class JupyterBuildHook(BuildHookInterface): @@ -72,7 +74,13 @@ def initialize(self, version, build_data): build_kwargs = normalize_kwargs(build_kwargs) log.info(f"Building with {config.build_function}") log.info(f"With kwargs: {build_kwargs}") - build_func(self.target_name, version, **build_kwargs) + try: + build_func(self.target_name, version, **build_kwargs) + except Exception as e: + if version == "editable" and config.optional_editable_build.lower() == "true": + warnings.warn(f"Encountered build error:\n{e}") + else: + raise e else: log.info("Skipping build") diff --git a/tests/test_plugin.py b/tests/test_plugin.py index f0c1d20..aa3c8e8 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,7 +1,9 @@ import os import platform import subprocess +import sys import venv +import warnings from pathlib import Path import pytest @@ -41,6 +43,7 @@ def foo(target_name, version, foo_bar=None, fizz_buzz=None): config["skip-if-exists"] = ["foo", "bar"] assert hook.initialize("standard", {}) + del config["skip-if-exists"] config["editable-build-kwargs"] = {"foo-bar": "2", "fizz_buzz": "3"} assert hook.initialize("editable", {}) @@ -48,9 +51,29 @@ def foo(target_name, version, foo_bar=None, fizz_buzz=None): hook = JupyterBuildHook(tmp_path, config, {}, {}, tmp_path, "foo") assert not hook.initialize("standard", {}) - os.environ["SKIP_JUPYTER_BUILD"] = "1" + text = """ +def foo(target_name, version, foo_bar=None, fizz_buzz=None): + raise RuntimeError('trigger error') +""" + test.write_text(text, encoding="utf-8") + # Force a re-import + del sys.modules["test"] + + hook = JupyterBuildHook(tmp_path, config, {}, {}, tmp_path, "wheel") + with pytest.raises(RuntimeError): + hook.initialize("editable", {}) + + os.environ["SKIP_JUPYTER_BUILDER"] = "1" assert not hook.initialize("standard", {}) - del os.environ["SKIP_JUPYTER_BUILD"] + del os.environ["SKIP_JUPYTER_BUILDER"] + + config["optional-editable-build"] = "true" + hook = JupyterBuildHook(tmp_path, config, {}, {}, tmp_path, "wheel") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + assert hook.initialize("editable", {}) + + del sys.modules["test"] HERE = Path(__file__).parent diff --git a/tests/test_utils.py b/tests/test_utils.py index dbbd5af..39983fd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -53,6 +53,8 @@ def test_get_build_func(tmp_path): with pytest.raises(AttributeError): utils.get_build_func("test.bar") + del sys.modules["test"] + def test_should_skip(tmp_path): assert not utils.should_skip("a")