Skip to content

Commit e0fd6a3

Browse files
authored
Merge branch 'main' into deprecate-well-panel
2 parents 331a2c6 + e51ffbb commit e0fd6a3

File tree

17 files changed

+238
-100
lines changed

17 files changed

+238
-100
lines changed

.github/py-shiny/setup/action.yaml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
python-version:
55
description: 'Python version to use'
66
required: false
7-
default: "3.12"
7+
default: "3.13"
88
runs:
99
using: "composite"
1010
steps:
@@ -39,12 +39,6 @@ runs:
3939
run: |
4040
make ci-install-wheel
4141
42-
- name: Install backports.tarfile
43-
if: ${{ startsWith(inputs.python-version, '3.8') }}
44-
shell: bash
45-
run: |
46-
uv pip install backports.tarfile
47-
4842
- name: Pip list
4943
shell: bash
5044
run: |

.github/workflows/build-docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.12"]
18+
python-version: ["3.13"]
1919
fail-fast: false
2020

2121
steps:

.github/workflows/pytest.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
matrix:
1919
# "3.10" must be a string; otherwise it is interpreted as 3.1.
20-
python-version: ["3.12", "3.11", "3.10", "3.9"]
20+
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
2121
os: [ubuntu-latest, windows-latest, macOS-latest]
2222
exclude:
2323
- python-version: ${{ github.event.pull_request.draft && '3.11' }}
@@ -73,7 +73,7 @@ jobs:
7373
with:
7474
fetch-depth: 0
7575
- name: "Set up Python 3.10"
76-
uses: actions/setup-python@v4
76+
uses: actions/setup-python@v5
7777
with:
7878
python-version: "3.10"
7979
- name: Install dependencies
@@ -107,7 +107,7 @@ jobs:
107107
runs-on: ubuntu-latest
108108
strategy:
109109
matrix:
110-
python-version: ["3.12", "3.11", "3.10", "3.9"]
110+
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
111111
browser: ["chromium", "firefox", "webkit"]
112112
exclude:
113113
- python-version: ${{ github.event.pull_request.draft && '3.11' }}
@@ -117,8 +117,6 @@ jobs:
117117
- browser: ${{ github.event.pull_request.draft && 'webkit' }}
118118
# There are many unexplained tests that fail on webkit w/ python 3.8, 3.9
119119
# Given the more recent versions of python work, we will exclude this combination
120-
- browser: "webkit"
121-
python-version: "3.8"
122120
- browser: "webkit"
123121
python-version: "3.9"
124122
fail-fast: false
@@ -157,7 +155,7 @@ jobs:
157155
runs-on: ubuntu-latest
158156
strategy:
159157
matrix:
160-
python-version: ["3.12", "3.11", "3.10", "3.9"]
158+
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
161159
browser: ["chromium", "firefox", "webkit"]
162160
exclude:
163161
- python-version: ${{ github.event.pull_request.draft && '3.11' }}

CHANGELOG.md

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

1010
### New features
1111

