def marshall_component(dg, element: Element) -> Union[Any, Type[NoValue]]: element.component_instance.component_name = self.name element.component_instance.form_id = current_form_id(dg) if self.url is not None: element.component_instance.url = self.url # Normally, a widget's element_hash (which determines # its identity across multiple runs of an app) is computed # by hashing the entirety of its protobuf. This means that, # if any of the arguments to the widget are changed, Streamlit # considers it a new widget instance and it loses its previous # state. # # However! If a *component* has a `key` argument, then the # component's hash identity is determined by entirely by # `component_name + url + key`. This means that, when `key` # exists, the component will maintain its identity even when its # other arguments change, and the component's iframe won't be # remounted on the frontend. # # So: if `key` is None, we marshall the element's arguments # *before* computing its widget_ui_value (which creates its hash). # If `key` is not None, we marshall the arguments *after*. def marshall_element_args(): element.component_instance.json_args = serialized_json_args element.component_instance.special_args.extend(special_args) if key is None: marshall_element_args() def deserialize_component(ui_value, widget_id=""): # ui_value is an object from json, an ArrowTable proto, or a bytearray return ui_value ctx = get_script_run_ctx() widget_value, _ = register_widget( element_type="component_instance", element_proto=element.component_instance, user_key=key, widget_func_name=self.name, deserializer=deserialize_component, serializer=lambda x: x, ctx=ctx, ) if key is not None: marshall_element_args() if widget_value is None: widget_value = default elif isinstance(widget_value, ArrowTableProto): widget_value = component_arrow.arrow_proto_to_dataframe( widget_value) # widget_value will be either None or whatever the component's most # recent setWidgetValue value is. We coerce None -> NoValue, # because that's what DeltaGenerator._enqueue expects. return widget_value if widget_value is not None else NoValue
def _block(self, block_proto=Block_pb2.Block()) -> "DeltaGenerator": # Operate on the active DeltaGenerator, in case we're in a `with` block. dg = self._active_dg # Prevent nested columns & expanders by checking all parents. block_type = block_proto.WhichOneof("type") # Convert the generator to a list, so we can use it multiple times. parent_block_types = frozenset(dg._parent_block_types) if block_type == "column" and block_type in parent_block_types: raise StreamlitAPIException( "Columns may not be nested inside other columns." ) if block_type == "expandable" and block_type in parent_block_types: raise StreamlitAPIException( "Expanders may not be nested inside other expanders." ) if dg._root_container is None or dg._cursor is None: return dg msg = ForwardMsg_pb2.ForwardMsg() msg.metadata.delta_path[:] = dg._cursor.delta_path msg.delta.add_block.CopyFrom(block_proto) # Normally we'd return a new DeltaGenerator that uses the locked cursor # below. But in this case we want to return a DeltaGenerator that uses # a brand new cursor for this new block we're creating. block_cursor = cursor.RunningCursor( root_container=dg._root_container, parent_path=dg._cursor.parent_path + (dg._cursor.index,), ) block_dg = DeltaGenerator( root_container=dg._root_container, cursor=block_cursor, parent=dg, block_type=block_type, ) # Blocks inherit their parent form ids. # NOTE: Container form ids aren't set in proto. block_dg._form_data = FormData(current_form_id(dg)) # Must be called to increment this cursor's index. dg._cursor.get_locked_cursor(last_index=None) _enqueue_message(msg) return block_dg