Skip to content

Commit 5fc7a8a

Browse files
committed
WIP improve io
1 parent 892abcf commit 5fc7a8a

26 files changed

+752
-721
lines changed

bioimageio/spec/__init__.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44
from bioimageio.spec import generic as generic
55
from bioimageio.spec import model as model
66
from bioimageio.spec import notebook as notebook
7+
from bioimageio.spec._description import LatestResourceDescription as LatestResourceDescription
8+
from bioimageio.spec._description import ResourceDescription as ResourceDescription
9+
from bioimageio.spec._description import SpecificResourceDescription as SpecificResourceDescription
10+
from bioimageio.spec._description import build_description as build_description
11+
from bioimageio.spec._description import dump_description as dump_description
12+
from bioimageio.spec._description import update_format as update_format
13+
from bioimageio.spec._description import validate_format as validate_format
714
from bioimageio.spec._internal.constants import VERSION
15+
from bioimageio.spec._io import load_description as load_description
16+
from bioimageio.spec._io import save_description as save_description
817
from bioimageio.spec.application import AnyApplication as AnyApplication
918
from bioimageio.spec.application import Application as Application
1019
from bioimageio.spec.collection import AnyCollection as AnyCollection
1120
from bioimageio.spec.collection import Collection as Collection
1221
from bioimageio.spec.dataset import AnyDataset as AnyDataset
1322
from bioimageio.spec.dataset import Dataset as Dataset
14-
from bioimageio.spec.description import LatestResourceDescription as LatestResourceDescription
15-
from bioimageio.spec.description import ResourceDescription as ResourceDescription
16-
from bioimageio.spec.description import SpecificResourceDescription as SpecificResourceDescription
17-
from bioimageio.spec.description import dump_description as dump_description
18-
from bioimageio.spec.description import load_description as load_description
19-
from bioimageio.spec.description import update_format as update_format
20-
from bioimageio.spec.description import validate_format as validate_format
2123
from bioimageio.spec.generic import AnyGeneric as AnyGeneric
2224
from bioimageio.spec.generic import Generic as Generic
2325
from bioimageio.spec.model import AnyModel as AnyModel

bioimageio/spec/description.py renamed to bioimageio/spec/_description.py

Lines changed: 38 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
1-
import traceback
2-
from copy import deepcopy
3-
from pathlib import PurePath
41
from typing import (
52
Any,
6-
ClassVar,
73
Dict,
8-
FrozenSet,
94
Iterable,
105
List,
116
Literal,
127
Optional,
138
Tuple,
149
Type,
15-
TypeVar,
1610
Union,
1711
)
18-
from urllib.parse import urljoin
1912

20-
import pydantic
2113
from pydantic import Field
2214
from pydantic_core import PydanticUndefined
23-
from typing_extensions import Annotated, LiteralString
15+
from typing_extensions import Annotated
2416

2517
import bioimageio.spec
2618
from bioimageio.spec import application, collection, dataset, generic, model, notebook
27-
from bioimageio.spec._internal.base_nodes import ResourceDescriptionBase
28-
from bioimageio.spec._internal.constants import DISCOVER, ERROR, INFO, LATEST, VERSION
29-
from bioimageio.spec._internal.types import RdfContent, YamlValue
19+
from bioimageio.spec._internal.base_nodes import InvalidDescription, ResourceDescriptionBase
20+
from bioimageio.spec._internal.constants import DISCOVER, LATEST, VERSION
21+
from bioimageio.spec._internal.types import BioimageioYamlContent, RelativeFilePath, YamlValue
3022
from bioimageio.spec._internal.utils import iterate_annotated_union
3123
from bioimageio.spec._internal.validation_context import (
32-
InternalValidationContext,
3324
ValidationContext,
3425
get_internal_validation_context,
3526
)
@@ -88,53 +79,42 @@
8879
"""Any of the implemented resource descriptions"""
8980

9081

91-
class InvalidDescription(ResourceDescriptionBase, extra="allow", title="An invalid resource description"):
92-
type: Any = "unknown"
93-
format_version: Any = "unknown"
94-
fields_to_set_explicitly: ClassVar[FrozenSet[LiteralString]] = frozenset()
95-
96-
9782
def update_format(
98-
rdf_content: RdfContent,
83+
data: BioimageioYamlContent,
84+
/,
85+
*,
9986
update_to_format: str = "latest",
10087
context: Optional[ValidationContext] = None,
101-
) -> RdfContent:
88+
) -> BioimageioYamlContent:
10289
"""Auto-update fields of a bioimage.io resource without any validation."""
103-
if not isinstance(rdf_content["type"], str):
104-
raise TypeError(f"RDF type '{rdf_content['type']}' must be a string (not '{type(rdf_content['type'])}').")
90+
if not isinstance(data["type"], str):
91+
raise TypeError(f"Description type '{data['type']}' must be a string (not '{type(data['type'])}').")
10592

