Skip to content

Commit

Permalink
Unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
petar-qb committed Nov 25, 2024
1 parent 53d73e5 commit db017a2
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 58 deletions.
5 changes: 4 additions & 1 deletion vizro-core/src/vizro/models/_components/form/_form_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ def validate_value(cls, value, values):
[entry["value"] for entry in values["options"]] if isinstance(values["options"][0], dict) else values["options"]
)

if value and ALL_OPTION not in value and not is_value_contained(value, possible_values):
if hasattr(value, "__iter__") and ALL_OPTION in value:
return value

if value and not is_value_contained(value, possible_values):
raise ValueError("Please provide a valid value from `options`.")

return value
Expand Down
25 changes: 21 additions & 4 deletions vizro-core/src/vizro/models/_controls/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy as np
import pandas as pd
from dash import dcc
from contextlib import suppress
from pandas.api.types import is_datetime64_any_dtype, is_numeric_dtype

from vizro.managers._data_manager import DataSourceName
Expand Down Expand Up @@ -95,7 +96,7 @@ class Filter(VizroBaseModel):
)
selector: SelectorType = None

_dynamic: bool = PrivateAttr(None)
_dynamic: bool = PrivateAttr(False)

# Component properties for actions and interactions
_output_component_property: str = PrivateAttr("children")
Expand Down Expand Up @@ -265,11 +266,22 @@ def _validate_column_type(self, targeted_data: pd.DataFrame) -> Literal["numeric

@staticmethod
def _get_min_max(targeted_data: pd.DataFrame, current_value=None) -> tuple[float, float]:
_min = targeted_data.min(axis=None)
_max = targeted_data.max(axis=None)

# Convert to datetime if the column is datetime64
if targeted_data.apply(is_datetime64_any_dtype).all():
_min = pd.to_datetime(_min)
_max = pd.to_datetime(_max)
current_value = pd.to_datetime(current_value)
# Convert DatetimeIndex to list of Timestamp objects so that we can use min and max functions below.
with suppress(AttributeError): current_value = current_value.tolist()

# Use item() to convert to convert scalar from numpy to Python type. This isn't needed during pre_build because
# pydantic will coerce the type, but it is necessary in __call__ where we don't update model field values
# and instead just pass straight to the Dash component.
_min = targeted_data.min(axis=None).item()
_max = targeted_data.max(axis=None).item()
with suppress(AttributeError): _min = _min.item()
with suppress(AttributeError): _max = _max.item()

if current_value is not None:
current_value = current_value if isinstance(current_value, list) else [current_value]
Expand All @@ -285,10 +297,15 @@ def _get_options(targeted_data: pd.DataFrame, current_value=None) -> list[Any]:
# values and instead just pass straight to the Dash component.
# The dropna() isn't strictly required here but will be in future pandas versions when the behavior of stack
# changes. See https://pandas.pydata.org/docs/whatsnew/v2.1.0.html#whatsnew-210-enhancements-new-stack.
# Also setting the dtype for the current_value_series to the dtype of the targeted_data_series to ensure it
# works when it's empty. See: https://pandas.pydata.org/docs/whatsnew/v2.1.0.html#other-deprecations

# Remove ALL_OPTION from the string or list of currently selected value for the categorical filters.
current_value = [] if current_value in (None, ALL_OPTION) else current_value
if isinstance(current_value, list) and ALL_OPTION in current_value:
current_value.remove(ALL_OPTION)

return np.unique(pd.concat([targeted_data.stack().dropna(), pd.Series(current_value)])).tolist() # noqa: PD013
targeted_data_series = targeted_data.stack().dropna()
current_value_series = pd.Series(current_value).astype(targeted_data_series.dtypes)

return sorted(list(pd.concat([targeted_data_series, current_value_series]).unique()))
1 change: 1 addition & 0 deletions vizro-core/src/vizro/models/_controls/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def check_data_frame_as_target_argument(cls, target):
f"Invalid target {target}. 'data_frame' target must be supplied in the form "
"<target_component>.data_frame.<dynamic_data_argument>"
)
# TODO: Add validation: Make sure the target data_frame is _DynamicData.
return target

@validator("targets")
Expand Down
46 changes: 0 additions & 46 deletions vizro-core/tests/unit/vizro/actions/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,11 @@ def iris():
return px.data.iris()


@pytest.fixture
def gapminder_dynamic_first_n_last_n_function(gapminder):
return lambda first_n=None, last_n=None: (
pd.concat([gapminder[:first_n], gapminder[-last_n:]])
if last_n
else gapminder[:first_n]
if first_n
else gapminder
)


@pytest.fixture
def box_params():
return {"x": "continent", "y": "lifeExp", "custom_data": ["continent"]}


@pytest.fixture
def box_chart(gapminder_2007, box_params):
return px.box(gapminder_2007, **box_params)


