[docs]classChildType(Enum):"""Enumerator of the child type."""MARKDOWN="MARKDOWN"EXCEPTION="EXCEPTION"
[docs]classChildRecord(NamedTuple):"""Child record as a NamedTuple."""type:ChildTypekwargs:Dict[str,Any]dg:DeltaGenerator
[docs]classMutableExpander:"""Streamlit expander that can be renamed and dynamically expanded/collapsed."""
[docs]def__init__(self,parent_container:DeltaGenerator,label:str,expanded:bool):"""Create a new MutableExpander. Parameters ---------- parent_container The `st.container` that the expander will be created inside. The expander transparently deletes and recreates its underlying `st.expander` instance when its label changes, and it uses `parent_container` to ensure it recreates this underlying expander in the same location onscreen. label The expander's initial label. expanded The expander's initial `expanded` value. """self._label=labelself._expanded=expandedself._parent_cursor=parent_container.empty()self._container=self._parent_cursor.expander(label,expanded)self._child_records:List[ChildRecord]=[]
@propertydeflabel(self)->str:"""Expander's label string."""returnself._label@propertydefexpanded(self)->bool:"""True if the expander was created with `expanded=True`."""returnself._expanded
[docs]defclear(self)->None:"""Remove the container and its contents entirely. A cleared container can't be reused. """self._container=self._parent_cursor.empty()self._child_records.clear()
[docs]defappend_copy(self,other:MutableExpander)->None:"""Append a copy of another MutableExpander's children to this MutableExpander. """other_records=other._child_records.copy()forrecordinother_records:self._create_child(record.type,record.kwargs)
[docs]defupdate(self,*,new_label:Optional[str]=None,new_expanded:Optional[bool]=None)->None:"""Change the expander's label and expanded state"""ifnew_labelisNone:new_label=self._labelifnew_expandedisNone:new_expanded=self._expandedifself._label==new_labelandself._expanded==new_expanded:# No change!returnself._label=new_labelself._expanded=new_expandedself._container=self._parent_cursor.expander(new_label,new_expanded)prev_records=self._child_recordsself._child_records=[]# Replay all children into the new containerforrecordinprev_records:self._create_child(record.type,record.kwargs)
[docs]defmarkdown(self,body:SupportsStr,unsafe_allow_html:bool=False,*,help:Optional[str]=None,index:Optional[int]=None,)->int:"""Add a Markdown element to the container and return its index."""kwargs={"body":body,"unsafe_allow_html":unsafe_allow_html,"help":help}new_dg=self._get_dg(index).markdown(**kwargs)record=ChildRecord(ChildType.MARKDOWN,kwargs,new_dg)returnself._add_record(record,index)
[docs]defexception(self,exception:BaseException,*,index:Optional[int]=None)->int:"""Add an Exception element to the container and return its index."""kwargs={"exception":exception}new_dg=self._get_dg(index).exception(**kwargs)record=ChildRecord(ChildType.EXCEPTION,kwargs,new_dg)returnself._add_record(record,index)
def_create_child(self,type:ChildType,kwargs:Dict[str,Any])->None:"""Create a new child with the given params"""iftype==ChildType.MARKDOWN:self.markdown(**kwargs)eliftype==ChildType.EXCEPTION:self.exception(**kwargs)else:raiseRuntimeError(f"Unexpected child type {type}")def_add_record(self,record:ChildRecord,index:Optional[int])->int:"""Add a ChildRecord to self._children. If `index` is specified, replace the existing record at that index. Otherwise, append the record to the end of the list. Return the index of the added record. """ifindexisnotNone:# Replace existing childself._child_records[index]=recordreturnindex# Append new childself._child_records.append(record)returnlen(self._child_records)-1def_get_dg(self,index:Optional[int])->DeltaGenerator:ifindexisnotNone:# Existing index: reuse child's DeltaGeneratorassert0<=index<len(self._child_records),f"Bad index: {index}"returnself._child_records[index].dg# No index: use container's DeltaGeneratorreturnself._container