Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Improve documentation (#54, #55)
* Improve type annotations and checks (#68)
* Include Dockerfile in built images (#55)
* Look for environment.yml automatically (#41)

## Changes in 0.1.1

Expand Down
35 changes: 25 additions & 10 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,35 @@

@patch("xcengine.core.ScriptCreator.__init__")
@pytest.mark.parametrize("tag", [None, "bar"])
@pytest.mark.parametrize("use_env", [False, True])
def test_image_builder_init(init_mock, tmp_path, tag, use_env):
@pytest.mark.parametrize("env_file_name", ["environment.yml", "foo.yaml", None])
@pytest.mark.parametrize("use_env_file_param", [False, True])
def test_image_builder_init(
init_mock,
tmp_path: pathlib.Path,
tag: str | None,
env_file_name: str | None,
use_env_file_param: bool,
):
nb_path = tmp_path / "foo.ipynb"
nb_path.touch()
if use_env:
environment = tmp_path / "environment.yml"
environment.touch()
if env_file_name is not None:
environment_path = tmp_path / env_file_name
environment_path.touch()
else:
environment = None
environment_path = None
build_path = tmp_path / "build"
build_path.mkdir()
init_mock.return_value = None
ib = ImageBuilder(
notebook=nb_path,
environment=environment,
environment=environment_path if use_env_file_param else None,
build_dir=build_path,
tag=tag,
)
assert ib.notebook == nb_path
assert ib.build_dir == build_path
assert ib.environment == environment
expected_env = environment_path if (use_env_file_param or env_file_name == "environment.yml") else None
assert ib.environment == expected_env
if tag is None:
assert abs(
datetime.datetime.now(datetime.UTC)
Expand Down Expand Up @@ -97,12 +105,13 @@ def test_runner_init_with_image():
)
assert runner.image == image


@pytest.mark.parametrize("keep", [False, True])
def test_runner_run_keep(keep: bool):
runner = xcengine.core.ContainerRunner(
image := Mock(docker.models.images.Image),
None,
client := Mock(DockerClient)
client := Mock(DockerClient),
)
image.tags = []
client.containers.run.return_value = (container := MagicMock(Container))
Expand All @@ -118,26 +127,32 @@ def test_runner_sigint():
runner = xcengine.core.ContainerRunner(
image := Mock(docker.models.images.Image),
None,
client := Mock(DockerClient)
client := Mock(DockerClient),
)
image.tags = []
client.containers.run.return_value = (container := Mock(Container))
container.status = "running"

def container_stop():
container.status = "stopped"

container.stop = container_stop
pid = os.getpid()

old_alarm_handler = signal.getsignal(signal.SIGALRM)

class AlarmException(Exception):
pass

def alarm_handler(signum, frame):
raise AlarmException()

signal.signal(signal.SIGALRM, alarm_handler)

def interrupt_process():
time.sleep(1) # allow one second for runner to start
os.kill(pid, signal.SIGINT)

thread = threading.Thread(target=interrupt_process, daemon=True)
thread.start()

Expand Down
16 changes: 12 additions & 4 deletions xcengine/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class ImageBuilder:
"""

tag_format: ClassVar[str] = "%Y.%m.%d.%H.%M.%S"
environment: pathlib.Path | None = None

def __init__(
self,
Expand Down Expand Up @@ -206,7 +207,9 @@ def __init__(
else:
self.tag = tag

if environment is None:
if environment is not None:
self.environment = environment
else:
LOGGER.info(
"Looking for environment file configuration in the notebook."
)
Expand All @@ -218,9 +221,14 @@ def __init__(
self.environment = notebook.parent / nb_env
else:
LOGGER.info(f"No environment specified in notebook.")
self.environment = None
else:
self.environment = environment
LOGGER.info(f"Looking for a file named \"environment.yml\".")
notebook_sibling = notebook.parent / "environment.yml"
if notebook_sibling.is_file():
self.environment = notebook_sibling
LOGGER.info(f"Using environment from {notebook_sibling}")
else:
LOGGER.info(f"No environment found at {notebook_sibling}")
self.environment = None

def build(self) -> Image:
self.script_creator.convert_notebook_to_script(self.build_dir)
Expand Down