Coverage for src/bioimageio/spec/_internal/io_packaging.py: 75%
64 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 15:08 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 15:08 +0000
1from __future__ import annotations
3import warnings
4from pathlib import Path
5from typing import (
6 Optional,
7 Union,
8)
10from pydantic import (
11 AnyUrl,
12 SerializationInfo,
13 SerializerFunctionWrapHandler,
14 WrapSerializer,
15)
16from typing_extensions import (
17 Annotated,
18 assert_never,
19)
21from .io import (
22 FileDescr,
23 FileSource,
24 RelativeFilePath,
25 extract_file_name,
26 wo_special_file_name,
27)
28from .io_basics import (
29 ALL_BIOIMAGEIO_YAML_NAMES,
30 FileName,
31 Sha256,
32)
33from .packaging_context import packaging_context_var
34from .url import HttpUrl
35from .utils import PrettyPlainSerializer
36from .validator_annotations import AfterValidator
39def _package_serializer(
40 source: FileSource, info: SerializationInfo
41) -> Union[str, Path, FileName]:
42 return _package(source, info, None)
45def package_file_descr_serializer(
46 value: FileDescr, handler: SerializerFunctionWrapHandler, info: SerializationInfo
47):
48 ret = handler(
49 value,
50 info, # pyright: ignore[reportArgumentType] # taken from pydantic docs
51 )
52 ret["source"] = _package(value.source, info, sha256=value.sha256)
53 return ret
56def _package(
57 source: FileSource, info: SerializationInfo, sha256: Optional[Sha256]
58) -> Union[str, Path, FileName]:
59 if (packaging_context := packaging_context_var.get()) is None:
60 # convert to standard python obj
61 # note: pydantic keeps returning Rootmodels (here `HttpUrl`) as-is, but if
62 # this function returns one RootModel, paths are "further serialized" by
63 # returning the 'root' attribute, which is incorrect.
64 # see https://github.com/pydantic/pydantic/issues/8963
65 # TODO: follow up on https://github.com/pydantic/pydantic/issues/8963
66 if isinstance(source, Path):
67 unpackaged = source
68 elif isinstance(source, HttpUrl):
69 unpackaged = source
70 elif isinstance(source, RelativeFilePath):
71 unpackaged = Path(source.path)
72 elif isinstance(source, AnyUrl):
73 unpackaged = str(source)
74 else:
75 assert_never(source)
77 if info.mode_is_json():
78 # convert to json value # TODO: remove and let pydantic do this?
79 if isinstance(unpackaged, (Path, HttpUrl)):
80 unpackaged = str(unpackaged)
81 elif isinstance(unpackaged, str):
82 pass
83 else:
84 assert_never(unpackaged)
85 else:
86 warnings.warn(
87 "dumping with mode='python' is currently not fully supported for "
88 + "fields that are included when packaging; returned objects are "
89 + "standard python objects"
90 )
92 return unpackaged # return unpackaged file source
94 fname = extract_file_name(source)
95 if not fname:
96 raise ValueError(f"Got empty file name for source {source}")
98 if fname == packaging_context.bioimageio_yaml_file_name:
99 raise ValueError(
100 f"Reserved file name '{packaging_context.bioimageio_yaml_file_name}' "
101 + "not allowed for a file to be packaged"
102 )
104 if packaging_context.local_files_only:
105 # skip regular or "relative" URLs
106 absolute_source = source.absolute()
107 if isinstance(absolute_source, HttpUrl):
108 return absolute_source
110 fsrcs = packaging_context.file_sources
111 assert not any(fname.endswith(special) for special in ALL_BIOIMAGEIO_YAML_NAMES), (
112 fname
113 )
114 if fname in fsrcs and fsrcs[fname].source != source:
115 for i in range(2, 20):
116 fn, *ext = fname.split(".")
117 alternative_file_name = ".".join([f"{fn}_{i}", *ext])
118 if (
119 alternative_file_name not in fsrcs
120 or fsrcs[alternative_file_name].source == source
121 ):
122 fname = alternative_file_name
123 break
124 else:
125 raise ValueError(f"Too many file name clashes for {fname}")
127 fsrcs[fname] = FileDescr(source=source, sha256=sha256)
128 return fname
131include_in_package = PrettyPlainSerializer(_package_serializer, when_used="unless-none")
132"""DEPRECATED serializer for `source` fields without corresponding `sha256` field."""
134include_when_packaging = WrapSerializer(
135 package_file_descr_serializer, when_used="unless-none"
136)
137"""Pydantic serializer that marks the annotated `FileDescr` to be included when packaging
138(saving a bioimageio zip package)."""
140FileSource_package = Annotated[
141 FileSource,
142 AfterValidator(wo_special_file_name),
143 include_in_package,
144]
145"""A file source that is included when packaging the resource."""
147FileDescr_package = Annotated[
148 FileDescr, AfterValidator(wo_special_file_name), include_when_packaging
149]
150"""A `FileDescr` whose **source** is included when packaging the resource."""