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.
@@ -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"]