def _maybe_compose_deltas(old_delta: Delta, new_delta: Delta) -> Optional[Delta]: """Combines new_delta onto old_delta if possible. If the combination takes place, the function returns a new Delta that should replace old_delta in the queue. If the new_delta is incompatible with old_delta, the function returns None. In this case, the new_delta should just be appended to the queue as normal. """ old_delta_type = old_delta.WhichOneof("type") if old_delta_type == "add_block": # We never replace add_block deltas, because blocks can have # other dependent deltas later in the queue. For example: # # placeholder = st.empty() # placeholder.columns(1) # placeholder.empty() # # The call to "placeholder.columns(1)" creates two blocks, a parent # container with delta_path (0, 0), and a column child with # delta_path (0, 0, 0). If the final "placeholder.empty()" Delta # is composed with the parent container Delta, the frontend will # throw an error when it tries to add that column child to what is # now just an element, and not a block. return None new_delta_type = new_delta.WhichOneof("type") if new_delta_type == "new_element": return new_delta if new_delta_type == "add_block": return new_delta return None
def _maybe_compose_deltas(old_delta: Delta, new_delta: Delta) -> Optional[Delta]: """Combines new_delta onto old_delta if possible. If the combination takes place, the function returns a new Delta that should replace old_delta in the queue. If the new_delta is incompatible with old_delta, the function returns None. In this case, the new_delta should just be appended to the queue as normal. """ old_delta_type = old_delta.WhichOneof("type") if old_delta_type == "add_block": # We never replace add_block deltas, because blocks can have # other dependent deltas later in the queue. For example: # # placeholder = st.empty() # placeholder.columns(1) # placeholder.empty() # # The call to "placeholder.columns(1)" creates two blocks, a parent # container with delta_path (0, 0), and a column child with # delta_path (0, 0, 0). If the final "placeholder.empty()" Delta # is composed with the parent container Delta, the frontend will # throw an error when it tries to add that column child to what is # now just an element, and not a block. return None new_delta_type = new_delta.WhichOneof("type") if new_delta_type == "new_element": return new_delta if new_delta_type == "add_block": return new_delta if new_delta_type == "add_rows": import streamlit.elements.legacy_data_frame as data_frame # We should make data_frame.add_rows *not* mutate any of the # inputs. In the meantime, we have to deepcopy the input that will be # mutated. composed_delta = copy.deepcopy(old_delta) data_frame.add_rows(composed_delta, new_delta, name=new_delta.add_rows.name) return composed_delta # We deliberately don't handle the "arrow_add_rows" delta type. With Arrow, # `add_rows` is a frontend-only operation. return None
def _enqueue_new_element_delta(self, marshall_element): """Fake enqueue new element delta. The real DeltaGenerator method actually enqueues the deltas but to test _with_element we just need this method to exist. The real enqueue_new_element_delta will be tested later on. """ delta = Delta() marshall_element(delta.new_element) return delta
def _get_data_frame(delta: Delta, name: Optional[str] = None) -> DataFrame: """Extract the dataframe protobuf from a delta protobuf.""" delta_type = delta.WhichOneof("type") if delta_type == "new_element": element_type = delta.new_element.WhichOneof("type") # Some element types don't support named datasets. if name and element_type in ("data_frame", "table", "chart"): raise ValueError("Dataset names not supported for st.%s" % element_type) if element_type in "data_frame": return delta.new_element.data_frame elif element_type in "table": return delta.new_element.table elif element_type == "chart": return delta.new_element.chart.data elif element_type == "vega_lite_chart": chart_proto = delta.new_element.vega_lite_chart if name: return _get_or_create_dataset(chart_proto.datasets, name) elif len(chart_proto.datasets) == 1: # Support the case where the dataset name was randomly given by # the charting library (e.g. Altair) and the user has no # knowledge of it. return chart_proto.datasets[0].data else: return chart_proto.data # TODO: Support DeckGL. Need to figure out how to handle layer indices # first. elif delta_type == "add_rows": if delta.add_rows.has_name and name != delta.add_rows.name: raise ValueError('No dataset found with name "%s".' % name) return delta.add_rows.data else: raise ValueError("Cannot extract DataFrame from %s." % delta_type)
def _enqueue(self, delta_type, element_proto): delta = Delta() el_proto = getattr(delta.new_element, delta_type) el_proto.CopyFrom(element_proto) return delta
def test_get_data_frame(self): """Test streamlit.data_frame_proto._get_data_frame.""" # Test delta not new_element or add_rows with pytest.raises(ValueError) as e: delta = Delta() data_frame_proto._get_data_frame(delta) err_msg = "Cannot extract DataFrame from None." self.assertEqual(err_msg, str(e.value)) # Generic Data aa = AnyArray() aa.int64s.data.extend([1, 2, 3]) # Delta DataFrame delta_df = Delta() delta_df.new_element.data_frame.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_df) self.assertEqual(df, delta_df.new_element.data_frame) # Delta Table delta_table = Delta() delta_table.new_element.table.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_table) self.assertEqual(df, delta_table.new_element.table) # Vega-Lite Chart delta_vega = Delta() delta_vega.new_element.vega_lite_chart.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_vega) self.assertEqual(df, delta_vega.new_element.vega_lite_chart.data) # Vega-Lite Chart w/ named dataset delta_vega_dataset = Delta() ds1 = NamedDataSet() ds1.name = "dataset 1" ds1.has_name = True ds1.data.data.cols.extend([aa]) delta_vega_dataset.new_element.vega_lite_chart.datasets.extend([ds1]) df = data_frame_proto._get_data_frame(delta_vega_dataset, "dataset 1") self.assertEqual( df, delta_vega_dataset.new_element.vega_lite_chart.datasets[0].data ) # Vega-Lite Chart w/ unnamed dataset delta_vega_unnamed_dataset = Delta() ds2 = NamedDataSet() ds2.has_name = False ds2.data.data.cols.extend([aa]) delta_vega_unnamed_dataset.new_element.vega_lite_chart.datasets.extend([ds2]) df = data_frame_proto._get_data_frame(delta_vega_unnamed_dataset) self.assertEqual( df, delta_vega_unnamed_dataset.new_element.vega_lite_chart.datasets[0].data ) # add_rows w/ name delta_add_rows = Delta() delta_add_rows.add_rows.name = "named dataset" delta_add_rows.add_rows.has_name = True delta_add_rows.add_rows.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_add_rows, "named dataset") self.assertEqual(df, delta_add_rows.add_rows.data) # add_rows w/out name with pytest.raises(ValueError) as e: delta_add_rows_noname = Delta() delta_add_rows_noname.add_rows.name = "named dataset" delta_add_rows_noname.add_rows.has_name = True delta_add_rows_noname.add_rows.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_add_rows_noname) err_msg = 'No dataset found with name "None".' self.assertEqual(err_msg, str(e.value))
def test_add_rows(self): """Test streamlit.data_frame_proto._add_rows.""" # Generic Data aa = AnyArray() aa.int64s.data.extend([1, 2]) cell_style = CellStyle() cell_style.css.extend([_css_style("color", "black")]) style = CellStyleArray() style.styles.extend([cell_style]) # Delta DataFrame dt1 = Delta() dt1.new_element.data_frame.data.cols.extend([aa]) dt1.new_element.data_frame.index.plain_index.data.int64s.data.extend([3, 4]) dt1.new_element.data_frame.columns.plain_index.data.int64s.data.extend([5, 6]) dt1.new_element.data_frame.style.cols.extend([style]) dt2 = Delta() dt2.new_element.data_frame.data.cols.extend([aa]) dt2.new_element.data_frame.index.plain_index.data.int64s.data.extend([3, 4]) dt2.new_element.data_frame.columns.plain_index.data.int64s.data.extend([5, 6]) dt2.new_element.data_frame.style.cols.extend([style]) combined = Delta() aa_combined = AnyArray() aa_combined.int64s.data.extend([1, 2, 1, 2]) style_combined = CellStyleArray() style_combined.styles.extend([cell_style, cell_style]) combined.new_element.data_frame.data.cols.extend([aa_combined]) row_index = combined.new_element.data_frame.index.plain_index row_index.data.int64s.data.extend([3, 4, 3, 4]) col_index = combined.new_element.data_frame.columns.plain_index col_index.data.int64s.data.extend([5, 6]) combined.new_element.data_frame.style.cols.extend([style_combined]) # Test both not empty data_frame_proto.add_rows(dt1, dt2) self.assertEqual(dt1, combined) # Test one empty dt0 = Delta() dt0.new_element.data_frame.data.cols.extend([]) data_frame_proto.add_rows(dt0, dt1) self.assertEqual(str(dt0), str(dt1)) # Test both empty empty0 = Delta() empty0.new_element.data_frame.data.cols.extend([]) empty1 = Delta() empty1.new_element.data_frame.data.cols.extend([]) data_frame_proto.add_rows(empty0, empty1) self.assertEqual(str(empty0), str(empty1)) # Test different data shapes diff0 = Delta() diff0.new_element.data_frame.data.cols.extend([aa, aa]) diff1 = Delta() diff1.new_element.data_frame.data.cols.extend([aa]) with pytest.raises(ValueError) as e: data_frame_proto.add_rows(diff0, diff1) err_msg = "Dataframes have incompatible shapes" self.assertEqual(err_msg, str(e.value))
def test_get_data_frame(self): """Test streamlit.data_frame_proto._get_data_frame.""" # Test delta not new_element or add_rows with pytest.raises(ValueError) as e: delta = Delta() data_frame_proto._get_data_frame(delta) err_msg = 'Cannot extract DataFrame from None.' self.assertEqual(err_msg, str(e.value)) # Test delta = new_element, a name is used and type is chart, df, table with pytest.raises(ValueError) as e: delta = Delta() # TODO(armando): test df and table delta.new_element.chart.type = 'some chart' data_frame_proto._get_data_frame(delta, name='some name') err_msg = 'Dataset names not supported for st.chart' self.assertEqual(err_msg, str(e.value)) # Generic Data aa = AnyArray() aa.int64s.data.extend([1, 2, 3]) # Delta DataFrame delta_df = Delta() delta_df.new_element.data_frame.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_df) self.assertEqual(df, delta_df.new_element.data_frame) # Delta Table delta_table = Delta() delta_table.new_element.table.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_table) self.assertEqual(df, delta_table.new_element.table) # Delta Chart delta_chart = Delta() delta_chart.new_element.chart.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_chart) self.assertEqual(df, delta_chart.new_element.chart.data) # Vega-Lite Chart delta_vega = Delta() delta_vega.new_element.vega_lite_chart.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_vega) self.assertEqual(df, delta_vega.new_element.vega_lite_chart.data) # Vega-Lite Chart w/ named dataset delta_vega_dataset = Delta() ds1 = NamedDataSet() ds1.name = 'dataset 1' ds1.has_name = True ds1.data.data.cols.extend([aa]) delta_vega_dataset.new_element.vega_lite_chart.datasets.extend([ds1]) df = data_frame_proto._get_data_frame(delta_vega_dataset, 'dataset 1') self.assertEqual( df, delta_vega_dataset.new_element.vega_lite_chart.datasets[0].data) # Vega-Lite Chart w/ unnamed dataset delta_vega_unnamed_dataset = Delta() ds2 = NamedDataSet() ds2.has_name = False ds2.data.data.cols.extend([aa]) delta_vega_unnamed_dataset.new_element.vega_lite_chart.datasets.extend( [ds2]) df = data_frame_proto._get_data_frame(delta_vega_unnamed_dataset) self.assertEqual( df, delta_vega_unnamed_dataset.new_element.vega_lite_chart. datasets[0].data) # add_rows w/ name delta_add_rows = Delta() delta_add_rows.add_rows.name = 'named dataset' delta_add_rows.add_rows.has_name = True delta_add_rows.add_rows.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_add_rows, 'named dataset') self.assertEqual(df, delta_add_rows.add_rows.data) # add_rows w/out name with pytest.raises(ValueError) as e: delta_add_rows_noname = Delta() delta_add_rows_noname.add_rows.name = 'named dataset' delta_add_rows_noname.add_rows.has_name = True delta_add_rows_noname.add_rows.data.data.cols.extend([aa]) df = data_frame_proto._get_data_frame(delta_add_rows_noname) err_msg = 'No dataset found with name "None".' self.assertEqual(err_msg, str(e.value))