bioimageio.spec.conda_env

  1import warnings
  2from typing import Any, Callable, List, Optional, Union, cast
  3
  4from pydantic import BaseModel, Field, field_validator, model_validator
  5
  6
  7class PipDeps(BaseModel):
  8    """Pip dependencies to include in conda dependecies"""
  9
 10    pip: List[str] = Field(default_factory=list)
 11
 12    @field_validator("pip", mode="after")
 13    @classmethod
 14    def _remove_empty_and_sort(cls, value: List[str]) -> List[str]:
 15        return sorted((vs for v in value if (vs := v.strip())))
 16
 17    def __lt__(self, other: Any):
 18        if isinstance(other, PipDeps):
 19            return len(self.pip) < len(other.pip)
 20        else:
 21            return False
 22
 23    def __gt__(self, other: Any):
 24        if isinstance(other, PipDeps):
 25            return len(self.pip) > len(other.pip)
 26        else:
 27            return False
 28
 29
 30class CondaEnv(BaseModel):
 31    """Represenation of the content of a conda environment.yaml file"""
 32
 33    name: Optional[str] = None
 34    channels: List[str] = Field(default_factory=list)
 35    dependencies: List[Union[str, PipDeps]] = Field(
 36        default_factory=cast(Callable[[], List[Union[str, PipDeps]]], list)
 37    )
 38
 39    @field_validator("name", mode="after")
 40    def _ensure_valid_conda_env_name(cls, value: Optional[str]) -> Optional[str]:
 41        if value is None:
 42            return None
 43
 44        for illegal in ("/", " ", ":", "#"):
 45            value = value.replace(illegal, "")
 46
 47        return value or "empty"
 48
 49    @property
 50    def wo_name(self):
 51        return self.model_construct(**{k: v for k, v in self if k != "name"})
 52
 53    def _get_version_pin(self, package: str):
 54        """Helper to return any version pin for **package**
 55
 56        TODO: improve: interprete version pin and return structured information.
 57        """
 58        for d in self.dependencies:
 59            if isinstance(d, PipDeps):
 60                for p in d.pip:
 61                    if p.startswith(package):
 62                        return p[len(package) :]
 63            elif d.startswith(package):
 64                return d[len(package) :]
 65            elif "::" in d and (d_wo_channel := d.split("::", 1)[-1]).startswith(
 66                package
 67            ):
 68                return d_wo_channel[len(package) :]
 69
 70    def get_pip_deps(self) -> List[str]:
 71        """Get the pip dependencies of this conda env."""
 72        for dep in self.dependencies:
 73            if isinstance(dep, PipDeps):
 74                return dep.pip
 75
 76        return []
 77
 78
 79class BioimageioCondaEnv(CondaEnv):
 80    """A special `CondaEnv` that
 81    - automatically adds bioimageio specific dependencies
 82    - sorts dependencies
 83    """
 84
 85    @model_validator(mode="after")
 86    def _normalize_bioimageio_conda_env(self):
 87        """update a conda env such that we have bioimageio.core and sorted dependencies"""
 88        for req_channel in ("conda-forge", "nodefaults"):
 89            if req_channel not in self.channels:
 90                self.channels.append(req_channel)
 91
 92        if "defaults" in self.channels:
 93            warnings.warn("removing 'defaults' from conda-channels")
 94            self.channels.remove("defaults")
 95
 96        if "pip" not in self.dependencies:
 97            self.dependencies.append("pip")
 98
 99        for dep in self.dependencies:
100            if isinstance(dep, PipDeps):
101                pip_section = dep
102                pip_section.pip.sort()
103                break
104        else:
105            pip_section = None
106
107        if (
108            pip_section is None
109            or not any(pd.startswith("bioimageio.core") for pd in pip_section.pip)
110        ) and not any(
111            d.startswith("bioimageio.core")
112            or d.startswith("conda-forge::bioimageio.core")
113            for d in self.dependencies
114            if not isinstance(d, PipDeps)
115        ):
116            self.dependencies.append("conda-forge::bioimageio.core")
117
118        self.dependencies.sort()
119        return self
class PipDeps(pydantic.main.BaseModel):
 8class PipDeps(BaseModel):
 9    """Pip dependencies to include in conda dependecies"""
