Skip to content

Commit

Permalink
Merge pull request #556 from bioimage-io/better_axis_id_repr
Browse files Browse the repository at this point in the history
Make AxisId inherite from str to be usable with xarray.DataArray
  • Loading branch information
FynnBe authored Mar 14, 2024
2 parents 7495962 + 89f52d8 commit 34f08da
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 86 deletions.
6 changes: 3 additions & 3 deletions bioimageio/spec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# """
# .. include:: ../../README.md
# """
"""
.. include:: ../../README.md
"""

from . import application as application
from . import collection as collection
Expand Down
14 changes: 13 additions & 1 deletion bioimageio/spec/_description.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
from types import MappingProxyType
from typing import Any, Literal, Optional, TypeVar, Union

Expand All @@ -6,7 +7,7 @@

from ._build_description import DISCOVER, build_description_impl, get_rd_class_impl
from ._internal.common_nodes import InvalidDescr
from ._internal.io import BioimageioYamlContent
from ._internal.io import BioimageioYamlContent, BioimageioYamlSource
from ._internal.types import FormatVersionPlaceholder
from ._internal.validation_context import ValidationContext, validation_context_var
from .application import AnyApplicationDescr, ApplicationDescr
Expand Down Expand Up @@ -157,8 +158,19 @@ def validate_format(
format_version: Union[Literal["discover", "latest"], str] = DISCOVER,
context: Optional[ValidationContext] = None,
) -> ValidationSummary:
"""validate a bioimageio.yaml file (RDF)"""
with context or validation_context_var.get():
rd = build_description(data, format_version=format_version)

assert rd.validation_summary is not None
return rd.validation_summary


def update_format(
source: BioimageioYamlSource,
*,
output_path: Optional[Path] = None,
target_format_version: Union[Literal["latest"], str] = LATEST,
) -> BioimageioYamlContent:
"""update a bioimageio.yaml file without validating it"""
raise NotImplementedError("Oh no! This feature is not yet implemented")
59 changes: 0 additions & 59 deletions bioimageio/spec/_internal/field_validation.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,22 @@
from __future__ import annotations

import collections.abc
import dataclasses
import sys
from dataclasses import dataclass
from datetime import date, datetime
from typing import (
Any,
Dict,
Hashable,
Mapping,
Sequence,
Type,
Union,
)

import annotated_types
import requests
from pydantic import GetCoreSchemaHandler, functional_validators
from pydantic_core.core_schema import CoreSchema, no_info_after_validator_function

from bioimageio.spec._internal._settings import settings
from bioimageio.spec._internal.constants import KNOWN_GH_USERS, KNOWN_INVALID_GH_USERS
from bioimageio.spec._internal.field_warning import issue_warning
from bioimageio.spec._internal.validation_context import validation_context_var

if sys.version_info < (3, 10):
SLOTS: Dict[str, bool] = {}
KW_ONLY: Dict[str, bool] = {}
else:
SLOTS = {"slots": True}
KW_ONLY = {"kw_only": True}


@dataclasses.dataclass(frozen=True, **SLOTS)
class RestrictCharacters:
alphabet: str

def __get_pydantic_core_schema__(
self, source: Type[Any], handler: GetCoreSchemaHandler
) -> CoreSchema:
if not self.alphabet:
raise ValueError("Alphabet may not be empty")
schema = handler(source) # get the CoreSchema from the type / inner constraints
if schema["type"] != "str":
raise TypeError("RestrictCharacters can only be applied to strings")
return no_info_after_validator_function(
self.validate,
schema,
)

def validate(self, value: str) -> str:
if any(c not in self.alphabet for c in value):
raise ValueError(f"{value!r} is not restricted to {self.alphabet!r}")
return value


def is_valid_yaml_leaf_value(value: Any) -> bool:
return value is None or isinstance(value, (bool, date, datetime, int, float, str))
Expand Down Expand Up @@ -97,27 +59,6 @@ def validate_unique_entries(seq: Sequence[Hashable]):
return seq


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class AfterValidator(functional_validators.AfterValidator):
def __str__(self):
return f"AfterValidator({self.func.__name__})"


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class BeforeValidator(functional_validators.BeforeValidator):
def __str__(self):
return f"BeforeValidator({self.func.__name__})"


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class Predicate(annotated_types.Predicate):
def __str__(self):
return f"Predicate({self.func.__name__})"


