Coverage for bioimageio/spec/_internal/utils.py: 67%
76 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-18 12:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-18 12:47 +0000
1from __future__ import annotations
3import re
4import sys
5from functools import wraps
6from inspect import signature
7from pathlib import Path
8from typing import (
9 Any,
10 Callable,
11 Dict,
12 Set,
13 Tuple,
14 Type,
15 TypeVar,
16 Union,
17)
19from ruyaml import Optional
20from typing_extensions import ParamSpec
22if sys.version_info < (3, 10): # pragma: no cover
23 SLOTS: Dict[str, bool] = {}
24else:
25 SLOTS = {"slots": True}
28K = TypeVar("K")
29V = TypeVar("V")
30NestedDict = Dict[K, "NestedDict[K, V] | V"]
32if sys.version_info < (3, 9): # pragma: no cover
33 from functools import lru_cache as cache
35 def files(package_name: str):
36 assert package_name == "bioimageio.spec", package_name
37 return Path(__file__).parent.parent
39else:
40 from functools import cache as cache
41 from importlib.resources import files as files
44def get_format_version_tuple(format_version: Any) -> Optional[Tuple[int, int, int]]:
45 if (
46 not isinstance(format_version, str)
47 or format_version.count(".") != 2
48 or any(not v.isdigit() for v in format_version.split("."))
49 ):
50 return None
52 parsed = tuple(map(int, format_version.split(".")))
53 assert len(parsed) == 3
54 return parsed
57def nest_dict(flat_dict: Dict[Tuple[K, ...], V]) -> NestedDict[K, V]:
58 res: NestedDict[K, V] = {}
59 for k, v in flat_dict.items():
60 node: Union[Dict[K, Union[NestedDict[K, V], V]], NestedDict[K, V]] = res
61 for kk in k[:-1]:
62 if not isinstance(node, dict):
63 raise ValueError(f"nesting level collision for flat key {k} at {kk}")
64 d: NestedDict[K, V] = {}
65 node = node.setdefault(kk, d) # type: ignore
67 if not isinstance(node, dict):
68 raise ValueError(f"nesting level collision for flat key {k}")
70 node[k[-1]] = v
72 return res
75FirstK = TypeVar("FirstK")
78def nest_dict_with_narrow_first_key(
79 flat_dict: Dict[Tuple[K, ...], V], first_k: Type[FirstK]
80) -> Dict[FirstK, "NestedDict[K, V] | V"]:
81 """convenience function to annotate a special version of a NestedDict.
82 Root level keys are of a narrower type than the nested keys. If not a ValueError is raisd.
83 """
84 nested = nest_dict(flat_dict)
85 invalid_first_keys = [k for k in nested if not isinstance(k, first_k)]
86 if invalid_first_keys:
87 raise ValueError(f"Invalid root level keys: {invalid_first_keys}")
89 return nested # type: ignore
92def unindent(text: str, ignore_first_line: bool = False):
93 """remove minimum count of spaces at beginning of each line.
95 Args:
96 text: indented text
97 ignore_first_line: allows to correctly unindent doc strings
98 """
99 first = int(ignore_first_line)
100 lines = text.split("\n")
101 filled_lines = [line for line in lines[first:] if line]
102 if len(filled_lines) < 2:
103 return "\n".join(line.strip() for line in lines)
105 indent = min(len(line) - len(line.lstrip(" ")) for line in filled_lines)
106 return "\n".join(lines[:first] + [line[indent:] for line in lines[first:]])
109T = TypeVar("T")
110P = ParamSpec("P")
113def assert_all_params_set_explicitly(fn: Callable[P, T]) -> Callable[P, T]:
114 @wraps(fn)
115 def wrapper(*args: P.args, **kwargs: P.kwargs):
116 n_args = len(args)
117 missing: Set[str] = set()
119 for p in signature(fn).parameters.values():
120 if p.kind == p.POSITIONAL_ONLY:
121 if n_args == 0:
122 missing.add(p.name)
123 else:
124 n_args -= 1 # 'use' positional arg
125 elif p.kind == p.POSITIONAL_OR_KEYWORD:
126 if n_args == 0:
127 if p.name not in kwargs:
128 missing.add(p.name)
129 else:
130 n_args -= 1 # 'use' positional arg
131 elif p.kind in (p.VAR_POSITIONAL, p.VAR_KEYWORD):
132 pass
133 elif p.kind == p.KEYWORD_ONLY:
134 if p.name not in kwargs:
135 missing.add(p.name)
137 assert not missing, f"parameters {missing} of {fn} are not set explicitly"
139 return fn(*args, **kwargs)
141 return wrapper
144def get_os_friendly_file_name(name: str) -> str:
145 return re.sub(r"\W+|^(?=\d)", "_", name)