10
11    pip: List[str] = Field(default_factory=list)
12
13    @field_validator("pip", mode="after")
14    @classmethod
15    def _remove_empty_and_sort(cls, value: List[str]) -> List[str]:
16        return sorted((vs for v in value if (vs := v.strip())))
17
18    def __lt__(self, other: Any):
19        if isinstance(other, PipDeps):
20            return len(self.pip) < len(other.pip)
21        else:
22            return False
23
24    def __gt__(self, other: Any):
25        if isinstance(other, PipDeps):
26            return len(self.pip) > len(other.pip)
27        else:
28            return False

Pip dependencies to include in conda dependecies

pip: List[str]
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class CondaEnv(pydantic.main.BaseModel):
31class CondaEnv(BaseModel):
32    """Represenation of the content of a conda environment.yaml file"""
33
34    name: Optional[str] = None
35    channels: List[str] = Field(default_factory=list)
36    dependencies: List[Union[str, PipDeps]] = Field(
37        default_factory=cast(Callable[[], List[Union[str, PipDeps]]], list)
38    )
39
40    @field_validator("name", mode="after")
41    def _ensure_valid_conda_env_name(cls, value: Optional[str]) -> Optional[str]:
42        if value is None:
43            return None
44
45        for illegal in ("/", " ", ":", "#"):
46            value = value.replace(illegal, "")
47
48        return value or "empty"
49
50    @property
51    def wo_name(self):
52        return self.model_construct(**{k: v for k, v in self if k != "name"})
53
54    def _get_version_pin(self, package: str):
55        """Helper to return any version pin for **package**
56
57        TODO: improve: interprete version pin and return structured information.
58        """
59        for d in self.dependencies:
60            if isinstance(d, PipDeps):
61                for p in d.pip:
62                    if p.startswith(package):
63                        return p[len(package) :]
64            elif d.startswith(package):
65                return d[len(package) :]
66            elif "::" in d and (d_wo_channel := d.split("::", 1)[-1]).startswith(
67                package
68            ):
69                return d_wo_channel[len(package) :]
70
71    def get_pip_deps(self) -> List[str]:
72        """Get the pip dependencies of this conda env."""
73        for dep in self.dependencies:
74            if isinstance(dep, PipDeps):
75                return dep.pip
76
77        return []

Represenation of the content of a conda environment.yaml file

name: Optional[str]
channels: List[str]
dependencies: List[Union[str, PipDeps]]
wo_name
50    @property
51    def wo_name(self):
52        return self.model_construct(**{k: v for k, v in self if k != "name"})
def get_pip_deps(self) -> List[str]:
71    def get_pip_deps(self) -> List[str]:
72        """Get the pip dependencies of this conda env."""
73        for dep in self.dependencies:
74            if isinstance(dep, PipDeps):
75                return dep.pip
76
77        return []

Get the pip dependencies of this conda env.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class BioimageioCondaEnv(CondaEnv):
 80class BioimageioCondaEnv(CondaEnv):
 81    """A special `CondaEnv` that
 82    - automatically adds bioimageio specific dependencies
 83    - sorts dependencies
 84    """
 85
 86    @model_validator(mode="after")
 87    def _normalize_bioimageio_conda_env(self):
 88        """update a conda env such that we have bioimageio.core and sorted dependencies"""
 89        for req_channel in ("conda-forge", "nodefaults"):
 90            if req_channel not in self.channels:
 91                self.channels.append(req_channel)
 92
 93        if "defaults" in self.channels:
 94            warnings.warn("removing 'defaults' from conda-channels")
 95            self.channels.remove("defaults")
 96
 97        if "pip" not in self.dependencies:
 98            self.dependencies.append("pip")
 99
100        for dep in self.dependencies:
101            if isinstance(dep, PipDeps):
102                pip_section = dep
103                pip_section.pip.sort()
104                break
105        else:
106            pip_section = None
107
108        if (
109            pip_section is None
110            or not any(pd.startswith("bioimageio.core") for pd in pip_section.pip)
111        ) and not any(
112            d.startswith("bioimageio.core")
113            or d.startswith("conda-forge::bioimageio.core")
114            for d in self.dependencies
115            if not isinstance(d, PipDeps)
116        ):
117            self.dependencies.append("conda-forge::bioimageio.core")
118
119        self.dependencies.sort()
120        return self

A special CondaEnv that

  • automatically adds bioimageio specific dependencies
  • sorts dependencies
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].