def test_stream_dataframe_dictionary_value_single(): # Given value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) tabulator = Tabulator(value=value) stream_value = {"x": 4, "y": "d"} # Used to test that value event is triggered global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT = 0 @param.depends(tabulator.param.value, watch=True) def _inc(*events): global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT += 1 # When PROVIDING A DICTIONARY ROW tabulator.stream(stream_value) # Then tabulator_source_df = tabulator._source.to_df().drop(columns=["index"]) expected = pd.DataFrame({"x": [1, 2, 4], "y": ["a", "b", "d"]}) pd.testing.assert_frame_equal(tabulator.value, expected) pd.testing.assert_frame_equal(tabulator_source_df, expected, check_column_type=False, check_dtype=False) assert VALUE_CHANGED_COUNT == 1
def test_config_none(): # Given css_count = len(pn.config.css_files) pn.config.js_files.clear() # When Tabulator.config(css=None) # Then assert len(pn.config.css_files) == css_count
def test_cell_change_when_column_data_source(): # Given value = ColumnDataSource(pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})) tabulator = Tabulator(value=value) # When tabulator._cell_change = {"c": "x", "i": 1, "v": 3} # Then we assume the columndatasource has been update on the js side # and therefore don't update on the python side assert tabulator.value.to_df().loc[1, "x"] == 2
def test_selection_dataframe(data_records, dataframe): # Given tabulator = Tabulator(value=dataframe) # When tabulator.selection = [0, 1, 2] actual = tabulator.selected_values # Then expected = pd.DataFrame(data=data_records[0:3]) pd.testing.assert_frame_equal(actual, expected)
def test_selection_column_data_source(data_records, column_data_source): # Given tabulator = Tabulator(value=column_data_source) # When tabulator.selection = [0, 1, 2] actual = tabulator.selected_values # Then # I could not find a more direct way to test this. expected_as_df = pd.DataFrame(data=data_records[0:3]) pd.testing.assert_frame_equal(actual.to_df().drop(columns="index"), expected_as_df)
def test_patch_from_partial_dataframe(): data = pd.DataFrame({"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]}) data1 = data.loc[0:1, ] data2 = data.loc[2:4] # When tabulator = Tabulator(value=data1) tabulator.value = data2.reset_index(drop=True) patch_value = tabulator.value["x"] + 2 tabulator.patch(patch_value) # Then expected = pd.DataFrame({"x": [5, 6], "y": ["c", "d"]}) pd.testing.assert_frame_equal(tabulator.value, expected)
def test_cell_change_when_dataframe(): # Given value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) tabulator = Tabulator(value=value) original_data = tabulator._source.data # When tabulator._cell_change = {"c": "x", "i": 1, "v": 3} # Then assert tabulator.value.loc[1, "x"] == 3 # And the tabulator._source.data shall not have been updated # We currently use the _pause_cds_updates parameter to avoid reupdating the _source.data assert tabulator._source.data is original_data
def tabulator_data_specified_as_column_data_source_value(): configuration = { "layout": "fitColumns", "initialSort": [ { "column": "y", "dir": "desc" }, ], "columns": [ { "title": "Value", "field": "x" }, { "title": "Item", "field": "y", "hozAlign": "right", "formatter": "money" }, ], } value = ColumnDataSource({"x": [1, 2], "y": ["a", "b"]}) return Tabulator(configuration=configuration, value=value)
def tabulator_data_specified_as_data_frame_value(): configuration = { "layout": "fitColumns", "initialSort": [ { "column": "y", "dir": "desc" }, ], "columns": [ { "title": "Value", "field": "x" }, { "title": "Item", "field": "y", "hozAlign": "right", "formatter": "money" }, ], } value = pd.DataFrame([{"x": [1], "y": "a"}, {"x": [2], "y": "b"}]) return Tabulator(configuration=configuration, value=value)
def tabulator_data_specified_in_configuration(): configuration = { "layout": "fitColumns", "data": [{ "x": [1], "y": "a" }, { "x": [2], "y": "b" }], "initialSort": [ { "column": "y", "dir": "desc" }, ], "columns": [ { "title": "Value", "field": "x" }, { "title": "Item", "field": "y", "hozAlign": "right", "formatter": "money" }, ], } return Tabulator(configuration=configuration)
def test_dataframe_to_columns_configuration(dataframe, columns): # Given value = dataframe # When actual = Tabulator.to_columns_configuration(value) # Then assert actual == columns
def test_constructor(): # When tabulator = Tabulator() # Then assert not tabulator.value assert isinstance(tabulator._source, ColumnDataSource) assert tabulator.configuration == {"autoColumns": True} assert tabulator.selection == [] assert tabulator.selected_values is None
def test_patch_and_reset(): """I experienced some strange behaviour which I test below. The code actually worked as it should. The problem was that I patched the original data so I could never "reset" back to the original data """ # Given data = pd.DataFrame({"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]}) data_copy = data.copy(deep=True) tabulator = Tabulator(value=data_copy) patch = tabulator.value["x"] + 2 # When patch Then tabulator.patch(patch_value=patch) assert set(tabulator._source.data["x"]) == {3, 4, 5, 6} # When reset Then tabulator.value = data assert set(tabulator._source.data["x"]) == {1, 2, 3, 4}
def test_range_index_of_dataframe_value(): # Given data = pd.DataFrame({"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]}) data2 = data.loc[2:4] # When with pytest.raises(ValueError) as e: Tabulator(value=data2) assert ( str(e.value) == "Please provide a DataFrame with RangeIndex starting at 0 and with step 1" )
def test_tabulator_comms(document, comm, column_data_source, configuration): # Given tabulator = Tabulator(value=column_data_source, configuration=configuration) widget = tabulator.get_root(document, comm=comm) # Then assert isinstance(widget, tabulator._widget_type) assert widget.source == column_data_source assert widget.configuration == configuration # When with param.edit_constant(tabulator): tabulator._process_events({ "configuration": { "a": 1 }, }) # Then assert tabulator.configuration == {"a": 1}
def test_stream_dataframe_dataframe_value(): # Given value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) tabulator = Tabulator(value=value) stream_value = pd.DataFrame({"x": [3, 4], "y": ["c", "d"]}) # Used to test that value event is triggered global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT = 0 @param.depends(tabulator.param.value, watch=True) def _inc(*events): global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT += 1 # When tabulator.stream(stream_value) # Then tabulator_source_df = tabulator._source.to_df().drop(columns=["index"]) expected = pd.DataFrame({"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]}) pd.testing.assert_frame_equal(tabulator.value, expected) pd.testing.assert_frame_equal(tabulator_source_df, expected) assert VALUE_CHANGED_COUNT == 1
def __init__(self, configuration: Dict, data: pd.DataFrame, **params): super().__init__(**params) self.data = data self.data_reset = ColumnDataSource(self.data.iloc[0:10, ]) self.tabulator = Tabulator( configuration=configuration, value=self.data_reset, sizing_mode="stretch_both", background="salmon", ) self.sizing_mode = "stretch_width" self.height = 950 self.rows_count = len(self.data) self.stream_count = 15 self.reset = self._reset_action self.replace = self._replace_action self.stream = self._stream_action self.patch = self._patch_action stylesheet = TabulatorStylesheet(theme="site") actions_pane = pn.Param( self, parameters=["reset", "replace", "stream", "patch"], name="Actions") tabulator_pane = pn.Param(self.tabulator, parameters=["selection"]) self[:] = [ stylesheet, self.tabulator, pn.WidgetBox(stylesheet.param.theme, actions_pane, tabulator_pane, sizing_mode="fixed", width=400), ]
def test_stream_cds_dictionary_value(): # Given value = ColumnDataSource({"x": [1, 2], "y": ["a", "b"]}) tabulator = Tabulator(value=value) stream_value = {"x": [3, 4], "y": ["c", "d"]} # Used to test that value event is triggered global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT = 0 @param.depends(tabulator.param.value, watch=True) def _inc(*events): global VALUE_CHANGED_COUNT VALUE_CHANGED_COUNT += 1 # When tabulator.stream(stream_value) # Then tabulator_source_json = tabulator._source.to_json( include_defaults=False)["data"] expected = {"x": [1, 2, 3, 4], "y": ["a", "b", "c", "d"]} assert tabulator.value is value assert tabulator_source_json == expected assert VALUE_CHANGED_COUNT == 1
def test_replace_stream_and_reset(): # Given data = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": ["a", "b", "c", "d", "e"]}) data1 = data.copy(deep=True).loc[0:1, ].reset_index(drop=True) data2 = data.copy(deep=True).loc[2:3, ].reset_index(drop=True) data3 = data.copy(deep=True).loc[4:4, ] tabulator = Tabulator(value=data1) # When replace, stream and reset tabulator.value = data2 tabulator.stream(stream_value=data3) tabulator.value = data.copy(deep=True).loc[0:1, ] # Then assert set(tabulator._source.data["x"]) == {1, 2}
def __init__(self, configuration, data: pd.DataFrame, **params): super().__init__(**params) self.data = data self.tabulator = params["tabulator"] = Tabulator( configuration=configuration, value=self.data.copy(deep=True).iloc[0:10, ], sizing_mode="stretch_both", background="salmon", ) self.sizing_mode = "stretch_width" self.height = 950 self.rows_count = len(self.data) self.stream_count = 15 self.reset = self._reset_action self.replace = self._replace_action self.stream = self._stream_action self.patch = self._patch_action stylesheet = TabulatorStylesheet(theme="site") actions_pane = pn.Param( self, parameters=[ "reset", "replace", "stream", "patch", "avg_rating", "value_edits" ], name="Actions", ) tabulator_pane = pn.Param(self.tabulator, parameters=["selection"]) self[:] = [ stylesheet, self.tabulator, pn.WidgetBox(stylesheet.param.theme, actions_pane, tabulator_pane, sizing_mode="fixed", width=400), ] self._update_avg_rating() self.tabulator.param.watch(self._update_avg_rating, "value")
def test_config_custom(): # When Tabulator.config(css="materialize") # Then assert CSS_HREFS["materialize"] in pn.config.css_files
component=TabulatorDataCDSApp, parameters={ "configuration": _configuration_basic, "data": _dataframe }, ) reloader_dataframe_actions = Reloader( component=TabulatorDataFrameApp, parameters={ "configuration": _configuration_basic, "data": _dataframe }, ) reloaders = [ reloader_dataframe_actions, reloader_cds_actions, tabulator_data_specified_in_configuration, tabulator_data_specified_as_data_frame_value, tabulator_data_specified_as_column_data_source_value, ] return Designer(components=reloaders) if __name__ == "__main__": Tabulator.config(css="site") # Tabulator() # test_designer().show() TabulatorDataFrameApp(configuration=_configuration_basic(), data=_dataframe()).show(port=5007)
class TabulatorDataCDSApp(pn.Column): """Extension Implementation""" tabulator = param.Parameter() reset = param.Action(label="RESET") replace = param.Action(label="REPLACE") stream = param.Action(label="STREAM") patch = param.Action(label="PATCH") # The _rename dict is used to keep track of Panel parameters to sync to Bokeh properties. # As dope is not a property on the Bokeh model we should set it to None _rename = { **pn.Column._rename, "tabulator": None, "reset": None, "replace": None, "stream": None, "patch": None, } def __init__(self, configuration: Dict, data: pd.DataFrame, **params): super().__init__(**params) self.data = data self.data_reset = ColumnDataSource(self.data.iloc[0:10, ]) self.tabulator = Tabulator( configuration=configuration, value=self.data_reset, sizing_mode="stretch_both", background="salmon", ) self.sizing_mode = "stretch_width" self.height = 950 self.rows_count = len(self.data) self.stream_count = 15 self.reset = self._reset_action self.replace = self._replace_action self.stream = self._stream_action self.patch = self._patch_action stylesheet = TabulatorStylesheet(theme="site") actions_pane = pn.Param( self, parameters=["reset", "replace", "stream", "patch"], name="Actions") tabulator_pane = pn.Param(self.tabulator, parameters=["selection"]) self[:] = [ stylesheet, self.tabulator, pn.WidgetBox(stylesheet.param.theme, actions_pane, tabulator_pane, sizing_mode="fixed", width=400), ] def _reset_action(self, *events): value = self.data.iloc[0:10, ] self.tabulator.value.data = value def _replace_action(self, *events): data = self.data.iloc[10:15, ] self.tabulator.value.data = data def _stream_action(self, *events): if self.stream_count == len(self.data): self.stream_count = 15 self._reset_action() else: stream_data = self.data.iloc[self.stream_count:self.stream_count + 1, ] self.tabulator.stream(stream_data) self.stream_count += 1 def _patch_action(self, *events): def _patch(value): value += 10 if value >= 100: return 0 return value data = self.tabulator.value.data progress = data["progress"] new_progress = [_patch(value) for value in progress] patches = { "progress": [(slice(len(progress)), new_progress)], } self.tabulator.patch(patches) def __repr__(self): return f"Tabulator({self.name})" def __str__(self): return f"Tabulator({self.name})"
def tabulator(configuration, dataframe): return Tabulator(configuration=configuration, value=dataframe)
def test_tabulator_from_dataframe(dataframe, configuration): tabulator = Tabulator(value=dataframe, configuration=configuration) assert isinstance(tabulator._source, ColumnDataSource)
def test_tabulator_from_column_data_source(column_data_source, configuration): tabulator = Tabulator(value=column_data_source, configuration=configuration) assert tabulator._source == tabulator.value
def test_config_default(): # When Tabulator.config() # Then assert CSS_HREFS["default"] in pn.config.css_files
def test_to_title(field, expected): assert Tabulator._to_title(field) == expected