Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4dbaf90
compat: Stop direct dependency on pandas itself
hoxbro Oct 6, 2025
02ab026
Update bokeh_diff
hoxbro Oct 7, 2025
815be12
Update src code to not import pandas and instead check sys.modules
hoxbro Oct 7, 2025
b6d877c
Use importorskip to ignore pandas required tests
hoxbro Oct 7, 2025
a6d1029
ignore --admin test as it uses tabulator directly
hoxbro Oct 7, 2025
0363bd4
Ignore all test_tables for now
hoxbro Oct 7, 2025
9bb8f81
fix bad test passing hv.Curve(None)
hoxbro Oct 7, 2025
f55ac3d
Update to use np.testing for vega test
hoxbro Oct 7, 2025
026bd21
Remove commented out import pandas as pd
hoxbro Oct 7, 2025
98b2a4d
fix lint
hoxbro Oct 7, 2025
ae3777e
gh review
hoxbro Oct 7, 2025
d1343c1
Fix failing tests
hoxbro Oct 7, 2025
2fa3018
Try to make examples test pass
hoxbro Oct 7, 2025
cc1375d
Add back typing
hoxbro Oct 7, 2025
a0235a1
Add back ui test
hoxbro Oct 7, 2025
e9354a6
Work around docs with pandas installed
hoxbro Oct 7, 2025
6e8479f
just add pandas import...
hoxbro Oct 7, 2025
7c9759b
Merge branch 'main' into compat_nopandas
hoxbro Oct 10, 2025
55b8e82
reorder vizzu
hoxbro Oct 10, 2025
82c3b1d
Merge branch 'main' into compat_nopandas
hoxbro Oct 15, 2025
99373df
Update conftest examples
hoxbro Oct 23, 2025
75b8d3d
Remove holoviews install
hoxbro Oct 27, 2025
2feb91b
Merge branch 'main' into compat_nopandas
hoxbro Nov 14, 2025
82c6efd
Update to use bokeh 3.9.0.dev4
hoxbro Nov 14, 2025
a47b553
loosen pin
hoxbro Nov 14, 2025
11dd401
tmp remove bokeh-fastapi
hoxbro Nov 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ jobs:
with:
environments: ${{ matrix.environment }}
opengl: true
- name: PATCH # HACK: Should never be merged into main
run: |
pixi run -e ${{ matrix.environment }} pip uninstall pandas -y
- name: Test unit
run: |
pixi run -e ${{ matrix.environment }} test-unit $COV
Expand Down
1 change: 1 addition & 0 deletions doc/how_to/custom_components/examples/plot_viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
```{pyodide}
import param
import panel as pn
import pandas as pd

from bokeh.sampledata.iris import flowers
from panel.viewable import Viewer
Expand Down
52 changes: 52 additions & 0 deletions examples/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,58 @@
]



if find_spec("pandas") is None:
collect_ignore_glob += [ # TODO: Need to be double checked
"gallery/altair_brushing.ipynb",
"gallery/deckgl_game_of_life.ipynb",
"gallery/gapminders.ipynb",
"gallery/glaciers.ipynb",
"gallery/hvplot_explorer.ipynb",
"gallery/iris_kmeans.ipynb",
"gallery/nyc_deckgl.ipynb",
"gallery/penguin_crossfilter.ipynb",
"gallery/penguin_kmeans.ipynb",
"gallery/portfolio_analyzer.ipynb",
"gallery/portfolio_optimizer.ipynb",
"gallery/vtk_interactive.ipynb",
"gallery/vtk_slicer.ipynb",
"gallery/vtk_warp.ipynb",
"gallery/windturbines.ipynb",
"gallery/xgboost_classifier.ipynb",
"reference/chat/ChatMessage.ipynb",
"reference/indicators/Tqdm.ipynb",
"reference/indicators/Trend.ipynb",
"reference/layouts/Swipe.ipynb",
"reference/panes/Bokeh.ipynb",
"reference/panes/DataFrame.ipynb",
"reference/panes/ECharts.ipynb",
"reference/panes/HTML.ipynb",
"reference/panes/HoloViews.ipynb",
"reference/panes/IPyWidget.ipynb",
"reference/panes/Matplotlib.ipynb",
"reference/panes/Param.ipynb",
"reference/panes/Perspective.ipynb",
"reference/panes/Plotly.ipynb",
"reference/panes/ReactiveExpr.ipynb",
"reference/panes/Reacton.ipynb",
"reference/panes/Streamz.ipynb",
"reference/panes/Vizzu.ipynb",
"reference/templates/Bootstrap.ipynb",
"reference/templates/EditableTemplate.ipynb",
"reference/templates/FastGridTemplate.ipynb",
"reference/templates/FastListTemplate.ipynb",
"reference/templates/GoldenLayout.ipynb",
"reference/templates/Material.ipynb",
"reference/templates/React.ipynb",
"reference/templates/Slides.ipynb",
"reference/templates/Vanilla.ipynb",
"reference/widgets/DataFrame.ipynb",
"reference/widgets/FileDownload.ipynb",
"reference/widgets/Tabulator.ipynb",
]


def pytest_runtest_makereport(item, call):
"""
Skip tests that fail because "the kernel died before replying to kernel_info"
Expand Down
4 changes: 2 additions & 2 deletions panel/pane/vizzu.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _get_data(self):
return data, {str(k): v for k, v in cols.items()}

def _get_columns(self):
import pandas as pd
pd = sys.modules.get("pandas")

columns = []
for col, array in self._data.items():
Expand All @@ -118,7 +118,7 @@ def _get_columns(self):
value = array[0]
if isinstance(value, dt.date):
columns.append({'name': col, 'type': 'datetime'})
elif isdatetime(value) or isinstance(value, pd.Period):
elif isdatetime(value) or pd and isinstance(value, pd.Period):
columns.append({'name': col, 'type': 'datetime'})
elif isinstance(value, str):
columns.append({'name': col, 'type': 'dimension'})
Expand Down
3 changes: 2 additions & 1 deletion panel/tests/chat/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from io import BytesIO
from zoneinfo import ZoneInfo

import pandas as pd
import pytest

from panel import Param, bind
Expand Down Expand Up @@ -388,6 +387,7 @@ def test_serialize_audio(self):
assert message.serialize() == f"Audio='{ASSETS / 'mp3.mp3'}'"

def test_serialize_dataframe(self):
pd = pytest.importorskip("pandas")
message = ChatMessage(DataFrame(pd.DataFrame({'a': [1, 2, 3]})))
assert message.serialize() == "DataFrame= a\n0 1\n1 2\n2 3"

Expand All @@ -396,5 +396,6 @@ def test_repr(self):
assert repr(message) == "ChatMessage(object='Hello', user='User', reactions=['favorite'])"

def test_repr_dataframe(self):
pd = pytest.importorskip("pandas")
message = ChatMessage(pd.DataFrame({'a': [1, 2, 3]}), avatar="D")
assert repr(message) == "ChatMessage(object= a\n0 1\n1 2\n2 3, user='User', reactions=[])"
2 changes: 2 additions & 0 deletions panel/tests/command/test_serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def test_autoreload_app_local_module(py_files):

@linux_only
def test_serve_admin(py_file):
pytest.importorskip("pandas") # TODO: Investigate if we can remove this
app = "import panel as pn; pn.Row('# Example').servable(title='A')"
write_file(app, py_file.file)

Expand All @@ -72,6 +73,7 @@ def test_serve_admin(py_file):

@linux_only
def test_serve_admin_custom_endpoint(py_file):
pytest.importorskip("pandas") # TODO: Investigate if we can remove this
app = "import panel as pn; pn.Row('# Example').servable(title='A')"
write_file(app, py_file.file)

Expand Down
5 changes: 4 additions & 1 deletion panel/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from functools import cache
from subprocess import PIPE, Popen

import pandas as pd
import pytest

from bokeh.client import pull_session
Expand Down Expand Up @@ -272,6 +271,7 @@ def port():

@pytest.fixture
def dataframe():
pd = pytest.importorskip("pandas")
return pd.DataFrame({
'int': [1, 2, 3],
'float': [3.14, 6.28, 9.42],
Expand All @@ -281,6 +281,7 @@ def dataframe():

@pytest.fixture
def df_mixed():
pd = pytest.importorskip("pandas")
df = pd.DataFrame({
'int': [1, 2, 3, 4],
'float': [3.14, 6.28, 9.42, -2.45],
Expand All @@ -294,6 +295,7 @@ def df_mixed():

@pytest.fixture
def df_multiindex(df_mixed):
pd = pytest.importorskip("pandas")
df_mi = df_mixed.copy()
df_mi.index = pd.MultiIndex.from_tuples([
('group0', 'subgroup0'),
Expand Down Expand Up @@ -573,6 +575,7 @@ def eh(exception):

@pytest.fixture
def df_strings():
pd = pytest.importorskip("pandas")
descr = [
'Under the Weather',
'Top Drawer',
Expand Down
34 changes: 24 additions & 10 deletions panel/tests/io/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import time

from collections import Counter
from importlib.util import find_spec
from typing import Any

import numpy as np
import pandas as pd
import param
import pytest
import requests
Expand Down Expand Up @@ -138,6 +139,7 @@ def test_ndarray_hash():
)

def test_dataframe_hash():
pd = pytest.importorskip("pandas")
data = {
"A": [0.0, 1.0, 2.0, 3.0, 4.0],
"B": [0.0, 1.0, 0.0, 1.0, 0.0],
Expand All @@ -150,6 +152,7 @@ def test_dataframe_hash():
assert not hashes_equal(df1, df2)

def test_series_hash():
pd = pytest.importorskip("pandas")
series1 = pd.Series([0.0, 1.0, 2.0, 3.0, 4.0])
series2 = series1.copy()
assert hashes_equal(series1, series2)
Expand Down Expand Up @@ -383,20 +386,31 @@ def expensive_calculation(self, value):

assert model.executions == 2

DF1 = pd.DataFrame({"x": [1]})
DF2 = pd.DataFrame({"y": [1]})

def test_hash_on_simple_dataframes():
assert _generate_hash(DF1)!=_generate_hash(DF2)

@pytest.mark.parametrize(["value", "other", "expected"], [
is_equal_parameterized: list[tuple[Any, Any, bool]] = [
(None, None, True),
(True, False, False), (False, True, False), (False, False, True), (True, True, True),
(None, 1, False), (1, None, False), (1, 1, True), (1,2,False),
(None, "a", False), ("a", None, False), ("a", "a", True), ("a","b",False),
(1,"1", False),
(None, DF1, False), (DF1, None, False), (DF1, DF1, True), (DF1, DF1.copy(), True), (DF1,DF2,False),
])
]

if find_spec("pandas"):
import pandas as pd
DF1 = pd.DataFrame({"x": [1]})
DF2 = pd.DataFrame({"y": [1]})
is_equal_parameterized.extend([
(None, DF1, False,),
(DF1, None, False,),
(DF1, DF1, True,),
(DF1, DF1.copy(), True,),
(DF1, DF2, False,),
])

def test_hash_on_simple_dataframes():
pytest.importorskip("pandas")
assert _generate_hash(DF1)!=_generate_hash(DF2)

@pytest.mark.parametrize(["value", "other", "expected"], is_equal_parameterized)
def test_is_equal(value, other, expected):
assert is_equal(value, other)==expected

Expand Down
9 changes: 7 additions & 2 deletions panel/tests/io/test_location.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pandas as pd
from importlib.util import find_spec

import param
import pytest

Expand Down Expand Up @@ -27,7 +28,8 @@ class SyncParameterized(param.Parameterized):

string = param.String(default=None)

dataframe = param.DataFrame(default=None)
if find_spec("pandas") is not None:
dataframe = param.DataFrame(default=None)


def test_location_update_query(location):
Expand Down Expand Up @@ -170,6 +172,7 @@ def test_iframe_srcdoc_location():

@pytest.fixture
def dataframe():
pd = pytest.importorskip("pandas")
return pd.DataFrame({"x": [1]})

def test_location_sync_from_dataframe(location, dataframe):
Expand All @@ -178,12 +181,14 @@ def test_location_sync_from_dataframe(location, dataframe):
assert location.search == "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"

def test_location_sync_to_dataframe(location, dataframe):
pd = pytest.importorskip("pandas")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we convert these to decorators, i.e.:

pd_available = pytest.mark.skipif(pd is None, reason="requires pandas")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do this before a merge. For now this is easier to do.

p = SyncParameterized()
location.search = "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"
location.sync(p)
pd.testing.assert_frame_equal(p.dataframe, dataframe)

def test_location_sync_to_dataframe_with_initial_value(location, dataframe):
pd = pytest.importorskip("pandas")
p = SyncParameterized(dataframe=pd.DataFrame({"y": [2]}))
location.search = "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"
location.sync(p)
Expand Down
4 changes: 1 addition & 3 deletions panel/tests/io/test_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from io import StringIO
from pathlib import Path

import numpy as np

from bokeh.resources import Resources

from panel.config import config
Expand Down Expand Up @@ -66,7 +64,7 @@ def test_save_cdn_resources():
def test_static_path_in_holoviews_save(tmpdir):
import holoviews as hv
hv.Store.set_current_backend('bokeh')
plot = hv.Curve(np.random.seed(42))
plot = hv.Curve([])
res = Resources(mode='server', root_url='/')
out_file = Path(tmpdir) / 'plot.html'
hv.save(plot, out_file, resources=res)
Expand Down
5 changes: 4 additions & 1 deletion panel/tests/pane/test_markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from unittest.mock import patch

import numpy as np
import pandas as pd
import pytest

from panel import config
Expand All @@ -21,10 +20,12 @@ def test_get_markdown_pane_type():
assert PaneBase.get_pane_type("**Markdown**") is Markdown

def test_get_dataframe_pane_type():
pd = pytest.importorskip("pandas")
df = pd.DataFrame({"A": [1, 2, 3]})
assert PaneBase.get_pane_type(df) is DataFrame

def test_get_series_pane_type():
pd = pytest.importorskip("pandas")
ser = pd.Series([1, 2, 3])
assert PaneBase.get_pane_type(ser) is DataFrame

Expand Down Expand Up @@ -246,6 +247,7 @@ def test_html_pane_sanitize_html(document, comm):
assert model.text.endswith('<h1><strong>HTML</h1></strong>')

def test_dataframe_pane_pandas(document, comm):
pd = pytest.importorskip("pandas")
pane = DataFrame(pd.DataFrame({"A": [1, 2, 3]}))

# Create pane
Expand All @@ -265,6 +267,7 @@ def test_dataframe_pane_pandas(document, comm):
assert pane._models == {}

def test_dataframe_pane_supports_escape(document, comm):
pd = pytest.importorskip("pandas")
url = "<a href='https://panel.holoviz.org/'>Panel</a>"
df = pd.DataFrame({"url": [url]})
pane = DataFrame(df)
Expand Down
4 changes: 3 additions & 1 deletion panel/tests/pane/test_plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
plotly_available = pytest.mark.skipif(plotly is None, reason="requires plotly")

import numpy as np
import pandas as pd

from panel.models.plotly import PlotlyPlot
from panel.pane import PaneBase, Plotly
Expand Down Expand Up @@ -216,6 +215,7 @@ def test_clean_relayout_data():
@pytest.mark.skipif(not find_spec("scipy"), reason="requires scipy")
@plotly_available
def test_plotly_swap_traces(document, comm):
pd = pytest.importorskip("pandas")
data_bar = pd.DataFrame({'Count': [1, 2, 3, 4], 'Category': ["A", "B", "C", "D"]})
data_cts = np.random.randn(1000)

Expand Down Expand Up @@ -248,6 +248,7 @@ def test_plotly_swap_traces(document, comm):
@plotly_available
def test_plotly_shape_datetime_converted(document, comm):
# see https://github.com/holoviz/panel/issues/5252
pd = pytest.importorskip("pandas")
start = pd.Timestamp('2022-05-11 0:00:00', tz=dt.timezone.utc)
date_range = pd.date_range(start=start, periods=20, freq='h')

Expand All @@ -269,6 +270,7 @@ def test_plotly_shape_datetime_converted(document, comm):
@plotly_available
def test_plotly_datetime_converted_2d_array(document, comm):
# see https://github.com/holoviz/panel/issues/7309
pd = pytest.importorskip("pandas")
n_points = 3
data = pd.DataFrame({
'timestamp': pd.date_range(start='2023-01-01', periods=n_points, freq='min'),
Expand Down
Loading
Loading