Skip to content

Commit ebdb97b

Browse files
schloerkeGordon Shotwell
andauthored
feat: Editable data frame (#1198)
Co-authored-by: Gordon Shotwell <[email protected]>
1 parent 2f75a90 commit ebdb97b

File tree

46 files changed

+2578
-383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2578
-383
lines changed

.github/workflows/pytest.yaml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,20 @@ jobs:
3333
run: |
3434
make check-tests
3535
36-
- name: Type check with pyright
36+
- name: Type check
3737
if: steps.install.outcome == 'success' && (success() || failure())
3838
run: |
3939
make check-types
4040
41-
- name: Lint with flake8
41+
- name: Lint code
4242
if: steps.install.outcome == 'success' && (success() || failure())
4343
run: |
4444
make check-lint
4545
46-
- name: black
46+
- name: Verify code formatting
4747
if: steps.install.outcome == 'success' && (success() || failure())
4848
run: |
49-
make check-black
50-
51-
- name: isort
52-
if: steps.install.outcome == 'success' && (success() || failure())
53-
run: |
54-
make check-isort
49+
make check-format
5550
5651
playwright-shiny:
5752
runs-on: ${{ matrix.os }}

.prettierrc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
{
2-
"organizeImportsSkipDestructiveCodeActions": true
2+
"organizeImportsSkipDestructiveCodeActions": true,
3+
"overrides": [
4+
{
5+
"files": "**/*.scss",
6+
"options": {
7+
"printWidth": 150
8+
}
9+
}
10+
]
311
}

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"posit.shiny-python",
4+
"esbenp.prettier-vscode",
5+
"ms-python.black-formatter",
6+
]
7+
}

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
"editor.formatOnSave": true,
2525
"editor.defaultFormatter": "esbenp.prettier-vscode"
2626
},
27+
"[scss]": {
28+
"editor.formatOnSave": true,
29+
"editor.defaultFormatter": "esbenp.prettier-vscode"
30+
},
2731
"[python]": {
2832
"editor.defaultFormatter": "ms-python.black-formatter",
2933
"editor.formatOnSave": true,

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Breaking Changes
1111

12+
* `@render.data_frame` return values of `DataTable` and `DataGrid` had their parameter of `row_selection: Literal["single", "multiple"]` become deprecated. Please use `mode="row_single"` or `mode="row_multiple"` instead. (#1198)
13+
1214
* The `col_widths` argument of `ui.layout_columns()` now sets the `sm` breakpoint by default, rather than the `md` breakpoint. For example, `col_widths=(12, 6, 6)` is now equivalent to `{"sm": (12, 6, 6)}` rather than `{"md": (12, 6, 6)}`. (#1222)
1315

1416
### New features
1517

18+
* Experimental: `@render.data_frame` return values of `DataTable` and `DataGrid` support `mode="edit"` to enable editing of the data table cells. (#1198)
19+
1620
* `ui.card()` and `ui.value_box()` now take an `id` argument that, when provided, is used to report the full screen state of the card or value box to the server. For example, when using `ui.card(id = "my_card", full_screen = TRUE)` you can determine if the card is currently in full screen mode by reading the boolean value of `input.my_card()["full_screen"]`. (#1215)
1721

1822
* Added support for using `shiny.express` in Quarto Dashboards. (#1217)

Makefile

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
.PHONY: help clean% check% format% docs% lint test pyright playwright% install% testrail% coverage release
1+
# https://www.gnu.org/software/make/manual/make.html#Phony-Targets
2+
# Prerequisites of .PHONY are always interpreted as literal target names, never as patterns (even if they contain ‘%’ characters).
3+
# # .PHONY: help clean% check% format% docs% lint test pyright playwright% install% testrail% coverage release js-*
4+
# Using `FORCE` as prerequisite to _force_ the target to always run; https://www.gnu.org/software/make/manual/make.html#index-FORCE
5+
FORCE: ;
6+
27
.DEFAULT_GOAL := help
38

49
define BROWSER_PYSCRIPT
@@ -23,114 +28,157 @@ export PRINT_HELP_PYSCRIPT
2328

2429
BROWSER := python -c "$$BROWSER_PYSCRIPT"
2530

26-
help:
31+
help: FORCE
2732
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
2833

2934
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
3035

31-
clean-build: ## remove build artifacts
36+
# Remove build artifacts
37+
clean-build: FORCE
3238
rm -fr build/
3339
rm -fr dist/
3440
rm -fr .eggs/
3541
find . -name '*.egg-info' -exec rm -fr {} +
3642
find . -name '*.egg' -exec rm -f {} +
3743

38-
clean-pyc: ## remove Python file artifacts
44+
# Remove Python file artifacts
45+
clean-pyc: FORCE
3946
find . -name '*.pyc' -exec rm -f {} +
4047
find . -name '*.pyo' -exec rm -f {} +
4148
find . -name '*~' -exec rm -f {} +
4249
find . -name '__pycache__' -exec rm -fr {} +
4350

44-
clean-test: ## remove test and coverage artifacts
51+
# Remove test and coverage artifacts
52+
clean-test: FORCE
4553
rm -fr .tox/
4654
rm -f .coverage
4755
rm -fr htmlcov/
4856
rm -fr .pytest_cache
4957
rm -rf typings/
5058

59+
typings/appdirs:
60+
echo "Creating appdirs stubs"
61+
pyright --createstub appdirs
62+
typings/folium:
63+
echo "Creating folium stubs"
64+
pyright --createstub folium
5165
typings/uvicorn:
66+
echo "Creating uvicorn stubs"
5267
pyright --createstub uvicorn
68+
typings/seaborn:
69+
echo "Creating seaborn stubs"
70+
pyright --createstub seaborn
5371

54-
typings/matplotlib/__init__.pyi: ## grab type stubs from GitHub
72+
typings/matplotlib/__init__.pyi:
73+
echo "Creating matplotlib stubs"
5574
mkdir -p typings
5675
git clone --depth 1 https://github.com/microsoft/python-type-stubs typings/python-type-stubs
5776
mv typings/python-type-stubs/stubs/matplotlib typings/
5877
rm -rf typings/python-type-stubs
5978

60-
typings/seaborn:
61-
pyright --createstub seaborn
79+
pyright-typings: typings/appdirs typings/folium typings/uvicorn typings/seaborn typings/matplotlib/__init__.pyi
6280

6381
check: check-format check-lint check-types check-tests ## check code, style, types, and test (basic CI)
6482
check-fix: format check-lint check-types check-tests ## check and format code, style, types, and test
6583
check-format: check-black check-isort
66-
check-lint:
67-
@echo "-------- Checking style with flake8 --------"
84+
check-lint: check-flake8
85+
check-types: check-pyright
86+
check-tests: check-pytest
87+
88+
check-flake8: FORCE
89+
@echo "-------- Checking style with flake8 ---------"
6890
flake8 --show-source .
69-
check-black:
70-
@echo "-------- Checking code with black --------"
91+
check-black: FORCE
92+
@echo "-------- Checking code with black -----------"
7193
black --check .
72-
check-isort:
73-
@echo "-------- Sorting imports with isort --------"
94+
check-isort: FORCE
95+
@echo "-------- Sorting imports with isort ---------"
7496
isort --check-only --diff .
75-
check-types: typings/uvicorn typings/matplotlib/__init__.pyi typings/seaborn
97+
check-pyright: pyright-typings
7698
@echo "-------- Checking types with pyright --------"
7799
pyright
78-
check-tests:
79-
@echo "-------- Running tests with pytest --------"
100+
check-pytest: FORCE
101+
@echo "-------- Running tests with pytest ----------"
80102
python3 tests/pytest/asyncio_prevent.py
81103
pytest
82104

83-
pyright: check-types ## check types with pyright
84-
lint: check-lint ## check style with flake8
105+
# Check types with pyright
106+
pyright: check-types
107+
# Check style with flake8
108+
lint: check-lint
85109
test: check-tests ## check tests quickly with the default Python
86110

87111
format: format-black format-isort ## format code with black and isort
88-
format-black:
112+
format-black: FORCE
89113
@echo "-------- Formatting code with black --------"
90114
black .
91-
format-isort:
115+
format-isort: FORCE
92116
@echo "-------- Sorting imports with isort --------"
93117
isort .
94118

95-
docs: ## docs: build docs with quartodoc
96-
@echo "-------- Building docs with quartodoc --------"
119+
docs: FORCE ## docs: build docs with quartodoc
120+
@echo "-------- Building docs with quartodoc ------"
97121
@cd docs && make quartodoc
98122

99-
docs-preview: ## docs: preview docs in browser
123+
docs-preview: FORCE ## docs: preview docs in browser
100124
@echo "-------- Previewing docs in browser --------"
101125
@cd docs && make serve
102126

127+
128+
install-npm: FORCE
129+
$(if $(shell which npm), @echo -n, $(error Please install node.js and npm first. See https://nodejs.org/en/download/ for instructions.))
130+
js/node_modules: install-npm
131+
@echo "-------- Installing node_modules -----------"
132+
@cd js && npm install
133+
js-build: js/node_modules ## Build JS assets
134+
@echo "-------- Building JS assets ----------------"
135+
@cd js && npm run build
136+
js-watch: js/node_modules
137+
@echo "-------- Continuously building JS assets ---"
138+
@cd js && npm run watch
139+
js-watch-fast: js/node_modules ## Continuously build JS assets (development)
140+
@echo "-------- Previewing docs in browser --------"
141+
@cd js && npm run watch-fast
142+
clean-js: FORCE
143+
@echo "-------- Removing js/node_modules ----------"
144+
rm -rf js/node_modules
145+
103146
# Default `SUB_FILE` to empty
104147
SUB_FILE:=
105148

106-
install-playwright:
149+
install-playwright: FORCE
107150
playwright install --with-deps
108151

109-
install-trcli:
110-
which trcli || pip install trcli
152+
install-trcli: FORCE
153+
$(if $(shell which trcli), @echo -n, $(shell pip install trcli))
111154

112-
install-rsconnect: ## install the main version of rsconnect till pypi version supports shiny express
155+
# Installs the main version of rsconnect till pypi version supports shiny express
156+
install-rsconnect: FORCE
113157
pip install git+https://github.com/rstudio/rsconnect-python.git#egg=rsconnect-python
114158

115-
playwright-shiny: install-playwright ## end-to-end tests with playwright
159+
# end-to-end tests with playwright; (SUB_FILE="" within tests/playwright/shiny/)
160+
playwright-shiny: install-playwright
116161
pytest tests/playwright/shiny/$(SUB_FILE)
117162

118-
playwright-deploys: install-playwright install-rsconnect ## end-to-end tests on examples with playwright
163+
# end-to-end tests on deployed apps with playwright; (SUB_FILE="" within tests/playwright/deploys/)
164+
playwright-deploys: install-playwright install-rsconnect
119165
pytest tests/playwright/deploys/$(SUB_FILE)
120166

121-
playwright-examples: install-playwright ## end-to-end tests on examples with playwright
167+
# end-to-end tests on all py-shiny examples with playwright; (SUB_FILE="" within tests/playwright/examples/)
168+
playwright-examples: install-playwright
122169
pytest tests/playwright/examples/$(SUB_FILE)
123170

124-
playwright-debug: install-playwright ## All end-to-end tests, chrome only, headed
171+
playwright-debug: install-playwright ## All end-to-end tests, chrome only, headed; (SUB_FILE="" within tests/playwright/)
125172
pytest -c tests/playwright/playwright-pytest.ini tests/playwright/$(SUB_FILE)
126173

127174
playwright-show-trace: ## Show trace of failed tests
128175
npx playwright show-trace test-results/*/trace.zip
129176

130-
testrail-junit: install-playwright install-trcli ## end-to-end tests with playwright and generate junit report
177+
# end-to-end tests with playwright and generate junit report
178+
testrail-junit: install-playwright install-trcli
131179
pytest tests/playwright/shiny/$(SUB_FILE) --junitxml=report.xml
132180

133-
coverage: ## check combined code coverage (must run e2e last)
181+
coverage: FORCE ## check combined code coverage (must run e2e last)
134182
pytest --cov-report term-missing --cov=shiny tests/pytest/ tests/playwright/shiny/$(SUB_FILE)
135183
coverage html
136184
$(BROWSER) htmlcov/index.html
@@ -151,9 +199,9 @@ install: dist
151199
pip uninstall -y shiny
152200
python3 -m pip install dist/shiny*.whl
153201

154-
install-deps: ## install dependencies
202+
install-deps: FORCE ## install dependencies
155203
pip install -e ".[dev,test]" --upgrade
156204

157205
# ## If caching is ever used, we could run:
158-
# install-deps: ## install latest dependencies
206+
# install-deps: FORCE ## install latest dependencies
159207
# pip install --editable ".[dev,test]" --upgrade --upgrade-strategy eager

examples/dataframe/app.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ def app_ui(req):
1818
ui.input_select(
1919
"selection_mode",
2020
"Selection mode",
21-
{"none": "(None)", "single": "Single", "multiple": "Multiple"},
21+
{
22+
"none": "(None)",
23+
"single_row": "Single",
24+
"multiple_row": "Multiple",
25+
},
2226
selected="multiple",
2327
),
28+
ui.input_switch("editable", "Edit", False),
2429
ui.input_switch("filters", "Filters", True),
2530
ui.input_switch("gridstyle", "Grid", True),
2631
ui.input_switch("fullwidth", "Take full width", True),
@@ -61,6 +66,12 @@ def server(input: Inputs, output: Outputs, session: Session):
6166
def update_df():
6267
return df.set(sns.load_dataset(req(input.dataset())))
6368

69+
@reactive.calc
70+
def selection_mode():
71+
if input.editable():
72+
return "edit"
73+
return input.selection_mode()
74+
6475
@render.data_frame
6576
def grid():
6677
height = 350
@@ -71,15 +82,15 @@ def grid():
7182
width=width,
7283
height=height,
7384
filters=input.filters(),
74-
row_selection_mode=input.selection_mode(),
85+
mode=selection_mode(),
7586
)
7687
else:
7788
return render.DataTable(
7889
df(),
7990
width=width,
8091
height=height,
8192
filters=input.filters(),
82-
row_selection_mode=input.selection_mode(),
93+
mode=selection_mode(),
8394
)
8495

8596
@reactive.effect
@@ -92,10 +103,10 @@ def handle_edit():
92103

93104
@render.text
94105
def detail():
95-
selected_rows = input.grid_selected_rows() or ()
106+
selected_rows = grid.input_selected_rows() or ()
96107
if len(selected_rows) > 0:
97108
# "split", "records", "index", "columns", "values", "table"
98-
return df().iloc[list(input.grid_selected_rows())]
109+
return df().iloc[list(grid.input_selected_rows())]
99110

100111

101112
app = App(app_ui, server)

js/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
"plugin:react/recommended",
1010
"plugin:@typescript-eslint/eslint-recommended",
1111
"plugin:@typescript-eslint/recommended",
12+
"plugin:react-hooks/recommended",
1213
],
1314
ignorePatterns: ["dist/*"],
1415
overrides: [],

js/build.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import { BuildOptions, build } from "esbuild";
22
import { sassPlugin } from "esbuild-sass-plugin";
33
import * as fs from "node:fs/promises";
44

5+
let minify = true;
6+
process.argv.forEach((val, index) => {
7+
if (val === "--minify=false") {
8+
console.log("Disabling minification");
9+
minify = false;
10+
}
11+
});
12+
513
const outDir = "../shiny/www/shared/py-shiny";
614

715
async function bundle_helper(
@@ -11,7 +19,7 @@ async function bundle_helper(
1119
const result = await build({
1220
format: "esm",
1321
bundle: true,
14-
minify: true,
22+
minify: minify,
1523
sourcemap: true,
1624
metafile: false,
1725
outdir: outDir,

0 commit comments

Comments
 (0)