def test_patch_bad_columns(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with self.assertRaises(ValueError) as cm: ds.patch(dict(c=[(0, 100)])) self.assertEqual(str(cm.exception), "Can only patch existing columns (extra: c)") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(0,100)], c=[(0, 100)], d=[(0, 100)])) self.assertEqual(str(cm.exception), "Can only patch existing columns (extra: c, d)")
def test_patch_bad_columns(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with pytest.raises( ValueError, match=r"Can only patch existing columns \(extra: c\)"): ds.patch(dict(c=[(0, 100)])) with pytest.raises( ValueError, match=r"Can only patch existing columns \(extra: c, d\)"): ds.patch(dict(a=[(0, 100)], c=[(0, 100)], d=[(0, 100)]))
def test_patch_good_data(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) ds._document = "doc" stuff = {} def mock(*args, **kw): stuff['args'] = args stuff['kw'] = kw ds.data._patch = mock ds.patch(dict(a=[(0,100), (1,101)], b=[(0,200)])) self.assertEqual(stuff['args'], ("doc", ds, dict(a=[(0,100), (1,101)], b=[(0,200)]))) self.assertEqual(stuff['kw'], {})
def test_patch_good_slice_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25])) ds._document = "doc" stuff = {} mock_setter = object() def mock(*args, **kw): stuff['args'] = args stuff['kw'] = kw ds.data._patch = mock ds.patch(dict(a=[(slice(2), [100, 101]), (slice(3, 5), [100, 101])], b=[(slice(0, None, 2), [100, 101, 102])]), mock_setter) assert stuff['args'] == ("doc", ds, dict(a=[(slice(2), [100, 101]), (slice(3, 5), [100, 101])], b=[(slice(0, None, 2), [100, 101, 102])]), mock_setter) assert stuff['kw'] == {}
def test_patch_good_simple_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) ds._document = "doc" stuff = {} mock_setter = object() def mock(*args, **kw): stuff['args'] = args stuff['kw'] = kw ds.data._patch = mock ds.patch(dict(a=[(0,100), (1,101)], b=[(0,200)]), mock_setter) assert stuff['args'] == ("doc", ds, dict(a=[(0,100), (1,101)], b=[(0,200)]), mock_setter) assert stuff['kw'] == {}
class Tabulator(Widget): """The Tabulator Pane wraps the [Tabulator](http://tabulator.info/) table to provide an awesome interative table. You can - Specify a `configuration` dictionary at instantation. See http://tabulator.info/. - Provide an initial `value` as a Pandas DataFrame or Bokeh ColumnDataSource. - `stream` (append) to the `value`. - `patch` (update) the `value`. Example: Data specified in configuration >>> from awesome_panel_extensions.widgets.tabulator import Tabulator >>> 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"} ... ], ... } >>> Tabulator(configuration=configuration) Tabulator(...) Example: Data specified as Pandas.DataFrame value >>> import pandas as pd >>> 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'} ... ]) >>> Tabulator(configuration=configuration, value=value) Tabulator(...) Example: Data specified as Bokeh ColumnDataSource 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"]}) >>> Tabulator(configuration=configuration, value=value) Tabulator(...) """ value = param.Parameter( doc="""One of pandas.DataFrame or bokeh.models.ColumnDataSource. Please note when specifying a Pandas.Dataframe we currently have some narrow requirements - The index should be a single index which should be called 'index' and be unique. To make sure things work we suggest you `.reset_index()` before usage. """) selection = param.List( doc="The list of selected row indexes. For example [1,4]. Default is []" ) configuration = param.Dict( constant=True, doc= """The initial Tabulator configuration. See https://tabulator.info for lots of examples. If None is provided at instantiation, then {'autocolumns': True} is added Please note that in order to get things working we add the below to the configuration on the js side. { "rowSelectionChanged": rowSelectionChanged, "cellEdited": cellEdited, "index": "index", } """, ) height = param.Integer( default=300, bounds=(0, None), doc= """The height of the Tabulator table. Specifying a height is mandatory.""", ) _source = param.ClassSelector( class_=ColumnDataSource, doc="Used to transfer the `value` efficiently to frontend") _cell_change = param.Dict( doc= """Changed whenever the user updates a cell in the client. Sends something like {"c": "<COLUMN NAME>", "i": "<INDEX>", "v": "<NEW VALUE>"}. Used to transfer the change efficiently and update the DataFrame as I could not find a similar property/ event on the ColumnDataSource""") _rename = { "value": None, "selection": None, "_source": "source", } _widget_type = _BkTabulator def __init__(self, **params): """The Tabulator Pane wraps the [Tabulator](http://tabulator.info/) table to provide an awesome interative table. You can - Specify a `configuration` dictionary at instantation. See http://tabulator.info/. - Provide an initial `value` as a Pandas DataFrame or Bokeh ColumnDataSource. - `stream` (append) to the `value`. - `patch` (update) the `value`. Example: Data specified in configuration >>> from awesome_panel_extensions.widgets.tabulator import Tabulator >>> 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"} ... ], ... } >>> Tabulator(configuration=configuration) Tabulator(...) Example: Data specified as Pandas.DataFrame value >>> import pandas as pd >>> 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'} ... ]) >>> Tabulator(configuration=configuration, value=value) Tabulator(...) Example: Data specified as Bokeh ColumnDataSource 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"]}) >>> Tabulator(configuration=configuration, value=value) Tabulator(...) """ if "configuration" not in params: params["configuration"] = _DEFAULT_CONFIGURATION.copy() if "selection" not in params: params["selection"] = [] super().__init__(**params) self._pause_cds_updates = False self._update_column_data_source() @param.depends("value", watch=True) def _update_column_data_source(self, *_): if self._pause_cds_updates: return if self.value is None: self._source = ColumnDataSource({}) elif isinstance(self.value, pd.DataFrame): if (not isinstance(self.value.index, pd.RangeIndex) or self.value.index.start != 0 or self.value.index.step != 1): raise ValueError( "Please provide a DataFrame with RangeIndex starting at 0 and with step 1" ) if self._source: self._source.data = self.value else: self._source = ColumnDataSource(self.value) elif isinstance(self.value, ColumnDataSource): self._source = self.value else: raise ValueError("The `data` provided is not of a supported type!") @param.depends("_cell_change", watch=True) def _update_value_with_cell_change(self): if isinstance(self.value, pd.DataFrame): column = self._cell_change["c"] # pylint: disable=unsubscriptable-object index = self._cell_change["i"] # pylint: disable=unsubscriptable-object new_value = self._cell_change["v"] # pylint: disable=unsubscriptable-object self._pause_cds_updates = True self.value.at[index, column] = new_value self.param.trigger("value") self._pause_cds_updates = False def stream(self, stream_value: Union[pd.DataFrame, pd.Series, Dict], reset_index: bool = True): """Streams (appends) the `stream_value` provided to the existing value in an efficient manner. Args: stream_value (Union[pd.DataFrame, pd.Series, Dict]): The new value(s) to append to the existing value. reset_index (bool, optional): If the stream_value is a DataFrame and `reset_index` is True then the index of it is reset if True. Helps to keep the index unique and named `index`. Defaults to True. Raises: ValueError: Raised if the stream_value is not a supported type. Example: Stream a Series to a DataFrame >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> stream_value = pd.Series({"x": 4, "y": "d"}) >>> tabulator.stream(stream_value) >>> tabulator.value.to_dict("list") {'x': [1, 2, 4], 'y': ['a', 'b', 'd']} Example: Stream a Dataframe to a Dataframe >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> stream_value = pd.DataFrame({"x": [3, 4], "y": ["c", "d"]}) >>> tabulator.stream(stream_value) >>> tabulator.value.to_dict("list") {'x': [1, 2, 3, 4], 'y': ['a', 'b', 'c', 'd']} Example: Stream a Dictionary row to a DataFrame >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> stream_value = {"x": 4, "y": "d"} >>> tabulator.stream(stream_value) >>> tabulator.value.to_dict("list") {'x': [1, 2, 4], 'y': ['a', 'b', 'd']} Example: Stream a Dictionary of Columns to a Dataframe >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> stream_value = {"x": [3, 4], "y": ["c", "d"]} >>> tabulator.stream(stream_value) >>> tabulator.value.to_dict("list") {'x': [1, 2, 3, 4], 'y': ['a', 'b', 'c', 'd']} """ if isinstance(self.value, pd.DataFrame): value_index_start = self.value.index.max() + 1 if isinstance(stream_value, pd.DataFrame): if reset_index: stream_value = stream_value.reset_index(drop=True) stream_value.index += value_index_start self._pause_cds_updates = True self.value = pd.concat([self.value, stream_value]) self._source.stream(stream_value) self._pause_cds_updates = False elif isinstance(stream_value, pd.Series): self._pause_cds_updates = True self.value.loc[value_index_start] = stream_value self._source.stream(stream_value) self.param.trigger("value") self._pause_cds_updates = False elif isinstance(stream_value, dict): if stream_value: try: stream_value = pd.DataFrame(stream_value) except ValueError: stream_value = pd.Series(stream_value) self.stream(stream_value) else: raise ValueError( "The patch value provided is not a DataFrame, Series or Dict!" ) else: self._pause_cds_updates = True self._source.stream(stream_value) self.param.trigger("value") self._pause_cds_updates = False def patch(self, patch_value: Union[pd.DataFrame, pd.Series, Dict]): """Patches (updates) the existing value with the `patch_value` in an efficient manner. Args: patch_value (Union[pd.DataFrame, pd.Series, Dict]): The value(s) to patch the existing value with. Raises: ValueError: Raised if the patch_value is not a supported type. Example: Patch a DataFrame with a Dictionary row. >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> patch_value = {"x": [(0, 3)]} >>> tabulator.patch(patch_value) >>> tabulator.value.to_dict("list") {'x': [3, 2], 'y': ['a', 'b']} Example: Patch a Dataframe with a Dictionary of Columns. >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> patch_value = {"x": [(slice(2), (3,4))], "y": [(1,'d')]} >>> tabulator.patch(patch_value) >>> tabulator.value.to_dict("list") {'x': [3, 4], 'y': ['a', 'd']} Example: Patch a DataFrame with a Series. Please note the index is used in the update >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> patch_value = pd.Series({"index": 1, "x": 4, "y": "d"}) >>> tabulator.patch(patch_value) >>> tabulator.value.to_dict("list") {'x': [1, 4], 'y': ['a', 'd']} Example: Patch a Dataframe with a Dataframe. Please note the index is used in the update. >>> value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]}) >>> tabulator = Tabulator(value=value) >>> patch_value = pd.DataFrame({"x": [3, 4], "y": ["c", "d"]}) >>> tabulator.patch(patch_value) >>> tabulator.value.to_dict("list") {'x': [3, 4], 'y': ['c', 'd']} """ if isinstance(self.value, pd.DataFrame): if isinstance(patch_value, pd.DataFrame): patch_value_dict = self._patch_dataframe_to_dict(patch_value) self.patch(patch_value_dict) elif isinstance(patch_value, pd.Series): patch_value_dict = self._patch_series_to_dict(patch_value) self.patch(patch_value_dict) elif isinstance(patch_value, dict): self._patch_from_dict(patch_value) else: raise ValueError( f"""Patching a patch_value of type {type(patch_value)} is not supported. Please provide a DataFrame, Series or Dict""") else: if isinstance(patch_value, dict): self._pause_cds_updates = True self._source.patch(patch_value) self.param.trigger("value") self._pause_cds_updates = False else: raise ValueError( f"""Patching a patch_value of type {type(patch_value)} is not supported. Please provide a dict""") @staticmethod def _patch_dataframe_to_dict(patch_value: pd.DataFrame) -> Dict: patch_value_dict: Dict[Any, Any] = {} for column in patch_value.columns: patch_value_dict[column] = [] for index in patch_value.index: patch_value_dict[column].append( (index, patch_value.loc[index, column])) return patch_value_dict @staticmethod def _patch_series_to_dict(patch_value: pd.Series) -> Dict: if "index" in patch_value: # Series orient is row patch_value_dict = { k: [(patch_value["index"], v)] for k, v in patch_value.items() } patch_value_dict.pop("index") else: # Series orient is column patch_value_dict = {patch_value.name: list(patch_value.items())} return patch_value_dict def _patch_from_dict(self, patch_value: Dict): self._pause_cds_updates = True for key, value in patch_value.items(): for update in value: self.value.loc[update[0], key] = update[1] self._source.patch(patch_value) self.param.trigger("value") self._pause_cds_updates = False @classmethod def to_columns_configuration( cls, value: Union[pd.DataFrame, ColumnDataSource]) -> List[Dict[str, str]]: """Returns a nice starter `columns` dictionary from the specified `value`. Args: value (Union[pd.DataFrame, ColumnDataSource]): The data source to transform. Returns: Dict: The columns configuration Example: >>> import pandas as pd >>> value = {"name": ["python", "panel"]} >>> df = pd.DataFrame(value) >>> Tabulator.to_columns_configuration(df) [{'title': 'Name', 'field': 'name', 'sorter': 'string', 'formatter': 'plaintext', \ 'hozAlign': 'left'}] """ col_conf = [] for field in value.columns: dtype = str(value.dtypes[field]) conf = cls._core(field=field, dtype=dtype) col_conf.append(conf) return col_conf @classmethod def _core(cls, field: str, dtype: str) -> Dict[str, str]: dtype_str = str(dtype) return { "title": cls._to_title(field), "field": field, "sorter": _SORTERS.get(dtype_str, "string"), "formatter": _FORMATTERS.get(dtype_str, "plaintext"), "hozAlign": _HOZ_ALIGNS.get(dtype_str, "left"), } @staticmethod def _to_title(field: str) -> str: return field.replace("_", " ").title() @staticmethod def config(css: Optional[str] = "default"): """Adds the specified css theme to pn.config.css_files Args: css (Optional[str], optional): [description]. Defaults to "default". """ if css: href = CSS_HREFS[css] if href not in pn.config.css_files: pn.config.css_files.append(href) @property def selected_values(self) -> Union[pd.DataFrame, ColumnDataSource, None]: """Returns the selected rows of the data based Raises: ValueError: If the value is not of the supported type. Returns: Union[pd.DataFrame, ColumnDataSource, None]: The selected values of the same type as value. Based on the the current selection. """ # Selection is a list of row indices. For example [0,2] if self.value is None: return None if isinstance(self.value, pd.DataFrame): return self.value.iloc[self.selection, ] if isinstance(self.value, ColumnDataSource): # I could not find a direct way to get a selected ColumnDataSource selected_data = self.value.to_df().iloc[self.selection, ] return ColumnDataSource(selected_data) raise ValueError("The value is not of a supported type!") @param.depends("selection", watch=True) def _update_source_selected_indices(self, *_): self._source.selected.indices = self.selection def _get_model(self, doc, root=None, parent=None, comm=None): model = super()._get_model(doc=doc, root=root, parent=parent, comm=comm) if root is None: root = model self._link_props(model.source.selected, ["indices"], doc, root, comm) return model def _process_events(self, events): if "indices" in events: self.selection = events.pop("indices") super()._process_events(events)
def test_patch_bad_slice_indices(self): ds = ColumnDataSource( data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25])) with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(10), list(range(10)))])) assert str( cm.exception ) == "Out-of bounds slice index stop (10) in patch for column: a" with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1), list(range(10)))])) assert str( cm.exception ) == "Patch slices must have start < end, got slice(10, 1, None)" with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))])) assert str( cm.exception ) == "Patch slices must have non-negative (start, stop, step) values, got slice(None, 10, -1)" with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))])) assert str( cm.exception ) == "Patch slices must have start < end, got slice(10, 1, 1)" with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))])) assert str( cm.exception ) == "Patch slices must have start < end, got slice(10, 1, -1)" with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))])) assert str( cm.exception ) == "Patch slices must have non-negative (start, stop, step) values, got slice(1, 10, -1)"
def test_patch_bad_simple_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with pytest.raises(ValueError) as cm: ds.patch(dict(a=[(3, 100)])) assert str(cm.exception ) == "Out-of bounds index (3) in patch for column: a"
def test_patch_bad_slice_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25])) with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(10), list(range(10)))])) self.assertEqual(str(cm.exception), "Out-of bounds slice index stop (10) in patch for column: a") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1), list(range(10)))])) self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, None)") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))])) self.assertEqual(str(cm.exception), "Patch slices must have non-negative (start, stop, step) values, got slice(None, 10, -1)") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))])) self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, 1)") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))])) self.assertEqual(str(cm.exception), "Patch slices must have start < end, got slice(10, 1, -1)") with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))])) self.assertEqual(str(cm.exception), "Patch slices must have non-negative (start, stop, step) values, got slice(1, 10, -1)")
def test_patch_bad_simple_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with self.assertRaises(ValueError) as cm: ds.patch(dict(a=[(3, 100)])) self.assertEqual(str(cm.exception), "Out-of bounds index (3) in patch for column: a")
class Controller: params = {} terminating = False def __init__(self, params={}) -> None: self.params = {} for param in PARAMETERS: self.update_parameter(param, PARAMETERS[param][1]) for param in params: self.update_parameter(param, params[param]) self.engine = self.make_engine() data = {} data['x'], data['y'], data['color'] = trisect( self.engine.agents) self.visualisation_source = ColumnDataSource(data) self.names = [status.name for status in AgentStatus][::-1] self.status_source = ColumnDataSource(pd.DataFrame( np.zeros((1, len(self.names))), columns=self.names)) self.controls = Controls(self) self.start() def update_parameter(self, key, value): try: self.params[key] = cajole( float(value), PARAMETERS[key][0], PARAMETERS[key][2]) except: self.params[key] = PARAMETERS[key][1] if key in ('agents', 'sickness_proximity', 'sickness_duration', 'quarantine_delay'): self.params[key] == int(self.params[key]) elif key == 'initial_immunity': self.params[key] /= 100 elif key == 'distancing_factor': self.params['distancing_factor'] /= 100 elif key == 'quarantining': self.params['quarantining'] = self.params['quarantining'] == 1 if 'quarantining' in self.params and not self.params['quarantining']: self.params['quarantine_delay'] = self.params['sickness_duration'] + 1 def make_engine(self) -> Engine: return Engine(n=int(self.params['agents']), SICKNESS_PROXIMITY=int(self.params['sickness_proximity']), SICKNESS_DURATION=int(self.params['sickness_duration']), DISTANCING_FACTOR=self.params['distancing_factor'], QUARANTINE_DELAY=int(self.params['quarantine_delay']), INITIAL_IMMUNITY=self.params['initial_immunity']) def show(self) -> None: self.visualisation = get_visualisation(self.visualisation_source) self.population_health_graph = get_population_health_graph( self.names, self.status_source, self.params['agents']) curdoc().add_root(gridplot([ [self.visualisation, self.population_health_graph], [self.controls.get_controls(), get_about_us()] ], toolbar_location="left", toolbar_options={'logo': None})) def start(self) -> None: self.update_callback = curdoc().add_periodic_callback( self.update, 1000 // TICKS_PER_SECOND) def update(self) -> None: self.engine.tick() s = slice(self.engine.agent_count) x, y, color = trisect(self.engine.agents) self.visualisation_source.patch({ 'x': [(s, x)], 'y': [(s, y)], 'color': [(s, color)] }) self.status_source.stream({ "index": [self.engine.ticks], AgentStatus.DEAD.name: [self.engine.stats.get(AgentStatus.DEAD.name, 0)], AgentStatus.IMMUNE.name: [self.engine.stats.get(AgentStatus.IMMUNE.name, 0)], AgentStatus.INFECTIOUS.name: [self.engine.stats.get(AgentStatus.INFECTIOUS.name, 0)], AgentStatus.SUSCEPTIBLE.name: [self.engine.stats.get(AgentStatus.SUSCEPTIBLE.name, 0)], }) if self.engine.stats.get(AgentStatus.INFECTIOUS.name, 0) == 0 and not self.terminating: curdoc().add_timeout_callback(self.terminate, 8000) def terminate(self) -> None: if self.terminating: return self.terminating = True try: curdoc().remove_periodic_callback(self.update_callback) except ValueError: pass def reset(self) -> None: self.terminate() self.engine = self.make_engine() data = {} data['x'], data['y'], data['color'] = trisect( self.engine.agents) self.visualisation_source.data = data self.status_source.data = { "index": [], AgentStatus.DEAD.name: [], AgentStatus.IMMUNE.name: [], AgentStatus.INFECTIOUS.name: [], AgentStatus.SUSCEPTIBLE.name: [], } self.terminating = False curdoc().add_next_tick_callback(self.start)
def test_patch_bad_slice_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11, 12, 13, 14, 15], b=[20, 21, 22, 23, 24, 25])) with pytest.raises(ValueError, match=r"Out-of bounds slice index stop \(10\) in patch for column: a"): ds.patch(dict(a=[(slice(10), list(range(10)))])) with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, None\)"): ds.patch(dict(a=[(slice(10, 1), list(range(10)))])) with pytest.raises(ValueError, match=r"Patch slices must have non-negative \(start, stop, step\) values, got slice\(None, 10, -1\)"): ds.patch(dict(a=[(slice(None, 10, -1), list(range(10)))])) with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, 1\)"): ds.patch(dict(a=[(slice(10, 1, 1), list(range(10)))])) with pytest.raises(ValueError, match=r"Patch slices must have start < end, got slice\(10, 1, -1\)"): ds.patch(dict(a=[(slice(10, 1, -1), list(range(10)))])) with pytest.raises(ValueError, match=r"Patch slices must have non-negative \(start, stop, step\) values, got slice\(1, 10, -1\)"): ds.patch(dict(a=[(slice(1, 10, -1), list(range(10)))]))
def test_patch_bad_simple_indices(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with pytest.raises(ValueError, match=r"Out-of bounds index \(3\) in patch for column: a"): ds.patch(dict(a=[(3, 100)]))
def test_patch_bad_columns(self): ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21])) with pytest.raises(ValueError, match=r"Can only patch existing columns \(extra: c\)"): ds.patch(dict(c=[(0, 100)])) with pytest.raises(ValueError, match=r"Can only patch existing columns \(extra: c, d\)"): ds.patch(dict(a=[(0,100)], c=[(0, 100)], d=[(0, 100)]))