diff --git a/bioimageio/spec/_description.py b/bioimageio/spec/_description.py
index d7b9ecce..29da578a 100644
--- a/bioimageio/spec/_description.py
+++ b/bioimageio/spec/_description.py
@@ -185,6 +185,10 @@ def update_format(
def ensure_description_is_model(
rd: Union[InvalidDescr, ResourceDescr],
) -> AnyModelDescr:
+ """
+ Raises:
+ ValueError: for invalid or non-model resources
+ """
if isinstance(rd, InvalidDescr):
rd.validation_summary.display()
raise ValueError("resource description is invalid")
diff --git a/bioimageio/spec/_internal/common_nodes.py b/bioimageio/spec/_internal/common_nodes.py
index 443244b5..b414b2a4 100644
--- a/bioimageio/spec/_internal/common_nodes.py
+++ b/bioimageio/spec/_internal/common_nodes.py
@@ -515,6 +515,8 @@ class InvalidDescr(
extra="allow",
title="An invalid resource description",
):
+ """A representation of an invalid resource description"""
+
type: Any = "unknown"
format_version: Any = "unknown"
fields_to_set_explicitly: ClassVar[FrozenSet[LiteralString]] = frozenset()
diff --git a/bioimageio/spec/_io.py b/bioimageio/spec/_io.py
index 2fb66b05..4473b7d4 100644
--- a/bioimageio/spec/_io.py
+++ b/bioimageio/spec/_io.py
@@ -81,6 +81,9 @@ def load_model_description(
) -> AnyModelDescr:
"""same as `load_description`, but addtionally ensures that the loaded
description is valid and of type 'model'.
+
+ Raises:
+ ValueError: for invalid or non-model resources
"""
rd = load_description(
source,
diff --git a/bioimageio/spec/conda_env.py b/bioimageio/spec/conda_env.py
index 520a4c06..9cb9d479 100644
--- a/bioimageio/spec/conda_env.py
+++ b/bioimageio/spec/conda_env.py
@@ -15,6 +15,12 @@ def __lt__(self, other: Any):
else:
return False
+ def __gt__(self, other: Any):
+ if isinstance(other, PipDeps):
+ return len(self.pip) > len(other.pip)
+ else:
+ return False
+
class CondaEnv(BaseModel):
"""Represenation of the content of a conda environment.yaml file"""
diff --git a/bioimageio/spec/summary.py b/bioimageio/spec/summary.py
index cf8013bc..8d50ada0 100644
--- a/bioimageio/spec/summary.py
+++ b/bioimageio/spec/summary.py
@@ -2,7 +2,7 @@
from io import StringIO
from itertools import chain
from pathlib import Path
-from tempfile import NamedTemporaryFile
+from tempfile import TemporaryDirectory
from types import MappingProxyType
from typing import (
Any,
@@ -152,19 +152,30 @@ class ValidationDetail(BaseModel, extra="allow"):
def model_post_init(self, __context: Any):
"""create `conda_compare` default value if needed"""
super().model_post_init(__context)
- if self.recommended_env is not None and self.conda_compare is None:
- dumped_env = self.recommended_env.model_dump(mode="json")
- if is_yaml_value(dumped_env):
- with NamedTemporaryFile(mode="w", encoding="utf-8") as f:
- write_yaml(dumped_env, f)
- self.conda_compare = subprocess.run(
- ["conda", "compare", f.name],
- capture_output=True,
- shell=True,
- text=True,
- ).stdout
- else:
- self.conda_compare = "Failed to dump recommended env to valid yaml"
+ if self.recommended_env is None or self.conda_compare is not None:
+ return
+
+ dumped_env = self.recommended_env.model_dump(mode="json")
+ if not is_yaml_value(dumped_env):
+ self.conda_compare = "Failed to dump recommended env to valid yaml"
+ return
+
+ with TemporaryDirectory() as d:
+ path = Path(d) / "env.yaml"
+ with path.open("w", encoding="utf-8") as f:
+ write_yaml(dumped_env, f)
+
+ compare_proc = subprocess.run(
+ ["conda", "compare", str(path)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=True,
+ text=True,
+ )
+ self.conda_compare = (
+ compare_proc.stdout
+ or f"conda compare exited with {compare_proc.returncode}"
+ )
def __str__(self):
return f"{self.__class__.__name__}:\n" + self.format()
@@ -330,17 +341,18 @@ def format_loc(loc: Loc):
json_env = d.recommended_env.model_dump(mode="json")
assert is_yaml_value(json_env)
write_yaml(json_env, rec_env)
+ rec_env_code = rec_env.getvalue().replace("\n", "
")
details.append(
[
"🐍",
"recommended conda env",
- f"```yaml\n{rec_env.read()}\n```".replace("\n", "
"),
+ f"{rec_env_code}
",
]
)
- if d.conda_compare is not None:
+ if d.conda_compare:
details.append(
- ["🐍", "actual conda env", d.conda_compare.replace("\n", "
")]
+ ["🐍", "conda compare", d.conda_compare.replace("\n", "
")]
)
for entry in d.errors:
diff --git a/tests/test_model/test_v0_5.py b/tests/test_model/test_v0_5.py
index 64310738..b1ad09c2 100644
--- a/tests/test_model/test_v0_5.py
+++ b/tests/test_model/test_v0_5.py
@@ -370,6 +370,7 @@ def test_model(model_data: Dict[str, Any], update: Dict[str, Any]):
summary = validate_format(
model_data, context=ValidationContext(perform_io_checks=False)
)
+ summary.display()
assert summary.status == "passed", summary.format()