1- import traceback
2- from copy import deepcopy
3- from pathlib import PurePath
41from 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
2113from pydantic import Field
2214from pydantic_core import PydanticUndefined
23- from typing_extensions import Annotated , LiteralString
15+ from typing_extensions import Annotated
2416
2517import bioimageio .spec
2618from 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
3022from bioimageio .spec ._internal .utils import iterate_annotated_union
3123from bioimageio .spec ._internal .validation_context import (
32- InternalValidationContext ,
3324 ValidationContext ,
3425 get_internal_validation_context ,
3526)
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-
9782def 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
172151def 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