bioimageio.spec.generic.v0_3
1from __future__ import annotations 2 3import string 4from typing import ( 5 TYPE_CHECKING, 6 Any, 7 Callable, 8 ClassVar, 9 Dict, 10 List, 11 Literal, 12 Optional, 13 Sequence, 14 Type, 15 TypeVar, 16 Union, 17 cast, 18) 19 20import annotated_types 21from annotated_types import Len, LowerCase, MaxLen, MinLen 22from pydantic import Field, RootModel, ValidationInfo, field_validator, model_validator 23from typing_extensions import Annotated 24 25from .._internal.common_nodes import Node, ResourceDescrBase 26from .._internal.constants import TAG_CATEGORIES 27from .._internal.field_validation import validate_github_user 28from .._internal.field_warning import as_warning, issue_warning, warn 29from .._internal.io import ( 30 BioimageioYamlContent, 31 FileDescr, 32 WithSuffix, 33 is_yaml_value, 34) 35from .._internal.io_basics import Sha256 36from .._internal.io_packaging import FileDescr_ 37from .._internal.license_id import DeprecatedLicenseId, LicenseId 38from .._internal.node_converter import Converter 39from .._internal.type_guards import is_dict 40from .._internal.types import FAIR, FileSource_, NotEmpty, RelativeFilePath 41from .._internal.url import HttpUrl 42from .._internal.validated_string import ValidatedString 43from .._internal.validator_annotations import ( 44 Predicate, 45 RestrictCharacters, 46) 47from .._internal.version_type import Version 48from .._internal.warning_levels import ALERT, INFO 49from ._v0_3_converter import convert_from_older_format 50from .v0_2 import Author as _Author_v0_2 51from .v0_2 import BadgeDescr, Doi, FileSource_cover, OrcidId, Uploader 52from .v0_2 import Maintainer as _Maintainer_v0_2 53 54__all__ = [ 55 "Author", 56 "BadgeDescr", 57 "CiteEntry", 58 "DeprecatedLicenseId", 59 "Doi", 60 "FileDescr", 61 "GenericDescr", 62 "HttpUrl", 63 "KNOWN_SPECIFIC_RESOURCE_TYPES", 64 "LicenseId", 65 "LinkedResource", 66 "Maintainer", 67 "OrcidId", 68 "RelativeFilePath", 69 "ResourceId", 70 "Sha256", 71 "Uploader", 72 "VALID_COVER_IMAGE_EXTENSIONS", 73 "Version", 74] 75 76KNOWN_SPECIFIC_RESOURCE_TYPES = ( 77 "application", 78 "collection", 79 "dataset", 80 "model", 81 "notebook", 82) 83VALID_COVER_IMAGE_EXTENSIONS = ( 84 ".gif", 85 ".jpeg", 86 ".jpg", 87 ".png", 88 ".svg", 89) 90 91 92class ResourceId(ValidatedString): 93 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[ 94 Annotated[ 95 NotEmpty[str], 96 RestrictCharacters(string.ascii_lowercase + string.digits + "_-/."), 97 annotated_types.Predicate( 98 lambda s: not (s.startswith("/") or s.endswith("/")) 99 ), 100 ] 101 ] 102 103 104def _has_no_slash(s: str) -> bool: 105 return "/" not in s and "\\" not in s 106 107 108class Author(_Author_v0_2): 109 name: Annotated[str, Predicate(_has_no_slash)] 110 github_user: Optional[str] = None 111 112 @field_validator("github_user", mode="after") 113 def _validate_github_user(cls, value: Optional[str]): 114 if value is None: 115 return None 116 else: 117 return validate_github_user(value) 118 119 120class _AuthorConv(Converter[_Author_v0_2, Author]): 121 def _convert( 122 self, src: _Author_v0_2, tgt: "type[Author] | type[dict[str, Any]]" 123 ) -> "Author | dict[str, Any]": 124 return tgt( 125 name=src.name, 126 github_user=src.github_user, 127 affiliation=src.affiliation, 128 email=src.email, 129 orcid=src.orcid, 130 ) 131 132 133_author_conv = _AuthorConv(_Author_v0_2, Author) 134 135 136class Maintainer(_Maintainer_v0_2): 137 name: Optional[Annotated[str, Predicate(_has_no_slash)]] = None 138 github_user: str 139 140 @field_validator("github_user", mode="after") 141 def validate_github_user(cls, value: str): 142 return validate_github_user(value) 143 144 145class _MaintainerConv(Converter[_Maintainer_v0_2, Maintainer]): 146 def _convert( 147 self, src: _Maintainer_v0_2, tgt: "type[Maintainer | dict[str, Any]]" 148 ) -> "Maintainer | dict[str, Any]": 149 return tgt( 150 name=src.name, 151 github_user=src.github_user, 152 affiliation=src.affiliation, 153 email=src.email, 154 orcid=src.orcid, 155 ) 156 157 158_maintainer_conv = _MaintainerConv(_Maintainer_v0_2, Maintainer) 159 160 161class CiteEntry(Node): 162 """A citation that should be referenced in work using this resource.""" 163 164 text: str 165 """free text description""" 166 167 doi: Optional[Doi] = None 168 """A digital object identifier (DOI) is the prefered citation reference. 169 See https://www.doi.org/ for details. 170 Note: 171 Either **doi** or **url** have to be specified. 172 """ 173 174 url: Optional[HttpUrl] = None 175 """URL to cite (preferably specify a **doi** instead/also). 176 Note: 177 Either **doi** or **url** have to be specified. 178 """ 179 180 @model_validator(mode="after") 181 def _check_doi_or_url(self): 182 if not self.doi and not self.url: 183 raise ValueError("Either 'doi' or 'url' is required") 184 185 return self 186 187 188class LinkedResourceBase(Node): 189 @model_validator(mode="before") 190 def _remove_version_number(cls, value: Any): 191 if is_dict(value): 192 vn = value.pop("version_number", None) 193 if vn is not None and value.get("version") is None: 194 value["version"] = vn 195 196 return value 197 198 version: Optional[Version] = None 199 """The version of the linked resource following SemVer 2.0.""" 200 201 202class LinkedResource(LinkedResourceBase): 203 """Reference to a bioimage.io resource""" 204 205 id: ResourceId 206 """A valid resource `id` from the official bioimage.io collection.""" 207 208 209class BioimageioConfig(Node, extra="allow"): 210 """bioimage.io internal metadata.""" 211 212 213class Config(Node, extra="allow"): 214 """A place to store additional metadata (often tool specific). 215 216 Such additional metadata is typically set programmatically by the respective tool 217 or by people with specific insights into the tool. 218 If you want to store additional metadata that does not match any of the other 219 fields, think of a key unlikely to collide with anyone elses use-case/tool and save 220 it here. 221 222 Please consider creating [an issue in the bioimageio.spec repository](https://github.com/bioimage-io/spec-bioimage-io/issues/new?template=Blank+issue) 223 if you are not sure if an existing field could cover your use case 224 or if you think such a field should exist. 225 """ 226 227 bioimageio: BioimageioConfig = Field(default_factory=BioimageioConfig) 228 """bioimage.io internal metadata.""" 229 230 @model_validator(mode="after") 231 def _validate_extra_fields(self): 232 if self.model_extra: 233 for k, v in self.model_extra.items(): 234 if not isinstance(v, Node) and not is_yaml_value(v): 235 raise ValueError( 236 f"config.{k} is not a valid YAML value or `Node` instance" 237 ) 238 239 return self 240 241 def __getitem__(self, key: str) -> Any: 242 """Allows to access the config as a dictionary.""" 243 return getattr(self, key) 244 245 def __setitem__(self, key: str, value: Any) -> None: 246 """Allows to set the config as a dictionary.""" 247 setattr(self, key, value) 248 249 250class GenericModelDescrBase(ResourceDescrBase): 251 """Base for all resource descriptions including of model descriptions""" 252 253 name: Annotated[ 254 Annotated[ 255 str, RestrictCharacters(string.ascii_letters + string.digits + "_+- ()") 256 ], 257 MinLen(5), 258 MaxLen(128), 259 warn(MaxLen(64), "Name longer than 64 characters.", INFO), 260 ] 261 """A human-friendly name of the resource description. 262 May only contains letters, digits, underscore, minus, parentheses and spaces.""" 263 264 description: FAIR[ 265 Annotated[ 266 str, 267 MaxLen(1024), 268 warn(MaxLen(512), "Description longer than 512 characters."), 269 ] 270 ] = "" 271 """A string containing a brief description.""" 272 273 covers: List[FileSource_cover] = Field( 274 default_factory=cast(Callable[[], List[FileSource_cover]], list), 275 description=( 276 "Cover images. Please use an image smaller than 500KB and an aspect" 277 " ratio width to height of 2:1 or 1:1.\nThe supported image formats" 278 f" are: {VALID_COVER_IMAGE_EXTENSIONS}" 279 ), 280 examples=[["cover.png"]], 281 ) 282 """Cover images.""" 283 284 id_emoji: Optional[ 285 Annotated[str, Len(min_length=1, max_length=2), Field(examples=["🦈", "🦥"])] 286 ] = None 287 """UTF-8 emoji for display alongside the `id`.""" 288 289 authors: FAIR[List[Author]] = Field( 290 default_factory=cast(Callable[[], List[Author]], list) 291 ) 292 """The authors are the creators of this resource description and the primary points of contact.""" 293 294 attachments: List[FileDescr_] = Field( 295 default_factory=cast(Callable[[], List[FileDescr_]], list) 296 ) 297 """file attachments""" 298 299 cite: FAIR[List[CiteEntry]] = Field( 300 default_factory=cast(Callable[[], List[CiteEntry]], list) 301 ) 302 """citations""" 303 304 license: FAIR[ 305 Annotated[ 306 Annotated[ 307 Union[LicenseId, DeprecatedLicenseId, None], 308 Field(union_mode="left_to_right"), 309 ], 310 warn( 311 Optional[LicenseId], 312 "{value} is deprecated, see https://spdx.org/licenses/{value}.html", 313 ), 314 Field(examples=["CC0-1.0", "MIT", "BSD-2-Clause"]), 315 ] 316 ] = None 317 """A [SPDX license identifier](https://spdx.org/licenses/). 318 We do not support custom license beyond the SPDX license list, if you need that please 319 [open a GitHub issue](https://github.com/bioimage-io/spec-bioimage-io/issues/new/choose) 320 to discuss your intentions with the community.""" 321 322 git_repo: Annotated[ 323 Optional[HttpUrl], 324 Field( 325 examples=[ 326 "https://github.com/bioimage-io/spec-bioimage-io/tree/main/example_descriptions/models/unet2d_nuclei_broad" 327 ], 328 ), 329 ] = None 330 """A URL to the Git repository where the resource is being developed.""" 331 332 icon: Union[Annotated[str, Len(min_length=1, max_length=2)], FileSource_, None] = ( 333 None 334 ) 335 """An icon for illustration, e.g. on bioimage.io""" 336 337 links: Annotated[ 338 List[str], 339 Field( 340 examples=[ 341 ( 342 "ilastik/ilastik", 343 "deepimagej/deepimagej", 344 "zero/notebook_u-net_3d_zerocostdl4mic", 345 ) 346 ], 347 ), 348 ] = Field(default_factory=list) 349 """IDs of other bioimage.io resources""" 350 351 uploader: Optional[Uploader] = None 352 """The person who uploaded the model (e.g. to bioimage.io)""" 353 354 maintainers: List[Maintainer] = Field( 355 default_factory=cast(Callable[[], List[Maintainer]], list) 356 ) 357 """Maintainers of this resource. 358 If not specified, `authors` are maintainers and at least some of them has to specify their `github_user` name""" 359 360 @model_validator(mode="after") 361 def _check_maintainers_exist(self): 362 if not self.maintainers and self.authors: 363 if all(a.github_user is None for a in self.authors): 364 issue_warning( 365 "Missing `maintainers` or any author in `authors` with a specified" 366 + " `github_user` name.", 367 value=self.authors, 368 field="authors", 369 severity=ALERT, 370 ) 371 372 return self 373 374 tags: FAIR[ 375 Annotated[ 376 List[str], 377 Field( 378 examples=[("unet2d", "pytorch", "nucleus", "segmentation", "dsb2018")] 379 ), 380 ] 381 ] = Field(default_factory=list) 382 """Associated tags""" 383 384 @as_warning 385 @field_validator("tags") 386 @classmethod 387 def warn_about_tag_categories( 388 cls, value: List[str], info: ValidationInfo 389 ) -> List[str]: 390 categories = TAG_CATEGORIES.get(info.data["type"], {}) 391 missing_categories: List[Dict[str, Sequence[str]]] = [] 392 for cat, entries in categories.items(): 393 if not any(e in value for e in entries): 394 missing_categories.append({cat: entries}) 395 396 if missing_categories: 397 raise ValueError( 398 f"Missing tags from bioimage.io categories: {missing_categories}" 399 ) 400 401 return value 402 403 version: Optional[Version] = None 404 """The version of the resource following SemVer 2.0.""" 405 406 @model_validator(mode="before") 407 def _remove_version_number(cls, value: Any): 408 if is_dict(value): 409 vn = value.pop("version_number", None) 410 if vn is not None and value.get("version") is None: 411 value["version"] = vn 412 413 return value 414 415 version_comment: Optional[Annotated[str, MaxLen(512)]] = None 416 """A comment on the version of the resource.""" 417 418 419FileSource_documentation = Annotated[ 420 FileSource_, 421 WithSuffix(".md", case_sensitive=True), 422 Field( 423 examples=[ 424 "https://raw.githubusercontent.com/bioimage-io/spec-bioimage-io/main/example_descriptions/models/unet2d_nuclei_broad/README.md", 425 "README.md", 426 ], 427 ), 428] 429 430 431class GenericDescrBase(GenericModelDescrBase): 432 """Base for all resource descriptions except for the model descriptions""" 433 434 implemented_format_version: ClassVar[Literal["0.3.0"]] = "0.3.0" 435 if TYPE_CHECKING: 436 format_version: Literal["0.3.0"] = "0.3.0" 437 else: 438 format_version: Literal["0.3.0"] 439 """The **format** version of this resource specification""" 440 441 @model_validator(mode="before") 442 @classmethod 443 def _convert_from_older_format( 444 cls, data: BioimageioYamlContent, / 445 ) -> BioimageioYamlContent: 446 cls.convert_from_old_format_wo_validation(data) 447 return data 448 449 @classmethod 450 def convert_from_old_format_wo_validation(cls, data: BioimageioYamlContent) -> None: 451 """Convert metadata following an older format version to this classes' format 452 without validating the result. 453 """ 454 convert_from_older_format(data) 455 456 documentation: FAIR[Optional[FileSource_documentation]] = None 457 """URL or relative path to a markdown file encoded in UTF-8 with additional documentation. 458 The recommended documentation file name is `README.md`. An `.md` suffix is mandatory.""" 459 460 badges: List[BadgeDescr] = Field( # pyright: ignore[reportUnknownVariableType] 461 default_factory=list 462 ) 463 """badges associated with this resource""" 464 465 config: Config = Field(default_factory=Config.model_construct) 466 """A field for custom configuration that can contain any keys not present in the RDF spec. 467 This means you should not store, for example, a GitHub repo URL in `config` since there is a `git_repo` field. 468 Keys in `config` may be very specific to a tool or consumer software. To avoid conflicting definitions, 469 it is recommended to wrap added configuration into a sub-field named with the specific domain or tool name, 470 for example: 471 ```yaml 472 config: 473 giraffe_neckometer: # here is the domain name 474 length: 3837283 475 address: 476 home: zoo 477 imagej: # config specific to ImageJ 478 macro_dir: path/to/macro/file 479 ``` 480 If possible, please use [`snake_case`](https://en.wikipedia.org/wiki/Snake_case) for keys in `config`. 481 You may want to list linked files additionally under `attachments` to include them when packaging a resource. 482 (Packaging a resource means downloading/copying important linked files and creating a ZIP archive that contains 483 an altered rdf.yaml file with local references to the downloaded files.)""" 484 485 486ResourceDescrType = TypeVar("ResourceDescrType", bound=GenericDescrBase) 487 488 489class GenericDescr(GenericDescrBase, extra="ignore"): 490 """Specification of the fields used in a generic bioimage.io-compliant resource description file (RDF). 491 492 An RDF is a YAML file that describes a resource such as a model, a dataset, or a notebook. 493 Note that those resources are described with a type-specific RDF. 494 Use this generic resource description, if none of the known specific types matches your resource. 495 """ 496 497 implemented_type: ClassVar[Literal["generic"]] = "generic" 498 if TYPE_CHECKING: 499 type: Annotated[str, LowerCase] = "generic" 500 """The resource type assigns a broad category to the resource.""" 501 else: 502 type: Annotated[str, LowerCase] 503 """The resource type assigns a broad category to the resource.""" 504 505 id: Optional[ 506 Annotated[ResourceId, Field(examples=["affable-shark", "ambitious-sloth"])] 507 ] = None 508 """bioimage.io-wide unique resource identifier 509 assigned by bioimage.io; version **un**specific.""" 510 511 parent: Optional[ResourceId] = None 512 """The description from which this one is derived""" 513 514 source: Optional[HttpUrl] = None 515 """The primary source of the resource""" 516 517 @field_validator("type", mode="after") 518 @classmethod 519 def check_specific_types(cls, value: str) -> str: 520 if value in KNOWN_SPECIFIC_RESOURCE_TYPES: 521 raise ValueError( 522 f"Use the {value} description instead of this generic description for" 523 + f" your '{value}' resource." 524 ) 525 526 return value
109class Author(_Author_v0_2): 110 name: Annotated[str, Predicate(_has_no_slash)] 111 github_user: Optional[str] = None 112 113 @field_validator("github_user", mode="after") 114 def _validate_github_user(cls, value: Optional[str]): 115 if value is None: 116 return None 117 else: 118 return validate_github_user(value)
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
145class BadgeDescr(Node): 146 """A custom badge""" 147 148 label: Annotated[str, Field(examples=["Open in Colab"])] 149 """badge label to display on hover""" 150 151 icon: Annotated[ 152 Optional[ 153 Union[ 154 Annotated[ 155 Union[FilePath, RelativeFilePath], 156 AfterValidator(wo_special_file_name), 157 include_in_package, 158 ], 159 Union[HttpUrl, pydantic.HttpUrl], 160 ] 161 ], 162 Field(examples=["https://colab.research.google.com/assets/colab-badge.svg"]), 163 ] = None 164 """badge icon (included in bioimage.io package if not a URL)""" 165 166 url: Annotated[ 167 HttpUrl, 168 Field( 169 examples=[ 170 "https://colab.research.google.com/github/HenriquesLab/ZeroCostDL4Mic/blob/master/Colab_notebooks/U-net_2D_ZeroCostDL4Mic.ipynb" 171 ] 172 ), 173 ] 174 """target URL"""
A custom badge
badge label to display on hover
badge icon (included in bioimage.io package if not a URL)
target URL
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
162class CiteEntry(Node): 163 """A citation that should be referenced in work using this resource.""" 164 165 text: str 166 """free text description""" 167 168 doi: Optional[Doi] = None 169 """A digital object identifier (DOI) is the prefered citation reference. 170 See https://www.doi.org/ for details. 171 Note: 172 Either **doi** or **url** have to be specified. 173 """ 174 175 url: Optional[HttpUrl] = None 176 """URL to cite (preferably specify a **doi** instead/also). 177 Note: 178 Either **doi** or **url** have to be specified. 179 """ 180 181 @model_validator(mode="after") 182 def _check_doi_or_url(self): 183 if not self.doi and not self.url: 184 raise ValueError("Either 'doi' or 'url' is required") 185 186 return self
A citation that should be referenced in work using this resource.
A digital object identifier (DOI) is the prefered citation reference. See https://www.doi.org/ for details.
Note:
Either doi or url have to be specified.
URL to cite (preferably specify a doi instead/also).
Note:
Either doi or url have to be specified.
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
24class DeprecatedLicenseId(ValidatedString): 25 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[DeprecatedLicenseIdLiteral]
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
the pydantic root model to validate the string
138class Doi(ValidatedString): 139 """A digital object identifier, see https://www.doi.org/""" 140 141 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[ 142 Annotated[str, StringConstraints(pattern=DOI_REGEX)] 143 ]
A digital object identifier, see https://www.doi.org/
255class FileDescr(Node): 256 """A file description""" 257 258 source: FileSource 259 """File source""" 260 261 sha256: Optional[Sha256] = None 262 """SHA256 hash value of the **source** file.""" 263 264 @model_validator(mode="after") 265 def _validate_sha256(self) -> Self: 266 if get_validation_context().perform_io_checks: 267 self.validate_sha256() 268 269 return self 270 271 def validate_sha256(self, force_recompute: bool = False) -> None: 272 """validate the sha256 hash value of the **source** file""" 273 context = get_validation_context() 274 src_str = str(self.source) 275 if not force_recompute and src_str in context.known_files: 276 actual_sha = context.known_files[src_str] 277 else: 278 reader = get_reader(self.source, sha256=self.sha256) 279 if force_recompute: 280 actual_sha = get_sha256(reader) 281 else: 282 actual_sha = reader.sha256 283 284 context.known_files[src_str] = actual_sha 285 286 if actual_sha is None: 287 return 288 elif self.sha256 == actual_sha: 289 pass 290 elif self.sha256 is None or context.update_hashes: 291 self.sha256 = actual_sha 292 elif self.sha256 != actual_sha: 293 raise ValueError( 294 f"Sha256 mismatch for {self.source}. Expected {self.sha256}, got " 295 + f"{actual_sha}. Update expected `sha256` or point to the matching " 296 + "file." 297 ) 298 299 def get_reader( 300 self, *, progressbar: Union[Progressbar, Callable[[], Progressbar], bool] = True 301 ): 302 """open the file source (download if needed)""" 303 return get_reader(self.source, progressbar=progressbar, sha256=self.sha256) 304 305 download = get_reader 306 """alias for get_reader() method"""
A file description
File source
271 def validate_sha256(self, force_recompute: bool = False) -> None: 272 """validate the sha256 hash value of the **source** file""" 273 context = get_validation_context() 274 src_str = str(self.source) 275 if not force_recompute and src_str in context.known_files: 276 actual_sha = context.known_files[src_str] 277 else: 278 reader = get_reader(self.source, sha256=self.sha256) 279 if force_recompute: 280 actual_sha = get_sha256(reader) 281 else: 282 actual_sha = reader.sha256 283 284 context.known_files[src_str] = actual_sha 285 286 if actual_sha is None: 287 return 288 elif self.sha256 == actual_sha: 289 pass 290 elif self.sha256 is None or context.update_hashes: 291 self.sha256 = actual_sha 292 elif self.sha256 != actual_sha: 293 raise ValueError( 294 f"Sha256 mismatch for {self.source}. Expected {self.sha256}, got " 295 + f"{actual_sha}. Update expected `sha256` or point to the matching " 296 + "file." 297 )
validate the sha256 hash value of the source file
299 def get_reader( 300 self, *, progressbar: Union[Progressbar, Callable[[], Progressbar], bool] = True 301 ): 302 """open the file source (download if needed)""" 303 return get_reader(self.source, progressbar=progressbar, sha256=self.sha256)
open the file source (download if needed)
299 def get_reader( 300 self, *, progressbar: Union[Progressbar, Callable[[], Progressbar], bool] = True 301 ): 302 """open the file source (download if needed)""" 303 return get_reader(self.source, progressbar=progressbar, sha256=self.sha256)
alias for get_reader() method
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
490class GenericDescr(GenericDescrBase, extra="ignore"): 491 """Specification of the fields used in a generic bioimage.io-compliant resource description file (RDF). 492 493 An RDF is a YAML file that describes a resource such as a model, a dataset, or a notebook. 494 Note that those resources are described with a type-specific RDF. 495 Use this generic resource description, if none of the known specific types matches your resource. 496 """ 497 498 implemented_type: ClassVar[Literal["generic"]] = "generic" 499 if TYPE_CHECKING: 500 type: Annotated[str, LowerCase] = "generic" 501 """The resource type assigns a broad category to the resource.""" 502 else: 503 type: Annotated[str, LowerCase] 504 """The resource type assigns a broad category to the resource.""" 505 506 id: Optional[ 507 Annotated[ResourceId, Field(examples=["affable-shark", "ambitious-sloth"])] 508 ] = None 509 """bioimage.io-wide unique resource identifier 510 assigned by bioimage.io; version **un**specific.""" 511 512 parent: Optional[ResourceId] = None 513 """The description from which this one is derived""" 514 515 source: Optional[HttpUrl] = None 516 """The primary source of the resource""" 517 518 @field_validator("type", mode="after") 519 @classmethod 520 def check_specific_types(cls, value: str) -> str: 521 if value in KNOWN_SPECIFIC_RESOURCE_TYPES: 522 raise ValueError( 523 f"Use the {value} description instead of this generic description for" 524 + f" your '{value}' resource." 525 ) 526 527 return value
Specification of the fields used in a generic bioimage.io-compliant resource description file (RDF).
An RDF is a YAML file that describes a resource such as a model, a dataset, or a notebook. Note that those resources are described with a type-specific RDF. Use this generic resource description, if none of the known specific types matches your resource.
bioimage.io-wide unique resource identifier assigned by bioimage.io; version unspecific.
518 @field_validator("type", mode="after") 519 @classmethod 520 def check_specific_types(cls, value: str) -> str: 521 if value in KNOWN_SPECIFIC_RESOURCE_TYPES: 522 raise ValueError( 523 f"Use the {value} description instead of this generic description for" 524 + f" your '{value}' resource." 525 ) 526 527 return value
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
337def init_private_attributes(self: BaseModel, context: Any, /) -> None: 338 """This function is meant to behave like a BaseModel method to initialise private attributes. 339 340 It takes context as an argument since that's what pydantic-core passes when calling it. 341 342 Args: 343 self: The BaseModel instance. 344 context: The context. 345 """ 346 if getattr(self, '__pydantic_private__', None) is None: 347 pydantic_private = {} 348 for name, private_attr in self.__private_attributes__.items(): 349 default = private_attr.get_default() 350 if default is not PydanticUndefined: 351 pydantic_private[name] = default 352 object_setattr(self, '__pydantic_private__', pydantic_private)
This function is meant to behave like a BaseModel method to initialise private attributes.
It takes context as an argument since that's what pydantic-core passes when calling it.
Arguments:
- self: The BaseModel instance.
- context: The context.
Inherited Members
138class HttpUrl(RootHttpUrl): 139 """A URL with the HTTP or HTTPS scheme.""" 140 141 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[pydantic.HttpUrl] 142 _exists: Optional[bool] = None 143 144 def _after_validator(self): 145 self = super()._after_validator() 146 context = get_validation_context() 147 if context.perform_io_checks: 148 _ = self.exists() 149 150 return self 151 152 def exists(self): 153 """True if URL is available""" 154 if self._exists is None: 155 ctxt = get_validation_context() 156 try: 157 with ctxt.replace(warning_level=warning_levels.WARNING): 158 self._validated = _validate_url(self._validated) 159 except Exception as e: 160 if ctxt.log_warnings: 161 logger.info(e) 162 163 self._exists = False 164 else: 165 self._exists = True 166 167 return self._exists
A URL with the HTTP or HTTPS scheme.
the pydantic root model to validate the string
152 def exists(self): 153 """True if URL is available""" 154 if self._exists is None: 155 ctxt = get_validation_context() 156 try: 157 with ctxt.replace(warning_level=warning_levels.WARNING): 158 self._validated = _validate_url(self._validated) 159 except Exception as e: 160 if ctxt.log_warnings: 161 logger.info(e) 162 163 self._exists = False 164 else: 165 self._exists = True 166 167 return self._exists
True if URL is available
20class LicenseId(ValidatedString): 21 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[LicenseIdLiteral]
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
the pydantic root model to validate the string
203class LinkedResource(LinkedResourceBase): 204 """Reference to a bioimage.io resource""" 205 206 id: ResourceId 207 """A valid resource `id` from the official bioimage.io collection."""
Reference to a bioimage.io resource
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
137class Maintainer(_Maintainer_v0_2): 138 name: Optional[Annotated[str, Predicate(_has_no_slash)]] = None 139 github_user: str 140 141 @field_validator("github_user", mode="after") 142 def validate_github_user(cls, value: str): 143 return validate_github_user(value)
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
165class OrcidId(ValidatedString): 166 """An ORCID identifier, see https://orcid.org/""" 167 168 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[ 169 Annotated[str, AfterValidator(_validate_orcid_id)] 170 ]
An ORCID identifier, see https://orcid.org/
204class RelativeFilePath( 205 RelativePathBase[Union[AbsoluteFilePath, HttpUrl, ZipPath]], frozen=True 206): 207 """A path relative to the `rdf.yaml` file (also if the RDF source is a URL).""" 208 209 def model_post_init(self, __context: Any) -> None: 210 """add validation @private""" 211 if not self.root.parts: # an empty path can only be a directory 212 raise ValueError(f"{self.root} is not a valid file path.") 213 214 super().model_post_init(__context) 215 216 def get_absolute( 217 self, root: "RootHttpUrl | Path | AnyUrl | ZipFile" 218 ) -> "AbsoluteFilePath | HttpUrl | ZipPath": 219 absolute = self._get_absolute_impl(root) 220 if ( 221 isinstance(absolute, Path) 222 and (context := get_validation_context()).perform_io_checks 223 and str(self.root) not in context.known_files 224 and not absolute.is_file() 225 ): 226 raise ValueError(f"{absolute} does not point to an existing file") 227 228 return absolute
A path relative to the rdf.yaml
file (also if the RDF source is a URL).
216 def get_absolute( 217 self, root: "RootHttpUrl | Path | AnyUrl | ZipFile" 218 ) -> "AbsoluteFilePath | HttpUrl | ZipPath": 219 absolute = self._get_absolute_impl(root) 220 if ( 221 isinstance(absolute, Path) 222 and (context := get_validation_context()).perform_io_checks 223 and str(self.root) not in context.known_files 224 and not absolute.is_file() 225 ): 226 raise ValueError(f"{absolute} does not point to an existing file") 227 228 return absolute
Inherited Members
93class ResourceId(ValidatedString): 94 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[ 95 Annotated[ 96 NotEmpty[str], 97 RestrictCharacters(string.ascii_lowercase + string.digits + "_-/."), 98 annotated_types.Predicate( 99 lambda s: not (s.startswith("/") or s.endswith("/")) 100 ), 101 ] 102 ]
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
40class Sha256(ValidatedString): 41 """A SHA-256 hash value""" 42 43 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[ 44 Annotated[ 45 str, 46 StringConstraints( 47 strip_whitespace=True, to_lower=True, min_length=64, max_length=64 48 ), 49 ] 50 ]
A SHA-256 hash value
113class Uploader(Node): 114 email: EmailStr 115 """Email""" 116 name: Optional[Annotated[str, AfterValidator(_remove_slashes)]] = None 117 """name"""
name
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
Inherited Members
10class Version(RootModel[Union[str, int, float]]): 11 """wraps a packaging.version.Version instance for validation in pydantic models""" 12 13 _version: packaging.version.Version = PrivateAttr() 14 15 def __str__(self): 16 return str(self._version) 17 18 def model_post_init(self, __context: Any) -> None: 19 """set `_version` attribute @private""" 20 self._version = packaging.version.Version(str(self.root)) 21 return super().model_post_init(__context) 22 23 def __lt__(self, other: Version): 24 return self._version < other._version 25 26 def __eq__(self, other: Version): 27 return self._version == other._version 28 29 # the properties below are adopted from and mirror properties of packaging.version.Version 30 @property 31 def epoch(self) -> int: 32 """The epoch of the version. 33 34 >>> Version("2.0.0").epoch 35 0 36 >>> Version("1!2.0.0").epoch 37 1 38 """ 39 return self._version.epoch 40 41 @property 42 def release(self) -> Tuple[int, ...]: 43 """The components of the "release" segment of the version. 44 45 >>> Version("1.2.3").release 46 (1, 2, 3) 47 >>> Version("2.0.0").release 48 (2, 0, 0) 49 >>> Version("1!2.0.0.post0").release 50 (2, 0, 0) 51 52 Includes trailing zeroes but not the epoch or any pre-release / development / 53 post-release suffixes. 54 """ 55 return self._version.release 56 57 @property 58 def pre(self) -> Optional[Tuple[str, int]]: 59 """The pre-release segment of the version. 60 61 >>> print(Version("1.2.3").pre) 62 None 63 >>> Version("1.2.3a1").pre 64 ('a', 1) 65 >>> Version("1.2.3b1").pre 66 ('b', 1) 67 >>> Version("1.2.3rc1").pre 68 ('rc', 1) 69 """ 70 return self._version.pre 71 72 @property 73 def post(self) -> Optional[int]: 74 """The post-release number of the version. 75 76 >>> print(Version("1.2.3").post) 77 None 78 >>> Version("1.2.3.post1").post 79 1 80 """ 81 return self._version.post 82 83 @property 84 def dev(self) -> Optional[int]: 85 """The development number of the version. 86 87 >>> print(Version("1.2.3").dev) 88 None 89 >>> Version("1.2.3.dev1").dev 90 1 91 """ 92 return self._version.dev 93 94 @property 95 def local(self) -> Optional[str]: 96 """The local version segment of the version. 97 98 >>> print(Version("1.2.3").local) 99 None 100 >>> Version("1.2.3+abc").local 101 'abc' 102 """ 103 return self._version.local 104 105 @property 106 def public(self) -> str: 107 """The public portion of the version. 108 109 >>> Version("1.2.3").public 110 '1.2.3' 111 >>> Version("1.2.3+abc").public 112 '1.2.3' 113 >>> Version("1.2.3+abc.dev1").public 114 '1.2.3' 115 """ 116 return self._version.public 117 118 @property 119 def base_version(self) -> str: 120 """The "base version" of the version. 121 122 >>> Version("1.2.3").base_version 123 '1.2.3' 124 >>> Version("1.2.3+abc").base_version 125 '1.2.3' 126 >>> Version("1!1.2.3+abc.dev1").base_version 127 '1!1.2.3' 128 129 The "base version" is the public version of the project without any pre or post 130 release markers. 131 """ 132 return self._version.base_version 133 134 @property 135 def is_prerelease(self) -> bool: 136 """Whether this version is a pre-release. 137 138 >>> Version("1.2.3").is_prerelease 139 False 140 >>> Version("1.2.3a1").is_prerelease 141 True 142 >>> Version("1.2.3b1").is_prerelease 143 True 144 >>> Version("1.2.3rc1").is_prerelease 145 True 146 >>> Version("1.2.3dev1").is_prerelease 147 True 148 """ 149 return self._version.is_prerelease 150 151 @property 152 def is_postrelease(self) -> bool: 153 """Whether this version is a post-release. 154 155 >>> Version("1.2.3").is_postrelease 156 False 157 >>> Version("1.2.3.post1").is_postrelease 158 True 159 """ 160 return self._version.is_postrelease 161 162 @property 163 def is_devrelease(self) -> bool: 164 """Whether this version is a development release. 165 166 >>> Version("1.2.3").is_devrelease 167 False 168 >>> Version("1.2.3.dev1").is_devrelease 169 True 170 """ 171 return self._version.is_devrelease 172 173 @property 174 def major(self) -> int: 175 """The first item of :attr:`release` or ``0`` if unavailable. 176 177 >>> Version("1.2.3").major 178 1 179 """ 180 return self._version.major 181 182 @property 183 def minor(self) -> int: 184 """The second item of :attr:`release` or ``0`` if unavailable. 185 186 >>> Version("1.2.3").minor 187 2 188 >>> Version("1").minor 189 0 190 """ 191 return self._version.minor 192 193 @property 194 def micro(self) -> int: 195 """The third item of :attr:`release` or ``0`` if unavailable. 196 197 >>> Version("1.2.3").micro 198 3 199 >>> Version("1").micro 200 0 201 """ 202 return self._version.micro
wraps a packaging.version.Version instance for validation in pydantic models
30 @property 31 def epoch(self) -> int: 32 """The epoch of the version. 33 34 >>> Version("2.0.0").epoch 35 0 36 >>> Version("1!2.0.0").epoch 37 1 38 """ 39 return self._version.epoch
The epoch of the version.
>>> Version("2.0.0").epoch
0
>>> Version("1!2.0.0").epoch
1
41 @property 42 def release(self) -> Tuple[int, ...]: 43 """The components of the "release" segment of the version. 44 45 >>> Version("1.2.3").release 46 (1, 2, 3) 47 >>> Version("2.0.0").release 48 (2, 0, 0) 49 >>> Version("1!2.0.0.post0").release 50 (2, 0, 0) 51 52 Includes trailing zeroes but not the epoch or any pre-release / development / 53 post-release suffixes. 54 """ 55 return self._version.release
The components of the "release" segment of the version.
>>> Version("1.2.3").release
(1, 2, 3)
>>> Version("2.0.0").release
(2, 0, 0)
>>> Version("1!2.0.0.post0").release
(2, 0, 0)
Includes trailing zeroes but not the epoch or any pre-release / development / post-release suffixes.
57 @property 58 def pre(self) -> Optional[Tuple[str, int]]: 59 """The pre-release segment of the version. 60 61 >>> print(Version("1.2.3").pre) 62 None 63 >>> Version("1.2.3a1").pre 64 ('a', 1) 65 >>> Version("1.2.3b1").pre 66 ('b', 1) 67 >>> Version("1.2.3rc1").pre 68 ('rc', 1) 69 """ 70 return self._version.pre
The pre-release segment of the version.
>>> print(Version("1.2.3").pre)
None
>>> Version("1.2.3a1").pre
('a', 1)
>>> Version("1.2.3b1").pre
('b', 1)
>>> Version("1.2.3rc1").pre
('rc', 1)
72 @property 73 def post(self) -> Optional[int]: 74 """The post-release number of the version. 75 76 >>> print(Version("1.2.3").post) 77 None 78 >>> Version("1.2.3.post1").post 79 1 80 """ 81 return self._version.post
The post-release number of the version.
>>> print(Version("1.2.3").post)
None
>>> Version("1.2.3.post1").post
1
83 @property 84 def dev(self) -> Optional[int]: 85 """The development number of the version. 86 87 >>> print(Version("1.2.3").dev) 88 None 89 >>> Version("1.2.3.dev1").dev 90 1 91 """ 92 return self._version.dev
The development number of the version.
>>> print(Version("1.2.3").dev)
None
>>> Version("1.2.3.dev1").dev
1
94 @property 95 def local(self) -> Optional[str]: 96 """The local version segment of the version. 97 98 >>> print(Version("1.2.3").local) 99 None 100 >>> Version("1.2.3+abc").local 101 'abc' 102 """ 103 return self._version.local
The local version segment of the version.
>>> print(Version("1.2.3").local)
None
>>> Version("1.2.3+abc").local
'abc'
105 @property 106 def public(self) -> str: 107 """The public portion of the version. 108 109 >>> Version("1.2.3").public 110 '1.2.3' 111 >>> Version("1.2.3+abc").public 112 '1.2.3' 113 >>> Version("1.2.3+abc.dev1").public 114 '1.2.3' 115 """ 116 return self._version.public
The public portion of the version.
>>> Version("1.2.3").public
'1.2.3'
>>> Version("1.2.3+abc").public
'1.2.3'
>>> Version("1.2.3+abc.dev1").public
'1.2.3'
118 @property 119 def base_version(self) -> str: 120 """The "base version" of the version. 121 122 >>> Version("1.2.3").base_version 123 '1.2.3' 124 >>> Version("1.2.3+abc").base_version 125 '1.2.3' 126 >>> Version("1!1.2.3+abc.dev1").base_version 127 '1!1.2.3' 128 129 The "base version" is the public version of the project without any pre or post 130 release markers. 131 """ 132 return self._version.base_version
The "base version" of the version.
>>> Version("1.2.3").base_version
'1.2.3'
>>> Version("1.2.3+abc").base_version
'1.2.3'
>>> Version("1!1.2.3+abc.dev1").base_version
'1!1.2.3'
The "base version" is the public version of the project without any pre or post release markers.
134 @property 135 def is_prerelease(self) -> bool: 136 """Whether this version is a pre-release. 137 138 >>> Version("1.2.3").is_prerelease 139 False 140 >>> Version("1.2.3a1").is_prerelease 141 True 142 >>> Version("1.2.3b1").is_prerelease 143 True 144 >>> Version("1.2.3rc1").is_prerelease 145 True 146 >>> Version("1.2.3dev1").is_prerelease 147 True 148 """ 149 return self._version.is_prerelease
Whether this version is a pre-release.
>>> Version("1.2.3").is_prerelease
False
>>> Version("1.2.3a1").is_prerelease
True
>>> Version("1.2.3b1").is_prerelease
True
>>> Version("1.2.3rc1").is_prerelease
True
>>> Version("1.2.3dev1").is_prerelease
True
151 @property 152 def is_postrelease(self) -> bool: 153 """Whether this version is a post-release. 154 155 >>> Version("1.2.3").is_postrelease 156 False 157 >>> Version("1.2.3.post1").is_postrelease 158 True 159 """ 160 return self._version.is_postrelease
Whether this version is a post-release.
>>> Version("1.2.3").is_postrelease
False
>>> Version("1.2.3.post1").is_postrelease
True
162 @property 163 def is_devrelease(self) -> bool: 164 """Whether this version is a development release. 165 166 >>> Version("1.2.3").is_devrelease 167 False 168 >>> Version("1.2.3.dev1").is_devrelease 169 True 170 """ 171 return self._version.is_devrelease
Whether this version is a development release.
>>> Version("1.2.3").is_devrelease
False
>>> Version("1.2.3.dev1").is_devrelease
True
173 @property 174 def major(self) -> int: 175 """The first item of :attr:`release` or ``0`` if unavailable. 176 177 >>> Version("1.2.3").major 178 1 179 """ 180 return self._version.major
The first item of release
or 0
if unavailable.
>>> Version("1.2.3").major
1
182 @property 183 def minor(self) -> int: 184 """The second item of :attr:`release` or ``0`` if unavailable. 185 186 >>> Version("1.2.3").minor 187 2 188 >>> Version("1").minor 189 0 190 """ 191 return self._version.minor
The second item of release
or 0
if unavailable.
>>> Version("1.2.3").minor
2
>>> Version("1").minor
0
193 @property 194 def micro(self) -> int: 195 """The third item of :attr:`release` or ``0`` if unavailable. 196 197 >>> Version("1.2.3").micro 198 3 199 >>> Version("1").micro 200 0 201 """ 202 return self._version.micro
The third item of release
or 0
if unavailable.
>>> Version("1.2.3").micro
3
>>> Version("1").micro
0