lhngrid 0.0.1__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.
- lhngrid-0.0.1/LICENSE +1 -0
- lhngrid-0.0.1/MANIFEST.in +2 -0
- lhngrid-0.0.1/PKG-INFO +196 -0
- lhngrid-0.0.1/README.md +175 -0
- lhngrid-0.0.1/lhngrid/__init__.py +270 -0
- lhngrid-0.0.1/lhngrid/_pagination.py +17 -0
- lhngrid-0.0.1/lhngrid/_snowpark.py +660 -0
- lhngrid-0.0.1/lhngrid/frontend/build/index-DFUyeuxJ.js +11 -0
- lhngrid-0.0.1/lhngrid/frontend/build/index-_hash_.css +2 -0
- lhngrid-0.0.1/lhngrid/pyproject.toml +8 -0
- lhngrid-0.0.1/lhngrid.egg-info/PKG-INFO +196 -0
- lhngrid-0.0.1/lhngrid.egg-info/SOURCES.txt +15 -0
- lhngrid-0.0.1/lhngrid.egg-info/dependency_links.txt +1 -0
- lhngrid-0.0.1/lhngrid.egg-info/requires.txt +12 -0
- lhngrid-0.0.1/lhngrid.egg-info/top_level.txt +1 -0
- lhngrid-0.0.1/pyproject.toml +34 -0
- lhngrid-0.0.1/setup.cfg +4 -0
lhngrid-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
YOUR LICENSE HERE
|
lhngrid-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lhngrid
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A custom grid to handle tabular data
|
|
5
|
+
Author-email: Anu <anu@abc.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: streamlit>=1.51
|
|
11
|
+
Provides-Extra: snowpark
|
|
12
|
+
Requires-Dist: snowflake-snowpark-python>=1.5.0; extra == "snowpark"
|
|
13
|
+
Provides-Extra: devel
|
|
14
|
+
Requires-Dist: wheel; extra == "devel"
|
|
15
|
+
Requires-Dist: pytest==7.4.0; extra == "devel"
|
|
16
|
+
Requires-Dist: playwright==1.48.0; extra == "devel"
|
|
17
|
+
Requires-Dist: requests==2.31.0; extra == "devel"
|
|
18
|
+
Requires-Dist: pytest-playwright-snapshot==1.0; extra == "devel"
|
|
19
|
+
Requires-Dist: pytest-rerunfailures==12.0; extra == "devel"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# lhngrid
|
|
23
|
+
|
|
24
|
+
A custom grid to handle tabular data
|
|
25
|
+
|
|
26
|
+
## Installation instructions
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
uv pip install lhngrid
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Development install (editable)
|
|
33
|
+
|
|
34
|
+
When developing this component locally, install it in editable mode so Streamlit picks up code changes without rebuilding a wheel. Run this from the directory that contains `pyproject.toml`:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
uv pip install -e . --force-reinstall
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage instructions
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import streamlit as st
|
|
44
|
+
|
|
45
|
+
from lhngrid import lhngrid
|
|
46
|
+
|
|
47
|
+
TABLE_KEY = "tasks"
|
|
48
|
+
PAGE_INDEX_KEY = f"{TABLE_KEY}_page_index"
|
|
49
|
+
PAGE_SIZE_KEY = f"{TABLE_KEY}_page_size"
|
|
50
|
+
|
|
51
|
+
st.session_state.setdefault(PAGE_INDEX_KEY, 0)
|
|
52
|
+
st.session_state.setdefault(PAGE_SIZE_KEY, 10)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def sync_grid_state(key: str) -> None:
|
|
56
|
+
component = st.session_state.get(key)
|
|
57
|
+
|
|
58
|
+
if component is None:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
st.session_state[PAGE_INDEX_KEY] = component.pageIndex
|
|
62
|
+
st.session_state[PAGE_SIZE_KEY] = component.pageSize
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
value = lhngrid(
|
|
66
|
+
rows=rows,
|
|
67
|
+
columns=columns,
|
|
68
|
+
row_key="id",
|
|
69
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
70
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
71
|
+
theme="streamlit-dataframe",
|
|
72
|
+
show_row_selection=False,
|
|
73
|
+
pagination_position="top-left",
|
|
74
|
+
key=TABLE_KEY,
|
|
75
|
+
on_pageIndex_change=sync_grid_state,
|
|
76
|
+
on_pageSize_change=sync_grid_state,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
st.write(value)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Snowpark DataFrames
|
|
83
|
+
|
|
84
|
+
LHNGrid can also accept a Snowpark DataFrame through the same `rows`
|
|
85
|
+
parameter. Install the optional Snowpark extra when using this path:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
uv pip install "lhngrid[snowpark]"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
When `rows` is a Snowpark DataFrame, LHNGrid queries only the current page,
|
|
92
|
+
applies supported sorting/filtering in Snowpark, and sends page-sized JSON rows
|
|
93
|
+
to the frontend.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
value = lhngrid(
|
|
97
|
+
rows=session.table("TASKS"),
|
|
98
|
+
columns=None, # infer columns from the Snowpark schema
|
|
99
|
+
row_key="ID",
|
|
100
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
101
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
102
|
+
sorting=st.session_state.get(SORTING_KEY, []),
|
|
103
|
+
key=TABLE_KEY,
|
|
104
|
+
on_pageIndex_change=sync_grid_state,
|
|
105
|
+
on_pageSize_change=sync_grid_state,
|
|
106
|
+
on_sorting_change=sync_grid_state,
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Explicit columns remain the source of truth when provided. Use `sourceKey` when
|
|
111
|
+
the Snowpark column name differs from the frontend row key.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
columns = [
|
|
115
|
+
{
|
|
116
|
+
"id": "task",
|
|
117
|
+
"header": "Task",
|
|
118
|
+
"accessorKey": "task",
|
|
119
|
+
"sourceKey": "TASK",
|
|
120
|
+
"type": "text",
|
|
121
|
+
"sortable": True,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "created_at",
|
|
125
|
+
"header": "Created At",
|
|
126
|
+
"accessorKey": "createdAt",
|
|
127
|
+
"sourceKey": "CREATED_AT",
|
|
128
|
+
"type": "date",
|
|
129
|
+
"sortable": True,
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
value = lhngrid(
|
|
134
|
+
rows=session.table("TASKS"),
|
|
135
|
+
columns=columns,
|
|
136
|
+
row_key="ID",
|
|
137
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
138
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
139
|
+
sorting=st.session_state.get(SORTING_KEY, []),
|
|
140
|
+
key=TABLE_KEY,
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Callbacks receive the component key so the handler can read the current
|
|
145
|
+
component value from `st.session_state[key]`. Exposed callbacks are
|
|
146
|
+
`on_pageIndex_change`, `on_pageSize_change`, `on_sorting_change`,
|
|
147
|
+
`on_columnFilters_change`, `on_selectedRowId_change`,
|
|
148
|
+
`on_selectedRowIds_change`, `on_rowOffset_change`, `on_rowLimit_change`, and
|
|
149
|
+
`on_rowAction_change`. The legacy `onIndexChange` parameter still aliases
|
|
150
|
+
`on_pageIndex_change`.
|
|
151
|
+
|
|
152
|
+
### Styling hooks
|
|
153
|
+
|
|
154
|
+
Available themes are `default` and `streamlit-dataframe`. The
|
|
155
|
+
`streamlit-dataframe` theme only changes styling so the grid visually matches
|
|
156
|
+
Streamlit's default DataFrame component; it does not add DataFrame features.
|
|
157
|
+
Set `show_row_selection=False` to hide the checkbox selection column.
|
|
158
|
+
Use `pagination_position` to place the pagination controls. Supported values are
|
|
159
|
+
`top-left`, `top-middle`, `top-right`, `bottom-left`, `bottom-middle`, and
|
|
160
|
+
`bottom-right`. The default is `bottom-right`.
|
|
161
|
+
|
|
162
|
+
The component uses readable, scoped class names so app-level CSS can target the
|
|
163
|
+
grid safely. Common hooks include `lhn-grid`, `lhn-grid-table-container`,
|
|
164
|
+
`lhn-grid-table`, `lhn-grid-header-cell`, `lhn-grid-cell`,
|
|
165
|
+
`lhn-grid-table-row`, `lhn-grid-table-row-selected`,
|
|
166
|
+
`lhn-grid-row-selection-checkbox`, `lhn-grid-badge`,
|
|
167
|
+
`lhn-grid-pagination`, `lhn-grid-page-size-select`, and
|
|
168
|
+
`lhn-grid-pagination-button`.
|
|
169
|
+
|
|
170
|
+
## Build a wheel
|
|
171
|
+
|
|
172
|
+
To package this component for distribution:
|
|
173
|
+
|
|
174
|
+
1. Build the frontend assets (from `lhngrid/frontend`):
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
npm i
|
|
178
|
+
npm run build
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
2. Build the Python wheel using UV (from the project root):
|
|
182
|
+
```sh
|
|
183
|
+
uv build
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
This will create a `dist/` directory containing your wheel. The wheel includes the compiled frontend from `lhngrid/frontend/build`.
|
|
187
|
+
|
|
188
|
+
### Requirements
|
|
189
|
+
|
|
190
|
+
- Python >= 3.10
|
|
191
|
+
- Node.js >= 24 (LTS)
|
|
192
|
+
|
|
193
|
+
### Expected output
|
|
194
|
+
|
|
195
|
+
- `dist/lhngrid-0.0.1-py3-none-any.whl`
|
|
196
|
+
- If you run `uv run --with build python -m build` (without `--wheel`), you’ll also get an sdist: `dist/lhngrid-0.0.1.tar.gz`
|
lhngrid-0.0.1/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# lhngrid
|
|
2
|
+
|
|
3
|
+
A custom grid to handle tabular data
|
|
4
|
+
|
|
5
|
+
## Installation instructions
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
uv pip install lhngrid
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Development install (editable)
|
|
12
|
+
|
|
13
|
+
When developing this component locally, install it in editable mode so Streamlit picks up code changes without rebuilding a wheel. Run this from the directory that contains `pyproject.toml`:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
uv pip install -e . --force-reinstall
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage instructions
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import streamlit as st
|
|
23
|
+
|
|
24
|
+
from lhngrid import lhngrid
|
|
25
|
+
|
|
26
|
+
TABLE_KEY = "tasks"
|
|
27
|
+
PAGE_INDEX_KEY = f"{TABLE_KEY}_page_index"
|
|
28
|
+
PAGE_SIZE_KEY = f"{TABLE_KEY}_page_size"
|
|
29
|
+
|
|
30
|
+
st.session_state.setdefault(PAGE_INDEX_KEY, 0)
|
|
31
|
+
st.session_state.setdefault(PAGE_SIZE_KEY, 10)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sync_grid_state(key: str) -> None:
|
|
35
|
+
component = st.session_state.get(key)
|
|
36
|
+
|
|
37
|
+
if component is None:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
st.session_state[PAGE_INDEX_KEY] = component.pageIndex
|
|
41
|
+
st.session_state[PAGE_SIZE_KEY] = component.pageSize
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
value = lhngrid(
|
|
45
|
+
rows=rows,
|
|
46
|
+
columns=columns,
|
|
47
|
+
row_key="id",
|
|
48
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
49
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
50
|
+
theme="streamlit-dataframe",
|
|
51
|
+
show_row_selection=False,
|
|
52
|
+
pagination_position="top-left",
|
|
53
|
+
key=TABLE_KEY,
|
|
54
|
+
on_pageIndex_change=sync_grid_state,
|
|
55
|
+
on_pageSize_change=sync_grid_state,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
st.write(value)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Snowpark DataFrames
|
|
62
|
+
|
|
63
|
+
LHNGrid can also accept a Snowpark DataFrame through the same `rows`
|
|
64
|
+
parameter. Install the optional Snowpark extra when using this path:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
uv pip install "lhngrid[snowpark]"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
When `rows` is a Snowpark DataFrame, LHNGrid queries only the current page,
|
|
71
|
+
applies supported sorting/filtering in Snowpark, and sends page-sized JSON rows
|
|
72
|
+
to the frontend.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
value = lhngrid(
|
|
76
|
+
rows=session.table("TASKS"),
|
|
77
|
+
columns=None, # infer columns from the Snowpark schema
|
|
78
|
+
row_key="ID",
|
|
79
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
80
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
81
|
+
sorting=st.session_state.get(SORTING_KEY, []),
|
|
82
|
+
key=TABLE_KEY,
|
|
83
|
+
on_pageIndex_change=sync_grid_state,
|
|
84
|
+
on_pageSize_change=sync_grid_state,
|
|
85
|
+
on_sorting_change=sync_grid_state,
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Explicit columns remain the source of truth when provided. Use `sourceKey` when
|
|
90
|
+
the Snowpark column name differs from the frontend row key.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
columns = [
|
|
94
|
+
{
|
|
95
|
+
"id": "task",
|
|
96
|
+
"header": "Task",
|
|
97
|
+
"accessorKey": "task",
|
|
98
|
+
"sourceKey": "TASK",
|
|
99
|
+
"type": "text",
|
|
100
|
+
"sortable": True,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "created_at",
|
|
104
|
+
"header": "Created At",
|
|
105
|
+
"accessorKey": "createdAt",
|
|
106
|
+
"sourceKey": "CREATED_AT",
|
|
107
|
+
"type": "date",
|
|
108
|
+
"sortable": True,
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
value = lhngrid(
|
|
113
|
+
rows=session.table("TASKS"),
|
|
114
|
+
columns=columns,
|
|
115
|
+
row_key="ID",
|
|
116
|
+
page_index=st.session_state[PAGE_INDEX_KEY],
|
|
117
|
+
page_size=st.session_state[PAGE_SIZE_KEY],
|
|
118
|
+
sorting=st.session_state.get(SORTING_KEY, []),
|
|
119
|
+
key=TABLE_KEY,
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Callbacks receive the component key so the handler can read the current
|
|
124
|
+
component value from `st.session_state[key]`. Exposed callbacks are
|
|
125
|
+
`on_pageIndex_change`, `on_pageSize_change`, `on_sorting_change`,
|
|
126
|
+
`on_columnFilters_change`, `on_selectedRowId_change`,
|
|
127
|
+
`on_selectedRowIds_change`, `on_rowOffset_change`, `on_rowLimit_change`, and
|
|
128
|
+
`on_rowAction_change`. The legacy `onIndexChange` parameter still aliases
|
|
129
|
+
`on_pageIndex_change`.
|
|
130
|
+
|
|
131
|
+
### Styling hooks
|
|
132
|
+
|
|
133
|
+
Available themes are `default` and `streamlit-dataframe`. The
|
|
134
|
+
`streamlit-dataframe` theme only changes styling so the grid visually matches
|
|
135
|
+
Streamlit's default DataFrame component; it does not add DataFrame features.
|
|
136
|
+
Set `show_row_selection=False` to hide the checkbox selection column.
|
|
137
|
+
Use `pagination_position` to place the pagination controls. Supported values are
|
|
138
|
+
`top-left`, `top-middle`, `top-right`, `bottom-left`, `bottom-middle`, and
|
|
139
|
+
`bottom-right`. The default is `bottom-right`.
|
|
140
|
+
|
|
141
|
+
The component uses readable, scoped class names so app-level CSS can target the
|
|
142
|
+
grid safely. Common hooks include `lhn-grid`, `lhn-grid-table-container`,
|
|
143
|
+
`lhn-grid-table`, `lhn-grid-header-cell`, `lhn-grid-cell`,
|
|
144
|
+
`lhn-grid-table-row`, `lhn-grid-table-row-selected`,
|
|
145
|
+
`lhn-grid-row-selection-checkbox`, `lhn-grid-badge`,
|
|
146
|
+
`lhn-grid-pagination`, `lhn-grid-page-size-select`, and
|
|
147
|
+
`lhn-grid-pagination-button`.
|
|
148
|
+
|
|
149
|
+
## Build a wheel
|
|
150
|
+
|
|
151
|
+
To package this component for distribution:
|
|
152
|
+
|
|
153
|
+
1. Build the frontend assets (from `lhngrid/frontend`):
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
npm i
|
|
157
|
+
npm run build
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
2. Build the Python wheel using UV (from the project root):
|
|
161
|
+
```sh
|
|
162
|
+
uv build
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This will create a `dist/` directory containing your wheel. The wheel includes the compiled frontend from `lhngrid/frontend/build`.
|
|
166
|
+
|
|
167
|
+
### Requirements
|
|
168
|
+
|
|
169
|
+
- Python >= 3.10
|
|
170
|
+
- Node.js >= 24 (LTS)
|
|
171
|
+
|
|
172
|
+
### Expected output
|
|
173
|
+
|
|
174
|
+
- `dist/lhngrid-0.0.1-py3-none-any.whl`
|
|
175
|
+
- If you run `uv run --with build python -m build` (without `--wheel`), you’ll also get an sdist: `dist/lhngrid-0.0.1.tar.gz`
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Any, Callable, Literal, cast
|
|
6
|
+
|
|
7
|
+
import streamlit as st
|
|
8
|
+
|
|
9
|
+
from ._pagination import normalize_pagination
|
|
10
|
+
from ._snowpark import is_snowpark_dataframe, prepare_snowpark_data
|
|
11
|
+
|
|
12
|
+
_component = st.components.v2.component(
|
|
13
|
+
"lhngrid.lhngrid",
|
|
14
|
+
js="index-*.js",
|
|
15
|
+
css="index-*.css",
|
|
16
|
+
html='<div class="react-root"></div>',
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _noop() -> None:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
GridCallback = Callable[[str], None]
|
|
25
|
+
GridTheme = Literal["default", "streamlit-dataframe"]
|
|
26
|
+
GridPaginationPosition = Literal[
|
|
27
|
+
"top-left",
|
|
28
|
+
"top-middle",
|
|
29
|
+
"top-right",
|
|
30
|
+
"bottom-left",
|
|
31
|
+
"bottom-middle",
|
|
32
|
+
"bottom-right",
|
|
33
|
+
]
|
|
34
|
+
_GRID_THEMES: set[str] = {"default", "streamlit-dataframe"}
|
|
35
|
+
_GRID_PAGINATION_POSITIONS: set[str] = {
|
|
36
|
+
"top-left",
|
|
37
|
+
"top-middle",
|
|
38
|
+
"top-right",
|
|
39
|
+
"bottom-left",
|
|
40
|
+
"bottom-middle",
|
|
41
|
+
"bottom-right",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _bind_callback(callback: GridCallback | None, key: str) -> Callable[[], None]:
|
|
46
|
+
if callback is None:
|
|
47
|
+
return _noop
|
|
48
|
+
|
|
49
|
+
return partial(callback, key)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _normalize_size(value: int | float | str | None) -> str | None:
|
|
53
|
+
if value is None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
if isinstance(value, bool):
|
|
57
|
+
raise TypeError("width and height must be a number, string, or None.")
|
|
58
|
+
|
|
59
|
+
if isinstance(value, (int, float)):
|
|
60
|
+
if value < 0:
|
|
61
|
+
raise ValueError("width and height must be non-negative.")
|
|
62
|
+
|
|
63
|
+
return f"{value}px"
|
|
64
|
+
|
|
65
|
+
if isinstance(value, str):
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
raise TypeError("width and height must be a number, string, or None.")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _normalize_theme(value: str) -> GridTheme:
|
|
72
|
+
if value not in _GRID_THEMES:
|
|
73
|
+
themes = ", ".join(sorted(_GRID_THEMES))
|
|
74
|
+
raise ValueError(f"theme must be one of: {themes}.")
|
|
75
|
+
|
|
76
|
+
return cast(GridTheme, value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _normalize_pagination_position(value: str) -> GridPaginationPosition:
|
|
80
|
+
if value not in _GRID_PAGINATION_POSITIONS:
|
|
81
|
+
positions = ", ".join(sorted(_GRID_PAGINATION_POSITIONS))
|
|
82
|
+
raise ValueError(f"pagination_position must be one of: {positions}.")
|
|
83
|
+
|
|
84
|
+
return cast(GridPaginationPosition, value)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _paginate_rows(
|
|
88
|
+
rows: Sequence[dict[str, Any]],
|
|
89
|
+
page_index: int,
|
|
90
|
+
page_size: int,
|
|
91
|
+
total_rows: int | None,
|
|
92
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int, int]:
|
|
93
|
+
total_rows_value = len(rows) if total_rows is None else max(0, int(total_rows))
|
|
94
|
+
page_index_value, page_size_value, row_offset, row_limit = normalize_pagination(
|
|
95
|
+
page_index,
|
|
96
|
+
page_size,
|
|
97
|
+
total_rows_value,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if total_rows is not None and total_rows_value > len(rows):
|
|
101
|
+
return (
|
|
102
|
+
list(rows),
|
|
103
|
+
page_index_value,
|
|
104
|
+
page_size_value,
|
|
105
|
+
total_rows_value,
|
|
106
|
+
row_offset,
|
|
107
|
+
row_limit,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
page_start = row_offset
|
|
111
|
+
page_end = page_start + row_limit
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
list(rows[page_start:page_end]),
|
|
115
|
+
page_index_value,
|
|
116
|
+
page_size_value,
|
|
117
|
+
total_rows_value,
|
|
118
|
+
row_offset,
|
|
119
|
+
row_limit,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _normalize_sequence_rows(rows: Any) -> Sequence[dict[str, Any]]:
|
|
124
|
+
if isinstance(rows, (str, bytes)) or not isinstance(rows, Sequence):
|
|
125
|
+
raise TypeError(
|
|
126
|
+
"rows must be a sequence of dictionaries or a Snowpark DataFrame."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return cast(Sequence[dict[str, Any]], rows)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def lhngrid(
|
|
133
|
+
*,
|
|
134
|
+
rows: Any,
|
|
135
|
+
columns: list[dict[str, Any]] | None = None,
|
|
136
|
+
row_key: str,
|
|
137
|
+
page_index: int = 0,
|
|
138
|
+
page_size: int = 10,
|
|
139
|
+
total_rows: int | None = None,
|
|
140
|
+
sorting: list[dict[str, Any]] | None = None,
|
|
141
|
+
column_filters: list[dict[str, Any]] | None = None,
|
|
142
|
+
loading: bool = False,
|
|
143
|
+
width: int | float | str | None = None,
|
|
144
|
+
height: int | float | str | None = None,
|
|
145
|
+
page_size_options: Sequence[int] = (5, 10, 25, 50, 100),
|
|
146
|
+
pagination_position: GridPaginationPosition = "bottom-right",
|
|
147
|
+
show_row_selection: bool = True,
|
|
148
|
+
show_actions: bool = False,
|
|
149
|
+
theme: GridTheme = "default",
|
|
150
|
+
key: str | None = None,
|
|
151
|
+
on_pageIndex_change: GridCallback | None = None,
|
|
152
|
+
on_pageSize_change: GridCallback | None = None,
|
|
153
|
+
on_sorting_change: GridCallback | None = None,
|
|
154
|
+
on_columnFilters_change: GridCallback | None = None,
|
|
155
|
+
on_selectedRowId_change: GridCallback | None = None,
|
|
156
|
+
on_selectedRowIds_change: GridCallback | None = None,
|
|
157
|
+
on_rowOffset_change: GridCallback | None = None,
|
|
158
|
+
on_rowLimit_change: GridCallback | None = None,
|
|
159
|
+
on_rowAction_change: GridCallback | None = None,
|
|
160
|
+
onIndexChange: GridCallback | None = None,
|
|
161
|
+
) -> Any:
|
|
162
|
+
"""Render an LHNGrid Streamlit component."""
|
|
163
|
+
if key is None:
|
|
164
|
+
raise ValueError("lhngrid requires a stable key.")
|
|
165
|
+
|
|
166
|
+
page_index_callback = on_pageIndex_change or onIndexChange
|
|
167
|
+
sorting_value = sorting or []
|
|
168
|
+
column_filters_value = column_filters or []
|
|
169
|
+
theme_value = _normalize_theme(theme)
|
|
170
|
+
pagination_position_value = _normalize_pagination_position(pagination_position)
|
|
171
|
+
|
|
172
|
+
if is_snowpark_dataframe(rows):
|
|
173
|
+
prepared_grid_data = prepare_snowpark_data(
|
|
174
|
+
dataframe=rows,
|
|
175
|
+
columns=columns,
|
|
176
|
+
row_key=row_key,
|
|
177
|
+
page_index=page_index,
|
|
178
|
+
page_size=page_size,
|
|
179
|
+
total_rows=total_rows,
|
|
180
|
+
sorting=sorting_value,
|
|
181
|
+
column_filters=column_filters_value,
|
|
182
|
+
)
|
|
183
|
+
paged_rows = prepared_grid_data.rows
|
|
184
|
+
component_columns = prepared_grid_data.columns
|
|
185
|
+
page_index_value = prepared_grid_data.page_index
|
|
186
|
+
page_size_value = prepared_grid_data.page_size
|
|
187
|
+
total_rows_value = prepared_grid_data.total_rows
|
|
188
|
+
row_offset = prepared_grid_data.row_offset
|
|
189
|
+
row_limit = prepared_grid_data.row_limit
|
|
190
|
+
else:
|
|
191
|
+
if columns is None:
|
|
192
|
+
raise ValueError(
|
|
193
|
+
"columns is required when rows is not a Snowpark DataFrame."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
sequence_rows = _normalize_sequence_rows(rows)
|
|
197
|
+
component_columns = columns
|
|
198
|
+
(
|
|
199
|
+
paged_rows,
|
|
200
|
+
page_index_value,
|
|
201
|
+
page_size_value,
|
|
202
|
+
total_rows_value,
|
|
203
|
+
row_offset,
|
|
204
|
+
row_limit,
|
|
205
|
+
) = _paginate_rows(sequence_rows, page_index, page_size, total_rows)
|
|
206
|
+
|
|
207
|
+
page_size_options_value = list(
|
|
208
|
+
dict.fromkeys(
|
|
209
|
+
max(1, int(option))
|
|
210
|
+
for option in (*page_size_options, page_size_value)
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return _component(
|
|
215
|
+
key=key,
|
|
216
|
+
data={
|
|
217
|
+
"rows": paged_rows,
|
|
218
|
+
"columns": component_columns,
|
|
219
|
+
"rowKey": row_key,
|
|
220
|
+
"pageIndex": page_index_value,
|
|
221
|
+
"pageSize": page_size_value,
|
|
222
|
+
"totalRows": total_rows_value,
|
|
223
|
+
"sorting": sorting_value,
|
|
224
|
+
"columnFilters": column_filters_value,
|
|
225
|
+
"loading": loading,
|
|
226
|
+
"width": _normalize_size(width),
|
|
227
|
+
"height": _normalize_size(height),
|
|
228
|
+
"rowOffset": row_offset,
|
|
229
|
+
"rowLimit": row_limit,
|
|
230
|
+
"pageSizeOptions": page_size_options_value,
|
|
231
|
+
"paginationPosition": pagination_position_value,
|
|
232
|
+
"showRowSelection": show_row_selection,
|
|
233
|
+
"showActions": show_actions,
|
|
234
|
+
"theme": theme_value,
|
|
235
|
+
"enabledCallbacks": {
|
|
236
|
+
"pageIndex": page_index_callback is not None,
|
|
237
|
+
"pageSize": on_pageSize_change is not None,
|
|
238
|
+
"sorting": on_sorting_change is not None,
|
|
239
|
+
"columnFilters": on_columnFilters_change is not None,
|
|
240
|
+
"selectedRowId": on_selectedRowId_change is not None,
|
|
241
|
+
"selectedRowIds": on_selectedRowIds_change is not None,
|
|
242
|
+
"rowOffset": on_rowOffset_change is not None,
|
|
243
|
+
"rowLimit": on_rowLimit_change is not None,
|
|
244
|
+
"rowAction": show_actions or on_rowAction_change is not None,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
default={
|
|
248
|
+
"pageIndex": page_index_value,
|
|
249
|
+
"pageSize": page_size_value,
|
|
250
|
+
"sorting": sorting_value,
|
|
251
|
+
"columnFilters": column_filters_value,
|
|
252
|
+
"selectedRowId": None,
|
|
253
|
+
"selectedRowIds": [],
|
|
254
|
+
"rowOffset": row_offset,
|
|
255
|
+
"rowLimit": row_limit,
|
|
256
|
+
"rowAction": None,
|
|
257
|
+
},
|
|
258
|
+
on_pageIndex_change=_bind_callback(page_index_callback, key),
|
|
259
|
+
on_pageSize_change=_bind_callback(on_pageSize_change, key),
|
|
260
|
+
on_sorting_change=_bind_callback(on_sorting_change, key),
|
|
261
|
+
on_columnFilters_change=_bind_callback(on_columnFilters_change, key),
|
|
262
|
+
on_selectedRowId_change=_bind_callback(on_selectedRowId_change, key),
|
|
263
|
+
on_selectedRowIds_change=_bind_callback(on_selectedRowIds_change, key),
|
|
264
|
+
on_rowOffset_change=_bind_callback(on_rowOffset_change, key),
|
|
265
|
+
on_rowLimit_change=_bind_callback(on_rowLimit_change, key),
|
|
266
|
+
on_rowAction_change=_bind_callback(on_rowAction_change, key),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
__all__ = ["lhngrid"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def normalize_pagination(
|
|
5
|
+
page_index: int,
|
|
6
|
+
page_size: int,
|
|
7
|
+
total_rows: int,
|
|
8
|
+
) -> tuple[int, int, int, int]:
|
|
9
|
+
page_index_value = max(0, int(page_index))
|
|
10
|
+
page_size_value = max(1, int(page_size))
|
|
11
|
+
total_rows_value = max(0, int(total_rows))
|
|
12
|
+
max_page_index = max((total_rows_value - 1) // page_size_value, 0)
|
|
13
|
+
page_index_value = min(page_index_value, max_page_index)
|
|
14
|
+
row_offset = page_index_value * page_size_value
|
|
15
|
+
row_limit = page_size_value
|
|
16
|
+
|
|
17
|
+
return page_index_value, page_size_value, row_offset, row_limit
|