Skip to content

Commit 3ff6567

Browse files
machowschloerke
andauthored
feat: support DataFrames via narwhals (#1570)
Co-authored-by: Barret Schloerke <[email protected]>
1 parent ffd06d3 commit 3ff6567

File tree

41 files changed

+1632
-1025
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1632
-1025
lines changed

.github/workflows/pytest.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ jobs:
7979
- name: Install dependencies
8080
run: |
8181
python -m pip install --upgrade pip
82-
pip install https://github.com/rstudio/py-htmltools/tarball/main
8382
make install-deps
8483
make install
8584
- name: "Build Package"

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### New features
1717

18+
* Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.data_frame`. This allows for any eager data frame supported by narwhals to be returned from a `@render.data_frame` output method. All internal methods and helper methods leverage the `narwhals` API to be data frame agnostic. (#1570)
19+
1820
### Other changes
1921

22+
* Incorporated `orjson` for faster data serialization in `@render.data_frame` outputs. (#1570)
23+
2024
* Added `PageNavbar` class to the list of `shiny.playwright.controllers` for testing `ui.page_navbar()`. (#1668)
2125

2226
* Added `.expect_widths()` to `NavsetPillList` in `shiny.playwright.controllers` for testing `ui.navset_pill_list(widths=)`. (#1668)
@@ -27,15 +31,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2731

2832
* Small improvements to the default pulse busy indicator to better blend with any background. It's also now slightly smaller by default.(#1707)
2933

34+
* Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.table`. This allows for any eager data frame supported by narwhals to be returned from a `@render.table` output method. (#1570)
35+
3036
### Bug fixes
3137

3238
* A few fixes for `ui.Chat()`, including:
3339
* Fixed a bug with `Chat()` sometimes silently dropping errors. (#1672)
3440
* Fixed a bug with `Chat()` sometimes not removing it's loading icon (on error or a `None` transform). (#1679)
3541
* `.messages(format="anthropic")` correctly removes non-user starting messages (once again). (#1685)
3642

37-
* `shiny create` now uses the template `id` rather than the directory name as the default directory.
38-
(#1666)
43+
* `shiny create` now uses the template `id` rather than the directory name as the default directory. (#1666)
3944

4045
* `ui.Theme()` now works correctly on Windows when the theme requires Sass compilation. (thanks @yuuuxt, #1684)
4146

@@ -45,11 +50,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4550

4651
* Fixed input controller `InputTextArea` in `shiny.playwright.controller` to correctly validate the `resize` style property in `.expect_resize()`. (#1705)
4752

48-
4953
* Fixed a bug in `ui.conditional_panel()` that would cause the panel to repeatedly show/hide itself when the provided condition did not evaluate to a boolean value. (#1707)
5054

5155
* Fixed a bug with `ui.input_slider()` when used as a range slider that made it impossible to change the slider value when both handles were at the maximum value. (#1707)
5256

57+
* Fixed bug in `@render.data_frame` where `bool` or `object` columns were not being rendered. (#1570)
58+
5359

5460
## [1.1.0] - 2024-09-03
5561

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,17 +216,14 @@ ci-install-wheel: dist FORCE
216216
install-deps: FORCE ## install dependencies
217217
pip install -e ".[dev,test]" --upgrade
218218
ci-install-deps: FORCE
219-
uv pip install "htmltools @ git+https://github.com/posit-dev/py-htmltools.git"
220219
uv pip install -e ".[dev,test]"
221220

222221
install-docs: FORCE
223222
pip install -e ".[dev,test,doc]"
224-
pip install https://github.com/posit-dev/py-htmltools/tarball/main
225223
pip install https://github.com/posit-dev/py-shinylive/tarball/main
226224
ci-install-docs: FORCE
227-
uv pip install -e ".[dev,test,doc]"
228-
uv pip install "htmltools @ git+https://github.com/posit-dev/py-htmltools.git" \
229-
"shinylive @ git+https://github.com/posit-dev/py-shinylive.git"
225+
uv pip install -e ".[dev,test,doc]" \
226+
"shinylive @ git+https://github.com/posit-dev/py-shinylive.git"
230227

231228
ci-install-rsconnect: FORCE
232229
uv pip install "rsconnect-python @ git+https://github.com/rstudio/rsconnect-python.git"

js/data-frame/cell.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ const isShinyHtml = (x: any): x is CellHtmlValue => {
6262
};
6363
type CellValue = string | CellHtmlValue | null;
6464
const getCellValueText = (cellValue: CellValue) => {
65-
if (isShinyHtml(cellValue)) return cellValue.obj.html;
6665
if (cellValue === null) return "";
67-
return cellValue as string;
66+
if (isShinyHtml(cellValue)) return cellValue.obj.html;
67+
return cellValue;
6868
};
6969

7070
interface TableBodyCellProps {
@@ -472,6 +472,7 @@ export const TableBodyCell: FC<TableBodyCellProps> = ({
472472

473473
// TODO-future; Use faster way to make a deep copy
474474
const cellValueObjDeepCopy = JSON.parse(JSON.stringify(cellValue.obj));
475+
// Render the Shiny content asynchronously to the table's cell
475476
window.Shiny.renderContentAsync(tdRef.current, cellValueObjDeepCopy);
476477

477478
const curTdRef = tdRef.current;

js/data-frame/index.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
115115
fill: false,
116116
styles: [],
117117
},
118+
htmlDeps,
118119
} = payload;
119120
const {
120121
width,
@@ -197,7 +198,36 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
197198
typeHint,
198199
},
199200
cell: ({ getValue }) => {
200-
return getValue() as string;
201+
const ret = getValue();
202+
203+
// Regardless of type, if the value is null or undefined,
204+
// return an empty string
205+
if (ret === null || ret === undefined) {
206+
return "";
207+
}
208+
switch (typeHint?.type) {
209+
// Return the value as is
210+
case "numeric":
211+
case "date":
212+
case "datetime":
213+
case "duration":
214+
case "categorical":
215+
case "html":
216+
return ret;
217+
// Convert the value to a string
218+
case "string":
219+
case "boolean":
220+
return String(ret);
221+
// Convert the value to a JSON string if it isn't a string already
222+
case "unknown":
223+
case "object":
224+
if (typeof ret === "string") {
225+
return ret;
226+
}
227+
return JSON.stringify(ret);
228+
default:
229+
return ret;
230+
}
201231
},
202232
enableSorting,
203233
};
@@ -427,6 +457,12 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = ({
427457
};
428458
}, [id, selection, tableData]);
429459

460+
useEffect(() => {
461+
if (!htmlDeps) return;
462+
// Register the Shiny HtmlDependencies
463+
Shiny.renderDependenciesAsync([...htmlDeps]);
464+
}, [htmlDeps]);
465+
430466
useEffect(() => {
431467
const handleColumnSort = (
432468
event: CustomEvent<{ sort: { col: number; desc: boolean }[] }>

js/data-frame/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { StyleInfo } from "./style-info";
22

3+
import type { HtmlDep } from "rstudio-shiny/srcts/types/src/shiny/render";
4+
35
export type ValueOf<T> = T[keyof T];
46

57
export const EditModeEnum = {
@@ -9,7 +11,17 @@ export const EditModeEnum = {
911
export type EditMode = ValueOf<typeof EditModeEnum>;
1012

1113
export interface TypeHint {
12-
type: "string" | "numeric" | "categorical" | "unknown" | "html";
14+
type:
15+
| "string"
16+
| "numeric"
17+
| "boolean"
18+
| "date"
19+
| "datetime"
20+
| "duration"
21+
| "object"
22+
| "unknown"
23+
| "html"
24+
| "categorical";
1325
}
1426

1527
export interface CategoricalTypeHint extends TypeHint {
@@ -34,6 +46,7 @@ export interface PandasData<TIndex> {
3446
data: unknown[][];
3547
options: DataGridOptions;
3648
typeHints?: ReadonlyArray<TypeHint>;
49+
htmlDeps?: ReadonlyArray<HtmlDep>;
3750
}
3851

3952
export interface PatchInfo {

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ dependencies = [
3333
"starlette",
3434
"websockets>=10.0",
3535
"python-multipart",
36-
"htmltools>=0.5.2",
36+
# "htmltools>=0.5.3.9002",
37+
"htmltools@git+https://github.com/posit-dev/py-htmltools@main",
3738
"click>=8.1.4;platform_system!='Emscripten'",
3839
"markdown-it-py>=1.1.0",
3940
"mdit-py-plugins>=0.3.0",
@@ -46,6 +47,8 @@ dependencies = [
4647
"prompt-toolkit;platform_system!='Emscripten'",
4748
"python-multipart>=0.0.7;platform_system!='Emscripten'",
4849
"setuptools;python_version>='3.12'",
50+
"narwhals>=1.9.0,<1.10.0",
51+
"orjson>=3.10.7",
4952
]
5053

5154
[project.optional-dependencies]

shiny/_deprecated.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class ShinyDeprecationWarning(RuntimeWarning):
2323
warnings.simplefilter("always", ShinyDeprecationWarning)
2424

2525

26-
def warn_deprecated(message: str):
27-
warnings.warn(message, ShinyDeprecationWarning, stacklevel=3)
26+
def warn_deprecated(message: str, stacklevel: int = 3):
27+
warnings.warn(message, ShinyDeprecationWarning, stacklevel=stacklevel)
2828

2929

3030
def render_text():

shiny/render/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from ._data_frame_utils._selection import CellSelection
1616
from ._data_frame_utils._types import ( # noqa: F401
1717
StyleInfo,
18-
DataFrameLikeT as _DataFrameLikeT, # pyright: ignore[reportUnusedImport]
1918
)
2019
from ._deprecated import ( # noqa: F401
2120
RenderFunction, # pyright: ignore[reportUnusedImport]

0 commit comments

Comments
 (0)