@pytest.fixture
def box_chart_dynamic_data_frame(box_params):
return px.box("gapminder_dynamic_first_n_last_n", **box_params)


@pytest.fixture
def scatter_params():
return {"x": "gdpPercap", "y": "lifeExp"}


@pytest.fixture
def scatter_chart(gapminder_2007, scatter_params):
return px.scatter(gapminder_2007, **scatter_params)
Expand All @@ -71,11 +45,6 @@ def scatter_matrix_chart(iris, scatter_matrix_params):
return px.scatter_matrix(iris, **scatter_matrix_params)


@pytest.fixture
def scatter_chart_dynamic_data_frame(scatter_params):
return px.scatter("gapminder_dynamic_first_n_last_n", **scatter_params)


@pytest.fixture
def target_scatter_filtered_continent(request, gapminder_2007, scatter_params):
continent = request.param
Expand Down Expand Up @@ -105,21 +74,6 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart):
Vizro._pre_build()


@pytest.fixture
def managers_one_page_two_graphs_with_dynamic_data(box_chart_dynamic_data_frame, scatter_chart_dynamic_data_frame):
"""Instantiates a simple model_manager and data_manager with a page, two graph models and the button component."""
vm.Page(
id="test_page",
title="My first dashboard",
components=[
vm.Graph(id="box_chart", figure=box_chart_dynamic_data_frame),
vm.Graph(id="scatter_chart", figure=scatter_chart_dynamic_data_frame),
vm.Button(id="button"),
],
)
Vizro._pre_build()


@pytest.fixture
def managers_one_page_two_graphs_one_table_one_aggrid_one_button(
box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id
Expand Down
47 changes: 47 additions & 0 deletions vizro-core/tests/unit/vizro/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Fixtures to be shared across several tests."""

import pandas as pd
import plotly.graph_objects as go
import pytest

Expand All @@ -20,6 +21,17 @@ def stocks():
return px.data.stocks()


@pytest.fixture
def gapminder_dynamic_first_n_last_n_function(gapminder):
return lambda first_n=None, last_n=None: (
pd.concat([gapminder[:first_n], gapminder[-last_n:]])
if last_n
else gapminder[:first_n]
if first_n
else gapminder
)


@pytest.fixture
def standard_px_chart(gapminder):
return px.scatter(
Expand All @@ -33,6 +45,26 @@ def standard_px_chart(gapminder):
)


@pytest.fixture
def scatter_params():
return {"x": "gdpPercap", "y": "lifeExp"}


@pytest.fixture
def scatter_chart_dynamic_data_frame(scatter_params):
return px.scatter("gapminder_dynamic_first_n_last_n", **scatter_params)


@pytest.fixture
def box_params():
return {"x": "continent", "y": "lifeExp", "custom_data": ["continent"]}


@pytest.fixture
def box_chart_dynamic_data_frame(box_params):
return px.box("gapminder_dynamic_first_n_last_n", **box_params)


@pytest.fixture
def standard_ag_grid(gapminder):
return dash_ag_grid(data_frame=gapminder)
Expand Down Expand Up @@ -88,6 +120,21 @@ def page_2():
return vm.Page(title="Page 2", components=[vm.Button()])


@pytest.fixture
def managers_one_page_two_graphs_with_dynamic_data(box_chart_dynamic_data_frame, scatter_chart_dynamic_data_frame):
"""Instantiates a simple model_manager and data_manager with a page, two graph models and the button component."""
vm.Page(
id="test_page",
title="My first dashboard",
components=[
vm.Graph(id="box_chart", figure=box_chart_dynamic_data_frame),
vm.Graph(id="scatter_chart", figure=scatter_chart_dynamic_data_frame),
vm.Button(id="button"),
],
)
Vizro._pre_build()


@pytest.fixture()
def vizro_app():
"""Fixture to instantiate Vizro/Dash app.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def expected_range_slider_default():
persistence_type="session",
className="slider-text-input-field",
),
dcc.Store(id="range_slider_input_store", storage_type="session", data=[None, None]),
dcc.Store(id="range_slider_input_store", storage_type="session"),
],
className="slider-text-input-container",
),
Expand Down Expand Up @@ -105,7 +105,7 @@ def expected_range_slider_with_optional():
persistence_type="session",
className="slider-text-input-field",
),
dcc.Store(id="range_slider_input_store", storage_type="session", data=[0, 10]),
dcc.Store(id="range_slider_input_store", storage_type="session"),
],
className="slider-text-input-container",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def expected_slider():
persistence_type="session",
className="slider-text-input-field",
),
dcc.Store(id="slider_id_input_store", storage_type="session", data=5.0),
dcc.Store(id="slider_id_input_store", storage_type="session"),
],
className="slider-text-input-container",
),
Expand Down
Loading

0 comments on commit db017a2

Please sign in to comment.