mm-streamlit-table 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_streamlit_table-0.0.2/LICENSE +21 -0
- mm_streamlit_table-0.0.2/PKG-INFO +130 -0
- mm_streamlit_table-0.0.2/README.md +114 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table/__init__.py +144 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table/frontend/build/assets/index-jqR6JZkW.js +85 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table/frontend/build/assets/index-jqR6JZkW.js.map +1 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table/frontend/build/index.html +12 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table/theme.py +59 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table.egg-info/PKG-INFO +130 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table.egg-info/SOURCES.txt +13 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table.egg-info/dependency_links.txt +1 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table.egg-info/requires.txt +2 -0
- mm_streamlit_table-0.0.2/mm_streamlit_table.egg-info/top_level.txt +1 -0
- mm_streamlit_table-0.0.2/pyproject.toml +27 -0
- mm_streamlit_table-0.0.2/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EPEC Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mm-streamlit-table
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Themeable Streamlit table component (React + TanStack) with stable in-component state.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: streamlit,table,datagrid,tanstack,react
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: streamlit>=1.30
|
|
14
|
+
Requires-Dist: pandas>=2.0
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# mm-streamlit-table
|
|
18
|
+
|
|
19
|
+
An opinionated, themeable Streamlit table component built with React +
|
|
20
|
+
[TanStack Table](https://tanstack.com/table). MIT-licensed and dependency-light.
|
|
21
|
+
|
|
22
|
+
Why this exists: most Streamlit table components either rerun the entire page
|
|
23
|
+
on every interaction (selection loops, lost filter state) or are styled in a
|
|
24
|
+
way that resists customisation. `mm-streamlit-table` keeps UI state inside
|
|
25
|
+
the component and only emits **semantic events** back to Streamlit.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **No rerun loops.** Sort, filter, expand, and resize are local. Streamlit
|
|
30
|
+
only hears about events you care about (selection, action clicks).
|
|
31
|
+
- **Themeable from Python** via a small set of generic design tokens that
|
|
32
|
+
map to CSS custom properties. Drop in your own tokens or override the
|
|
33
|
+
CSS variables directly.
|
|
34
|
+
- **Real React renderers** — bring your own cell components (tags, JSON
|
|
35
|
+
chips, copy-to-clipboard, badges, anything you write in TSX).
|
|
36
|
+
- **No enterprise license.** Filtering features that AG Grid puts behind a
|
|
37
|
+
paywall (multi-filter, set filter) are built in.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install mm-streamlit-table
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import pandas as pd
|
|
49
|
+
import streamlit as st
|
|
50
|
+
from mm_streamlit_table import mm_table
|
|
51
|
+
|
|
52
|
+
df = pd.DataFrame(
|
|
53
|
+
{
|
|
54
|
+
"id": [1, 2, 3],
|
|
55
|
+
"name": ["Alpha", "Bravo", "Charlie"],
|
|
56
|
+
"score": [42, 17, 99],
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = mm_table(df, selection="single", row_id_column="id", key="demo")
|
|
61
|
+
|
|
62
|
+
if result and result.get("event") == "select":
|
|
63
|
+
st.write("You picked:", result["row_ids"])
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Theming
|
|
67
|
+
|
|
68
|
+
Pass any subset of these tokens via `theme_tokens=`; missing keys fall back
|
|
69
|
+
to neutral defaults:
|
|
70
|
+
|
|
71
|
+
| Token | Purpose |
|
|
72
|
+
| ---------------- | ----------------------------- |
|
|
73
|
+
| `bg` | Table background |
|
|
74
|
+
| `bg_alt` | Striped row background |
|
|
75
|
+
| `text` | Body text colour |
|
|
76
|
+
| `text_muted` | Empty-state and hints |
|
|
77
|
+
| `border` | Outer border |
|
|
78
|
+
| `border_subtle` | Row dividers |
|
|
79
|
+
| `header_bg` | Header row background |
|
|
80
|
+
| `header_text` | Header row text |
|
|
81
|
+
| `accent` | Accent / selection ring |
|
|
82
|
+
| `selected_bg` | Selected row background |
|
|
83
|
+
| `row_hover_bg` | Hover background |
|
|
84
|
+
| `font` | Body font stack |
|
|
85
|
+
| `font_mono` | Header / mono font stack |
|
|
86
|
+
|
|
87
|
+
## Repo layout
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
mm_streamlit_table/ ← Python package
|
|
91
|
+
__init__.py ← declare_component + public API
|
|
92
|
+
frontend/build/ ← built JS bundle (generated)
|
|
93
|
+
frontend/ ← React/TS source
|
|
94
|
+
src/
|
|
95
|
+
package.json
|
|
96
|
+
vite.config.ts
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Dev loop
|
|
100
|
+
|
|
101
|
+
In one terminal:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd frontend
|
|
105
|
+
npm install
|
|
106
|
+
npm run dev # serves on http://localhost:3001
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In another terminal, in your app:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install -e ../mm-streamlit-table
|
|
113
|
+
export MM_STREAMLIT_TABLE_DEV=1 # or set MM_STREAMLIT_TABLE_DEV=1 on Windows
|
|
114
|
+
streamlit run your_app.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Edit a `.tsx` file → browser updates. No Streamlit restart needed.
|
|
118
|
+
|
|
119
|
+
## Release build
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cd frontend
|
|
123
|
+
npm run build # writes to ../mm_streamlit_table/frontend/build
|
|
124
|
+
cd ..
|
|
125
|
+
python -m build # builds the wheel
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# mm-streamlit-table
|
|
2
|
+
|
|
3
|
+
An opinionated, themeable Streamlit table component built with React +
|
|
4
|
+
[TanStack Table](https://tanstack.com/table). MIT-licensed and dependency-light.
|
|
5
|
+
|
|
6
|
+
Why this exists: most Streamlit table components either rerun the entire page
|
|
7
|
+
on every interaction (selection loops, lost filter state) or are styled in a
|
|
8
|
+
way that resists customisation. `mm-streamlit-table` keeps UI state inside
|
|
9
|
+
the component and only emits **semantic events** back to Streamlit.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **No rerun loops.** Sort, filter, expand, and resize are local. Streamlit
|
|
14
|
+
only hears about events you care about (selection, action clicks).
|
|
15
|
+
- **Themeable from Python** via a small set of generic design tokens that
|
|
16
|
+
map to CSS custom properties. Drop in your own tokens or override the
|
|
17
|
+
CSS variables directly.
|
|
18
|
+
- **Real React renderers** — bring your own cell components (tags, JSON
|
|
19
|
+
chips, copy-to-clipboard, badges, anything you write in TSX).
|
|
20
|
+
- **No enterprise license.** Filtering features that AG Grid puts behind a
|
|
21
|
+
paywall (multi-filter, set filter) are built in.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install mm-streamlit-table
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pandas as pd
|
|
33
|
+
import streamlit as st
|
|
34
|
+
from mm_streamlit_table import mm_table
|
|
35
|
+
|
|
36
|
+
df = pd.DataFrame(
|
|
37
|
+
{
|
|
38
|
+
"id": [1, 2, 3],
|
|
39
|
+
"name": ["Alpha", "Bravo", "Charlie"],
|
|
40
|
+
"score": [42, 17, 99],
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
result = mm_table(df, selection="single", row_id_column="id", key="demo")
|
|
45
|
+
|
|
46
|
+
if result and result.get("event") == "select":
|
|
47
|
+
st.write("You picked:", result["row_ids"])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Theming
|
|
51
|
+
|
|
52
|
+
Pass any subset of these tokens via `theme_tokens=`; missing keys fall back
|
|
53
|
+
to neutral defaults:
|
|
54
|
+
|
|
55
|
+
| Token | Purpose |
|
|
56
|
+
| ---------------- | ----------------------------- |
|
|
57
|
+
| `bg` | Table background |
|
|
58
|
+
| `bg_alt` | Striped row background |
|
|
59
|
+
| `text` | Body text colour |
|
|
60
|
+
| `text_muted` | Empty-state and hints |
|
|
61
|
+
| `border` | Outer border |
|
|
62
|
+
| `border_subtle` | Row dividers |
|
|
63
|
+
| `header_bg` | Header row background |
|
|
64
|
+
| `header_text` | Header row text |
|
|
65
|
+
| `accent` | Accent / selection ring |
|
|
66
|
+
| `selected_bg` | Selected row background |
|
|
67
|
+
| `row_hover_bg` | Hover background |
|
|
68
|
+
| `font` | Body font stack |
|
|
69
|
+
| `font_mono` | Header / mono font stack |
|
|
70
|
+
|
|
71
|
+
## Repo layout
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
mm_streamlit_table/ ← Python package
|
|
75
|
+
__init__.py ← declare_component + public API
|
|
76
|
+
frontend/build/ ← built JS bundle (generated)
|
|
77
|
+
frontend/ ← React/TS source
|
|
78
|
+
src/
|
|
79
|
+
package.json
|
|
80
|
+
vite.config.ts
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Dev loop
|
|
84
|
+
|
|
85
|
+
In one terminal:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd frontend
|
|
89
|
+
npm install
|
|
90
|
+
npm run dev # serves on http://localhost:3001
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
In another terminal, in your app:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install -e ../mm-streamlit-table
|
|
97
|
+
export MM_STREAMLIT_TABLE_DEV=1 # or set MM_STREAMLIT_TABLE_DEV=1 on Windows
|
|
98
|
+
streamlit run your_app.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Edit a `.tsx` file → browser updates. No Streamlit restart needed.
|
|
102
|
+
|
|
103
|
+
## Release build
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd frontend
|
|
107
|
+
npm run build # writes to ../mm_streamlit_table/frontend/build
|
|
108
|
+
cd ..
|
|
109
|
+
python -m build # builds the wheel
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""mm-streamlit-table — public Python API.
|
|
2
|
+
|
|
3
|
+
Two modes:
|
|
4
|
+
- Dev: set MM_STREAMLIT_TABLE_DEV=1 and run `npm run dev` in frontend/.
|
|
5
|
+
The component points at http://localhost:3001 and hot-reloads.
|
|
6
|
+
- Release: serves the built bundle from mm_streamlit_table/frontend/build/.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Iterable, Literal, Optional
|
|
14
|
+
|
|
15
|
+
import pandas as pd
|
|
16
|
+
import streamlit.components.v1 as components
|
|
17
|
+
|
|
18
|
+
from .theme import ThemeTokens
|
|
19
|
+
|
|
20
|
+
_DEV = os.environ.get("MM_STREAMLIT_TABLE_DEV", "").strip().lower() in {"1", "true", "yes", "on"}
|
|
21
|
+
_DEV_URL = os.environ.get("MM_STREAMLIT_TABLE_DEV_URL", "http://localhost:3001")
|
|
22
|
+
|
|
23
|
+
if _DEV:
|
|
24
|
+
_component_func = components.declare_component("mm_streamlit_table", url=_DEV_URL)
|
|
25
|
+
else:
|
|
26
|
+
_build_dir = Path(__file__).parent / "frontend" / "build"
|
|
27
|
+
_component_func = components.declare_component("mm_streamlit_table", path=str(_build_dir))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
SelectionMode = Literal["none", "single", "multi"]
|
|
31
|
+
ColumnType = Literal["text", "number", "date", "set", "multi"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _infer_column_type(series: pd.Series) -> ColumnType:
|
|
35
|
+
"""Map a pandas dtype to one of the component's filter column types.
|
|
36
|
+
|
|
37
|
+
Object columns default to "text" — set filters often produce huge value
|
|
38
|
+
lists on free-form text, so callers opt in with column_types overrides.
|
|
39
|
+
"""
|
|
40
|
+
dtype = series.dtype
|
|
41
|
+
if pd.api.types.is_bool_dtype(dtype):
|
|
42
|
+
return "set"
|
|
43
|
+
if pd.api.types.is_numeric_dtype(dtype):
|
|
44
|
+
return "number"
|
|
45
|
+
if pd.api.types.is_datetime64_any_dtype(dtype):
|
|
46
|
+
return "date"
|
|
47
|
+
return "text"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def mm_table(
|
|
51
|
+
df: pd.DataFrame,
|
|
52
|
+
*,
|
|
53
|
+
selection: SelectionMode = "none",
|
|
54
|
+
row_id_column: Optional[str] = None,
|
|
55
|
+
default_selected_ids: Optional[Iterable[Any]] = None,
|
|
56
|
+
column_types: Optional[dict[str, ColumnType]] = None,
|
|
57
|
+
cell_renderers: Optional[dict[str, str]] = None,
|
|
58
|
+
theme_tokens: Optional[ThemeTokens] = None,
|
|
59
|
+
height: int = 400,
|
|
60
|
+
show_quick_filter: bool = True,
|
|
61
|
+
quick_filter_placeholder: str = "Filter…",
|
|
62
|
+
key: Optional[str] = None,
|
|
63
|
+
) -> Optional[dict[str, Any]]:
|
|
64
|
+
"""Render a DataFrame as an interactive table.
|
|
65
|
+
|
|
66
|
+
Returns None on most reruns. Returns a dict only when the user produces a
|
|
67
|
+
semantic event (e.g. selection change):
|
|
68
|
+
|
|
69
|
+
{"event": "select", "row_ids": [...], "rows": [...]}
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
df: source data.
|
|
73
|
+
selection: "none", "single", or "multi".
|
|
74
|
+
row_id_column: column whose values identify rows. Defaults to the
|
|
75
|
+
DataFrame index.
|
|
76
|
+
cell_renderers: {column_name: renderer_id}. Built-in ids:
|
|
77
|
+
"tag_list", "json_object", "copy_link". (Wired in later steps.)
|
|
78
|
+
theme_tokens: design tokens applied as CSS variables. See README
|
|
79
|
+
for the supported keys; any subset works, missing keys fall back
|
|
80
|
+
to neutral defaults.
|
|
81
|
+
height: pixel height of the table viewport.
|
|
82
|
+
key: stable Streamlit key.
|
|
83
|
+
"""
|
|
84
|
+
if df is None:
|
|
85
|
+
df = pd.DataFrame()
|
|
86
|
+
|
|
87
|
+
if row_id_column is not None and row_id_column not in df.columns:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"row_id_column '{row_id_column}' not in columns: {list(df.columns)}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
rows = df.to_dict(orient="records")
|
|
93
|
+
if row_id_column is None:
|
|
94
|
+
row_ids: list[Any] = [_jsonable(i) for i in df.index.tolist()]
|
|
95
|
+
else:
|
|
96
|
+
row_ids = [_jsonable(r.get(row_id_column)) for r in rows]
|
|
97
|
+
|
|
98
|
+
overrides = column_types or {}
|
|
99
|
+
columns = []
|
|
100
|
+
for c in df.columns:
|
|
101
|
+
col_id = str(c)
|
|
102
|
+
col_type = overrides.get(col_id) or _infer_column_type(df[c])
|
|
103
|
+
columns.append(
|
|
104
|
+
{"id": col_id, "header": col_id.replace("_", " ").title(), "type": col_type}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return _component_func(
|
|
108
|
+
rows=[_jsonable_row(r) for r in rows],
|
|
109
|
+
row_ids=row_ids,
|
|
110
|
+
columns=columns,
|
|
111
|
+
selection=selection,
|
|
112
|
+
default_selected_ids=[_jsonable(x) for x in (default_selected_ids or [])],
|
|
113
|
+
cell_renderers=cell_renderers or {},
|
|
114
|
+
theme_tokens=theme_tokens or {},
|
|
115
|
+
height=int(height),
|
|
116
|
+
show_quick_filter=bool(show_quick_filter),
|
|
117
|
+
quick_filter_placeholder=str(quick_filter_placeholder),
|
|
118
|
+
key=key,
|
|
119
|
+
default=None,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _jsonable(v: Any) -> Any:
|
|
124
|
+
if v is None:
|
|
125
|
+
return None
|
|
126
|
+
try:
|
|
127
|
+
if pd.isna(v):
|
|
128
|
+
return None
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
pass
|
|
131
|
+
if isinstance(v, (str, int, float, bool)):
|
|
132
|
+
return v
|
|
133
|
+
if isinstance(v, (list, tuple)):
|
|
134
|
+
return [_jsonable(x) for x in v]
|
|
135
|
+
if isinstance(v, dict):
|
|
136
|
+
return {str(k): _jsonable(val) for k, val in v.items()}
|
|
137
|
+
return str(v)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _jsonable_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
141
|
+
return {str(k): _jsonable(v) for k, v in row.items()}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = ["mm_table", "SelectionMode", "ColumnType", "ThemeTokens"]
|