Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ci tests #10

Merged
merged 13 commits into from
Aug 8, 2024
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
on:
push:
branches:
- main
pull_request:
branches:
- main
release:
types: [published]

name: CI Tests

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
fail-fast: false

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
# we are using the -e flag, so that code cov finds the source.
# this is not ideal, since installing an editable can technically
# differ from a normal install in surprising ways.
python -m pip install "git+https://github.com/posit-dev/great-tables.git@feat-interactive"
pip install -e '.[all]'
- name: Unit tests
run: |
pytest

#- name: Upload coverage reports to Codecov
# uses: codecov/codecov-action@v4
# with:
# name: "py${{ matrix.python-version }}"
# token: ${{ secrets.CODECOV_TOKEN }}
release-pypi:
name: "Release to pypi"
runs-on: ubuntu-latest
needs: [test]
if: github.event_name == 'release'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: "Build Package"
run: |
python -m pip install build wheel
python -m build --sdist --wheel
- name: "Deploy to PyPI"
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
env_url: "https://${{ steps.deployment.outputs.env }}--quartodoc.netlify.app"
env_url: "https://${{ steps.deployment.outputs.env }}--react-tables-preview.netlify.app"
logs: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- uses: peaceiris/actions-gh-pages@v3
if: github.event_name == 'release'
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/_site
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# reactable-py

reactable generates interactive tables in Python.
It's a port of the R package [reactable](https://github.com/glin/reactable) by [@glin](https://github.com/glin).

See these handy documentation pages:

