Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
bbe6261
feat: start rough draft of narwhals support
machow Jul 25, 2024
12d39cf
Merge branch 'main' into feat-narwhals
schloerke Jul 26, 2024
57fe20f
add narwhals to deps
schloerke Jul 26, 2024
81058ea
lint
schloerke Jul 26, 2024
c86ec1e
Merge branch 'main' into machow-feat-narwhals
schloerke Jul 26, 2024
869aa7a
Fix typing issues
schloerke Jul 26, 2024
271ec5f
Rename fixtures
schloerke Jul 26, 2024
a5714e4
Partial for narwhals serialize frame. Still needs work. Need datetime…
schloerke Jul 26, 2024
f11769f
Merge branch 'main' into machow-feat-narwhals
schloerke Aug 26, 2024
db83bc7
add missing narwhals dep in config
schloerke Aug 26, 2024
1155868
Merge branch 'main' into machow-feat-narwhals
schloerke Aug 30, 2024
be6985a
Merge branch 'main' into machow-feat-narwhals
schloerke Sep 5, 2024
9318a5b
Disable timedelta dtype within pandas (as moving towards all narwhals…
schloerke Sep 5, 2024
42d0353
Scaffold out more dtypes
schloerke Sep 5, 2024
3e3c8c0
Use `orjson` instead of `json` package to serialize json. (Native dat…
schloerke Sep 5, 2024
ef2ee1c
Update tests, trying to support more dtypes (more to go). Disable non…
schloerke Sep 5, 2024
824f6b4
Merge branch 'main' into feat-narwhals
schloerke Sep 16, 2024
563bc52
Bump min version of narwhals to v1.8.0 for `nw.Series.scatter(rows, v…
schloerke Sep 16, 2024
861329a
Expose narwhals types of `DataFrame`, `DataFrameT`, `IntoDataFrame`, …
schloerke Sep 16, 2024
5172734
Add narwhals for types and integrate into _tbl_data.py
schloerke Sep 16, 2024
ce6cd35
Styles function will accept `IntoDataFrameT` type
schloerke Sep 16, 2024
3fea35c
Be explicit on type of object being inspected
schloerke Sep 16, 2024
dc8179f
Disable all pandas specific functions for data frame
schloerke Sep 16, 2024
002ba7e
DataGrid and DataTable can now accept `IntroDataFrameT`
schloerke Sep 16, 2024
2eacaa5
Upgrade `render.data_frame` to handle `IntoDataFrameT`. Add `._nw_dat…
schloerke Sep 16, 2024
32fe466
Update typing
schloerke Sep 16, 2024
00088be
Update tests to handle the new types
schloerke Sep 16, 2024
cf6b283
Fix port test to be more robust
schloerke Sep 16, 2024
9cf92d3
Add quick short circuit
schloerke Sep 17, 2024
cfb5ac7
Add TODO with hint from Marco
schloerke Sep 17, 2024
aa0d8dd
Fix html column bug
schloerke Sep 17, 2024
8b88353
Fix narwhals subset bugs
schloerke Sep 17, 2024
cf5a845
Do not convert data early
schloerke Sep 17, 2024
08e70ad
Update _data_frame.py
schloerke Sep 17, 2024
3b4be82
Fix another html column issue
schloerke Sep 17, 2024
1f8c9c6
lint
schloerke Sep 18, 2024
6273885
Update tests for narwhals. Note: py-htmltools is holding up PR's test…
schloerke Sep 18, 2024
815baa4
Merge branch 'main' into machow-feat-narwhals
schloerke Sep 23, 2024
d0282aa
Remove `SeriesLike` type
schloerke Sep 23, 2024
c9009e8
Tighten up code. Remove mention of singledispatch
schloerke Sep 23, 2024
e7d1fe8
Merge branch 'main' into machow-feat-narwhals
schloerke Sep 24, 2024
a0681b7
Use latest narwhals (1.8.3) and dev htmltools
schloerke Sep 24, 2024
d0772e4
Remove many TODOs
schloerke Sep 24, 2024
77d6085
Update _html.py
schloerke Sep 25, 2024
d66e1de
Update docs
schloerke Sep 25, 2024
a950d7d
Test more narwhals types. Add comment for file.
schloerke Sep 25, 2024
baed7f9
Add more cell types to data frame js. Add test app for all types and …
schloerke Sep 25, 2024
90276a3
Update app.py
schloerke Sep 27, 2024
246819f
If the cell value is `null`, display `""` for table cell value
schloerke Sep 27, 2024
0e57199
Test that `DataFrame.data()` returns the same type as render method
schloerke Sep 27, 2024
f4caa1f
Temporarily disable htmltools install
schloerke Sep 27, 2024
cb960c4
Install htmltools from `taglist_not_list` branch on GH
schloerke Sep 27, 2024
df72692
Add new narwhals types
schloerke Sep 27, 2024
6e22352
Update app.py
schloerke Sep 27, 2024
71ead9f
Update htmltools dep to include new TagList inheriting from UserList
schloerke Sep 30, 2024
f8d7c13
Add note about narwhals inspecting only the first sentence
schloerke Sep 30, 2024
46a1203
Be sure to return the cell text
schloerke Sep 30, 2024
da98bd8
Rename internal method `cell_contains_htmltoolslike` to `ui_must_be_p…
schloerke Sep 30, 2024
4fc66b3
Leverage htmltools's new HTML/UserString and TagList/UserList objects…
schloerke Sep 30, 2024
98d40e1
Update app to use html within the first row to tell narwhals it's an …
schloerke Sep 30, 2024
17c3b2a
Add `reactive_method()` and tests
schloerke Oct 1, 2024
f7c2e65
Utilize new `@reactive_calc_method` decorator in data frame class
schloerke Oct 1, 2024
5a47cc8
Merge branch 'main' into machow-feat-narwhals
schloerke Oct 1, 2024
ee2f485
Update _reactive_method.py
schloerke Oct 1, 2024
f817510
Update date warning when non-narwhals compatible data is received
schloerke Oct 1, 2024
8f68a4b
Update narwhals
schloerke Oct 1, 2024
758fd86
Change equality subclass checks to isinstance checks
schloerke Oct 1, 2024
f5e2c6a
Update test_render_data_frame_tbl_data.py
schloerke Oct 1, 2024
47b855c
Update test_render_data_frame_tbl_data.py
schloerke Oct 1, 2024
1a9aa96
Update _data_frame.py
schloerke Oct 1, 2024
a9d3c67
Add another future impl for reactive_value_method
schloerke Oct 1, 2024
a25a3d1
Restore odd skip as it is still required
schloerke Oct 1, 2024
fddd87f
Drop testing of python 3.8 (minimal changes)
schloerke Oct 1, 2024
a992b2f
Revert "chore: revert check where airmass skips on python 3.9 (#1708)"
schloerke Oct 1, 2024
12544b6
Update Makefile
schloerke Oct 1, 2024
6f28b06
Revert htmltools install as it isn't needed anymore
schloerke Oct 1, 2024
3f3b9be
Drop `is_into_data_frame`
schloerke Oct 2, 2024
bc99573
Code review
schloerke Oct 2, 2024
e0caacd
Remove unused `._type_hints`
schloerke Oct 2, 2024
3791a3b
Update _data_frame.py
schloerke Oct 3, 2024
e2444d1
Collect all JS deps of a data frame sep from the content. For each ce…
schloerke Oct 3, 2024
1eef355
Update test_render_data_frame_tbl_data.py
schloerke Oct 3, 2024
f5e9099
Try requiring py-htmltools directly. Do not install py-htmltools manu…
schloerke Oct 3, 2024
c397e82
Use htmltools github ref
schloerke Oct 3, 2024
3ce71f9
Remove small and unused narwals wrapper methods
schloerke Oct 3, 2024
1a6e917
Update Makefile
schloerke Oct 3, 2024
6142df3
Store the reactive calc on self directly. This adds a two-way pointer…
schloerke Oct 3, 2024
529f371
Move py-shinylive github install to pyproject
schloerke Oct 3, 2024
1a26d7d
Revert "Move py-shinylive github install to pyproject"
schloerke Oct 3, 2024
b29e037
Perform the install in a single command
schloerke Oct 3, 2024
1fcef38
Merge branch 'main' into feat-narwhals
schloerke Oct 3, 2024
1f46fb3
Un-export `render.[DataFrame, DataFrameT, IntoDataFrame, IntoDataFram…
schloerke Oct 3, 2024
6630f61
Add narwhals support for `@render.table`; Remove no longer necessary …
schloerke Oct 3, 2024
d1b53ff
Update CHANGELOG.md
schloerke Oct 3, 2024
9f2a29d
Fix test
schloerke Oct 3, 2024
a1a9e54
Add changelog for `@render.table`
schloerke Oct 3, 2024
6ee52a9
Set max version of narwhals to `<1.10.0`
schloerke Oct 3, 2024
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ dependencies = [
"prompt-toolkit;platform_system!='Emscripten'",
"python-multipart>=0.0.7;platform_system!='Emscripten'",
"setuptools;python_version>='3.12'",
"narwhals>=1.1.7",
"orjson>=3.10.7",
]

[project.optional-dependencies]
Expand Down
9 changes: 7 additions & 2 deletions shiny/render/_data_frame_utils/_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from ._types import CellHtml, ReprHtml, SeriesLike

if TYPE_CHECKING:
import narwhals.stable.v1 as nw

from ...session import Session


Expand Down Expand Up @@ -41,8 +43,11 @@ def maybe_as_cell_html(
return cast(Jsonifiable, x)


def col_contains_shiny_html(col: SeriesLike) -> bool:
return any(is_shiny_html(val) for _, val in enumerate(col))
def col_contains_shiny_html(col: SeriesLike | nw.Series) -> bool:
for val in col:
if is_shiny_html(val):
return True
return False


# TODO-barret-test; Add test to assert the union type of `TagNode` contains `str` and (HTML | Tagifiable | MetadataNode | ReprHtml). Until a `is tag renderable` method is available in htmltools, we need to check for these types manually and must stay in sync with the `TagNode` union type.
Expand Down
9 changes: 7 additions & 2 deletions shiny/render/_data_frame_utils/_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ def wrap_shiny_html_with_session(x: TagNode):

res["typeHints"] = type_hints

if "index" in res:
del res["index"]

# print(json.dumps(res, indent=4))
return res

Expand Down Expand Up @@ -119,8 +122,10 @@ def serialize_pd_dtype(
}
elif t in {"datetime64", "datetime"}:
t = "datetime"
elif t in {"timedelta", "timedelta64"}:
t = "timedelta"

# # Disable timedelta as narwhals does not support it
# elif t in {"timedelta", "timedelta64"}:
# t = "timedelta"
else:
if col_contains_shiny_html(col):
t = "html"
Expand Down
122 changes: 117 additions & 5 deletions shiny/render/_data_frame_utils/_tbl_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
from functools import singledispatch
from typing import Any, List, Tuple, cast

import narwhals.stable.v1 as nw
from htmltools import TagNode

from ..._typing_extensions import TypeIs
from ...session import require_active_session
from ._html import maybe_as_cell_html

# from ...types import Jsonifiable
from ._types import (
CellPatch,
ColsList,
Expand Down Expand Up @@ -184,7 +183,7 @@ def _(col: PlSeries) -> FrameDtype:

from ._html import col_contains_shiny_html

if col.dtype.is_(pl.String):
if col.dtype == pl.String():
if col_contains_shiny_html(col):
type_ = "html"
else:
Expand All @@ -203,6 +202,58 @@ def _(col: PlSeries) -> FrameDtype:
return {"type": type_}


nw_boolean = nw.Boolean()
nw_categorical = nw.Categorical()
nw_enum = nw.Enum()
nw_string = nw.String()
nw_datetime = nw.Datetime()
nw_duration = nw.Duration()
nw_object = nw.Object()


@serialize_dtype.register
def _(col: nw.Series) -> FrameDtype:

from ._html import col_contains_shiny_html

if col.dtype == nw_string:
if col_contains_shiny_html(col):
type_ = "html"
else:
type_ = "string"
elif col.dtype.is_numeric():
type_ = "numeric"

elif col.dtype == nw_categorical:
categories = col.cat.get_categories().to_list()
return {"type": "categorical", "categories": categories}
elif col.dtype == nw_enum:
raise NotImplementedError("boolean type not tested")
cat_col = col.cast(nw.Categorical)
categories = cat_col.cat.get_categories().to_list()
return {"type": "categorical", "categories": categories}

elif col.dtype == nw_boolean:
raise NotImplementedError("boolean type not tested")
type_ = "boolean"
elif col.dtype == nw_duration:
raise NotImplementedError("duration type not tested")
type_ = "duration"
elif col.dtype == nw_datetime:
type_ = "datetime"
elif col.dtype == nw_object:
type_ = "object"
if col_contains_shiny_html(col):
type_ = "html"

else:
type_ = "unknown"
if col_contains_shiny_html(col):
type_ = "html"

return {"type": type_}


# serialize_frame ----------------------------------------------------------------------


Expand All @@ -218,6 +269,7 @@ def _(data: PdDataFrame) -> FrameJson:
return serialize_frame_pd(data)


# TODO: test this
@serialize_frame.register
def _(data: PlDataFrame) -> FrameJson:
import json
Expand Down Expand Up @@ -249,6 +301,61 @@ def wrap_shiny_html_with_session(x: TagNode):
}


@serialize_frame.register(nw.DataFrame)
def _(data: nw.DataFrame[Any]) -> FrameJson:

type_hints = [serialize_dtype(data[col_name]) for col_name in data.columns]
type_hints_type = {type_hint["type"] for type_hint in type_hints}

data_rows = data.rows(named=False)

# print(data_rows)
# print(data.rows(named=False))

# Shiny tag support
if "html" in type_hints_type:
session = require_active_session(None)

def wrap_shiny_html_with_session(x: TagNode):
return maybe_as_cell_html(x, session=session)

html_column_positions = [
i for i, x in enumerate(type_hints_type) if x == "html"
]

new_rows: list[tuple[Any, ...]] = []

# Wrap the corresponding columns with the cell html object
for row in data_rows:
new_row = list(row)
for html_column_position in html_column_positions:
new_row[html_column_position] = wrap_shiny_html_with_session(
new_row[html_column_position]
)
new_rows.append(tuple(new_row))

data_rows = new_rows

import orjson

data_val = orjson.loads(
orjson.dumps(
data_rows,
default=str,
option=orjson.OPT_UTC_Z,
)
)

# import json
# data_val = json.loads(json.dumps(data_rows, default=str))

return {
"columns": data.columns,
"data": data_val,
"typeHints": type_hints,
}


# subset_frame -------------------------------------------------------------------------
def subset_frame__typed(
data: DataFrameLikeT, *, rows: RowsList = None, cols: ColsList = None
Expand Down Expand Up @@ -308,6 +415,7 @@ def _(
return data.iloc[indx_rows, indx_cols]


@subset_frame.register(nw.DataFrame)
@subset_frame.register
def _(
data: PlDataFrame,
Expand All @@ -321,7 +429,7 @@ def _(
else slice(None)
)
indx_rows = rows if rows is not None else slice(None)
return data[indx_rows, indx_cols]
return data[indx_cols][indx_rows]


# get_frame_cell -----------------------------------------------------------------------
Expand All @@ -341,9 +449,10 @@ def _(data: PdDataFrame, row: int, col: int) -> Any:
)


@get_frame_cell.register(nw.DataFrame)
@get_frame_cell.register
def _(data: PlDataFrame, row: int, col: int) -> Any:
return data[row, col]
return data.item(row, col)


# shape --------------------------------------------------------------------------------
Expand All @@ -359,6 +468,7 @@ def _(data: PdDataFrame) -> Tuple[int, ...]:
return data.shape


@frame_shape.register(nw.DataFrame)
@frame_shape.register
def _(data: PlDataFrame) -> Tuple[int, ...]:
return data.shape
Expand All @@ -377,6 +487,7 @@ def _(data: PdDataFrame) -> PdDataFrame:
return data.copy()


@copy_frame.register(nw.DataFrame)
@copy_frame.register
def _(data: PlDataFrame) -> PlDataFrame:
return data.clone()
Expand All @@ -393,6 +504,7 @@ def _(data: PdDataFrame) -> List[str]:
return data.columns.to_list()


@frame_column_names.register(nw.DataFrame)
@frame_column_names.register
def _(data: PlDataFrame) -> List[str]:
return data.columns
13 changes: 12 additions & 1 deletion shiny/render/_data_frame_utils/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class DataFrameLike(ABC): ...
DataFrameLike.register(PlDataFrame)

DataFrameLikeT = TypeVar("DataFrameLikeT", PdDataFrame, PlDataFrame)
SeriesLikeT = TypeVar("SeriesLikeT", PdSeries, PlSeries)

# ---------------------------------------------------------------------

Expand Down Expand Up @@ -175,7 +176,17 @@ class FrameJson(TypedDict):


class FrameDtypeSubset(TypedDict):
type: Literal["numeric", "string", "html", "datetime", "timedelta", "unknown"]
type: Literal[
"boolean",
"numeric",
"string",
"html",
"datetime",
# "timedelta",
"duration",
"object",
"unknown",
]


class FrameDtypeCategories(TypedDict):
Expand Down
Loading
Loading