106-
rd_class = _get_rd_class(rdf_content["type"], update_to_format)
93+
rd_class = _get_rd_class(data["type"], update_to_format)
10794
if isinstance(rd_class, str):
10895
raise ValueError(rd_class)
10996

110-
updated = dict(rdf_content)
97+
updated = dict(data)
11198
_ = rd_class.convert_from_older_format(updated, get_internal_validation_context(context))
11299
return updated
113100

114101

115-
RD = TypeVar("RD", bound=ResourceDescription)
116-
117-
118-
def dump_description(rd: ResourceDescription, exclude_unset: bool = True) -> RdfContent:
102+
def dump_description(rd: ResourceDescription, exclude_unset: bool = True) -> BioimageioYamlContent:
119103
"""Converts a resource to a dictionary containing only simple types that can directly be serialzed to YAML."""
120104
return rd.model_dump(mode="json", exclude_unset=exclude_unset)
121105

122106

123-
def load_description(
124-
rdf_content: Union[RdfContent, ResourceDescription],
107+
def build_description(
108+
data: BioimageioYamlContent,
109+
/,
125110
*,
126111
context: Optional[ValidationContext] = None,
127112
format_version: Union[Literal["discover"], Literal["latest"], str] = DISCOVER,
128113
) -> Union[ResourceDescription, InvalidDescription]:
129-
if isinstance(rdf_content, ResourceDescriptionBase):
130-
old_ctxt = rdf_content._internal_validation_context # pyright: ignore[reportPrivateUsage]
131-
context = context or ValidationContext(root=old_ctxt["root"], file_name=old_ctxt["file_name"])
132-
rdf_content = dump_description(rdf_content)
133-
134-
discovered_type, discovered_format_version, use_format_version = _check_type_and_format_version(rdf_content)
114+
discovered_type, discovered_format_version, use_format_version = _check_type_and_format_version(data)
135115
if use_format_version != discovered_format_version:
136-
rdf_content = dict(rdf_content)
137-
rdf_content["format_version"] = use_format_version
116+
data = dict(data)
117+
data["format_version"] = use_format_version
138118
future_patch_warning = WarningEntry(
139119
loc=("format_version",),
140120
msg=f"Treated future patch version {discovered_format_version} as {use_format_version}.",
@@ -148,43 +128,44 @@ def load_description(
148128
context = context or ValidationContext()
149129
if isinstance(rd_class, str):
150130
rd = InvalidDescription()
151-
summary = ValidationSummary(
152-
bioimageio_spec_version=VERSION,
153-
errors=[ErrorEntry(loc=(), msg=rd_class, type="error")],
154-
name=f"bioimageio.spec static {discovered_type} validation (format version: {format_version}).",
155-
source_name=(context.root / context.file_name).as_posix()
156-
if isinstance(context.root, PurePath)
157-
else urljoin(str(context.root), context.file_name),
158-
status="failed",
159-
warnings=[],
131+
rd.validation_summaries.append(
132+
ValidationSummary(
133+
bioimageio_spec_version=VERSION,
134+
errors=[ErrorEntry(loc=(), msg=rd_class, type="error")],
135+
name=f"bioimageio.spec static {discovered_type} validation (format version: {format_version}).",
136+
source_name=str(RelativeFilePath(context.file_name).get_absolute(context.root)),
137+
status="failed",
138+
warnings=[],
139+
)
160140
)
161141
else:
162-
rd, summary = _load_descr_with_known_rd_class(rdf_content, context=context, rd_class=rd_class)
142+
rd = rd_class.load(data, context=context)
163143

144+
assert rd.validation_summaries, "missing validation summary"
164145
if future_patch_warning:
165-
summary.warnings.insert(0, future_patch_warning)
146+
rd.validation_summaries[0].warnings.insert(0, future_patch_warning)
166147

167-
assert not rd.validation_summaries, "encountered unexpected validation summary"
168-
rd.validation_summaries.append(summary)
169148
return rd
170149

171150

172151
def validate_format(
173-
rdf_content: RdfContent,
152+
data: BioimageioYamlContent,
153+
/,
154+
*,
174155
context: Optional[ValidationContext] = None,
175156
as_format: Union[Literal["discover", "latest"], str] = DISCOVER,
176157
) -> ValidationSummary:
177-
rd = load_description(rdf_content, context=context, format_version=as_format)
158+
rd = build_description(data, context=context, format_version=as_format)
178159
return rd.validation_summaries[0]
179160

180161

181-
def _check_type_and_format_version(data: Union[YamlValue, RdfContent]) -> Tuple[str, str, str]:
162+
def _check_type_and_format_version(data: Union[YamlValue, BioimageioYamlContent]) -> Tuple[str, str, str]:
182163
if not isinstance(data, dict):
183-
raise TypeError(f"Invalid RDF content of type '{type(data)}'")
164+
raise TypeError(f"Invalid content of type '{type(data)}'")
184165

185166
typ = data.get("type")
186167
if not isinstance(typ, str):
187-
raise TypeError(f"Invalid resource type '{typ}' of type {type(typ)}")
168+
raise TypeError(f"Invalid type '{typ}' of type {type(typ)}")
188169

189170
fv = data.get("format_version")
190171
if not isinstance(fv, str):
@@ -276,69 +257,3 @@ def _iterate_over_latest_rd_classes() -> Iterable[Tuple[str, Type[ResourceDescri
276257

277258
assert isinstance(typ, str)
278259
yield typ, rd_class
279-
280-
281-
def _load_descr_with_known_rd_class(
282-
rdf_content: RdfContent,
283-
*,
284-
context: ValidationContext,
285-
rd_class: Type[RD],
286-
) -> Tuple[Union[RD, InvalidDescription], ValidationSummary]:
287-
raw_rd = deepcopy(dict(rdf_content))
288-
rd, errors, tb, val_warnings = _load_descr_impl(
289-
rd_class,
290-
raw_rd,
291-
get_internal_validation_context(context.model_dump(), warning_level=ERROR),
292-
) # ignore any warnings using warning level 'ERROR'/'CRITICAL' on first loading attempt
293-
294-
assert not val_warnings, f"already got warnings: {val_warnings}"
295-
_, error2, tb2, val_warnings = _load_descr_impl(
296-
rd_class, raw_rd, get_internal_validation_context(context.model_dump(), warning_level=INFO)
297-
)
298-
assert not error2 or isinstance(rd, InvalidDescription), f"decreasing warning level caused errors: {error2}"
299-
assert not tb2 or isinstance(rd, InvalidDescription), f"decreasing warning level lead to error traceback: {tb2}"
300-
301-
summary = ValidationSummary(
302-
bioimageio_spec_version=VERSION,
303-
errors=errors,
304-
name=f"bioimageio.spec static validation of {rd.type} (format version: {rd.format_version}).",
305-
source_name=(context.root / context.file_name).as_posix()
306-
if isinstance(context.root, PurePath)
307-
else urljoin(str(context.root), context.file_name),
308-
status="failed" if errors else "passed",
309-
warnings=val_warnings,
310-
traceback=tb,
311-
)
312-
313-
return rd, summary
314-
315-
316-
def _load_descr_impl(
317-
rd_class: Type[RD], rdf_content: RdfContent, context: InternalValidationContext
318-
) -> Tuple[Union[RD, InvalidDescription], List[ErrorEntry], List[str], List[WarningEntry]]:
319-
rd: Union[RD, InvalidDescription, None] = None
320-
val_errors: List[ErrorEntry] = []
321-
val_warnings: List[WarningEntry] = []
322-
tb: List[str] = []
323-
324-
try:
325-
rd = rd_class.model_validate(rdf_content, context=dict(context))
326-
except pydantic.ValidationError as e:
327-
for ee in e.errors(include_url=False):
328-
if (severity := ee.get("ctx", {}).get("severity", ERROR)) < ERROR:
329-
val_warnings.append(WarningEntry(loc=ee["loc"], msg=ee["msg"], type=ee["type"], severity=severity))
330-
else:
331-
val_errors.append(ErrorEntry(loc=ee["loc"], msg=ee["msg"], type=ee["type"]))
332-
except Exception as e:
333-
val_errors.append(ErrorEntry(loc=(), msg=str(e), type=type(e).__name__))
334-
tb = traceback.format_tb(e.__traceback__)
335-
336-
if rd is None:
337-
try:
338-
rd = InvalidDescription.model_validate(rdf_content)
339-
except Exception:
340-
resource_type = rd_class.model_fields["type"].default
341-
format_version = rd_class.implemented_format_version
342-
rd = InvalidDescription(type=resource_type, format_version=format_version)
343-
344-
return rd, val_errors, tb, val_warnings

0 commit comments

Comments
 (0)