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 190 @model_validator(mode="before") 191 def _remove_version_number(cls, value: Any): 192 if is_dict(value): 193 vn = value.pop("version_number", None) 194 if vn is not None and value.get("version") is None: 195 value["version"] = vn 196 197 return value 198 199 version: Optional[Version] = None 200 """The version of the linked resource following SemVer 2.0.""" 201 202 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.""" 208 209 210class BioimageioConfig(Node, extra="allow"): 211 """bioimage.io internal metadata.""" 212 213 214class Config(Node, extra="allow"): 215 """A place to store additional metadata (often tool specific). 216 217 Such additional metadata is typically set programmatically by the respective tool 218 or by people with specific insights into the tool. 219 If you want to store additional metadata that does not match any of the other 220 fields, think of a key unlikely to collide with anyone elses use-case/tool and save 221 it here. 222 223 Please consider creating [an issue in the bioimageio.spec repository](https://github.com/bioimage-io/spec-bioimage-io/issues/new?template=Blank+issue) 224 if you are not sure if an existing field could cover your use case 225 or if you think such a field should exist. 226 """ 227 228 bioimageio: BioimageioConfig = Field(default_factory=BioimageioConfig) 229 """bioimage.io internal metadata.""" 230 231 @model_validator(mode="after") 232 def _validate_extra_fields(self): 233 if self.model_extra: 234 for k, v in self.model_extra.items(): 235 if not isinstance(v, Node) and not is_yaml_value(v): 236 raise ValueError( 237 f"config.{k} is not a valid YAML value or `Node` instance" 238 ) 239 240 return self 241 242 def __getitem__(self, key: str) -> Any: 243 """Allows to access the config as a dictionary.""" 244 return getattr(self, key) 245 246 def __setitem__(self, key: str, value: Any) -> None: 247 """Allows to set the config as a dictionary.""" 248 setattr(self, key, value) 249 250 251class GenericModelDescrBase(ResourceDescrBase): 252 """Base for all resource descriptions including of model descriptions""" 253 254 name: Annotated[ 255 Annotated[ 256 str, RestrictCharacters(string.ascii_letters + string.digits + "_+- ()") 257 ], 258 MinLen(5), 259 MaxLen(128), 260 warn(MaxLen(64), "Name longer than 64 characters.", INFO), 261 ] 262 """A human-friendly name of the resource description. 263 May only contains letters, digits, underscore, minus, parentheses and spaces.""" 264 265 description: FAIR[ 266 Annotated[ 267 str, 268 MaxLen(1024), 269 warn(MaxLen(512), "Description longer than 512 characters."), 270 ] 271 ] = "" 272 """A string containing a brief description.""" 273 274 covers: List[FileSource_cover] = Field( 275 default_factory=cast(Callable[[], List[FileSource_cover]], list), 276 description=( 277 "Cover images. Please use an image smaller than 500KB and an aspect" 278 " ratio width to height of 2:1 or 1:1.\nThe supported image formats" 279 f" are: {VALID_COVER_IMAGE_EXTENSIONS}" 280 ), 281 examples=[["cover.png"]], 282 ) 283 """Cover images.""" 284 285 id_emoji: Optional[ 286 Annotated[str, Len(min_length=1, max_length=2), Field(examples=["🦈", "🦥"])] 287 ] = None 288 """UTF-8 emoji for display alongside the `id`.""" 289 290 authors: FAIR[List[Author]] = Field( 291 default_factory=cast(Callable[[], List[Author]], list) 292 ) 293 """The authors are the creators of this resource description and the primary points of contact.""" 294 295 attachments: List[FileDescr_] = Field( 296 default_factory=cast(Callable[[], List[FileDescr_]], list) 297 ) 298 """file attachments""" 299 300 cite: FAIR[List[CiteEntry]] = Field( 301 default_factory=cast(Callable[[], List[CiteEntry]], list) 302 ) 303 """citations""" 304 305 license: FAIR[ 306 Annotated[ 307 Annotated[ 308 Union[LicenseId, DeprecatedLicenseId, None], 309 Field(union_mode="left_to_right"), 310 ], 311 warn( 312 Optional[LicenseId], 313 "{value} is deprecated, see https://spdx.org/licenses/{value}.html", 314 ), 315 Field(examples=["CC0-1.0", "MIT", "BSD-2-Clause"]), 316 ] 317 ] = None 318 """A [SPDX license identifier](https://spdx.org/licenses/). 319 We do not support custom license beyond the SPDX license list, if you need that please 320 [open a GitHub issue](https://github.com/bioimage-io/spec-bioimage-io/issues/new/choose) 321 to discuss your intentions with the community.""" 322 323 git_repo: Annotated[ 324 Optional[HttpUrl], 325 Field( 326 examples=[ 327 "https://github.com/bioimage-io/spec-bioimage-io/tree/main/example_descriptions/models/unet2d_nuclei_broad" 328 ], 329 ), 330 ] = None 331 """A URL to the Git repository where the resource is being developed.""" 332 333 icon: Union[Annotated[str, Len(min_length=1, max_length=2)], FileSource_, None] = ( 334 None 335 ) 336 """An icon for illustration, e.g. on bioimage.io""" 337 338 links: Annotated[ 339 List[str], 340 Field( 341 examples=[ 342 ( 343 "ilastik/ilastik", 344 "deepimagej/deepimagej", 345 "zero/notebook_u-net_3d_zerocostdl4mic", 346 ) 347 ], 348 ), 349 ] = Field(default_factory=list) 350 """IDs of other bioimage.io resources""" 351 352 uploader: Optional[Uploader] = None 353 """The person who uploaded the model (e.g. to bioimage.io)""" 354 355 maintainers: List[Maintainer] = Field( 356 default_factory=cast(Callable[[], List[Maintainer]], list) 357 ) 358 """Maintainers of this resource. 359 If not specified, `authors` are maintainers and at least some of them has to specify their `github_user` name""" 360 361 @model_validator(mode="after") 362 def _check_maintainers_exist(self): 363 if not self.maintainers and self.authors: 364 if all(a.github_user is None for a in self.authors): 365 issue_warning( 366 "Missing `maintainers` or any author in `authors` with a specified" 367 + " `github_user` name.", 368 value=self.authors, 369 field="authors", 370 severity=ALERT, 371 ) 372 373 return self 374 375 tags: FAIR[ 376 Annotated[ 377 List[str], 378 Field( 379 examples=[("unet2d", "pytorch", "nucleus", "segmentation", "dsb2018")] 380 ), 381 ] 382 ] = Field(default_factory=list) 383 """Associated tags""" 384 385 @as_warning 386 @field_validator("tags") 387 @classmethod 388 def warn_about_tag_categories( 389 cls, value: List[str], info: ValidationInfo 390 ) -> List[str]: 391 categories = TAG_CATEGORIES.get(info.data["type"], {}) 392 missing_categories: List[Dict[str, Sequence[str]]] = [] 393 for cat, entries in categories.items(): 394 if not any(e in value for e in entries): 395 missing_categories.append({cat: entries}) 396 397 if missing_categories: 398 raise ValueError( 399 f"Missing tags from bioimage.io categories: {missing_categories}" 400 ) 401 402 return value 403 404 version: Optional[Version] = None 405 """The version of the resource following SemVer 2.0.""" 406 407 @model_validator(mode="before") 408 def _remove_version_number(cls, value: Any): 409 if is_dict(value): 410 vn = value.pop("version_number", None) 411 if vn is not None and value.get("version") is None: 412 value["version"] = vn 413 414 return value 415 416 version_comment: Optional[Annotated[str, MaxLen(512)]] = None 417 """A comment on the version of the resource.""" 418 419 420FileSource_documentation = Annotated[ 421 FileSource_, 422 WithSuffix(".md", case_sensitive=True), 423 Field( 424 examples=[ 425 "https://raw.githubusercontent.com/bioimage-io/spec-bioimage-io/main/example_descriptions/models/unet2d_nuclei_broad/README.md", 426 "README.md", 427 ], 428 ), 429] 430 431 432class GenericDescrBase(GenericModelDescrBase): 433 """Base for all resource descriptions except for the model descriptions""" 434 435 implemented_format_version: ClassVar[Literal["0.3.0"]] = "0.3.0" 436 if TYPE_CHECKING: 437 format_version: Literal["0.3.0"] = "0.3.0" 438 else: 439 format_version: Literal["0.3.0"] 440 """The **format** version of this resource specification""" 441 442 @model_validator(mode="before") 443 @classmethod 444 def _convert_from_older_format( 445 cls, data: BioimageioYamlContent, / 446 ) -> BioimageioYamlContent: 447 cls.convert_from_old_format_wo_validation(data) 448 return data 449 450 @classmethod 451 def convert_from_old_format_wo_validation(cls, data: BioimageioYamlContent) -> None: 452 """Convert metadata following an older format version to this classes' format 453 without validating the result. 454 """ 455 convert_from_older_format(data) 456 457 documentation: FAIR[Optional[FileSource_documentation]] = None 458 """URL or relative path to a markdown file encoded in UTF-8 with additional documentation. 459 The recommended documentation file name is `README.md`. An `.md` suffix is mandatory.""" 460 461 badges: List[BadgeDescr] = Field( # pyright: ignore[reportUnknownVariableType] 462 default_factory=list 463 ) 464 """badges associated with this resource""" 465 466 config: Config = Field(default_factory=Config.model_construct) 467 """A field for custom configuration that can contain any keys not present in the RDF spec. 468 This means you should not store, for example, a GitHub repo URL in `config` since there is a `git_repo` field. 469 Keys in `config` may be very specific to a tool or consumer software. To avoid conflicting definitions, 470 it is recommended to wrap added configuration into a sub-field named with the specific domain or tool name, 471 for example: 472 ```yaml 473 config: 474 giraffe_neckometer: # here is the domain name 475 length: 3837283 476 address: 477 home: zoo 478 imagej: # config specific to ImageJ 479 macro_dir: path/to/macro/file 480 ``` 481 If possible, please use [`snake_case`](https://en.wikipedia.org/wiki/Snake_case) for keys in `config`. 482 You may want to list linked files additionally under `attachments` to include them when packaging a resource. 483 (Packaging a resource means downloading/copying important linked files and creating a ZIP archive that contains 484 an altered rdf.yaml file with local references to the downloaded files.)""" 485 486 487ResourceDescrType = TypeVar("ResourceDescrType", bound=GenericDescrBase) 488 489 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
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
491class GenericDescr(GenericDescrBase, extra="ignore"): 492 """Specification of the fields used in a generic bioimage.io-compliant resource description file (RDF). 493 494 An RDF is a YAML file that describes a resource such as a model, a dataset, or a notebook. 495 Note that those resources are described with a type-specific RDF. 496 Use this generic resource description, if none of the known specific types matches your resource. 497 """ 498 499 implemented_type: ClassVar[Literal["generic"]] = "generic" 500 if TYPE_CHECKING: 501 type: Annotated[str, LowerCase] = "generic" 502 """The resource type assigns a broad category to the resource.""" 503 else: 504 type: Annotated[str, LowerCase] 505 """The resource type assigns a broad category to the resource.""" 506 507 id: Optional[ 508 Annotated[ResourceId, Field(examples=["affable-shark", "ambitious-sloth"])] 509 ] = None 510 """bioimage.io-wide unique resource identifier 511 assigned by bioimage.io; version **un**specific.""" 512 513 parent: Optional[ResourceId] = None 514 """The description from which this one is derived""" 515 516 source: Optional[HttpUrl] = None 517 """The primary source of the resource""" 518 519 @field_validator("type", mode="after") 520 @classmethod 521 def check_specific_types(cls, value: str) -> str: 522 if value in KNOWN_SPECIFIC_RESOURCE_TYPES: 523 raise ValueError( 524 f"Use the {value} description instead of this generic description for" 525 + f" your '{value}' resource." 526 ) 527 528 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.
519 @field_validator("type", mode="after") 520 @classmethod 521 def check_specific_types(cls, value: str) -> str: 522 if value in KNOWN_SPECIFIC_RESOURCE_TYPES: 523 raise ValueError( 524 f"Use the {value} description instead of this generic description for" 525 + f" your '{value}' resource." 526 ) 527 528 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
139class HttpUrl(RootHttpUrl): 140 """A URL with the HTTP or HTTPS scheme.""" 141 142 root_model: ClassVar[Type[RootModel[Any]]] = RootModel[pydantic.HttpUrl] 143 _exists: Optional[bool] = None 144 145 def _after_validator(self): 146 self = super()._after_validator() 147 context = get_validation_context() 148 if context.perform_io_checks: 149 _ = self.exists() 150 151 return self 152 153 def exists(self): 154 """True if URL is available""" 155 if self._exists is None: 156 ctxt = get_validation_context() 157 try: 158 with ctxt.replace(warning_level=warning_levels.WARNING): 159 self._validated = _validate_url(self._validated) 160 except Exception as e: 161 if ctxt.log_warnings: 162 logger.info(e) 163 164 self._exists = False 165 else: 166 self._exists = True 167 168 return self._exists
A URL with the HTTP or HTTPS scheme.
the pydantic root model to validate the string
153 def exists(self): 154 """True if URL is available""" 155 if self._exists is None: 156 ctxt = get_validation_context() 157 try: 158 with ctxt.replace(warning_level=warning_levels.WARNING): 159 self._validated = _validate_url(self._validated) 160 except Exception as e: 161 if ctxt.log_warnings: 162 logger.info(e) 163 164 self._exists = False 165 else: 166 self._exists = True 167 168 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
204class LinkedResource(LinkedResourceBase): 205 """Reference to a bioimage.io resource""" 206 207 id: ResourceId 208 """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