Skip to content

Commit 71dc65e

Browse files
committed
WIP unfreeze aftermath and update pydantic
1 parent e12fba6 commit 71dc65e

File tree

26 files changed

+448
-448
lines changed

26 files changed

+448
-448
lines changed

bioimageio/spec/_internal/base_nodes.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929
StringConstraints,
3030
TypeAdapter,
3131
ValidationInfo,
32-
model_validator, # type: ignore
32+
model_validator,
3333
)
3434
from pydantic_core import PydanticUndefined, core_schema
3535
from typing_extensions import Annotated, LiteralString, Self
3636

3737
from bioimageio.spec._internal.constants import IN_PACKAGE_MESSAGE
38-
from bioimageio.spec._internal.types import NotEmpty, RdfContent, Version, YamlValue
38+
from bioimageio.spec._internal.types import RdfContent, Version
3939
from bioimageio.spec._internal.utils import unindent
4040
from bioimageio.spec._internal.validation_context import InternalValidationContext, get_internal_validation_context
4141
from bioimageio.spec.summary import ValidationSummary
@@ -137,9 +137,9 @@ def set_fields_explicitly(cls, data: Union[Any, Dict[Any, Any]]) -> Union[Any, D
137137
class ResourceDescriptionBase(NodeWithExplicitlySetFields):
138138
"""base class for all resource descriptions"""
139139

140-
type: str
141-
format_version: str
142-
_internal_validation_context: InternalValidationContext
140+
_internal_validation_context: InternalValidationContext = PrivateAttr(
141+
default_factory=get_internal_validation_context
142+
)
143143
_validation_summaries: List[ValidationSummary] = PrivateAttr(default_factory=list)
144144

145145
fields_to_set_explicitly: ClassVar[FrozenSet[LiteralString]] = frozenset({"type", "format_version"})
@@ -174,7 +174,7 @@ def remember_internal_validation_context(self, info: ValidationInfo) -> Self:
174174
@classmethod
175175
def __pydantic_init_subclass__(cls, **kwargs: Any):
176176
super().__pydantic_init_subclass__(**kwargs)
177-
if cls.model_fields["format_version"].default is not PydanticUndefined:
177+
if "format_version" in cls.model_fields and cls.model_fields["format_version"].default is not PydanticUndefined:
178178
cls.implemented_format_version = cls.model_fields["format_version"].default
179179
if "." not in cls.implemented_format_version:
180180
cls.implemented_format_version_tuple = (0, 0, 0)
@@ -228,6 +228,8 @@ def model_validate(
228228

229229

230230
class StringNode(collections.UserString, ABC):
231+
"""deprecated! don't use for new spec fields!"""
232+
231233
_pattern: ClassVar[str]
232234
_node_class: Type[Node]
233235
_node: Optional[Node] = None
@@ -266,7 +268,7 @@ def __getattr__(self, name: str):
266268
@classmethod
267269
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
268270
assert issubclass(source, StringNode)
269-
return core_schema.general_after_validator_function(
271+
return core_schema.with_info_after_validator_function(
270272
cls._validate,
271273
core_schema.str_schema(pattern=cls._pattern),
272274
serialization=core_schema.plain_serializer_function_ser_schema(
@@ -294,5 +296,15 @@ def _serialize(self) -> str:
294296
return self.data
295297

296298

297-
ConfigNode = Dict[NotEmpty[str], YamlValue]
298-
Kwargs = Dict[NotEmpty[str], YamlValue]
299+
class KwargsNode(Node):
300+
def get(self, item: str, default: Any = None) -> Any:
301+
return self[item] if item in self else default
302+
303+
def __getitem__(self, item: str) -> Any:
304+
if item in self.model_fields:
305+
return getattr(self, item)
306+
else:
307+
raise KeyError(item)
308+
309+
def __contains__(self, item: str) -> int:
310+
return item in self.model_fields

bioimageio/spec/_internal/field_warning.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
import pydantic.functional_validators
77
from annotated_types import BaseMetadata, GroupedMetadata
8-
from pydantic import FieldValidationInfo, TypeAdapter
8+
from pydantic import TypeAdapter
99
from pydantic._internal._decorators import inspect_validator
1010
from pydantic_core import PydanticCustomError
11-
from pydantic_core.core_schema import FieldValidatorFunction, NoInfoValidatorFunction
11+
from pydantic_core.core_schema import NoInfoValidatorFunction, ValidationInfo, WithInfoValidatorFunction
1212
from typing_extensions import Annotated, LiteralString
1313

1414
from bioimageio.spec._internal.constants import ERROR, WARNING, WARNING_LEVEL_CONTEXT_KEY
@@ -24,7 +24,7 @@
2424
SLOTS = {"slots": True}
2525

2626

27-
ValidatorFunction = Union[NoInfoValidatorFunction, FieldValidatorFunction]
27+
ValidatorFunction = Union[NoInfoValidatorFunction, WithInfoValidatorFunction]
2828

2929
AnnotationMetaData = Union[BaseMetadata, GroupedMetadata]
3030

@@ -46,7 +46,7 @@ def call_validator_func(
4646
func: "_V2Validator",
4747
mode: Literal["after", "before", "plain", "wrap"],
4848
value: Any,
49-
info: FieldValidationInfo,
49+
info: ValidationInfo,
5050
) -> Any:
5151
info_arg = inspect_validator(func, mode)
5252
if info_arg:
@@ -83,7 +83,7 @@ def as_warning(
8383
) -> ValidatorFunction:
8484
"""turn validation function into a no-op, based on warning level"""
8585

86-
def wrapper(value: Any, info: FieldValidationInfo) -> Any:
86+
def wrapper(value: Any, info: ValidationInfo) -> Any:
8787
try:
8888
call_validator_func(func, mode, value, info)
8989
except (AssertionError, ValueError) as e:

bioimageio/spec/_internal/types/__init__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing_extensions import Annotated
99

1010
from bioimageio.spec._internal.constants import DOI_REGEX, SI_UNIT_REGEX
11+
from bioimageio.spec._internal.types._file_source import AbsoluteFilePath as AbsoluteFilePath
1112
from bioimageio.spec._internal.types._file_source import FileSource as FileSource
1213
from bioimageio.spec._internal.types._file_source import RelativeFilePath as RelativeFilePath
1314
from bioimageio.spec._internal.types._generated_spdx_license_type import DeprecatedLicenseId as DeprecatedLicenseId
@@ -21,12 +22,10 @@
2122
validate_is_not_keyword,
2223
validate_orcid_id,
2324
)
24-
from bioimageio.spec._internal.validation_context import ValidationContext as ValidationContext
2525

2626
T = TypeVar("T")
2727
S = TypeVar("S", bound=Sequence[Any])
2828

29-
# types to describe RDF as pydantic models
3029
NotEmpty = Annotated[S, annotated_types.MinLen(1)]
3130

3231
Datetime = Annotated[datetime, BeforeValidator(validate_datetime)]
@@ -68,11 +67,14 @@
6867

6968
# types as loaded from YAML 1.2 (with ruamel.yaml)
7069
YamlLeafValue = Union[bool, date, datetime, float, int, str, None]
71-
YamlArray = List["YamlValue"] # YAML Array is cast to list, but...
72-
YamlKey = Union[ # ... YAML Arrays are cast to tuples if used as key in mappings
70+
YamlKey = Union[ # YAML Arrays are cast to tuples if used as key in mappings
7371
YamlLeafValue, Tuple[YamlLeafValue, ...] # (nesting is not allowed though)
7472
]
75-
YamlMapping = Dict[YamlKey, "YamlValue"] # YAML Mappings are cast to dict
73+
YamlArray = List["YamlValue"]
74+
YamlMapping = Dict[YamlKey, "YamlValue"]
75+
# note: for use in pydantic see https://docs.pydantic.dev/latest/concepts/types/#named-recursive-types
76+
# and don't open another issue a la https://github.com/pydantic/pydantic/issues/8021
7677
YamlValue = Union[YamlLeafValue, YamlArray, YamlMapping]
78+
7779
RdfContent = Dict[str, YamlValue]
7880
Doi = NewType("Doi", Annotated[str, StringConstraints(pattern=DOI_REGEX)])

bioimageio/spec/_internal/types/_file_source.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __repr__(self) -> str:
5252

5353
@classmethod
5454
def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
55-
return core_schema.general_after_validator_function(
55+
return core_schema.with_info_after_validator_function(
5656
cls._validate,
5757
core_schema.union_schema(
5858
[

bioimageio/spec/_internal/types/field_validation.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from datetime import datetime
77
from keyword import iskeyword
88
from pathlib import PurePath
9-
from typing import Any, Hashable, Mapping, Sequence, Type, TypeVar, Union, get_args
9+
from typing import Any, Hashable, Mapping, Sequence, Tuple, Type, TypeVar, Union, get_args
1010

1111
import annotated_types
1212
from dateutil.parser import isoparse
1313
from pydantic import AnyUrl, GetCoreSchemaHandler, functional_validators
1414
from pydantic_core.core_schema import CoreSchema, no_info_after_validator_function
15+
from typing_extensions import LiteralString
1516

1617
from bioimageio.spec._internal.constants import SLOTS
1718

@@ -41,15 +42,15 @@ def validate(self, value: str) -> str:
4142

4243
@dataclasses.dataclass(frozen=True, **SLOTS)
4344
class WithSuffix:
44-
suffix: Union[str, Sequence[str]]
45+
suffix: Union[LiteralString, Tuple[LiteralString, ...]]
4546
case_sensitive: bool
4647

4748
def __get_pydantic_core_schema__(self, source: Type[Any], handler: GetCoreSchemaHandler) -> CoreSchema:
4849
if not self.suffix:
4950
raise ValueError("suffix may not be empty")
5051

5152
schema = handler(source)
52-
if schema["type"] != str and source != FileSource and not issubclass(source, (RelativePath, PurePath)):
53+
if schema["type"] != str and source != FileSource and not issubclass(source, (AnyUrl, RelativePath, PurePath)):
5354
raise TypeError("WithSuffix can only be applied to strings, URLs and paths")
5455

5556
return no_info_after_validator_function(
@@ -132,7 +133,7 @@ def is_valid_yaml_value(value: Any) -> bool:
132133
return any(is_valid(value) for is_valid in (is_valid_yaml_key, is_valid_yaml_mapping, is_valid_yaml_sequence))
133134

134135

135-
V_suffix = TypeVar("V_suffix", bound=Union[AnyUrl, PurePath, RelativePath])
136+
V_suffix = TypeVar("V_suffix", bound=FileSource)
136137

137138

138139
def validate_suffix(value: V_suffix, *suffixes: str, case_sensitive: bool) -> V_suffix:
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
from typing import Literal
1+
from typing import Literal, Optional
22

3+
from pydantic import Field
34
from pydantic import HttpUrl as HttpUrl
5+
from typing_extensions import Annotated
46

7+
from bioimageio.spec._internal.types import FileSource as FileSource
58
from bioimageio.spec._internal.types import RelativeFilePath as RelativeFilePath
69
from bioimageio.spec.generic.v0_2 import Attachments as Attachments
710
from bioimageio.spec.generic.v0_2 import Author as Author
811
from bioimageio.spec.generic.v0_2 import Badge as Badge
912
from bioimageio.spec.generic.v0_2 import CiteEntry as CiteEntry
10-
from bioimageio.spec.generic.v0_2 import GenericBase
13+
from bioimageio.spec.generic.v0_2 import GenericBase, WithGenericFormatVersion
1114
from bioimageio.spec.generic.v0_2 import LinkedResource as LinkedResource
1215
from bioimageio.spec.generic.v0_2 import Maintainer as Maintainer
1316

1417

15-
class Application(GenericBase, title="bioimage.io application specification"):
18+
class Application(GenericBase, WithGenericFormatVersion, title="bioimage.io application specification"):
1619
"""Bioimage.io description of an application."""
1720

1821
type: Literal["application"] = "application"
22+
23+
source: Annotated[Optional[FileSource], Field(description="URL or path to the source of the application")] = None
24+
"""The primary source of the application"""
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
from typing import Literal
1+
from typing import Literal, Optional
22

3+
from pydantic import Field
34
from pydantic import HttpUrl as HttpUrl
5+
from typing_extensions import Annotated
46

7+
from bioimageio.spec._internal.types import FileSource as FileSource
58
from bioimageio.spec._internal.types import RelativeFilePath as RelativeFilePath
69
from bioimageio.spec.generic.v0_3 import Attachment as Attachment
710
from bioimageio.spec.generic.v0_3 import Author as Author
811
from bioimageio.spec.generic.v0_3 import Badge as Badge
912
from bioimageio.spec.generic.v0_3 import CiteEntry as CiteEntry
10-
from bioimageio.spec.generic.v0_3 import GenericBase
13+
from bioimageio.spec.generic.v0_3 import GenericBase, WithGenericFormatVersion
1114
from bioimageio.spec.generic.v0_3 import LinkedResource as LinkedResource
1215
from bioimageio.spec.generic.v0_3 import Maintainer as Maintainer
1316

1417

15-
class Application(GenericBase, title="bioimage.io application specification"):
18+
class Application(GenericBase, WithGenericFormatVersion, title="bioimage.io application specification"):
1619
"""Bioimage.io description of an application."""
1720

1821
type: Literal["application"] = "application"
22+
23+
source: Annotated[Optional[FileSource], Field(description="URL or path to the source of the application")] = None
24+
"""The primary source of the application"""

bioimageio/spec/collection/v0_2.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import collections.abc
2-
from typing import Any, ClassVar, Dict, Literal, Optional, Tuple, Union
2+
from typing import Any, ClassVar, Dict, List, Literal, Optional, Sequence, Union
33

44
from pydantic import (
55
Field,
66
PrivateAttr,
77
TypeAdapter,
88
field_validator,
9-
model_validator, # type: ignore
9+
model_validator,
1010
)
1111
from pydantic import (
1212
HttpUrl as HttpUrl,
@@ -27,7 +27,7 @@
2727
from bioimageio.spec.generic.v0_2 import Badge as Badge
2828
from bioimageio.spec.generic.v0_2 import CiteEntry as CiteEntry
2929
from bioimageio.spec.generic.v0_2 import Generic as Generic
30-
from bioimageio.spec.generic.v0_2 import GenericBase
30+
from bioimageio.spec.generic.v0_2 import GenericBase, WithGenericFormatVersion
3131
from bioimageio.spec.generic.v0_2 import LinkedResource as LinkedResource
3232
from bioimageio.spec.generic.v0_2 import Maintainer as Maintainer
3333
from bioimageio.spec.model.v0_4 import Model as Model
@@ -111,24 +111,24 @@ def entry(self) -> EntryNode:
111111
return self._entry
112112

113113

114-
class Collection(GenericBase, extra="allow", title="bioimage.io collection specification"):
114+
class Collection(GenericBase, WithGenericFormatVersion, extra="allow", title="bioimage.io collection specification"):
115115
"""A bioimage.io collection describes several other bioimage.io resources.
116116
Note that collections cannot be nested; resources listed under `collection` may not be collections themselves.
117117
"""
118118

119119
type: Literal["collection"] = "collection"
120120

121-
collection: NotEmpty[Tuple[CollectionEntry, ...]]
121+
collection: NotEmpty[List[CollectionEntry]]
122122
"""Collection entries"""
123123

124124
@field_validator("collection")
125125
@classmethod
126-
def check_unique_ids(cls, value: NotEmpty[Tuple[CollectionEntry, ...]]) -> NotEmpty[Tuple[CollectionEntry, ...]]:
126+
def check_unique_ids(cls, value: NotEmpty[List[CollectionEntry]]) -> NotEmpty[List[CollectionEntry]]:
127127
cls.check_unique_ids_impl(value)
128128
return value
129129

130130
@staticmethod
131-
def check_unique_ids_impl(value: NotEmpty[Tuple[CollectionEntryBase, ...]]):
131+
def check_unique_ids_impl(value: NotEmpty[Sequence[CollectionEntryBase]]):
132132
seen: Dict[str, int] = {}
133133
for i, v in enumerate(value):
134134
if v.id is None:

bioimageio/spec/collection/v0_3.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import ClassVar, Literal, Tuple, Union
1+
from typing import ClassVar, List, Literal, Tuple, Union
22

33
from pydantic import Field, TypeAdapter, field_validator
44
from pydantic import HttpUrl as HttpUrl
@@ -17,7 +17,7 @@
1717
from bioimageio.spec.generic.v0_3 import Author as Author
1818
from bioimageio.spec.generic.v0_3 import Badge as Badge
1919
from bioimageio.spec.generic.v0_3 import CiteEntry as CiteEntry
20-
from bioimageio.spec.generic.v0_3 import Generic, GenericBase
20+
from bioimageio.spec.generic.v0_3 import Generic, GenericBase, WithGenericFormatVersion
2121
from bioimageio.spec.generic.v0_3 import LinkedResource as LinkedResource
2222
from bioimageio.spec.generic.v0_3 import Maintainer as Maintainer
2323
from bioimageio.spec.model.v0_4 import Model as Model04
@@ -55,7 +55,7 @@ def entry(self) -> EntryNode:
5555
return self._entry
5656

5757

58-
class Collection(GenericBase, extra="allow", title="bioimage.io collection specification"):
58+
class Collection(GenericBase, WithGenericFormatVersion, extra="allow", title="bioimage.io collection specification"):
5959
"""A bioimage.io collection resource description file (collection RDF) describes a collection of bioimage.io
6060
resources.
6161
The resources listed in a collection RDF have types other than 'collection'; collections cannot be nested.
@@ -70,12 +70,12 @@ def _update_context(cls, context: InternalValidationContext, data: RdfContent) -
7070
assert "collection_base_content" not in context or context["collection_base_content"] == collection_base_content
7171
context["collection_base_content"] = collection_base_content
7272

73-
collection: NotEmpty[Tuple[CollectionEntry, ...]]
73+
collection: NotEmpty[List[CollectionEntry]]
7474
"""Collection entries"""
7575

7676
@field_validator("collection")
7777
@classmethod
78-
def check_unique_ids(cls, value: NotEmpty[Tuple[CollectionEntry, ...]]) -> NotEmpty[Tuple[CollectionEntry, ...]]:
78+
def check_unique_ids(cls, value: NotEmpty[List[CollectionEntry]]) -> NotEmpty[List[CollectionEntry]]:
7979
v0_2.Collection.check_unique_ids_impl(value)
8080
return value
8181

bioimageio/spec/dataset/v0_2.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
from typing import Literal
1+
from typing import Literal, Optional
22

3-
from bioimageio.spec._internal.types import DatasetId
3+
from pydantic import Field
4+
from typing_extensions import Annotated
5+
6+
from bioimageio.spec._internal.base_nodes import Node
7+
from bioimageio.spec._internal.types import DatasetId as DatasetId
8+
from bioimageio.spec._internal.types import FileSource as FileSource
49
from bioimageio.spec.generic.v0_2 import Attachments as Attachments
510
from bioimageio.spec.generic.v0_2 import Author as Author
611
from bioimageio.spec.generic.v0_2 import Badge as Badge
712
from bioimageio.spec.generic.v0_2 import CiteEntry as CiteEntry
8-
from bioimageio.spec.generic.v0_2 import GenericBase
9-
from bioimageio.spec.generic.v0_2 import LinkedResource as LinkedResource
13+
from bioimageio.spec.generic.v0_2 import GenericBase, WithGenericFormatVersion
1014
from bioimageio.spec.generic.v0_2 import Maintainer as Maintainer
1115

1216

13-
class Dataset(GenericBase, title="bioimage.io dataset specification"):
17+
class Dataset(GenericBase, WithGenericFormatVersion, title="bioimage.io dataset specification"):
1418
"""A bioimage.io dataset resource description file (dataset RDF) describes a dataset relevant to bioimage
1519
processing.
1620
"""
1721

1822
type: Literal["dataset"] = "dataset"
1923

24+
source: Annotated[Optional[FileSource], Field(description="URL or path to the source of the dataset.")] = None
25+
"""The primary source of the dataset"""
26+
2027

21-
class LinkedDataset(LinkedResource):
28+
class LinkedDataset(Node):
2229
"""Reference to a bioimage.io dataset."""
2330

2431
id: DatasetId

0 commit comments

Comments
 (0)