Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions examples/data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pandas as pd
import polars as pl

df = pd.DataFrame(
df = pl.DataFrame(
[
{
"col1": "val1",
Expand Down
6 changes: 3 additions & 3 deletions examples/input_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from typing import Annotated, Optional

import pandas as pd
import polars as pl
from uiwiz import server
from fastapi import Depends, Request
from pydantic import BaseModel
Expand Down Expand Up @@ -43,7 +43,7 @@ def get_log():

@app.post("/data")
def get_data():
df = pd.DataFrame([{"asd": "val"}, {"asd": "val2", "col2": 12}])
df = pl.DataFrame([{"asd": "val"}, {"asd": "val2", "col2": 12}])
return ui.aggrid.response(df)


Expand Down Expand Up @@ -115,7 +115,7 @@ async def test(page: Annotated[MyDefinition, Depends()], req: Request):
ui.spinner().ball().large()

g = ui.aggrid(
pd.DataFrame(
pl.DataFrame(
[
{
"asd": 2,
Expand Down
8 changes: 4 additions & 4 deletions examples/run_simple.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from io import BytesIO

import pandas as pd
import polars as pl
from fastapi import Request, UploadFile

import uiwiz.ui as ui
Expand All @@ -19,8 +19,8 @@ def create_nav():
@app.ui("/some/comnponent")
async def handle_upload(file: UploadFile):
file_output = await file.read()
df = pd.read_excel(BytesIO(file_output), engine="openpyxl")
ui.table(df)
df = pl.read_excel(BytesIO(file_output))
ui.table.from_dataframe(df)


@app.page("/second")
Expand Down Expand Up @@ -50,7 +50,7 @@ async def test(request: Request):

ui.upload(name="file").on_upload(on_upload=handle_upload, target=lambda: table.id)
ui.checkbox("check")
table = ui.table.from_dataframe(pd.DataFrame())
table = ui.table.from_dataframe(pl.DataFrame())
ui.dropdown("select", ["item 1", "item 2"], "Pick item")
ui.toggle("val")
ui.toggle("val2", True)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ requires-python = ">=3.10"
dependencies = [
"fastapi>=0.115.1",
"markdown2[all]>=2.5.3",
"openpyxl>=3.1.5",
"pandas>=2.2.3",
"polars>=1.0.0",
"pyhumps>=3.8.0",
"python-multipart>=0.0.22",
"starlette>=0.49.1",
Expand Down Expand Up @@ -103,4 +102,4 @@ ignore = [
"D102",
"EM101",
"PLR0913",
]
]
21 changes: 11 additions & 10 deletions src/uiwiz/elements/aggrid/aggrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any

import numpy as np
from fastapi.responses import JSONResponse

from uiwiz.element import Element

if TYPE_CHECKING:
import pandas as pd
import polars as pl

LIB_PATH = Path(__file__).parent / "aggrid-community.min.js"
CSS_PATH = Path(__file__).parent / "aggridtheme.css"
Expand All @@ -27,7 +26,7 @@ class OPTIONS(str, Enum):
class Aggrid(Element, extensions=[CSS_PATH, LIB_PATH, JS_PATH]):
_classes: str = "ag-theme-quartz ag-theme-uiwiz w-full"

def __init__(self, df: pd.DataFrame) -> None:
def __init__(self, df: pl.DataFrame | None) -> None:
"""Aggrid

Use aggrid to display a DataFrame in a grid format.
Expand All @@ -37,9 +36,9 @@ def __init__(self, df: pd.DataFrame) -> None:
.. code-block:: python

from uiwiz import ui
import pandas as pd
import polars as pl

df = pd.DataFrame({
df = pl.DataFrame({
"Name": ["Alice", "Bob", "Charlie"],
"Age": [25, 30, 35],
"City": ["New York", "Los Angeles", "Chicago"]
Expand All @@ -60,22 +59,24 @@ def __init__(self, df: pd.DataFrame) -> None:
self.attributes["hx-aggrid"] = "/data"

@staticmethod
def create_cols_and_rows(df: pd.DataFrame, escape: bool = True) -> tuple[list[Any], list[Any]]:
def create_cols_and_rows(
df: pl.DataFrame | None,
escape: bool = True,
) -> tuple[list[Any] | str, list[Any] | str]:
cols = []
rows = []

if df is not None:
df = df.replace({np.nan: None})
cols = [{"field": item} for item in df.columns.to_list()]
rows = df.to_dict("records")
cols = [{"field": item} for item in df.columns]
rows = df.to_dicts()
if escape:
cols = html.escape(json.dumps(cols))
rows = html.escape(json.dumps(rows, default=str))

return cols, rows

@staticmethod
def response(df: pd.DataFrame, headers: dict[str, str] = {}) -> JSONResponse:
def response(df: pl.DataFrame | None, headers: dict[str, str] = {}) -> JSONResponse:
_headers = {"HX-Trigger": "aggridUpdate"} | headers
cols, rows = Aggrid.create_cols_and_rows(df, False)
return JSONResponse({"cols": cols, "rows": rows}, headers=_headers)
13 changes: 6 additions & 7 deletions src/uiwiz/elements/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from typing import TYPE_CHECKING, Callable, Optional, get_type_hints

import numpy as np
from pydantic import BaseModel

from uiwiz.element import Element
from uiwiz.elements.button import Button
from uiwiz.models.model_handler import ModelForm

if TYPE_CHECKING:
import pandas as pd
import polars as pl

from uiwiz.element_types import ELEMENT_SIZE
from uiwiz.elements.form import Form
Expand Down Expand Up @@ -296,13 +295,13 @@ def render_row(
return container

@classmethod
def from_dataframe(cls, df: pd.DataFrame) -> Element:
"""Render a pandas.DataFrame
def from_dataframe(cls, df: pl.DataFrame) -> Element:
"""Render a polars.DataFrame

:param df: The DataFrame to render
:return: The container element
"""
df = df.replace({np.nan: "None"})
df = df.fill_null("None")

with Element().classes(Table._classes_container) as container:
with Element("table").classes(Table._classes_table):
Expand All @@ -312,8 +311,8 @@ def from_dataframe(cls, df: pd.DataFrame) -> Element:
Element("th", content=col)
# rows
with Element("tbody"):
for _, row in df.iterrows():
for row in df.iter_rows():
with Element("tr"):
for val in row.values():
for val in row:
Element("td", content=val)
return container
10 changes: 10 additions & 0 deletions tests/elements/test_table.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from bs4 import BeautifulSoup
import polars as pl
from pydantic import BaseModel

from uiwiz import ui
Expand Down Expand Up @@ -73,3 +74,12 @@ def render_row_func():
assert render_endpoint == soup.select("button")[1].attrs["hx-post"]
assert "outerHTML" == soup.select("button")[1].attrs["hx-swap"]
assert "Save" == soup.select("button")[1].contents[0]


def test_table_from_dataframe_polars():
output = str(ui.table.from_dataframe(pl.DataFrame([{"input": "data"}, {"input": None}])))
soup = BeautifulSoup(output, "html.parser")

assert soup.select("th")[0].contents[0] == "input"
assert soup.select("td")[0].contents[0] == "data"
assert soup.select("td")[1].contents[0] == "None"
Loading