- [📚 User guide](https://machow.github.io/reactable-py/get-started)
- [🧩 Examples](http://machow.github.io/reactable-py/examples)

## Features

- **controls**: sorting, filtering, and pagination.
- **structure**: grouping, aggregating, expanding rows.
- **format**: represent numbers, dates, currencies.
- **style**: conditional styling for headers, cell values, and more.

## Installing

```bash
pip install reactable
```

## Basic use

Use with jupyter notebooks or quarto documents (.qmd) to display tables.

```python
from reactable import Reactable, embed_css
from reactable.data import sleep

embed_css()

Reactable(sleep)
```

![reactable example table using the sleep dataset](https://machow.github.io/reactable-py/assets/sleep-table.png)

## Learn more

**Essentials**

- [Code basics](https://machow.github.io/reactable-py/get-started/code-structure.html)
- [Displaying tables](https://machow.github.io/reactable-py/get-started/display-export.html)

**Controls**

- [Sorting](https://machow.github.io/reactable-py/get-started/controls-sorting.html)
- [Filtering](https://machow.github.io/reactable-py/get-started/controls-filtering.html)
- [Searching](https://machow.github.io/reactable-py/get-started/controls-searching.html)
- [Pagination](https://machow.github.io/reactable-py/get-started/controls-pagination.html)
- [Column resizing](https://machow.github.io/reactable-py/get-started/controls-resizing.html)
- [Cell click actions](https://machow.github.io/reactable-py/get-started/controls-click-actions.html)

**Structure**

- [Column headers](https://machow.github.io/reactable-py/get-started/structure-headers.html)
- [Row groups and aggregation](https://machow.github.io/reactable-py/get-started/structure-grouping.html)
- [Expandable details](https://machow.github.io/reactable-py/get-started/structure-details.html)
- [Footers](https://machow.github.io/reactable-py/get-started/structure-footers.html)
- [Rownames](https://machow.github.io/reactable-py/get-started/structure-rownames.html)

**Format**

- [Column formatting](https://machow.github.io/reactable-py/get-started/format-columns.html)
- [Column aggregated cells](https://machow.github.io/reactable-py/get-started/format-aggregated.html)
- [Rendering cells](https://machow.github.io/reactable-py/get-started/format-cell.html)
- [Rendering header and footer](https://machow.github.io/reactable-py/get-started/format-header-footer.html)
- [Rendering details](https://machow.github.io/reactable-py/get-started/format-details.html)

**Style**

- [Table styling](https://machow.github.io/reactable-py/get-started/style-table.html)
- [Conditional styling (python)](https://machow.github.io/reactable-py/get-started/style-conditional.html)
- [Custom sort indicators](https://machow.github.io/reactable-py/get-started/style-custom-sort-indicators.html)
- [Theming](https://machow.github.io/reactable-py/get-started/style-theming.html)

**Extra**

- [Javascript formatters](https://machow.github.io/reactable-py/get-started/format-custom-rendering.html)
- [Javascript styling](https://machow.github.io/reactable-py/get-started/style-conditional-js.html)
- [Javascript filters](https://machow.github.io/reactable-py/get-started/extra-advanced-filters.html)
- [Using reactable with htmltools](https://machow.github.io/reactable-py/get-started/extra-htmltools.html)
3 changes: 2 additions & 1 deletion docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ website:
title: "reactable-py"
search:
show-item-context: true
page-navigation: true
navbar:
left:
- text: Get Started
Expand All @@ -30,7 +31,7 @@ website:
# text: Reference
right:
- icon: github
href: https://github.com/machow/bigblock
href: https://github.com/machow/reactable-py
#- icon: rss
# href: blog/index.xml
sidebar:
Expand Down
Binary file added docs/assets/sleep-table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/get-started/index.qmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
title: Get Started
jupyter: python3
aliases:
- ../index.html
---

`reactable` is a Python library for interactively viewing DataFrames. It focuses on three things:
Expand Down
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ include = ["reactable"]

[project]
name = "reactable"
description = "Easily generate information-rich, publication-quality tables from Python."
description = "Interactive tables for Python."
readme = "README.md"
keywords = ["tables"]
license.file = "LICENSE"
Expand Down Expand Up @@ -37,6 +37,7 @@ classifiers = [
]
dependencies = [
"htmltools",
"databackend",
"ipyreact",
"IPython",
"importlib-metadata",
Expand All @@ -48,6 +49,7 @@ requires-python = ">=3.9"

[project.optional-dependencies]
all = [
"reactable[dev]"
]

extra = [
Expand All @@ -59,6 +61,7 @@ dev = [
"quartodoc>=0.7.1; python_version >= '3.9'",
"faicons",
"griffe==0.38.1",
"great-tables",
"mizani",
"pandas",
"polars",
Expand All @@ -73,8 +76,8 @@ dev = [


[project.urls]
homepage = "https://github.com/posit-dev/great-tables"
documentation = "https://posit-dev.github.io/great-tables/"
homepage = "https://github.com/machow/reactable-py"
documentation = "https://machow.github.io/reactable-py"

[tool.flake8]
exclude = ["docs", ".venv", "tests/*"]
Expand Down
107 changes: 102 additions & 5 deletions reactable/_tbl_data.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
from __future__ import annotations

import polars as pl
from .simpleframe import SimpleFrame, SimpleColumn

from datetime import datetime, date, time
from functools import singledispatch
from typing import Union, Literal
from typing import TYPE_CHECKING, Any, Union, Literal
from typing_extensions import TypeAlias

from .simpleframe import SimpleColumn
from polars import Series as PlSeries
from abc import ABC

ColumnLike: TypeAlias = Union[PlSeries, SimpleColumn]

if TYPE_CHECKING:
from polars import DataFrame as PlDataFrame, Series as PlSeries
from pandas import DataFrame as PdDataFrame, Series as PdSeries

else:
from databackend import AbstractBackend

class PlDataFrame(AbstractBackend):
_backends = [("polars", "DataFrame")]

class PdDataFrame(AbstractBackend):
_backends = [("pandas", "DataFrame")]

class PlSeries(AbstractBackend):
_backends = [("polars", "Series")]

class PdSeries(AbstractBackend):
_backends = [("pandas", "Series")]

class DataFrameLike(ABC):
"""Represent a DataFrame"""

class ColumnLike(ABC):
"""Represent a Column"""

DataFrameLike.register(PlDataFrame)
DataFrameLike.register(PdDataFrame)
DataFrameLike.register(SimpleFrame)

ColumnLike.register(PlSeries)
ColumnLike.register(PdSeries)
ColumnLike.register(SimpleColumn)


# col_type -------------------------------------------------------------

WidgetColTypes: TypeAlias = 'None | Literal["numeric", "Date", "character", "factor", "logical"]'

Expand All @@ -22,6 +56,8 @@ def col_type(x: ColumnLike) -> WidgetColTypes:

@col_type.register(PlSeries)
def _(x: PlSeries) -> WidgetColTypes:
import polars as pl

dtype = x.dtype
if dtype.is_numeric():
return "numeric"
Expand All @@ -38,6 +74,11 @@ def _(x: PlSeries) -> WidgetColTypes:
return "UNKNOWN"


@col_type.register(PdSeries)
def _(x: PdSeries) -> WidgetColTypes:
return "UNKNOWN"


@col_type.register(list)
@col_type.register(SimpleColumn)
def _(x: SimpleColumn | list) -> WidgetColTypes:
Expand Down Expand Up @@ -66,3 +107,59 @@ def _peek_type(col: SimpleColumn | list):
return list(types)[0]

return None


# to_dict -------------------------------------------------------------


@singledispatch
def to_dict(data: DataFrameLike) -> "dict[str, list[Any]]":
raise TypeError(f"Unsupported type: {type(data)}")


@to_dict.register
def _(data: PlDataFrame) -> "dict[str, list[Any]]":
return data.to_dict(as_series=False)


@to_dict.register
def _(data: PdDataFrame) -> "dict[str, list[Any]]":
return data.to_dict(orient="list")


@to_dict.register
def _(data: SimpleFrame) -> "dict[str, list[Any]]":
return data.to_dict()


# column_names ---------------------------------------------------------


@singledispatch
def column_names(data: DataFrameLike) -> "list[str]":
raise TypeError(f"Unsupported type: {type(data)}")


@column_names.register
def _(data: PlDataFrame) -> "list[str]":
return data.columns


@column_names.register
def _(data: PdDataFrame) -> "list[str]":
# note that column names don't have to be strings in pandas
names = list(data.columns)
for name in names:
if not isinstance(name, str):
raise TypeError(
"Column names must be strings, received:\n"
f"\n * type: {type(name)}"
f"\n * value: {name}"
)

return names


@column_names.register
def _(data: SimpleFrame) -> "list[str]":
return list(data.columns)
Loading
Loading