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
34 changes: 32 additions & 2 deletions src/exaspim_swc_processing/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import sys
from collections.abc import Mapping, Sequence
from datetime import datetime, timezone
from importlib import metadata
from pathlib import Path

from aind_data_schema.components.identifiers import Code
Expand Down Expand Up @@ -54,11 +55,37 @@ def _resources() -> ResourceUsage:
)


def installed_version(distribution: str) -> str | None:
"""Return the installed version of a distribution, if it is installed.

Used as the fallback for ``Code.version``. Code Ocean generates ``main.nf`` and
exports only ``CO_CAPSULE_ID``, ``CO_CPUS`` and ``CO_MEMORY``, so the pinned capsule
commit is not available to the running code. The version of the library the capsule
installed is available, is what actually determines behaviour for these thin
capsules, and is a value AIND accepts in place of a commit hash.

Parameters
----------
distribution : str
Distribution name, e.g. ``"exaspim-swc-processing"``.

Returns
-------
str | None
The installed version, or ``None`` if the distribution is not installed.
"""
try:
return metadata.version(distribution)
except metadata.PackageNotFoundError:
return None


def resolve_code(
name: str,
url: str | None = None,
version: str | None = None,
run_script: str = "code/run",
distribution: str = "exaspim-swc-processing",
) -> Code:
"""Describe the code a stage ran, from the environment.

Expand All @@ -70,9 +97,12 @@ def resolve_code(
Repository URL. AIND prefers a GitHub URL; when omitted the Code Ocean capsule
URL is built from ``CO_CAPSULE_ID``.
version : str | None, optional
Version or commit of the code that ran. Read from ``CODE_VERSION`` when omitted.
Version of the code that ran. When omitted, ``CODE_VERSION`` is read, then the
installed version of ``distribution``. See :func:`installed_version`.
run_script : str, optional
Entry point, relative to the repository root, by default ``"code/run"``.
distribution : str, optional
Installed package to fall back to for the version, by default this library.

Returns
-------
Expand All @@ -85,7 +115,7 @@ def resolve_code(
return Code(
url=resolved_url,
name=name,
version=version or os.environ.get("CODE_VERSION") or None,
version=version or os.environ.get("CODE_VERSION") or installed_version(distribution),
run_script=Path(run_script),
language="Python",
language_version=(
Expand Down
23 changes: 21 additions & 2 deletions tests/test_stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from exaspim_swc_processing.stage import (
DATA_PROCESS_FILENAME,
build_stage_process,
installed_version,
resolve_code,
write_stage_process,
)
Expand Down Expand Up @@ -41,13 +42,31 @@ def test_a_github_url_is_preferred_when_given(monkeypatch: pytest.MonkeyPatch) -
assert code.url == "https://github.com/peter-grotz/exaspim-swc-processing"


def test_version_is_none_rather_than_empty_when_unknown(
def test_version_falls_back_to_the_installed_library(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Code Ocean generates main.nf and does not export the pinned capsule commit.

The installed library version is what actually determines a thin capsule's
behaviour, and AIND accepts a version in place of a commit hash.
"""
monkeypatch.setenv("CO_CAPSULE_ID", CAPSULE_ID)
monkeypatch.delenv("CODE_VERSION", raising=False)
assert resolve_code("x").version == installed_version("exaspim-swc-processing")


def test_version_is_none_when_nothing_identifies_the_code(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""An absent version is null, not an empty string that looks recorded."""
monkeypatch.setenv("CO_CAPSULE_ID", CAPSULE_ID)
monkeypatch.delenv("CODE_VERSION", raising=False)
assert resolve_code("x").version is None
assert resolve_code("x", distribution="not-installed-anywhere").version is None


def test_installed_version_returns_none_for_an_absent_distribution() -> None:
"""A capsule that did not install the library still produces a valid record."""
assert installed_version("definitely-not-installed") is None


def test_url_is_empty_outside_code_ocean(monkeypatch: pytest.MonkeyPatch) -> None:
Expand Down
Loading