def validate_gh_user(username: str, hotfix_known_errorenous_names: bool = True) -> str:
if hotfix_known_errorenous_names:
if username == "Constantin Pape":
Expand Down
2 changes: 1 addition & 1 deletion bioimageio/spec/_internal/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import pooch
import pydantic
from pydantic import (
AfterValidator,
AnyUrl,
DirectoryPath,
FilePath,
Expand Down Expand Up @@ -66,6 +65,7 @@
from .._internal.validation_context import (
validation_context_var,
)
from .validator_annotations import AfterValidator

if sys.version_info < (3, 10):
SLOTS: Dict[str, bool] = {}
Expand Down
10 changes: 5 additions & 5 deletions bioimageio/spec/_internal/io_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ def open_bioimageio_yaml(

if local_source.is_dir():
root = local_source
local_source = local_source / find_description_file_name(local_source)
local_source = local_source / find_bioimageio_yaml_file_name(local_source)

content = _sanitize_bioimageio_yaml(read_yaml(local_source))

return OpenedBioimageioYaml(content, root, downloaded.original_file_name)


def identify_bioimageio_yaml_file(file_names: Iterable[FileName]) -> FileName:
def identify_bioimageio_yaml_file_name(file_names: Iterable[FileName]) -> FileName:
file_names = sorted(file_names)
for bioimageio_name in ALL_BIOIMAGEIO_YAML_NAMES:
for fname in file_names:
Expand All @@ -106,17 +106,17 @@ def identify_bioimageio_yaml_file(file_names: Iterable[FileName]) -> FileName:
)


def find_description_file_name(path: Path) -> FileName:
def find_bioimageio_yaml_file_name(path: Path) -> FileName:
if path.is_file():
if not is_zipfile(path):
return path.name

with ZipFile(path, "r") as f:
file_names = identify_bioimageio_yaml_file(f.namelist())
file_names = identify_bioimageio_yaml_file_name(f.namelist())
else:
file_names = [p.name for p in path.glob("*")]

return identify_bioimageio_yaml_file(file_names)
return identify_bioimageio_yaml_file_name(file_names)


def unzip(
Expand Down
3 changes: 2 additions & 1 deletion bioimageio/spec/_internal/root_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from urllib.parse import urlsplit, urlunsplit

import pydantic
from pydantic import AfterValidator, TypeAdapter
from pydantic import TypeAdapter
from typing_extensions import Annotated

from .validated_string import ValidatedString
from .validator_annotations import AfterValidator

_http_url_adapter = TypeAdapter(pydantic.HttpUrl) # pyright: ignore[reportCallIssue]

Expand Down
2 changes: 1 addition & 1 deletion bioimageio/spec/_internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from typing_extensions import Annotated, Literal

from .constants import DOI_REGEX, SI_UNIT_REGEX
from .field_validation import AfterValidator, BeforeValidator
from .io import FileSource as FileSource
from .io import ImportantFileSource as ImportantFileSource
from .io import PermissiveFileSource as PermissiveFileSource
Expand All @@ -23,6 +22,7 @@
from .license_id import LicenseId as LicenseId
from .url import HttpUrl as HttpUrl
from .validated_string import ValidatedString
from .validator_annotations import AfterValidator, BeforeValidator
from .version_type import Version as Version

S = TypeVar("S", bound=Sequence[Any])
Expand Down
59 changes: 59 additions & 0 deletions bioimageio/spec/_internal/validator_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import sys
from dataclasses import dataclass
from typing import Any, Dict, Type

import annotated_types
from pydantic import GetCoreSchemaHandler, functional_validators
from pydantic_core import CoreSchema
from pydantic_core.core_schema import no_info_after_validator_function

if sys.version_info < (3, 10):
SLOTS: Dict[str, bool] = {}
KW_ONLY: Dict[str, bool] = {}
else:
SLOTS = {"slots": True}
KW_ONLY = {"kw_only": True}


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class AfterValidator(functional_validators.AfterValidator):
def __str__(self):
return f"AfterValidator({self.func.__name__})"


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class BeforeValidator(functional_validators.BeforeValidator):
def __str__(self):
return f"BeforeValidator({self.func.__name__})"


# TODO: make sure we use this one everywhere and not the vanilla pydantic one
@dataclass(frozen=True, **SLOTS)
class Predicate(annotated_types.Predicate):
def __str__(self):
return f"Predicate({self.func.__name__})"


@dataclass(frozen=True, **SLOTS)
class RestrictCharacters:
alphabet: str

def __get_pydantic_core_schema__(
self, source: Type[Any], handler: GetCoreSchemaHandler
) -> CoreSchema:
if not self.alphabet:
raise ValueError("Alphabet may not be empty")
schema = handler(source) # get the CoreSchema from the type / inner constraints
if schema["type"] != "str":
raise TypeError("RestrictCharacters can only be applied to strings")
return no_info_after_validator_function(
self.validate,
schema,
)

def validate(self, value: str) -> str:
if any(c not in self.alphabet for c in value):
raise ValueError(f"{value!r} is not restricted to {self.alphabet!r}")
return value
3 changes: 3 additions & 0 deletions bioimageio/spec/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from ._internal.io import Sha256 as Sha256
from ._internal.io import YamlValue as YamlValue
from ._internal.io_basics import FileName as FileName
from ._internal.io_utils import (
identify_bioimageio_yaml_file_name as identify_bioimageio_yaml_file_name,
)
from ._internal.root_url import RootHttpUrl as RootHttpUrl
from ._internal.types import FileSource as FileSource
from ._internal.types import PermissiveFileSource as PermissiveFileSource
Expand Down
10 changes: 4 additions & 6 deletions bioimageio/spec/generic/v0_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@

from .._internal.common_nodes import Node, ResourceDescrBase
from .._internal.constants import TAG_CATEGORIES
from .._internal.field_validation import (
AfterValidator as _AfterValidator,
)
from .._internal.field_warning import as_warning, issue_warning, warn
from .._internal.io import (
BioimageioYamlContent,
Expand All @@ -40,6 +37,7 @@
from .._internal.types import RelativeFilePath as RelativeFilePath
from .._internal.types import ResourceId as ResourceId
from .._internal.url import HttpUrl as HttpUrl
from .._internal.validator_annotations import AfterValidator
from .._internal.version_type import Version as Version
from ._v0_2_converter import convert_from_older_format as _convert_from_older_format

Expand Down Expand Up @@ -81,7 +79,7 @@ def _remove_slashes(s: str):
class Uploader(Node):
email: EmailStr
"""Email"""
name: Optional[Annotated[str, _AfterValidator(_remove_slashes)]] = None
name: Optional[Annotated[str, AfterValidator(_remove_slashes)]] = None
"""name"""


Expand All @@ -101,12 +99,12 @@ class _Person(Node):


class Author(_Person):
name: Annotated[str, _AfterValidator(_remove_slashes)]
name: Annotated[str, AfterValidator(_remove_slashes)]
github_user: Optional[str] = None # TODO: validate github_user


class Maintainer(_Person):
name: Optional[Annotated[str, _AfterValidator(_remove_slashes)]] = None
name: Optional[Annotated[str, AfterValidator(_remove_slashes)]] = None
github_user: str


Expand Down
2 changes: 1 addition & 1 deletion bioimageio/spec/generic/v0_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from .._internal.constants import (
TAG_CATEGORIES,
)
from .._internal.field_validation import AfterValidator, Predicate
from .._internal.field_warning import as_warning, warn
from .._internal.io import (
BioimageioYamlContent,
Expand All @@ -38,6 +37,7 @@
from .._internal.types import RelativeFilePath as RelativeFilePath
from .._internal.types import ResourceId as ResourceId
from .._internal.url import HttpUrl as HttpUrl
from .._internal.validator_annotations import AfterValidator, Predicate
from .._internal.version_type import Version as Version
from .._internal.warning_levels import ALERT
from ._v0_3_converter import convert_from_older_format
Expand Down
7 changes: 2 additions & 5 deletions bioimageio/spec/model/v0_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@
StringNode,
)
from .._internal.constants import SHA256_HINT
from .._internal.field_validation import (
AfterValidator,
RestrictCharacters,
validate_unique_entries,
)
from .._internal.field_validation import validate_unique_entries
from .._internal.field_warning import issue_warning, warn
from .._internal.io import (
BioimageioYamlContent,
Expand All @@ -62,6 +58,7 @@
from .._internal.types import NotEmpty as NotEmpty
from .._internal.types import ResourceId as ResourceId
from .._internal.url import HttpUrl as HttpUrl
from .._internal.validator_annotations import AfterValidator, RestrictCharacters
from .._internal.version_type import Version as Version
from .._internal.warning_levels import ALERT, INFO
from ..dataset.v0_2 import DatasetDescr as DatasetDescr
Expand Down
Loading

0 comments on commit 34f08da

Please sign in to comment.