Source code for pykechain.models.sidebar.sidebar_manager
from collections.abc import Iterable
from typing import Any, Dict, List, Optional
from pykechain.enums import (
KEChainPageIcons,
KEChainPageLabels,
KEChainPages,
SidebarType,
SubprocessDisplayMode,
URITarget,
)
from pykechain.exceptions import NotFoundError
from pykechain.models.input_checks import (
check_enum,
check_list_of_dicts,
check_text,
check_type,
check_url,
)
from pykechain.models.sidebar.sidebar_base import SideBarItem
from pykechain.models.sidebar.sidebar_button import SideBarButton
from pykechain.models.sidebar.sidebar_card import SideBarCard
from pykechain.utils import find
[docs]
class SideBarManager(Iterable):
"""
Sidebar manager class.
:ivar scope: Scope object for which the side-bar is managed.
:ivar bulk_creation: boolean to create buttons in bulk, postponing updating of KE-chain until the manager is
deleted from memory (end of your function)
"""
__existing_managers = (
dict()
) # storage of manager objects to enforce 1 manager object per Scope
def __new__(cls, scope: "Scope", *args, **kwargs):
"""Overwrite superclass method to enforce singleton manager per Scope object."""
instance = super().__new__(cls)
# Singleton manager per scope: this is required to support bulk_creation
if scope.id in cls.__existing_managers:
instance = cls.__existing_managers[scope.id]
else:
cls.__existing_managers[scope.id] = instance
return instance
def __init__(self, scope: "Scope", **kwargs):
"""
Create a side-bar manager object for the Scope object.
:param scope: Scope for which to create the side-bar manager.
:param bulk_creation: flag whether to update once (True) or continuously (False, default)
"""
super().__init__(**kwargs)
from pykechain.models import Scope
check_type(scope, Scope, "scope")
self.scope: Scope = scope
self._override: bool = scope.options.get("overrideSideBar", False)
self._scope_uri = f"#/scopes/{self.scope.id}"
self._perform_bulk_creation = False
self._items: List[SideBarItem] = []
# Load existing buttons from the scope
for item_dict in scope.options.get("customNavigation", []):
if item_dict.get("itemType", SidebarType.BUTTON) == SidebarType.BUTTON:
self._items.append(SideBarButton(side_bar_manager=self, json=item_dict))
elif item_dict.get("itemType") == SidebarType.CARD:
self._items.append(SideBarCard(side_bar_manager=self, json=item_dict))
self._iter = iter(self._items)
def __repr__(self) -> str: # pragma: no cover
return f"<pyke {self.__class__.__name__}: {self.__len__()} items>"
def __iter__(self):
return self
def __len__(self) -> int:
return len(self._items)
def __next__(self) -> SideBarItem:
return next(self._iter)
def __getitem__(self, key: Any) -> SideBarItem:
found = None
if isinstance(key, SideBarItem):
found = find(self._items, lambda b: b == key)
if isinstance(key, int):
found = self._items[key]
elif isinstance(key, str):
found = find(self._items, lambda p: key == p.display_name)
if found is not None:
return found
raise NotFoundError(f"Could not find button with index or name '{key}'")
def __enter__(self):
"""
Open context manager using the `with` keyword to postpone updates to KE-chain.
>>> with scope.side_bar() as manager:
>>> button = manager.add_ke_chain_page(page_name=KEChainPages.EXPLORER)
>>> manager.insert(index=0, button=button)
"""
self._perform_bulk_creation = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._perform_bulk_creation = False
self._update()
[docs]
def refresh(self) -> None:
"""Reload the scope options from KE-chain to refresh the side-bar data."""
self.scope.refresh()
self.__init__(scope=self.scope)
[docs]
def remove(self, key: Any) -> None:
"""
Remove a button from the side-bar.
:param key: either a button, index, or name.
:returns None
"""
self.delete_button(key=key)
[docs]
def insert(self, index: int, button: SideBarItem) -> None:
"""
Place a button at index `index` of the button-list.
:param index: location index of the new button
:param button: a side-bar button object
"""
if button in self._items:
self._items.remove(button)
self._items.insert(check_type(index, int, "index"), button)
[docs]
def create_card(self, order: Optional[int] = None, *args, **kwargs) -> SideBarCard:
"""Create a side bar card.
:param order: Optional input to specify where the button is injected in the list of items.
:return: new side-bar card
"""
if order is None:
index = len(self._items)
else:
index = check_type(order, int, "order")
card = SideBarCard(side_bar_manager=self, *args, **kwargs)
# insert the button on a certain position in the list of items.
self._items.insert(index, card)
self._update()
return card
[docs]
def create_button(
self, order: Optional[int] = None, *args, **kwargs
) -> SideBarButton:
"""
Create a side-bar button.
:param order: Optional input to specify where the button is injected in the list of items.
:return: new side-bar button
"""
if order is None:
index = len(self._items)
else:
index = check_type(order, int, "order")
button = SideBarButton(side_bar_manager=self, *args, **kwargs)
# insert the button on a certain position in the list of items.
self._items.insert(index, button)
self._update()
return button
[docs]
def add_task_button(
self,
activity: "Activity",
title: Optional[str] = None,
task_display_mode: Optional[
SubprocessDisplayMode
] = SubprocessDisplayMode.ACTIVITIES,
*args,
**kwargs,
) -> SideBarButton:
"""
Add a side-bar button to a KE-chain activity.
:param activity: Activity object
:param title: Title of the side-bar button, defaults to the activity name
:param task_display_mode: for sub-processes, vary the display mode in KE-chain
:return: new side-bar button
"""
from pykechain.models import Activity
check_type(activity, Activity, "activity")
check_enum(task_display_mode, SubprocessDisplayMode, "task_display_mode")
title = check_text(title, "title") or activity.name
uri = f"{self._scope_uri}/{task_display_mode}/{activity.id}"
uri_target = (
URITarget.INTERNAL
if activity.scope_id == self.scope.id
else URITarget.EXTERNAL
)
return self.create_button(
uri=uri, uri_target=uri_target, title=title, *args, **kwargs
)
[docs]
def add_ke_chain_page(
self, page_name: KEChainPages, title: Optional[str] = None, *args, **kwargs
) -> SideBarButton:
"""
Add a side-bar button to a built-in KE-chain page.
:param page_name: name of the KE-chain page
:param title: Title of the side-bar button, defaults to the page_name
:return: new side-bar button
"""
page_name = check_enum(page_name, KEChainPages, "page_name")
title = check_text(title, "title") or KEChainPageLabels[page_name]
icon = KEChainPageIcons[page_name]
if "icon" in kwargs:
icon = kwargs.pop("icon")
uri = f"{self._scope_uri}/{page_name}"
return self.create_button(
uri=uri,
uri_target=URITarget.INTERNAL,
title=title,
icon=icon,
*args,
**kwargs,
)
[docs]
def add_external_button(
self, url: str, title: str, *args, **kwargs
) -> SideBarButton:
"""
Add a side-bar button to an external page defined by an URL.
:param title: title of the button
:param url: URL to an external page
:return: new side-bar button
"""
button = self.create_button(
title=check_text(title, "title"),
uri=check_url(url),
uri_target=URITarget.EXTERNAL,
*args,
**kwargs,
)
return button
[docs]
def add_buttons(
self, buttons: List[Dict], override_sidebar: bool
) -> List[SideBarItem]:
"""
Create a list of buttons in bulk. Each button is defined by a dict, provided in a sorted list.
:param buttons: list of dicts
:param override_sidebar: whether to override the default sidebar menu items.
:return: list of SideBarButton objects
"""
check_list_of_dicts(buttons, "buttons")
check_type(override_sidebar, bool, "override_sidebar")
for index, button in enumerate(buttons):
button = SideBarButton(side_bar_manager=self, json=button)
self._items.append(button)
self.override_sidebar = override_sidebar
self._update()
return self._items
[docs]
def delete_button(self, key: Any) -> None:
"""
Similar to the `remove` method, deletes a button.
:param key: either a button, index or name
:return: None
"""
item = self[key]
self._items.remove(item)
self._update()
@property
def override_sidebar(self) -> bool:
"""
Flag to indicate whether the original KE-chain side-bar is still shown.
:return: boolean, True if original side-bar is not visible
"""
return self._override
@override_sidebar.setter
def override_sidebar(self, value: bool) -> None:
"""
Flag to indicate whether the original KE-chain side-bar is still shown.
:param value: new boolean value
:return: None
"""
check_type(value, bool, "override_sidebar")
self._override = value
self._update()
def _update(self) -> None:
"""
Update the side-bar using the scope.options attribute.
:return: None
"""
if self._perform_bulk_creation:
# Update will proceed during deletion of the manager.
return
options = dict(self.scope.options)
custom_navigation = list()
for item in self._items:
custom_navigation.append(item.as_dict())
options.update(
customNavigation=custom_navigation,
overrideSideBar=self._override,
)
self.scope.options = options