12+
* Added support for python 3.13. (#1711)
13+
1214
* `ui.sidebar()` is now interactively resizable. (#2020)
1315

1416
* `ui.update_*()` functions now accept `ui.TagChild` (i.e., HTML) as input to the `label` and `icon` arguments. (#2020)
@@ -31,6 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3133

3234
* Fix missing session when trying to display an error duing bookmarking. (#1984)
3335

36+
* Fixed `set()` method of `InputSelectize` controller so it clears existing selections before applying new values. (#2024)
37+
3438
### Deprecations
3539

3640
* `ui.panel_well()` is deprecated in favor of `ui.card()`. (#2038)

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ classifiers = [
2626
"Programming Language :: Python :: 3.10",
2727
"Programming Language :: Python :: 3.11",
2828
"Programming Language :: Python :: 3.12",
29+
"Programming Language :: Python :: 3.13",
2930
]
3031
dependencies = [
3132
"typing-extensions>=4.10.0",
@@ -54,8 +55,8 @@ theme = ["libsass>=0.23.0", "brand_yml>=0.1.0"]
5455
test = [
5556
"pytest>=6.2.4",
5657
"pytest-asyncio>=0.17.2",
57-
"pytest-playwright>=0.3.0",
58-
"playwright>=1.43.0",
58+
"pytest-playwright>=0.5.2",
59+
"playwright>=1.48.0",
5960
"pytest-xdist",
6061
"pytest-timeout",
6162
"pytest-rerunfailures",
@@ -86,7 +87,6 @@ test = [
8687
"faicons",
8788
"ridgeplot",
8889
"great_tables",
89-
"modin[all]",
9090
"polars",
9191
"dask[dataframe]",
9292
"pyarrow",

shiny/_datastructures.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ class PriorityQueueFIFO(Generic[T]):
1515
"""
1616

1717
def __init__(self) -> None:
18-
# Using Tuple instead of tuple because in Python 3.8 and earlier, tuple isn't
19-
# generic
2018
self._pq: PriorityQueue[tuple[int, int, T]] = PriorityQueue()
2119
self._counter: int = 0
2220

shiny/_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,14 @@ def sort_keys_length(x: dict[str, T], descending: bool = False) -> dict[str, T]:
7272

7373

7474
def guess_mime_type(
75-
url: "str | os.PathLike[str]",
75+
url: str | os.PathLike[str],
7676
default: str = "application/octet-stream",
7777
strict: bool = True,
7878
) -> str:
7979
"""
8080
Guess the MIME type of a file. This is a wrapper for mimetypes.guess_type, but it
8181
only returns the type (and not encoding), and it allows a default value.
8282
"""
83-
# Note that in the parameters above, "os.PathLike[str]" is in quotes to avoid
84-
# "TypeError: 'ABCMeta' object is not subscriptable", in Python<=3.8.
8583
if url:
8684
# Work around issue #1601, some installations of Windows 10 return text/plain
8785
# as the mime type for .js files

shiny/playwright/controller/_input_controls.py

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ def set(
579579
The timeout for the action. Defaults to `None`.
580580
"""
581581
if not isinstance(selected, str):
582-
raise TypeError("`selected` must be a string")
582+
raise TypeError("`selected=` must be a string")
583583

584584
# Only need to set.
585585
# The Browser will _unset_ the previously selected radio button
@@ -778,10 +778,10 @@ def set(
778778
"""
779779
# Having an arr of size 0 is allowed. Will uncheck everything
780780
if not isinstance(selected, list):
781-
raise TypeError("`selected` must be a list or tuple")
781+
raise TypeError("`selected=` must be a list or tuple")
782782
for item in selected:
783783
if not isinstance(item, str):
784-
raise TypeError("`selected` must be a list of strings")
784+
raise TypeError("`selected=` must be a list of strings")
785785

786786
# Make sure the selected items exist
787787
# Similar to `self.expect_choices(choices = selected)`, but with
@@ -1187,25 +1187,139 @@ def set(
11871187
"""
11881188
Sets the selected option(s) of the input selectize.
11891189
1190+
Selected items are altered as follows:
1191+
1. Click on the selectize input to open the dropdown.
1192+
2. Starting from the first selected item, each position in the currently selected list should match `selected`. If the item is not a match, remove it and try again.
1193+
3. Add any remaining items in `selected` that are not currently selected by clicking on them in the dropdown.
1194+
4. Press the `"Escape"` key to close the dropdown.
1195+
11901196
Parameters
11911197
----------
11921198
selected
1193-
The value(s) of the selected option(s).
1199+
The [ordered] value(s) of the selected option(s).
11941200
timeout
11951201
The maximum time to wait for the selection to be set. Defaults to `None`.
11961202
"""
1197-
if isinstance(selected, str):
1198-
selected = [selected]
1199-
self._loc_events.click()
1200-
for value in selected:
1201-
self._loc_selectize.locator(f"[data-value='{value}']").click(
1203+
1204+
def click_item(data_value: str, error_str: str) -> None:
1205+
"""
1206+
Clicks the item in the dropdown by its `data-value` attribute.
1207+
"""
1208+
if not isinstance(data_value, str):
1209+
raise TypeError(error_str)
1210+
1211+
# Wait for the item to exist
1212+
playwright_expect(
1213+
self._loc_selectize.locator(f"[data-value='{data_value}']")
1214+
).to_have_count(1, timeout=timeout)
1215+
# Click the item
1216+
self._loc_selectize.locator(f"[data-value='{data_value}']").click(
12021217
timeout=timeout
12031218
)
1204-
self._loc_events.press("Escape")
1219+
1220+
# Make sure the selectize exists
1221+
playwright_expect(self._loc_events).to_have_count(1, timeout=timeout)
1222+
1223+
if self.loc.get_attribute("multiple") is None:
1224+
# Single element selectize
1225+
if isinstance(selected, list):
1226+
if len(selected) != 1:
1227+
raise ValueError(
1228+
"Expected a `str` value (or a list of a single `str` value) when setting a single-select input."
1229+
)
1230+
selected = selected[0]
1231+
1232+
# Open the dropdown
1233+
self._loc_events.click(timeout=timeout)
1234+
1235+
try:
1236+
# Click the item (which closes the dropdown)
1237+
click_item(selected, "`selected=` value must be a `str`")
1238+
finally:
1239+
# Be sure to close the dropdown
1240+
# (While this is not necessary on a sucessful `set()`, it is cleaner
1241+
# than a catch all except)
1242+
self._loc_events.press("Escape", timeout=timeout)
1243+
1244+
else:
1245+
# Multiple element selectize
1246+
1247+
def delete_item(item_loc: Locator) -> None:
1248+
"""
1249+
Deletes the item by clicking on it and pressing the Delete key.
1250+
"""
1251+
1252+
item_loc.click()
1253+
self.page.keyboard.press("Delete")
1254+
1255+
if isinstance(selected, str):
1256+
selected = [selected]
1257+
if not isinstance(selected, list):
1258+
raise TypeError(
1259+
"`selected=` must be a single `str` value or a list of `str` values when setting a multiple-select input"
1260+
)
1261+
1262+
# Open the dropdown
1263+
self._loc_events.click()
1264+
1265+
try:
1266+
# Sift through the selected items
1267+
# From left to right, we will remove ordered items that are not in the
1268+
# ordered `selected`
1269+
# If any selected items are not in the current selection, we will add
1270+
# them at the end
1271+
1272+
# All state transitions examples have an end goal of
1273+
# A,B,C,D,E
1274+
#
1275+
# Items wrapped in `[]` are the item of interest at position `i`
1276+
# Ex: `Z`,i=3 in A,B,C,[Z],E
1277+
1278+
i = 0
1279+
while i < self._loc_events.locator("> .item").count():
1280+
item_loc = self._loc_events.locator("> .item").nth(i)
1281+
item_data_value = item_loc.get_attribute("data-value")
1282+
1283+
# If the item has no data-value, remove it
1284+
# Transition: A,B,C,[?],D,E -> A,B,C,[D],E
1285+
if item_data_value is None:
1286+
delete_item(item_loc)
1287+
continue
1288+
1289+
# If there are more items than selected, remove the extras
1290+
# Transition: A,B,C,D,E,[Z] -> A,B,C,D,E,[]
1291+
if i >= len(selected):
1292+
delete_item(item_loc)
1293+
continue
1294+
1295+
selected_data_value = selected[i]
1296+
1297+
# If the item is not the next `selected` value, remove it
1298+
# Transition: A,B,[Z],C,D,E -> A,B,[C],D,E
1299+
if item_data_value != selected_data_value:
1300+
delete_item(item_loc)
1301+
continue
1302+
1303+
# The item is the next `selected` value
1304+
# Increment the index! (No need to remove it and add it back)
1305+
# A,B,[C],D,E -> A,B,C,[D],E
1306+
i += 1
1307+
1308+
# Add the remaining items
1309+
# A,B,[] -> A,B,C,D,E
1310+
if i < len(selected):
1311+
for data_value in selected[i:]:
1312+
click_item(
1313+
data_value, f"`selected[{i}]=` value must be a `str`"
1314+
)
1315+
1316+
finally:
1317+
# Be sure to close the dropdown
1318+
self._loc_events.press("Escape", timeout=timeout)
1319+
return
12051320

12061321
def expect_choices(
12071322
self,
1208-
# TODO-future; support patterns?
12091323
choices: ListPatternOrStr,
12101324
*,
12111325
timeout: Timeout = None,

shiny/render/_render.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
import os
55
import sys
66
import typing
7-
8-
# `typing.Dict` sed for python 3.8 compatibility
9-
# Can use `dict` in python >= 3.9
107
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast
118

129
from htmltools import Tag, TagAttrValue, TagChild

shiny/render/renderer/_renderer.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,6 @@ def _auto_register(self) -> None:
336336
self._auto_registered = True
337337

338338

339-
# Not inheriting from `WrapAsync[[], IT]` as python 3.8 needs typing extensions that
340-
# doesn't support `[]` for a ParamSpec definition. :-( Would be minimal/clean if we
341-
# could do `class AsyncValueFn(WrapAsync[[], IT]):`
342339
class AsyncValueFn(Generic[IT]):
343340
"""
344341
App-supplied output value function which returns type `IT`.

0 commit comments

Comments
 (0)