Skip to content

Commit 9ad111b

Browse files
committed
improve update commands
1 parent db0f0da commit 9ad111b

File tree

3 files changed

+91
-36
lines changed

3 files changed

+91
-36
lines changed

bioimageio/core/cli.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,16 @@
5656
update_format,
5757
update_hashes,
5858
)
59+
from bioimageio.spec._internal.io import is_yaml_value
5960
from bioimageio.spec._internal.io_basics import ZipPath
60-
from bioimageio.spec._internal.io_utils import yaml
61+
from bioimageio.spec._internal.io_utils import open_bioimageio_yaml
6162
from bioimageio.spec._internal.types import NotEmpty
6263
from bioimageio.spec.dataset import DatasetDescr
6364
from bioimageio.spec.model import ModelDescr, v0_4, v0_5
6465
from bioimageio.spec.notebook import NotebookDescr
65-
from bioimageio.spec.utils import download, ensure_description_is_model
66+
from bioimageio.spec.utils import download, ensure_description_is_model, write_yaml
6667

67-
from .commands import (
68-
WeightFormatArgAll,
69-
WeightFormatArgAny,
70-
package,
71-
test,
72-
)
68+
from .commands import WeightFormatArgAll, WeightFormatArgAny, package, test
7369
from .common import MemberId, SampleId, SupportedWeightsFormat
7470
from .digest_spec import get_member_ids, load_sample_for_model
7571
from .io import load_dataset_stat, save_dataset_stat, save_sample
@@ -83,7 +79,7 @@
8379
)
8480
from .sample import Sample
8581
from .stat_measures import Stat
86-
from .utils import VERSION
82+
from .utils import VERSION, compare
8783
from .weight_converters._add_weights import add_weights
8884

8985

@@ -193,7 +189,7 @@ def run(self):
193189
)
194190

195191

196-
class PackageCmd(CmdBase, WithSource):
192+
class PackageCmd(CmdBase, WithSource, WithSummaryLogging):
197193
"""Save a resource's metadata with its associated files."""
198194

199195
path: CliPositionalArg[Path]
@@ -206,10 +202,8 @@ class PackageCmd(CmdBase, WithSource):
206202

207203
def run(self):
208204
if isinstance(self.descr, InvalidDescr):
209-
paths = self.descr.validation_summary.log()
210-
raise ValueError(
211-
f"Invalid {self.descr.type} description. Logged details to {paths}"
212-
)
205+
self.log(self.descr)
206+
raise ValueError(f"Invalid {self.descr.type} description.")
213207

