Skip to content

Commit 676ab3d

Browse files
gadenbuienpelikan
andauthored
fix(df_to_html): Needs nw.from_native() (#79)
* fix to df_to_html * refactor(df_to_html): Simplify * chore: rename test file * refactor: rename test fixture * chore: add changelog item * `devtools::document()` (GitHub Actions) --------- Co-authored-by: Nick Pelikan <[email protected]> Co-authored-by: gadenbuie <[email protected]>
1 parent 8987ee1 commit 676ab3d

File tree

5 files changed

+130
-6
lines changed

5 files changed

+130
-6
lines changed

pkg-py/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [UNRELEASED]
9+
10+
* Fixed an issue with the query tool when used with SQLAlchemy data sources. (@npelikan #79)
11+
812
## [0.2.0] - 2025-09-02
913

1014
* `querychat.init()` now accepts a `client` argument, replacing the previous `create_chat_callback` argument. (#60)

pkg-py/src/querychat/querychat.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ def df_to_html(df: IntoFrame, maxrows: int = 5) -> str:
224224
HTML string representation of the table
225225
226226
"""
227-
if isinstance(df, (nw.LazyFrame, nw.DataFrame)):
228-
df_short = df.lazy().head(maxrows).collect()
229-
nrow_full = df.lazy().select(nw.len()).collect().item()
227+
ndf = nw.from_native(df)
228+
229+
if isinstance(ndf, (nw.LazyFrame, nw.DataFrame)):
230+
df_short = ndf.lazy().head(maxrows).collect()
231+
nrow_full = ndf.lazy().select(nw.len()).collect().item()
230232
else:
231-
raise TypeError("df must be a Narwhals DataFrame or LazyFrame")
233+
raise TypeError(
234+
"Must be able to convert `df` into a Narwhals DataFrame or LazyFrame",
235+
)
232236

233237
# Generate HTML table
234238
table_html = df_short.to_pandas().to_html(

pkg-py/tests/test_df_to_html.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import sqlite3
2+
import tempfile
3+
from pathlib import Path
4+
5+
import pandas as pd
6+
import pytest
7+
from sqlalchemy import create_engine
8+
from src.querychat.datasource import DataFrameSource, SQLAlchemySource
9+
from src.querychat.querychat import df_to_html
10+
11+
12+
@pytest.fixture
13+
def sample_dataframe():
14+
"""Create a sample pandas DataFrame for testing."""
15+
return pd.DataFrame(
16+
{
17+
"id": [1, 2, 3, 4, 5],
18+
"name": ["Alice", "Bob", "Charlie", "Diana", "Eve"],
19+
"age": [25, 30, 35, 28, 32],
20+
"salary": [50000, 60000, 70000, 55000, 65000],
21+
},
22+
)
23+
24+
25+
@pytest.fixture
26+
def sample_sqlite():
27+
"""Create a temporary SQLite database with test data."""
28+
temp_db = tempfile.NamedTemporaryFile(delete=False, suffix=".db") # noqa: SIM115
29+
temp_db.close()
30+
31+
conn = sqlite3.connect(temp_db.name)
32+
cursor = conn.cursor()
33+
34+
cursor.execute("""
35+
CREATE TABLE employees (
36+
id INTEGER PRIMARY KEY,
37+
name TEXT,
38+
age INTEGER,
39+
salary REAL
40+
)
41+
""")
42+
43+
test_data = [
44+
(1, "Alice", 25, 50000),
45+
(2, "Bob", 30, 60000),
46+
(3, "Charlie", 35, 70000),
47+
(4, "Diana", 28, 55000),
48+
(5, "Eve", 32, 65000),
49+
]
50+
51+
cursor.executemany(
52+
"INSERT INTO employees (id, name, age, salary) VALUES (?, ?, ?, ?)",
53+
test_data,
54+
)
55+
56+
conn.commit()
57+
conn.close()
58+
59+
engine = create_engine(f"sqlite:///{temp_db.name}")
60+
yield engine
61+
62+
# Cleanup
63+
Path(temp_db.name).unlink()
64+
65+
66+
def test_df_to_html_with_dataframe_source_result(sample_dataframe):
67+
"""Test that df_to_html() works with results from DataFrameSource.execute_query()."""
68+
source = DataFrameSource(sample_dataframe, "employees")
69+
70+
# Execute query to get pandas DataFrame
71+
result_df = source.execute_query("SELECT * FROM employees WHERE age > 25")
72+
73+
# This should succeed after the fix
74+
html_output = df_to_html(result_df)
75+
76+
# Verify the HTML contains expected content
77+
assert isinstance(html_output, str)
78+
assert "<table" in html_output
79+
assert "Bob" in html_output
80+
assert "Charlie" in html_output
81+
assert "Diana" in html_output
82+
assert "Eve" in html_output
83+
84+
85+
def test_df_to_html_with_sqlalchemy_source_result(sample_sqlite):
86+
"""Test that df_to_html() works with results from SQLAlchemySource.execute_query()."""
87+
source = SQLAlchemySource(sample_sqlite, "employees")
88+
89+
# Execute query to get pandas DataFrame
90+
result_df = source.execute_query("SELECT * FROM employees WHERE age > 25")
91+
92+
# This should succeed after the fix
93+
html_output = df_to_html(result_df)
94+
95+
# Verify the HTML contains expected content
96+
assert isinstance(html_output, str)
97+
assert "<table" in html_output
98+
assert "Bob" in html_output
99+
assert "Charlie" in html_output
100+
assert "Diana" in html_output
101+
assert "Eve" in html_output
102+
103+
104+
def test_df_to_html_with_truncation(sample_dataframe):
105+
"""Test that df_to_html() properly truncates large datasets."""
106+
source = DataFrameSource(sample_dataframe, "employees")
107+
108+
# Execute query to get all rows
109+
result_df = source.execute_query("SELECT * FROM employees")
110+
111+
# Test with maxrows=3 to trigger truncation
112+
html_output = df_to_html(result_df, maxrows=3)
113+
114+
# Should show truncation message
115+
assert "Showing only the first 3 rows out of 5" in html_output
116+
assert "<table" in html_output

pkg-r/DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ Remotes:
4242
Config/testthat/edition: 3
4343
Encoding: UTF-8
4444
Roxygen: list(markdown = TRUE)
45-
RoxygenNote: 7.3.2
45+
RoxygenNote: 7.3.3

pkg-r/man/querychat_app.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)