Spaces:
Runtime error
Runtime error
"""Functionality around tasks | |
Tasks are used to implement "undo" and "redo" functionality. | |
""" | |
from __future__ import annotations | |
from pathlib import Path | |
from uuid import uuid4 | |
from skops import card | |
from skops.card._model_card import PlotSection, split_subsection_names | |
from streamlit.runtime.uploaded_file_manager import UploadedFile | |
class Task: | |
"""(Abstract) base class for tasks""" | |
def do(self) -> None: | |
raise NotImplementedError | |
def undo(self) -> None: | |
raise NotImplementedError | |
class TaskState: | |
"""Tracking the state of tasks""" | |
def __init__(self) -> None: | |
self.done_list: list[Task] = [] | |
self.undone_list: list[Task] = [] | |
def undo(self) -> None: | |
if not self.done_list: | |
return | |
task = self.done_list.pop(-1) | |
task.undo() | |
self.undone_list.append(task) | |
def redo(self) -> None: | |
if not self.undone_list: | |
return | |
task = self.undone_list.pop(-1) | |
task.do() | |
self.done_list.append(task) | |
def add(self, task: Task) -> None: | |
task.do() | |
self.done_list.append(task) | |
self.undone_list.clear() | |
def reset(self) -> None: | |
self.done_list.clear() | |
self.undone_list.clear() | |
class AddSectionTask(Task): | |
"""Add a new text section""" | |
def __init__( | |
self, | |
model_card: card.Card, | |
title: str, | |
content: str, | |
) -> None: | |
self.model_card = model_card | |
self.title = title | |
self.key = title + " " + str(uuid4())[:6] | |
self.content = content | |
def do(self) -> None: | |
self.model_card.add(**{self.key: self.content}) | |
section = self.model_card.select(self.key) | |
section.title = split_subsection_names(self.title)[-1] | |
def undo(self) -> None: | |
self.model_card.delete(self.key) | |
class AddFigureTask(Task): | |
"""Add a new figure section""" | |
def __init__( | |
self, | |
model_card: card.Card, | |
title: str, | |
content: str, | |
) -> None: | |
self.model_card = model_card | |
self.title = title | |
self.key = title + " " + str(uuid4())[:6] | |
self.content = content | |
def do(self) -> None: | |
self.model_card.add_plot(**{self.key: self.content}) | |
section = self.model_card.select(self.key) | |
section.title = split_subsection_names(self.title)[-1] | |
section.is_fig = True # type: ignore | |
def undo(self) -> None: | |
self.model_card.delete(self.key) | |
class DeleteSectionTask(Task): | |
"""Delete a section | |
The section is not completely removed from the underlying data structure, | |
but only turned invisible. | |
""" | |
def __init__( | |
self, | |
model_card: card.Card, | |
key: str, | |
) -> None: | |
self.model_card = model_card | |
self.key = key | |
def do(self) -> None: | |
self.model_card.select(self.key).visible = False | |
def undo(self) -> None: | |
self.model_card.select(self.key).visible = True | |
class UpdateSectionTask(Task): | |
"""Change the title or content of a text section""" | |
def __init__( | |
self, | |
model_card: card.Card, | |
key: str, | |
old_name: str, | |
new_name: str, | |
old_content: str, | |
new_content: str, | |
) -> None: | |
self.model_card = model_card | |
self.key = key | |
self.old_name = old_name | |
self.new_name = new_name | |
self.old_content = old_content | |
self.new_content = new_content | |
def do(self) -> None: | |
section = self.model_card.select(self.key) | |
new_title = split_subsection_names(self.new_name)[-1] | |
section.title = new_title | |
section.content = self.new_content | |
def undo(self) -> None: | |
section = self.model_card.select(self.key) | |
old_title = split_subsection_names(self.old_name)[-1] | |
section.title = old_title | |
section.content = self.old_content | |
class UpdateFigureTask(Task): | |
"""Change the title or image of a figure section""" | |
def __init__( | |
self, | |
model_card: card.Card, | |
key: str, | |
old_name: str, | |
new_name: str, | |
data: UploadedFile | None, | |
path: Path | None, | |
) -> None: | |
self.model_card = model_card | |
self.key = key | |
self.old_name = old_name | |
self.new_name = new_name | |
self.old_data = self.model_card.select(self.key).content | |
self.path = path | |
if not data: | |
self.new_data = self.old_data | |
else: | |
self.new_data = data | |
def do(self) -> None: | |
section = self.model_card.select(self.key) | |
new_title = split_subsection_names(self.new_name)[-1] | |
section.title = self.title = new_title | |
if self.new_data == self.old_data: # image is same | |
return | |
# write figure | |
# note: this can still be the same image if the image is a file, there | |
# is no test to check, e.g., the hash of the image | |
with open(self.path, "wb") as f: | |
f.write(self.new_data.getvalue()) | |
section.content = PlotSection( | |
alt_text=self.new_data.name, | |
path=self.path, | |
).format() | |
def undo(self) -> None: | |
section = self.model_card.select(self.key) | |
old_title = split_subsection_names(self.old_name)[-1] | |
section.title = old_title | |
if self.new_data == self.old_data: # image is same | |
return | |
self.path.unlink(missing_ok=True) | |
section.content = self.old_data | |