214208
sys.exit(
215209
package(
@@ -258,6 +252,12 @@ class UpdateCmdBase(CmdBase, WithSource, ABC):
258252
output: Union[Literal["render", "stdout"], Path] = "render"
259253
"""Output updated bioimageio.yaml to the terminal or write to a file."""
260254

255+
diff: Union[bool, Path] = Field(True, alias="diff")
256+
"""Output a diff of original and updated bioimageio.yaml.
257+
If a given path has an `.html` extension, a standalone HTML file is written,
258+
otherwise the diff is saved in unified diff format (pure text).
259+
"""
260+
261261
exclude_unset: bool = Field(True, alias="exclude-unset")
262262
"""Exclude fields that have not explicitly be set."""
263263

@@ -269,29 +269,50 @@ def updated(self) -> Union[ResourceDescr, InvalidDescr]:
269269
raise NotImplementedError
270270

271271
def run(self):
272-
if self.output == "render":
273-
out = StringIO()
274-
elif self.output == "stdout":
275-
out = sys.stdout
276-
else:
277-
out = self.output
272+
original_yaml = open_bioimageio_yaml(self.source).local_source.read_text(
273+
encoding="utf-8"
274+
)
275+
assert isinstance(original_yaml, str)
276+
stream = StringIO()
278277

279278
save_bioimageio_yaml_only(
280279
self.updated,
281-
out,
280+
stream,
282281
exclude_unset=self.exclude_unset,
283282
exclude_defaults=self.exclude_defaults,
284283
)
284+
updated_yaml = stream.getvalue()
285+
286+
diff = compare(
287+
original_yaml.split("\n"),
288+
updated_yaml.split("\n"),
289+
diff_format=(
290+
"html"
291+
if isinstance(self.diff, Path) and self.diff.suffix == ".html"
292+
else "unified"
293+
),
294+
)
285295

286-
if self.output == "render":
287-
assert isinstance(out, StringIO)
288-
updated_md = f"```yaml\n{out.getvalue()}\n```"
296+
if isinstance(self.diff, Path):
297+
_ = self.diff.write_text(diff, encoding="utf-8")
298+
elif self.diff:
299+
diff_md = f"````````diff\n{diff}\n````````"
300+
rich.console.Console().print(rich.markdown.Markdown(diff_md))
289301

290-
rich_markdown = rich.markdown.Markdown(updated_md)
291-
console = rich.console.Console()
292-
console.print(rich_markdown)
293-
elif self.output != "stdout":
302+
if isinstance(self.output, Path):
303+
_ = self.output.write_text(updated_yaml, encoding="utf-8")
294304
logger.info(f"written updated description to {self.output}")
305+
elif self.output == "render":
306+
updated_md = f"```yaml\n{updated_yaml}\n```"
307+
rich.console.Console().print(rich.markdown.Markdown(updated_md))
308+
elif self.output == "stdout":
309+
print(updated_yaml)
310+
else:
311+
assert_never(self.output)
312+
313+
if isinstance(self.updated, InvalidDescr):
314+
logger.warning("Update resulted in invalid description")
315+
_ = self.updated.validation_summary.display()
295316

296317

297318
class UpdateFormatCmd(UpdateCmdBase):
@@ -339,7 +360,7 @@ class PredictCmd(CmdBase, WithSource):
339360
340361
Example inputs to process sample 'a' and 'b'
341362
for a model expecting a 'raw' and a 'mask' input tensor:
342-
--inputs="[[\"a_raw.tif\",\"a_mask.tif\"],[\"b_raw.tif\",\"b_mask.tif\"]]"
363+
--inputs="[[\\"a_raw.tif\\",\\"a_mask.tif\\"],[\\"b_raw.tif\\",\\"b_mask.tif\\"]]"
343364
(Note that JSON double quotes need to be escaped.)
344365
345366
Alternatively a `bioimageio-cli.yaml` (or `bioimageio-cli.json`) file
@@ -435,13 +456,15 @@ def _example(self):
435456
bioimageio_cli_path = example_path / YAML_FILE
436457
stats_file = "dataset_statistics.json"
437458
stats = (example_path / stats_file).as_posix()
438-
yaml.dump(
439-
dict(
440-
inputs=inputs,
441-
outputs=output_pattern,
442-
stats=stats_file,
443-
blockwise=self.blockwise,
444-
),
459+
cli_example_args = dict(
460+
inputs=inputs,
461+
outputs=output_pattern,
462+
stats=stats_file,
463+
blockwise=self.blockwise,
464+
)
465+
assert is_yaml_value(cli_example_args)
466+
write_yaml(
467+
cli_example_args,
445468
bioimageio_cli_path,
446469
)
447470

bioimageio/core/utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import sys
33
from pathlib import Path
44

5+
from ._compare import compare as compare
6+
57
if sys.version_info < (3, 9):
68

79
def files(package_name: str):

bioimageio/core/utils/_compare.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from difflib import HtmlDiff, unified_diff
2+
from typing import Sequence
3+
4+
from typing_extensions import Literal, assert_never
5+
6+
7+
def compare(
8+
a: Sequence[str],
9+
b: Sequence[str],
10+
name_a: str = "source",
11+
name_b: str = "updated",
12+
*,
13+
diff_format: Literal["unified", "html"],
14+
):
15+
if diff_format == "html":
16+
diff = HtmlDiff().make_file(a, b, name_a, name_b, charset="utf-8")
17+
elif diff_format == "unified":
18+
diff = "\n".join(
19+
unified_diff(
20+
a,
21+
b,
22+
name_a,
23+
name_b,
24+
lineterm="",
25+
)
26+
)
27+
else:
28+
assert_never(diff_format)
29+
30+
return diff

0 commit comments

Comments
 (0)