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()