diff --git a/Makefile b/Makefile index 92e41c721..c594ce2a5 100644 --- a/Makefile +++ b/Makefile @@ -186,15 +186,12 @@ clean-test: FORCE ## remove test and coverage artifacts # Check lint, test, and format of code # ----------------- check: check-lint check-types check-tests ## check code, style, types, and test (basic CI) -check-fix: format check-lint check-types check-tests ## check and format code, style, types, and test -check-lint: check-ruff ## check code formatting and style +check-fix: format check-types check-tests ## check and format code, style, types, and test +check-lint: check-ruff ## check code lints and format check-ruff: $(RUFF) FORCE - @echo "-------- Running ruff lint and formatting checks --------" + @echo "-------- Running ruff lint and format checks --------" @# Check imports in addition to code - @# Reason for two commands: https://github.com/astral-sh/ruff/issues/8232 - # . $(PYBIN)/activate && \ - # ruff check --select I --fix . # Check lints . $(PYBIN)/activate && \ ruff check . @@ -225,13 +222,17 @@ format: format-ruff ## format code format-ruff: $(RUFF) FORCE @echo "-------- Formatting code with ruff --------" @# Reason for two commands: https://github.com/astral-sh/ruff/issues/8232 - @# Fix imports + @# Fix lints . $(PYBIN)/activate && \ - ruff check --select I --fix . + ruff check --fix . @# Fix formatting . $(PYBIN)/activate && \ ruff format . +format-ruff-unsafe: $(RUFF) FORCE + @echo "-------- Formatting code with ruff (unsafe) --------" + . $(PYBIN)/activate && \ + ruff check --fix --unsafe-fixes . # ----------------- # Documentation # ----------------- diff --git a/examples/brownian/brownian_motion.py b/examples/brownian/brownian_motion.py index 92defcba5..d2f46a09a 100644 --- a/examples/brownian/brownian_motion.py +++ b/examples/brownian/brownian_motion.py @@ -5,14 +5,14 @@ # * `brownian_motion()` is a function that generates a random walk # * `brownian_widget()` uses restructured plotting code given in article -rs = np.random.RandomState() +rng = np.random.default_rng() # https://plotly.com/python/3d-line-plots/ def brownian_motion(T=1, N=100, mu=0.1, sigma=0.01, S0=20): dt = float(T) / N t = np.linspace(0, T, N) - W = rs.standard_normal(size=N) + W = rng.standard_normal(size=N) W = np.cumsum(W) * np.sqrt(dt) # standard brownian motion X = (mu - 0.5 * sigma**2) * t + sigma * W S = S0 * np.exp(X) # geometric brownian motion @@ -24,7 +24,7 @@ def brownian_data(n=100, mu=(0.0, 0.00), sigma=(0.1, 0.1), S0=(1.0, 1.0)): "x": brownian_motion(T=1, N=n, mu=mu[0], sigma=sigma[0], S0=S0[0]), "y": brownian_motion(T=1, N=n, mu=mu[1], sigma=sigma[1], S0=S0[1]), # "y": [i for i in range(n)], - "z": [i for i in range(n)], + "z": list(range(n)), } @@ -35,12 +35,12 @@ def brownian_widget(width=600, height=600): x=[], y=[], z=[], - marker=dict( - size=4, - color=[], - colorscale="Viridis", - ), - line=dict(color="darkblue", width=2), + marker={ + "size": 4, + "color": [], + "colorscale": "Viridis", + }, + line={"color": "darkblue", "width": 2}, ) ], layout={"showlegend": False, "width": width, "height": height}, diff --git a/examples/brownian/mediapipe.py b/examples/brownian/mediapipe.py index 1a2b6bd98..91f482b60 100644 --- a/examples/brownian/mediapipe.py +++ b/examples/brownian/mediapipe.py @@ -60,8 +60,8 @@ def rel_hand(start_pos: int, end_pos: int): normal_unit_vec = normal_vec / np.linalg.norm(normal_vec) def list_to_xyz(x): - x = list(map(lambda y: round(y, 2), x)) - return dict(x=x[0], y=x[1], z=x[2]) + x = [round(y, 2) for y in x] + return {"x": x[0], "y": x[1], "z": x[2]} # Invert, for some reason normal_unit_vec = normal_unit_vec * -1.0 @@ -82,8 +82,8 @@ def list_to_xyz(x): def xyz_mean(points): - return dict( - x=mean([p["x"] for p in points]), - y=mean([p["y"] for p in points]), - z=mean([p["z"] for p in points]), - ) + return { + "x": mean([p["x"] for p in points]), + "y": mean([p["y"] for p in points]), + "z": mean([p["z"] for p in points]), + } diff --git a/examples/cpuinfo/app.py b/examples/cpuinfo/app.py index 7cf934283..37d788b43 100644 --- a/examples/cpuinfo/app.py +++ b/examples/cpuinfo/app.py @@ -8,7 +8,7 @@ from math import ceil -import matplotlib +import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -17,7 +17,7 @@ # The agg matplotlib backend seems to be a little more efficient than the default when # running on macOS, and also gives more consistent results across operating systems -matplotlib.use("agg") +mpl.use("agg") # max number of samples to retain MAX_SAMPLES = 1000 @@ -166,7 +166,7 @@ def plot(): ncols=ncols, squeeze=False, ) - for i in range(0, ncols * nrows): + for i in range(ncols * nrows): row = i // ncols col = i % ncols axes = axeses[row, col] diff --git a/examples/cpuinfo/fakepsutil.py b/examples/cpuinfo/fakepsutil.py index de42c8189..50a455646 100644 --- a/examples/cpuinfo/fakepsutil.py +++ b/examples/cpuinfo/fakepsutil.py @@ -7,12 +7,13 @@ def cpu_count(logical: bool = True): return 8 if logical else 4 -last_sample = np.random.uniform(0, 100, size=cpu_count(True)) +rng = np.random.default_rng() +last_sample = rng.uniform(0, 100, size=cpu_count(True)) def cpu_percent(percpu: bool = False): global last_sample - delta = np.random.normal(scale=10, size=len(last_sample)) + delta = rng.normal(scale=10, size=len(last_sample)) last_sample = (last_sample + delta).clip(0, 100) if percpu: return last_sample.tolist() diff --git a/examples/dataframe/app.py b/examples/dataframe/app.py index 99374627e..3568bbcb6 100644 --- a/examples/dataframe/app.py +++ b/examples/dataframe/app.py @@ -1,9 +1,13 @@ -import pandas as pd +from typing import TYPE_CHECKING + import seaborn as sns from shinyswatch.theme import darkly from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui +if TYPE_CHECKING: + import pandas as pd + def app_ui(req): dark = True if "dark" in req.query_params else None diff --git a/examples/express/accordion_app.py b/examples/express/accordion_app.py index fdd722c34..76e2505d2 100644 --- a/examples/express/accordion_app.py +++ b/examples/express/accordion_app.py @@ -17,6 +17,5 @@ def txt(): @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True) diff --git a/examples/express/column_wrap_app.py b/examples/express/column_wrap_app.py index 5e5c44f06..b64660ea9 100644 --- a/examples/express/column_wrap_app.py +++ b/examples/express/column_wrap_app.py @@ -12,14 +12,12 @@ @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True) with ui.card(): @render.plot def histogram2(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True, color="red") diff --git a/examples/express/nav_app.py b/examples/express/nav_app.py index 0a72627ff..9410b6085 100644 --- a/examples/express/nav_app.py +++ b/examples/express/nav_app.py @@ -13,8 +13,7 @@ @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True) with ui.navset_card_underline(): @@ -25,6 +24,5 @@ def histogram(): @render.plot def histogram2(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n2(), density=True) diff --git a/examples/express/plot_app.py b/examples/express/plot_app.py index 605fe6e62..9d31dddf5 100644 --- a/examples/express/plot_app.py +++ b/examples/express/plot_app.py @@ -9,6 +9,5 @@ @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True) diff --git a/examples/express/shared_app.py b/examples/express/shared_app.py index 89c587a1c..10778565a 100644 --- a/examples/express/shared_app.py +++ b/examples/express/shared_app.py @@ -12,8 +12,7 @@ @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, shared.rv(), density=True) diff --git a/examples/express/sidebar_app.py b/examples/express/sidebar_app.py index ac1e97844..b75714204 100644 --- a/examples/express/sidebar_app.py +++ b/examples/express/sidebar_app.py @@ -10,6 +10,5 @@ @render.plot def histogram(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) plt.hist(x, input.n(), density=True) diff --git a/examples/model-score/app.py b/examples/model-score/app.py index 30d919fb6..e1f2cc815 100644 --- a/examples/model-score/app.py +++ b/examples/model-score/app.py @@ -58,7 +58,7 @@ def df(): params=[150], ) # Convert timestamp to datetime object, which SQLite doesn't support natively - tbl["timestamp"] = pd.to_datetime(tbl["timestamp"], utc=True) + tbl["timestamp"] = pd.to_datetime(tbl["timestamp"], utc=True, format="ISO8601") # Create a short label for readability tbl["time"] = tbl["timestamp"].dt.strftime("%H:%M:%S") # Reverse order of rows @@ -81,10 +81,7 @@ def read_time_period(from_time, to_time): model_names = ["model_1", "model_2", "model_3", "model_4"] -model_colors = { - name: color - for name, color in zip(model_names, px.colors.qualitative.D3[0 : len(model_names)]) -} +model_colors = dict(zip(model_names, px.colors.qualitative.D3[0 : len(model_names)])) def app_ui(req): @@ -224,7 +221,7 @@ def plot_timeseries(): filtered_df(), x="time", y="score", - labels=dict(score="accuracy"), + labels={"score": "accuracy"}, color="model", color_discrete_map=model_colors, # The default for render_mode is "auto", which switches between @@ -238,13 +235,13 @@ def plot_timeseries(): fig.add_hline( THRESHOLD_LOW, line_dash="dash", - line=dict(color=THRESHOLD_LOW_COLOR, width=2), + line={"color": THRESHOLD_LOW_COLOR, "width": 2}, opacity=0.3, ) fig.add_hline( THRESHOLD_MID, line_dash="dash", - line=dict(color=THRESHOLD_MID_COLOR, width=2), + line={"color": THRESHOLD_MID_COLOR, "width": 2}, opacity=0.3, ) @@ -260,7 +257,7 @@ def plot_dist(): facet_row="model", nbins=20, x="score", - labels=dict(score="accuracy"), + labels={"score": "accuracy"}, color="model", color_discrete_map=model_colors, template="simple_white", @@ -269,13 +266,13 @@ def plot_dist(): fig.add_vline( THRESHOLD_LOW, line_dash="dash", - line=dict(color=THRESHOLD_LOW_COLOR, width=2), + line={"color": THRESHOLD_LOW_COLOR, "width": 2}, opacity=0.3, ) fig.add_vline( THRESHOLD_MID, line_dash="dash", - line=dict(color=THRESHOLD_MID_COLOR, width=2), + line={"color": THRESHOLD_MID_COLOR, "width": 2}, opacity=0.3, ) diff --git a/examples/static_plots/app.py b/examples/static_plots/app.py index b5c928169..f0dc0af5f 100644 --- a/examples/static_plots/app.py +++ b/examples/static_plots/app.py @@ -54,13 +54,14 @@ def server(input: Inputs, output: Outputs, session: Session): + rng = np.random.default_rng() + @reactive.calc def fake_data(): n = 5000 mean = [0, 0] - rng = np.random.RandomState(0) cov = [(input.var(), input.cov()), (input.cov(), 1 / input.var())] - return rng.multivariate_normal(mean, cov, n).T + return np.random.default_rng(seed=0).multivariate_normal(mean, cov, n).T @render.plot def seaborn(): @@ -98,7 +99,8 @@ def plotnine(): @render.plot def pandas(): ts = pd.Series( - np.random.randn(1000), index=pd.date_range("1/1/2000", periods=1000) + rng.standard_normal(1000), + index=pd.date_range("1/1/2000", periods=1000), ) ts = ts.cumsum() return ts.plot() diff --git a/ruff.toml b/ruff.toml index 6cab8552d..e40f6e361 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,84 +1,67 @@ -extend-exclude = [ - "docs", - ".venv", - "venv", - "typings", - "build", - "_dev", - "shiny/__init__.py", -] +extend-exclude = ["docs", ".venv", "venv", "typings", "build", "_dev"] [lint] -# E501: Line too long -# Conflicting lint rules: https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules -# * indentation-with-invalid-multiple (E111) -# * indentation-with-invalid-multiple-comment (E114) -# * over-indented (E117) -# * indent-with-spaces (D206) -# * triple-single-quotes (D300) -# * bad-quotes-inline-string (Q000) -# * bad-quotes-multiline-string (Q001) -# * bad-quotes-docstring (Q002) -# * avoidable-escaped-quote (Q003) -# * missing-trailing-comma (COM812) -# * prohibited-trailing-comma (COM819) -# * single-line-implicit-string-concatenation (ISC001) -# * multi-line-implicit-string-concatenation (ISC002) + extend-ignore = [ - "E501", - "E111", - "E114", - "E117", - "D206", - "D300", - "Q000", - "Q001", - "Q002", - "Q003", - "COM812", - "COM819", - "ISC001", - "ISC002", + "E501", # E501: Line too long + "PT011", # PT011 `pytest.raises(ValueError)` is too broad + "PT022", # PT022 [*] No teardown in fixture + "B904", # B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling + # Conflicting lint rules: https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "E111", # * indentation-with-invalid-multiple (E111) + "E114", # * indentation-with-invalid-multiple-comment (E114) + "E117", # * over-indented (E117) + "D206", # * indent-with-spaces (D206) + "D300", # * triple-single-quotes (D300) + "Q000", # * bad-quotes-inline-string (Q000) + "Q001", # * bad-quotes-multiline-string (Q001) + "Q002", # * bad-quotes-docstring (Q002) + "Q003", # * avoidable-escaped-quote (Q003) + "COM812", # * missing-trailing-comma (COM812) + "COM819", # * prohibited-trailing-comma (COM819) + "ISC001", # * single-line-implicit-string-concatenation (ISC001) + "ISC002", # * multi-line-implicit-string-concatenation (ISC002) ] # Rules to add https://docs.astral.sh/ruff/rules/ # Default `select = ["E4", "E7", "E9", "F"]` -# E4, E7, E9; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w -# F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f -# I; isort: https://docs.astral.sh/ruff/rules/#isort-i -# B; flake8-bugbear: https://docs.astral.sh/ruff/rules/#flake8-bugbear-b -# Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q -# C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ -# COM; Commas: https://docs.astral.sh/ruff/rules/#flake8-commas-com -# C4; flake8-comprehensions: https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 -# DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz -# FA; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa -# ISC; flake8-s=implicit-str-concat: https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc -# ICN; flake8-import-conventions: https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn -# G; flake8-logging-format: https://docs.astral.sh/ruff/rules/#flake8-logging-format-g -# PIE; flake8-pie: https://docs.astral.sh/ruff/rules/#flake8-pie-pie -# T20; flake8-print: https://docs.astral.sh/ruff/rules/#flake8-print-t20 -# PYI013; flake8-pyi Non-empty class body must not contain `...`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi -# PYI030; flake8-pyi Multiple literal members in a union: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi -# PYI034; flake8-pyi `__new__` methods usually reutrn `Self`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi -# PT; flake8-pytest-style: https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt -# SIM118; flake8-simplify Use `key {operator} dict`: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim -# TCH; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch -# FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix -# PD; pandas-vet: https://docs.astral.sh/ruff/rules/#pandas-vet-pd -# PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh -# FLY; flynt: https://docs.astral.sh/ruff/rules/#flynt-fly -# NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy -# RUF005; Ruff specific rules Consider {expression} instead of concatenation: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf -# RUF100; Ruff specific rules Unused `noqa` directive https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf -# extend-select = ["E", "I", "B", "Q", "C90", "COM", "C4", "DTZ", "FA", "ISC", "ICN", "G", "PIE", "T20", "PYI013", "PYI030", "PYI034", "PT", "SIM118", "TCH", "FIX", "PD", "PGH", "FLY", "NPY", "RUF100", "RUF005"] + +extend-select = [ + # "C90", # Many false positives # C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ + # "DTZ", # Dates with timezones are different from dates without timezones # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + + "E", # E; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "F", # F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # I; isort: https://docs.astral.sh/ruff/rules/#isort-i + "B", # B; flake8-bugbear: https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "Q", # Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q + "COM", # COM; Commas: https://docs.astral.sh/ruff/rules/#flake8-commas-com + "C4", # C4; flake8-comprehensions: https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "FA102", # FA102; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + "ISC", # ISC; flake8-implicit-str-concat: https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + "ICN", # ICN; flake8-import-conventions: https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + "PIE", # PIE; flake8-pie: https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "PYI013", # PYI013; flake8-pyi Non-empty class body must not contain `...`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PYI030", # PYI030; flake8-pyi Multiple literal members in a union: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PYI034", # PYI034; flake8-pyi `__new__` methods usually reutrn `Self`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PT", # PT; flake8-pytest-style: https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "SIM118", # SIM118; flake8-simplify Use `key {operator} dict`: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "TCH", # TCH; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + # "FIX", # FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + # "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + "NPY", # NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + "RUF005", # RUF005; Ruff specific rules Consider {expression} instead of concatenation: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + "RUF100", # RUF100; Ruff specific rules Unused `noqa` directive https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf +] [lint.extend-per-file-ignores] +# I: isort; Do not reformat imports in `__init__.py` files. +"**/__init__.py" = ["I"] # F403: 'from module import *' used; unable to detect undefined names # Also ignore `F403` in all `__init__.py` files. -"shiny/__init__.py" = ["F403"] +"shiny/__init__.py" = ["F403", "F405"] # B018: Found useless expression. Either assign it to a variable or remove it. # This check is incompatible with the `express` framework. "**/app-express.py" = ["B018"] @@ -90,8 +73,8 @@ extend-ignore = [ "shiny/templates/**" = ["T20"] "shiny/api-examples/**" = ["T20"] "tests/**" = ["T20"] -# I: isort; Do not reformat imports in `__init__.py` files. -"**/__init__.py" = ["I"] +# PT019: pytest +"tests/pytest/test_output_transformer.py" = ["PT019"] [format] diff --git a/scripts/generate-imports.py b/scripts/generate-imports.py index a3bfc7f15..a11d836dd 100755 --- a/scripts/generate-imports.py +++ b/scripts/generate-imports.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations import importlib import sys diff --git a/shiny/_connection.py b/shiny/_connection.py index f2e0323fe..411259eb3 100644 --- a/shiny/_connection.py +++ b/shiny/_connection.py @@ -137,5 +137,3 @@ def get_http_conn(self) -> HTTPConnection: class ConnectionClosed(Exception): """Raised when a Connection is closed from the other side.""" - - pass diff --git a/shiny/_custom_component_template_questions.py b/shiny/_custom_component_template_questions.py index 00e64e4b0..a44743917 100644 --- a/shiny/_custom_component_template_questions.py +++ b/shiny/_custom_component_template_questions.py @@ -61,12 +61,7 @@ def validate(self, document: Document): # Check for quotations - if ( - name.startswith('"') - or name.endswith('"') - or name.startswith("'") - or name.endswith("'") - ): + if name.startswith(('"', "'")) or name.endswith(('"', "'")): raise ValidationError( message="The name should be unquoted.", cursor_position=len(name), diff --git a/shiny/_docstring.py b/shiny/_docstring.py index 8505f96b0..93395489c 100644 --- a/shiny/_docstring.py +++ b/shiny/_docstring.py @@ -45,7 +45,7 @@ def decorator(func: F) -> F: current.extend(["express", "core"]) else: current.append(mode) - setattr(func, "__no_example", current) # noqa: B010 + setattr(func, "__no_example", current) return func return decorator @@ -238,7 +238,7 @@ def __str__(self): return ( f"Could not find {type} example file named " - + f"{' or '.join(self.file_names)} in {self.dir}." + f"{' or '.join(self.file_names)} in {self.dir}." ) diff --git a/shiny/_error.py b/shiny/_error.py index 33f807dbc..bee5f6964 100644 --- a/shiny/_error.py +++ b/shiny/_error.py @@ -1,10 +1,12 @@ from __future__ import annotations -from typing import cast +from typing import TYPE_CHECKING, cast import starlette.exceptions as exceptions import starlette.responses as responses -from starlette.types import ASGIApp, Receive, Scope, Send + +if TYPE_CHECKING: + from starlette.types import ASGIApp, Receive, Scope, Send class ErrorMiddleware: diff --git a/shiny/_hostenv.py b/shiny/_hostenv.py index 89f1e0273..74876b770 100644 --- a/shiny/_hostenv.py +++ b/shiny/_hostenv.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import os import re import typing @@ -9,6 +8,9 @@ from typing import Pattern from urllib.parse import ParseResult, urlparse +if typing.TYPE_CHECKING: + import logging + def is_workbench() -> bool: return bool(os.getenv("RS_SERVER_URL") and os.getenv("RS_SESSION_URL")) diff --git a/shiny/_main.py b/shiny/_main.py index bf61f6899..10f4653ba 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -8,9 +8,8 @@ import platform import re import sys -import types from pathlib import Path -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, Optional import click import uvicorn @@ -24,6 +23,9 @@ from .express import is_express_app from .express._utils import escape_to_var_name +if TYPE_CHECKING: + import types + @click.group("main") def main() -> None: @@ -561,10 +563,10 @@ def create( shinylive export APPDIR DESTDIR """, - context_settings=dict( - ignore_unknown_options=True, - allow_extra_args=True, - ), + context_settings={ + "ignore_unknown_options": True, + "allow_extra_args": True, + }, ) def static() -> None: print( diff --git a/shiny/_typing_extensions.py b/shiny/_typing_extensions.py index 39cc1ac29..5047478a4 100644 --- a/shiny/_typing_extensions.py +++ b/shiny/_typing_extensions.py @@ -1,6 +1,6 @@ # # Within file flags to ignore unused imports -# flake8: noqa: F401 # pyright: reportUnusedImport=false +from __future__ import annotations __all__ = ( "Concatenate", diff --git a/shiny/_utils.py b/shiny/_utils.py index 74daf50e5..89489050e 100644 --- a/shiny/_utils.py +++ b/shiny/_utils.py @@ -13,12 +13,23 @@ import sys import tempfile import warnings -from pathlib import Path -from types import ModuleType -from typing import Any, Awaitable, Callable, Generator, Optional, TypeVar, cast +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Generator, + Optional, + TypeVar, + cast, +) from ._typing_extensions import ParamSpec, TypeGuard +if TYPE_CHECKING: + from pathlib import Path + from types import ModuleType + CancelledError = asyncio.CancelledError T = TypeVar("T") @@ -182,7 +193,7 @@ def random_port( 10080, ] - unusable = set([x for x in unsafe_ports if x >= min and x <= max]) + unusable = {x for x in unsafe_ports if x >= min and x <= max} while n > 0: if (max - min + 1) <= len(unusable): break @@ -413,7 +424,7 @@ def _step(fut: Optional["asyncio.Future[None]"] = None): assert fut.done() try: fut.result() - except BaseException as e: # noqa: B036 + except BaseException as e: exc = e if result_future.cancelled(): @@ -439,7 +450,7 @@ def _step(fut: Optional["asyncio.Future[None]"] = None): except (KeyboardInterrupt, SystemExit) as e: result_future.set_exception(e) raise - except BaseException as e: # noqa: B036 + except BaseException as e: result_future.set_exception(e) else: # If we get here, the coro didn't finish. Schedule it for completion. diff --git a/shiny/api-examples/data_frame/app-core.py b/shiny/api-examples/data_frame/app-core.py index a592ef238..0f27aa96f 100644 --- a/shiny/api-examples/data_frame/app-core.py +++ b/shiny/api-examples/data_frame/app-core.py @@ -1,4 +1,4 @@ -import pandas # noqa: F401 (this line needed for Shinylive to load plotly.express) +import pandas as pd # noqa: F401 (this line needed for Shinylive to load plotly.express) import plotly.express as px from shinywidgets import output_widget, render_widget diff --git a/shiny/api-examples/data_frame/app-express.py b/shiny/api-examples/data_frame/app-express.py index 24a5b47ba..98644c0d9 100644 --- a/shiny/api-examples/data_frame/app-express.py +++ b/shiny/api-examples/data_frame/app-express.py @@ -1,4 +1,4 @@ -import pandas # noqa: F401 (this line needed for Shinylive to load plotly.express) +import pandas as pd # noqa: F401 (this line needed for Shinylive to load plotly.express) import plotly.express as px from shinywidgets import render_widget diff --git a/shiny/api-examples/download/app-core.py b/shiny/api-examples/download/app-core.py index 74edfa82d..b57f20d0a 100644 --- a/shiny/api-examples/download/app-core.py +++ b/shiny/api-examples/download/app-core.py @@ -77,6 +77,8 @@ def make_example(id: str, label: str, title: str, desc: str, extra: Any = None): def server(input: Inputs, output: Outputs, session: Session): + rng = np.random.default_rng() + @render.download() def download1(): """ @@ -98,8 +100,8 @@ def download2(): """ print(input.num_points()) - x = np.random.uniform(size=input.num_points()) - y = np.random.uniform(size=input.num_points()) + x = rng.uniform(size=input.num_points()) + y = rng.uniform(size=input.num_points()) plt.figure() plt.scatter(x, y) plt.title(input.title()) @@ -108,7 +110,7 @@ def download2(): yield buf.getvalue() @render.download( - filename=lambda: f"新型-{date.today().isoformat()}-{np.random.randint(100, 999)}.csv" + filename=lambda: f"新型-{date.today().isoformat()}-{rng.randint(100, 999)}.csv" ) async def download3(): await asyncio.sleep(0.25) diff --git a/shiny/api-examples/download/app-express.py b/shiny/api-examples/download/app-express.py index 9eaa60647..a3298a81e 100644 --- a/shiny/api-examples/download/app-express.py +++ b/shiny/api-examples/download/app-express.py @@ -8,6 +8,8 @@ from shiny.express import render, ui +rng = np.random.default_rng() + ui.page_opts(title="Various download examples") with ui.accordion(open=True): @@ -41,8 +43,8 @@ def download2(): """ print(input.num_points()) - x = np.random.uniform(size=input.num_points()) - y = np.random.uniform(size=input.num_points()) + x = rng.uniform(size=input.num_points()) + y = rng.uniform(size=input.num_points()) plt.figure() plt.scatter(x, y) plt.title(input.title()) @@ -57,7 +59,7 @@ def download2(): @render.download( label="Download filename", - filename=lambda: f"新型-{date.today().isoformat()}-{np.random.randint(100, 999)}.csv", + filename=lambda: f"新型-{date.today().isoformat()}-{rng.randint(100, 999)}.csv", ) async def download3(): await asyncio.sleep(0.25) diff --git a/shiny/api-examples/input_action_button/app-core.py b/shiny/api-examples/input_action_button/app-core.py index 66f6abe13..b8dcbeb8b 100644 --- a/shiny/api-examples/input_action_button/app-core.py +++ b/shiny/api-examples/input_action_button/app-core.py @@ -16,8 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): # (not when the slider is changed) @reactive.event(input.go, ignore_none=False) def plot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(input.n()) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) return fig diff --git a/shiny/api-examples/input_action_button/app-express.py b/shiny/api-examples/input_action_button/app-express.py index 925ebc4d6..5936c2d86 100644 --- a/shiny/api-examples/input_action_button/app-express.py +++ b/shiny/api-examples/input_action_button/app-express.py @@ -13,8 +13,7 @@ # (not when the slider is changed) @reactive.event(input.go, ignore_none=False) def plot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(input.n()) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) return fig diff --git a/shiny/api-examples/input_action_link/app-core.py b/shiny/api-examples/input_action_link/app-core.py index 054ebacaa..a7bc48a80 100644 --- a/shiny/api-examples/input_action_link/app-core.py +++ b/shiny/api-examples/input_action_link/app-core.py @@ -16,8 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): # the slider is changed @reactive.event(input.go, ignore_none=False) def plot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(input.n()) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) return fig diff --git a/shiny/api-examples/input_action_link/app-express.py b/shiny/api-examples/input_action_link/app-express.py index 39939d756..0e5ac98b3 100644 --- a/shiny/api-examples/input_action_link/app-express.py +++ b/shiny/api-examples/input_action_link/app-express.py @@ -13,8 +13,7 @@ # the slider is changed @reactive.event(input.go, ignore_none=False) def plot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(input.n()) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) return fig diff --git a/shiny/api-examples/input_dark_mode/app-core.py b/shiny/api-examples/input_dark_mode/app-core.py index 30539ac30..dd6bf0f16 100644 --- a/shiny/api-examples/input_dark_mode/app-core.py +++ b/shiny/api-examples/input_dark_mode/app-core.py @@ -45,8 +45,7 @@ def _(): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/input_dark_mode/app-express.py b/shiny/api-examples/input_dark_mode/app-express.py index 063f215d8..f46b20aee 100644 --- a/shiny/api-examples/input_dark_mode/app-express.py +++ b/shiny/api-examples/input_dark_mode/app-express.py @@ -13,8 +13,7 @@ @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/input_file/app-core.py b/shiny/api-examples/input_file/app-core.py index 4cd0d99ee..9331d5ba7 100644 --- a/shiny/api-examples/input_file/app-core.py +++ b/shiny/api-examples/input_file/app-core.py @@ -1,7 +1,13 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import pandas as pd from shiny import App, Inputs, Outputs, Session, reactive, render, ui -from shiny.types import FileInfo + +if TYPE_CHECKING: + from shiny.types import FileInfo app_ui = ui.page_fluid( ui.input_file("file1", "Choose CSV File", accept=[".csv"], multiple=False), diff --git a/shiny/api-examples/input_file/app-express.py b/shiny/api-examples/input_file/app-express.py index 6a035a555..b930ad284 100644 --- a/shiny/api-examples/input_file/app-express.py +++ b/shiny/api-examples/input_file/app-express.py @@ -1,8 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import pandas as pd from shiny import reactive from shiny.express import input, render, ui -from shiny.types import FileInfo + +if TYPE_CHECKING: + from shiny.types import FileInfo ui.input_file("file1", "Choose CSV File", accept=[".csv"], multiple=False) ui.input_checkbox_group( diff --git a/shiny/api-examples/input_slider/app-core.py b/shiny/api-examples/input_slider/app-core.py index c8a238506..0c13e3e9e 100644 --- a/shiny/api-examples/input_slider/app-core.py +++ b/shiny/api-examples/input_slider/app-core.py @@ -12,8 +12,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot def distPlot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.obs(), density=True) diff --git a/shiny/api-examples/input_slider/app-express.py b/shiny/api-examples/input_slider/app-express.py index e6e701594..4af08f8c1 100644 --- a/shiny/api-examples/input_slider/app-express.py +++ b/shiny/api-examples/input_slider/app-express.py @@ -8,8 +8,7 @@ @render.plot def distPlot(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.obs(), density=True) diff --git a/shiny/api-examples/isolate/app-core.py b/shiny/api-examples/isolate/app-core.py index f6a12559d..b80278463 100644 --- a/shiny/api-examples/isolate/app-core.py +++ b/shiny/api-examples/isolate/app-core.py @@ -18,8 +18,9 @@ def plot(): # ...but don't take a reactive dependency on the slider with reactive.isolate(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal( + input.n() + ) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) diff --git a/shiny/api-examples/isolate/app-express.py b/shiny/api-examples/isolate/app-express.py index 52f8b9c38..192b01344 100644 --- a/shiny/api-examples/isolate/app-express.py +++ b/shiny/api-examples/isolate/app-express.py @@ -15,8 +15,7 @@ def plot(): # ...but don't take a reactive dependency on the slider with reactive.isolate(): - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(input.n()) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(input.n()) fig, ax = plt.subplots() ax.hist(x, bins=30, density=True) diff --git a/shiny/api-examples/layout_columns/model_plots.py b/shiny/api-examples/layout_columns/model_plots.py index 951204df9..c8b9f6cc5 100644 --- a/shiny/api-examples/layout_columns/model_plots.py +++ b/shiny/api-examples/layout_columns/model_plots.py @@ -3,10 +3,12 @@ from shiny import ui +rng = np.random.default_rng() + def plot_loss_over_time(): epochs = np.arange(1, 101) - loss = 1000 / np.sqrt(epochs) + np.random.rand(100) * 25 + loss = 1000 / np.sqrt(epochs) + rng.uniform(size=100) * 25 fig = plt.figure(figsize=(10, 6)) plt.plot(epochs, loss) @@ -17,7 +19,7 @@ def plot_loss_over_time(): def plot_accuracy_over_time(): epochs = np.arange(1, 101) - accuracy = np.sqrt(epochs) / 12 + np.random.rand(100) * 0.15 + accuracy = np.sqrt(epochs) / 12 + rng.uniform(size=100) * 0.15 accuracy = [np.min([np.max(accuracy[:i]), 1]) for i in range(1, 101)] fig = plt.figure(figsize=(10, 6)) @@ -29,7 +31,7 @@ def plot_accuracy_over_time(): def plot_feature_importance(): features = ["Product Category", "Price", "Brand", "Rating", "Number of Reviews"] - importance = np.random.rand(5) + importance = rng.uniform(size=5) fig = plt.figure(figsize=(10, 6)) plt.barh(features, importance) diff --git a/shiny/api-examples/layout_sidebar/app-core.py b/shiny/api-examples/layout_sidebar/app-core.py index 78e36e744..2a819cace 100644 --- a/shiny/api-examples/layout_sidebar/app-core.py +++ b/shiny/api-examples/layout_sidebar/app-core.py @@ -16,8 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/layout_sidebar/app-express.py b/shiny/api-examples/layout_sidebar/app-express.py index 10482a396..724afe970 100644 --- a/shiny/api-examples/layout_sidebar/app-express.py +++ b/shiny/api-examples/layout_sidebar/app-express.py @@ -9,8 +9,7 @@ @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/notification_show/app-core.py b/shiny/api-examples/notification_show/app-core.py index 2f8e3a056..216bbe434 100644 --- a/shiny/api-examples/notification_show/app-core.py +++ b/shiny/api-examples/notification_show/app-core.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from shiny import App, Inputs, Outputs, Session, reactive, ui app_ui = ui.page_fluid( diff --git a/shiny/api-examples/notification_show/app-express.py b/shiny/api-examples/notification_show/app-express.py index d64a57431..e92050a85 100644 --- a/shiny/api-examples/notification_show/app-express.py +++ b/shiny/api-examples/notification_show/app-express.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from shiny import reactive from shiny.express import input, ui diff --git a/shiny/api-examples/output_image/app-core.py b/shiny/api-examples/output_image/app-core.py index 09452566a..b530f29be 100644 --- a/shiny/api-examples/output_image/app-core.py +++ b/shiny/api-examples/output_image/app-core.py @@ -1,5 +1,9 @@ +from typing import TYPE_CHECKING + from shiny import App, Inputs, Outputs, Session, render, ui -from shiny.types import ImgData + +if TYPE_CHECKING: + from shiny.types import ImgData app_ui = ui.page_fluid(ui.output_image("image")) diff --git a/shiny/api-examples/output_plot/app-core.py b/shiny/api-examples/output_plot/app-core.py index 339a1a6b7..1abd1b5b4 100644 --- a/shiny/api-examples/output_plot/app-core.py +++ b/shiny/api-examples/output_plot/app-core.py @@ -14,8 +14,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot def p(): - np.random.seed(19680801) - x_rand = 100 + 15 * np.random.randn(437) + x_rand = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x_rand, int(input.n()), density=True) return fig diff --git a/shiny/api-examples/output_plot/app-express.py b/shiny/api-examples/output_plot/app-express.py index f328cb6eb..39aea5fde 100644 --- a/shiny/api-examples/output_plot/app-express.py +++ b/shiny/api-examples/output_plot/app-express.py @@ -8,8 +8,7 @@ @render.plot def p(): - np.random.seed(19680801) - x_rand = 100 + 15 * np.random.randn(437) + x_rand = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x_rand, int(input.n()), density=True) return fig diff --git a/shiny/api-examples/output_table/app-core.py b/shiny/api-examples/output_table/app-core.py index 1dac0bab9..b23415044 100644 --- a/shiny/api-examples/output_table/app-core.py +++ b/shiny/api-examples/output_table/app-core.py @@ -53,7 +53,7 @@ def result(): } ) .set_table_styles( - [dict(selector="th", props=[("text-align", "right")])] + [{"selector": "th", "props": [("text-align", "right")]}] ) .highlight_min(color="silver") .highlight_max(color="yellow") diff --git a/shiny/api-examples/output_table/app-express.py b/shiny/api-examples/output_table/app-express.py index 12d8cb8aa..38cc6abd8 100644 --- a/shiny/api-examples/output_table/app-express.py +++ b/shiny/api-examples/output_table/app-express.py @@ -37,7 +37,7 @@ def result(): "qsec": "{0:0.2f}", } ) - .set_table_styles([dict(selector="th", props=[("text-align", "right")])]) + .set_table_styles([{"selector": "th", "props": [("text-align", "right")]}]) .highlight_min(color="silver") .highlight_max(color="yellow") ) diff --git a/shiny/api-examples/page_fixed/app-core.py b/shiny/api-examples/page_fixed/app-core.py index 80521e92c..42ae1e313 100644 --- a/shiny/api-examples/page_fixed/app-core.py +++ b/shiny/api-examples/page_fixed/app-core.py @@ -18,8 +18,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/page_fixed/app-express.py b/shiny/api-examples/page_fixed/app-express.py index 91f751edd..b3f80d4db 100644 --- a/shiny/api-examples/page_fixed/app-express.py +++ b/shiny/api-examples/page_fixed/app-express.py @@ -12,8 +12,7 @@ @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/page_fluid/app-core.py b/shiny/api-examples/page_fluid/app-core.py index a05d3618a..5e1888905 100644 --- a/shiny/api-examples/page_fluid/app-core.py +++ b/shiny/api-examples/page_fluid/app-core.py @@ -16,8 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/page_fluid/app-express.py b/shiny/api-examples/page_fluid/app-express.py index 587ec743a..86d522912 100644 --- a/shiny/api-examples/page_fluid/app-express.py +++ b/shiny/api-examples/page_fluid/app-express.py @@ -12,8 +12,7 @@ @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/page_sidebar/app-core.py b/shiny/api-examples/page_sidebar/app-core.py index 8ce58f8b8..be30313ea 100644 --- a/shiny/api-examples/page_sidebar/app-core.py +++ b/shiny/api-examples/page_sidebar/app-core.py @@ -16,8 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/page_sidebar/app-express.py b/shiny/api-examples/page_sidebar/app-express.py index 4781f851c..12a2854a5 100644 --- a/shiny/api-examples/page_sidebar/app-express.py +++ b/shiny/api-examples/page_sidebar/app-express.py @@ -9,8 +9,7 @@ @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/api-examples/poll/app-core.py b/shiny/api-examples/poll/app-core.py index 6079a0be4..37ab86486 100644 --- a/shiny/api-examples/poll/app-core.py +++ b/shiny/api-examples/poll/app-core.py @@ -99,7 +99,7 @@ def stock_quotes() -> pd.DataFrame: """ ), ui.input_selectize( - "symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True + "symbols", "Filter by symbol", ["", *SYMBOLS], multiple=True ), ui.output_data_frame("table"), fill=False, diff --git a/shiny/api-examples/poll/app-express.py b/shiny/api-examples/poll/app-express.py index df1f9c2cd..bba5b78ce 100644 --- a/shiny/api-examples/poll/app-express.py +++ b/shiny/api-examples/poll/app-express.py @@ -96,7 +96,7 @@ def stock_quotes() -> pd.DataFrame: case, an in-memory sqlite3) with the help of `shiny.reactive.poll`. """ ) - ui.input_selectize("symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True) + ui.input_selectize("symbols", "Filter by symbol", ["", *SYMBOLS], multiple=True) @render.data_frame def table(): diff --git a/shiny/api-examples/remove_accordion_panel/app-core.py b/shiny/api-examples/remove_accordion_panel/app-core.py index fa51fd56d..fc473c077 100644 --- a/shiny/api-examples/remove_accordion_panel/app-core.py +++ b/shiny/api-examples/remove_accordion_panel/app-core.py @@ -27,7 +27,7 @@ def make_panel(letter: str) -> ui.AccordionPanel: def server(input: Inputs, output: Outputs, session: Session): # Copy the list for user - user_choices = [choice for choice in choices] + user_choices = choices.copy() @reactive.effect @reactive.event(input.remove_panel) diff --git a/shiny/api-examples/remove_accordion_panel/app-express.py b/shiny/api-examples/remove_accordion_panel/app-express.py index d01f643e6..ce2138077 100644 --- a/shiny/api-examples/remove_accordion_panel/app-express.py +++ b/shiny/api-examples/remove_accordion_panel/app-express.py @@ -20,19 +20,16 @@ f"Some narrative for section {letter}" -user_choices = [choice for choice in choices] - - @reactive.effect @reactive.event(input.remove_panel) def _(): - if len(user_choices) == 0: + if len(choices) == 0: ui.notification_show("No more panels to remove!") return - ui.remove_accordion_panel("acc", f"Section {user_choices.pop()}") + ui.remove_accordion_panel("acc", f"Section {choices.pop()}") label = "No more panels to remove!" - if len(user_choices) > 0: - label = f"Remove Section {user_choices[-1]}" + if len(choices) > 0: + label = f"Remove Section {choices[-1]}" ui.update_action_button("remove_panel", label=label) diff --git a/shiny/api-examples/render_image/app-core.py b/shiny/api-examples/render_image/app-core.py index 60d35d90a..a3c7d6732 100644 --- a/shiny/api-examples/render_image/app-core.py +++ b/shiny/api-examples/render_image/app-core.py @@ -1,5 +1,9 @@ +from typing import TYPE_CHECKING + from shiny import App, Inputs, Outputs, Session, render, ui -from shiny.types import ImgData + +if TYPE_CHECKING: + from shiny.types import ImgData app_ui = ui.page_fluid(ui.output_image("image")) diff --git a/shiny/api-examples/row/app-core.py b/shiny/api-examples/row/app-core.py index be69085a7..ee82d3e25 100644 --- a/shiny/api-examples/row/app-core.py +++ b/shiny/api-examples/row/app-core.py @@ -14,8 +14,7 @@ def server(input: Inputs, output: Outputs, session: Session): @render.plot(alt="A histogram") def plot() -> object: - np.random.seed(19680801) - x = 100 + 15 * np.random.randn(437) + x = 100 + 15 * np.random.default_rng(seed=19680801).standard_normal(437) fig, ax = plt.subplots() ax.hist(x, input.n(), density=True) diff --git a/shiny/experimental/ui/_deprecated.py b/shiny/experimental/ui/_deprecated.py index e35a0dcbd..4a1b74e33 100644 --- a/shiny/experimental/ui/_deprecated.py +++ b/shiny/experimental/ui/_deprecated.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Literal, Optional, Sequence, TypeVar, overload +from typing import TYPE_CHECKING, Any, Literal, Optional, Sequence, TypeVar, overload from htmltools import ( MetadataNode, @@ -46,10 +46,6 @@ from ...ui._page import page_fillable as main_page_fillable from ...ui._page import page_navbar as main_page_navbar from ...ui._page import page_sidebar as main_page_sidebar -from ...ui._plot_output_opts import BrushOpts as MainBrushOpts -from ...ui._plot_output_opts import ClickOpts as MainClickOpts -from ...ui._plot_output_opts import DblClickOpts as MainDblClickOpts -from ...ui._plot_output_opts import HoverOpts as MainHoverOpts from ...ui._sidebar import DeprecatedPanelMain, DeprecatedPanelSidebar from ...ui._sidebar import Sidebar as MainSidebar from ...ui._sidebar import SidebarOpenSpec as MainSidebarOpenSpec @@ -72,6 +68,12 @@ from ...ui.fill._fill import is_fill_item as main_is_fill_item from ...ui.fill._fill import is_fillable_container as main_is_fillable_container +if TYPE_CHECKING: + from ...ui._plot_output_opts import BrushOpts as MainBrushOpts + from ...ui._plot_output_opts import ClickOpts as MainClickOpts + from ...ui._plot_output_opts import DblClickOpts as MainDblClickOpts + from ...ui._plot_output_opts import HoverOpts as MainHoverOpts + __all__ = ( # Input Switch "toggle_switch", @@ -727,8 +729,6 @@ class AccordionPanel(MainAccordionPanel): Deprecated. Please use `shiny.ui.AccordionPanel` instead. """ - ... - # Deprecated 2023-09-12 def accordion( diff --git a/shiny/express/_output.py b/shiny/express/_output.py index a09484c52..6d89b1eb5 100644 --- a/shiny/express/_output.py +++ b/shiny/express/_output.py @@ -1,13 +1,16 @@ from __future__ import annotations -from contextlib import AbstractContextManager -from typing import Callable, TypeVar +from typing import TYPE_CHECKING, Callable, TypeVar from .._deprecated import warn_deprecated from .._typing_extensions import ParamSpec -from ..render.renderer import RendererT from .ui import hold +if TYPE_CHECKING: + from contextlib import AbstractContextManager + + from ..render.renderer import RendererT + __all__ = ("suspend_display",) P = ParamSpec("P") diff --git a/shiny/express/_recall_context.py b/shiny/express/_recall_context.py index aad43bb37..337b0bb7e 100644 --- a/shiny/express/_recall_context.py +++ b/shiny/express/_recall_context.py @@ -2,13 +2,15 @@ import functools import sys -from types import TracebackType -from typing import Callable, Generic, Mapping, Optional, Type, TypeVar +from typing import TYPE_CHECKING, Callable, Generic, Mapping, Optional, Type, TypeVar from htmltools import MetadataNode, Tag, TagList, wrap_displayhook_handler from .._typing_extensions import ParamSpec +if TYPE_CHECKING: + from types import TracebackType + P = ParamSpec("P") R = TypeVar("R") U = TypeVar("U") @@ -24,7 +26,7 @@ def __init__( ): self.fn = fn if args is None: - args = tuple() + args = () if kwargs is None: kwargs = {} self.args: list[object] = list(args) diff --git a/shiny/express/_run.py b/shiny/express/_run.py index cdfc9a9d0..2373c8613 100644 --- a/shiny/express/_run.py +++ b/shiny/express/_run.py @@ -4,7 +4,7 @@ import os import sys from pathlib import Path -from typing import cast +from typing import TYPE_CHECKING, cast from htmltools import Tag, TagList @@ -15,13 +15,15 @@ from ..session import Inputs, Outputs, Session, get_current_session, session_context from ..types import MISSING, MISSING_TYPE from ._mock_session import ExpressMockSession -from ._recall_context import RecallContextManager from .expressify_decorator._func_displayhook import _expressify_decorator_function_def from .expressify_decorator._node_transformers import ( DisplayFuncsTransformer, expressify_decorator_func_name, ) +if TYPE_CHECKING: + from ._recall_context import RecallContextManager + __all__ = ( "app_opts", "wrap_express_app", diff --git a/shiny/express/app.py b/shiny/express/app.py index 9612d0892..5e7bcea38 100644 --- a/shiny/express/app.py +++ b/shiny/express/app.py @@ -1,11 +1,14 @@ from __future__ import annotations from pathlib import Path +from typing import TYPE_CHECKING -from .._app import App from ._run import wrap_express_app from ._utils import unescape_from_var_name +if TYPE_CHECKING: + from .._app import App + # If someone requests shiny.express.app:_2f_path_2f_to_2f_app_2e_py, then we will call # wrap_express_app(Path("/path/to/app.py")) and return the result. diff --git a/shiny/express/expressify_decorator/_node_transformers.py b/shiny/express/expressify_decorator/_node_transformers.py index b80333f7e..478235d4e 100644 --- a/shiny/express/expressify_decorator/_node_transformers.py +++ b/shiny/express/expressify_decorator/_node_transformers.py @@ -1,11 +1,13 @@ from __future__ import annotations import ast -import types -from typing import Any, Callable, cast +from typing import TYPE_CHECKING, Any, Callable, cast from ._helpers import ast_matches_func +if TYPE_CHECKING: + import types + sys_alias = "__auto_displayhook_sys__" expressify_decorator_func_name = "_expressify_decorator_function_def" diff --git a/shiny/express/ui/_cm_components.py b/shiny/express/ui/_cm_components.py index f7ff8386c..f749a9104 100644 --- a/shiny/express/ui/_cm_components.py +++ b/shiny/express/ui/_cm_components.py @@ -1,22 +1,25 @@ "Context manager components for Shiny Express" +# ruff: noqa: C408 # https://docs.astral.sh/ruff/rules/unnecessary-literal-dict/ from __future__ import annotations -from typing import Literal, Optional - -from htmltools import Tag, TagAttrs, TagAttrValue, TagChild, TagFunction, TagList +from typing import TYPE_CHECKING, Literal, Optional from ... import ui from ..._docstring import add_example, no_example from ...types import MISSING, MISSING_TYPE -from ...ui._accordion import AccordionPanel -from ...ui._card import CardItem -from ...ui._layout_columns import BreakpointsUser -from ...ui._navs import NavMenu, NavPanel, NavSet, NavSetBar, NavSetCard -from ...ui._sidebar import SidebarOpenSpec, SidebarOpenValue -from ...ui.css import CssUnit from .._recall_context import RecallContextManager +if TYPE_CHECKING: + from htmltools import Tag, TagAttrs, TagAttrValue, TagChild, TagFunction, TagList + + from ...ui._accordion import AccordionPanel + from ...ui._card import CardItem + from ...ui._layout_columns import BreakpointsUser + from ...ui._navs import NavMenu, NavPanel, NavSet, NavSetBar, NavSetCard + from ...ui._sidebar import SidebarOpenSpec, SidebarOpenValue + from ...ui.css import CssUnit + __all__ = ( "sidebar", "layout_sidebar", diff --git a/shiny/express/ui/_hold.py b/shiny/express/ui/_hold.py index b6fe8d295..be5c820dd 100644 --- a/shiny/express/ui/_hold.py +++ b/shiny/express/ui/_hold.py @@ -1,14 +1,16 @@ from __future__ import annotations import sys -from types import TracebackType -from typing import Callable, Optional, Type, TypeVar +from typing import TYPE_CHECKING, Callable, Optional, Type, TypeVar from htmltools import wrap_displayhook_handler from ..._docstring import no_example from ..._typing_extensions import ParamSpec +if TYPE_CHECKING: + from types import TracebackType + __all__ = ("hold",) P = ParamSpec("P") @@ -46,7 +48,7 @@ def hold() -> HoldContextManager: class HoldContextManager: def __init__(self): - self.content: list[object] = list() + self.content: list[object] = [] def __enter__(self) -> list[object]: self.prev_displayhook = sys.displayhook diff --git a/shiny/express/ui/_page.py b/shiny/express/ui/_page.py index d005540e9..9ff00060d 100644 --- a/shiny/express/ui/_page.py +++ b/shiny/express/ui/_page.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import Callable - -from htmltools import Tag +from typing import TYPE_CHECKING, Callable from ... import ui from ..._docstring import no_example @@ -10,6 +8,9 @@ from .._recall_context import RecallContextManager from .._run import get_top_level_recall_context_manager +if TYPE_CHECKING: + from htmltools import Tag + __all__ = ("page_opts",) diff --git a/shiny/http_staticfiles.py b/shiny/http_staticfiles.py index db38f543c..257131050 100644 --- a/shiny/http_staticfiles.py +++ b/shiny/http_staticfiles.py @@ -17,8 +17,11 @@ ) import sys +from typing import TYPE_CHECKING -from starlette.background import BackgroundTask +if TYPE_CHECKING: + from starlette.background import BackgroundTask + from starlette.types import Receive, Scope, Send if "pyodide" not in sys.modules: # Running in native mode; use starlette StaticFiles @@ -39,7 +42,6 @@ from typing import Iterable, MutableMapping, Optional from starlette.responses import PlainTextResponse - from starlette.types import Receive, Scope, Send from . import _utils diff --git a/shiny/plotutils.py b/shiny/plotutils.py index 9d513a248..4d7eef4d0 100644 --- a/shiny/plotutils.py +++ b/shiny/plotutils.py @@ -10,12 +10,13 @@ from typing import TYPE_CHECKING, Literal, Optional, Union, cast from ._typing_extensions import TypedDict -from .types import BrushInfo, CoordInfo, CoordXY if TYPE_CHECKING: import numpy.typing as npt import pandas as pd + from .types import BrushInfo, CoordInfo, CoordXY + DataFrameColumn = Union[ "pd.Series[int]", "pd.Series[float]", @@ -227,7 +228,7 @@ def near_points( # For no current coordinfo if coordinfo is None: if add_dist: - new_df["dist"] = np.NaN + new_df["dist"] = np.nan if all_rows: new_df["selected_"] = False diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index f82658d09..5c7b1498c 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -483,7 +483,7 @@ def __init__( if isinstance(fn, Renderer): raise TypeError( "`@reactive.effect` can not be combined with `@render.xx`.\n" - + "Please remove your call of `@reactive.effect`." + "Please remove your call of `@reactive.effect`." ) # The EffectAsync subclass will pass in an async function, but it tells the @@ -794,10 +794,10 @@ def event( ``@render.ui``, etc). """ - if any([not callable(arg) for arg in args]): + if any(not callable(arg) for arg in args): raise TypeError( "All objects passed to event decorator must be callable.\n" - + "If you are calling `@reactive.event(f())`, try calling `@reactive.event(f)` instead." + "If you are calling `@reactive.event(f())`, try calling `@reactive.event(f)` instead." ) if len(args) == 0: @@ -809,14 +809,14 @@ def decorator(user_fn: Callable[[], T]) -> Callable[[], T]: if not callable(user_fn): raise TypeError( "`@reactive.event()` must be applied to a function or Callable object.\n" - + "It should usually be applied before `@Calc`,` @Effect`, or `@render.xx` function.\n" - + "In other words, `@reactive.event()` goes below the other decorators." + "It should usually be applied before `@Calc`,` @Effect`, or `@render.xx` function.\n" + "In other words, `@reactive.event()` goes below the other decorators." ) if isinstance(user_fn, Calc_): raise TypeError( "`@reactive.event()` must be applied before `@reactive.calc`.\n" - + "In other words, `@reactive.calc` must be above `@reactive.event()`." + "In other words, `@reactive.calc` must be above `@reactive.event()`." ) # This is here instead of at the top of the .py file in order to avoid a @@ -828,7 +828,7 @@ def decorator(user_fn: Callable[[], T]) -> Callable[[], T]: # use case. For now we'll disallow it, for simplicity. raise TypeError( "`@reactive.event()` must be applied before `@render.xx` .\n" - + "In other words, `@render.xx` must be above `@reactive.event()`." + "In other words, `@render.xx` must be above `@reactive.event()`." ) initialized = False @@ -862,10 +862,10 @@ async def new_user_async_fn(): return new_user_async_fn # type: ignore - elif any([is_async_callable(arg) for arg in args]): + elif any(is_async_callable(arg) for arg in args): raise TypeError( - "When decorating a synchronous function with @reactive.event(), all" - + "arguments to @reactive.event() must be synchronous functions." + "When decorating a synchronous function with @reactive.event(), all " + "arguments to @reactive.event() must be synchronous functions." ) else: diff --git a/shiny/render/_coordmap.py b/shiny/render/_coordmap.py index e82fcb903..18baa25a2 100644 --- a/shiny/render/_coordmap.py +++ b/shiny/render/_coordmap.py @@ -5,17 +5,6 @@ from copy import deepcopy from typing import TYPE_CHECKING, Any, cast -from ..types import ( - Coordmap, - CoordmapDims, - CoordmapPanel, - CoordmapPanelDomain, - CoordmapPanelLog, - CoordmapPanelMapping, - CoordmapPanelRange, - PlotnineFigure, -) - if TYPE_CHECKING: import numpy as np import numpy.typing as npt @@ -23,6 +12,17 @@ from matplotlib.figure import Figure from matplotlib.gridspec import SubplotSpec + from ..types import ( + Coordmap, + CoordmapDims, + CoordmapPanel, + CoordmapPanelDomain, + CoordmapPanelLog, + CoordmapPanelMapping, + CoordmapPanelRange, + PlotnineFigure, + ) + def get_coordmap(fig: Figure) -> Coordmap | None: dims_ar = fig.get_size_inches() * fig.get_dpi() diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index 16c0509f3..1d770d138 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -4,8 +4,6 @@ import json from typing import TYPE_CHECKING, Any, Literal, Protocol, Union, cast, runtime_checkable -from htmltools import Tag - from .. import ui from .._docstring import add_example, no_example from ._dataframe_unsafe import serialize_numpy_dtypes @@ -13,6 +11,7 @@ if TYPE_CHECKING: import pandas as pd + from htmltools import Tag class AbstractTabularData(abc.ABC): @@ -96,15 +95,15 @@ def __init__( def to_payload(self) -> Jsonifiable: res = serialize_pandas_df(self.data) - res["options"] = dict( - width=self.width, - height=self.height, - summary=self.summary, - filters=self.filters, - row_selection_mode=self.row_selection_mode, - style="grid", - fill=self.height is None, - ) + res["options"] = { + "width": self.width, + "height": self.height, + "summary": self.summary, + "filters": self.filters, + "row_selection_mode": self.row_selection_mode, + "style": "grid", + "fill": self.height is None, + } return res @@ -164,9 +163,7 @@ def __init__( height: Union[str, float, None] = "500px", summary: Union[bool, str] = True, filters: bool = False, - row_selection_mode: Union[ - Literal["none"], Literal["single"], Literal["multiple"] - ] = "none", + row_selection_mode: Literal["none", "single", "multiple"] = "none", ): import pandas as pd @@ -186,14 +183,14 @@ def __init__( def to_payload(self) -> Jsonifiable: res = serialize_pandas_df(self.data) - res["options"] = dict( - width=self.width, - height=self.height, - summary=self.summary, - filters=self.filters, - row_selection_mode=self.row_selection_mode, - style="table", - ) + res["options"] = { + "width": self.width, + "height": self.height, + "summary": self.summary, + "filters": self.filters, + "row_selection_mode": self.row_selection_mode, + "style": "table", + } return res diff --git a/shiny/render/_express.py b/shiny/render/_express.py index cc7d65b60..a37fb2fd2 100644 --- a/shiny/render/_express.py +++ b/shiny/render/_express.py @@ -1,12 +1,11 @@ from __future__ import annotations import sys -from typing import Optional +from typing import TYPE_CHECKING, Optional from htmltools import Tag, TagAttrValue, TagFunction, TagList, wrap_displayhook_handler from .. import ui as _ui -from .._typing_extensions import Self from ..session._utils import require_active_session from ..types import MISSING, MISSING_TYPE from .renderer import AsyncValueFn, Renderer, ValueFn @@ -16,6 +15,9 @@ set_kwargs_value, ) +if TYPE_CHECKING: + from .._typing_extensions import Self + class express(Renderer[None]): """ diff --git a/shiny/render/_render.py b/shiny/render/_render.py index af4c30c95..23cace29a 100644 --- a/shiny/render/_render.py +++ b/shiny/render/_render.py @@ -24,13 +24,13 @@ if TYPE_CHECKING: import pandas as pd + from .._typing_extensions import Self from ..session._utils import RenderedDeps from .. import _utils from .. import ui as _ui from .._docstring import add_example, no_example from .._namespaces import ResolvedId -from .._typing_extensions import Self from ..express._mock_session import ExpressMockSession from ..session import get_current_session, require_active_session from ..session._session import DownloadHandler, DownloadInfo @@ -384,8 +384,8 @@ def cast_result(result: ImgData | None) -> dict[str, Jsonifiable] | None: raise Exception( f"@render.plot doesn't know to render objects of type '{str(type(x))}'. " - + "Consider either requesting support for this type of plot object, and/or " - + " explictly saving the object to a (png) file and using @render.image." + "Consider either requesting support for this type of plot object, and/or " + " explictly saving the object to a (png) file and using @render.image." ) @@ -535,7 +535,7 @@ def __init__( # TODO: deal with kwargs collision with output_table async def transform(self, value: TableResult) -> dict[str, Jsonifiable]: - import pandas + import pandas as pd import pandas.io.formats.style html: str @@ -545,7 +545,7 @@ async def transform(self, value: TableResult) -> dict[str, Jsonifiable]: value.to_html(**self.kwargs), # pyright: ignore ) else: - if not isinstance(value, pandas.DataFrame): + if not isinstance(value, pd.DataFrame): if not isinstance(value, PandasCompatible): raise TypeError( "@render.table doesn't know how to render objects of type " diff --git a/shiny/render/_try_render_plot.py b/shiny/render/_try_render_plot.py index c54789fc1..dff6e6d47 100644 --- a/shiny/render/_try_render_plot.py +++ b/shiny/render/_try_render_plot.py @@ -137,8 +137,7 @@ def try_render_matplotlib( return (False, None) try: - import matplotlib - import matplotlib.pyplot as plt # pyright: ignore[reportUnusedImport] # noqa: F401 + import matplotlib.pyplot as plt # pyright: ignore[reportUnusedImport] pixelratio = plot_size_info.pixelratio @@ -226,9 +225,9 @@ def try_render_matplotlib( return (True, res) finally: - import matplotlib.pyplot + import matplotlib.pyplot as plt - matplotlib.pyplot.close(fig) # pyright: ignore[reportUnknownMemberType] + plt.close(fig) # pyright: ignore[reportUnknownMemberType] def get_matplotlib_figure(x: object, allow_global: bool) -> Figure | None: # pyright: ignore @@ -258,8 +257,8 @@ def get_matplotlib_figure(x: object, allow_global: bool) -> Figure | None: # py if isinstance(x, Animation): raise RuntimeError( "Matplotlib's Animation class isn't supported by @render.plot. " - + "Consider explictly saving the animation to a file and " - + "then using @render.image instead to render it." + "Consider explictly saving the animation to a file and " + "then using @render.image instead to render it." ) # Libraries like pandas, xarray, etc have plot() methods that can return a wide diff --git a/shiny/render/renderer/__init__.py b/shiny/render/renderer/__init__.py index 057a08725..af692e879 100644 --- a/shiny/render/renderer/__init__.py +++ b/shiny/render/renderer/__init__.py @@ -1,4 +1,4 @@ -from ._renderer import ( # noqa: F401 +from ._renderer import ( Renderer, ValueFn, Jsonifiable, diff --git a/shiny/render/renderer/_renderer.py b/shiny/render/renderer/_renderer.py index ba4b28d88..022107c16 100644 --- a/shiny/render/renderer/_renderer.py +++ b/shiny/render/renderer/_renderer.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import ( + TYPE_CHECKING, Any, Awaitable, Callable, @@ -17,9 +18,11 @@ from htmltools import MetadataNode, Tag, TagList from ..._docstring import add_example -from ..._typing_extensions import Self from ..._utils import is_async_callable, wrap_async +if TYPE_CHECKING: + from ..._typing_extensions import Self + # TODO-barret-future: Double check docs are rendererd # Missing first paragraph from some classes: Example: TransformerMetadata. # No init method for TransformerParams. This is because the `DocClass` object does not @@ -137,7 +140,7 @@ class Renderer(Generic[IT]): # Q: Could we do this with typing without putting `P` in the Generic? # A: No. Even if we had a `P` in the Generic, the calling decorator would not have access to it. # Idea: Possibly use a chained method of `.ui_kwargs()`? https://github.com/posit-dev/py-shiny/issues/971 - _auto_output_ui_kwargs: dict[str, Any] = dict() + _auto_output_ui_kwargs: dict[str, Any] = {} __name__: str """ diff --git a/shiny/render/renderer/_utils.py b/shiny/render/renderer/_utils.py index 8bed1c415..74eedfe20 100644 --- a/shiny/render/renderer/_utils.py +++ b/shiny/render/renderer/_utils.py @@ -1,13 +1,15 @@ from __future__ import annotations -from typing import Any, Dict, cast +from typing import TYPE_CHECKING, Any, Dict, cast -from htmltools import TagFunction - -from ...session._utils import RenderedDeps from ...types import MISSING_TYPE, ImgData from ._renderer import Jsonifiable +if TYPE_CHECKING: + from htmltools import TagFunction + + from ...session._utils import RenderedDeps + JsonifiableDict = Dict[str, Jsonifiable] diff --git a/shiny/render/transformer/_transformer.py b/shiny/render/transformer/_transformer.py index 1476e1679..5a2a01e3f 100644 --- a/shiny/render/transformer/_transformer.py +++ b/shiny/render/transformer/_transformer.py @@ -33,10 +33,10 @@ ) from ..renderer import Jsonifiable, Renderer -from ..renderer._renderer import DefaultUIFn, DefaultUIFnResultOrNone if TYPE_CHECKING: from ...session import Session + from ..renderer._renderer import DefaultUIFn, DefaultUIFnResultOrNone from ..._deprecated import warn_deprecated from ..._docstring import add_example @@ -263,8 +263,8 @@ def __init__( self._default_ui = default_ui self._default_ui_passthrough_args = default_ui_passthrough_args - self._default_ui_args: tuple[object, ...] = tuple() - self._default_ui_kwargs: dict[str, object] = dict() + self._default_ui_args: tuple[object, ...] = () + self._default_ui_kwargs: dict[str, object] = {} # Allow for App authors to not require `@output` self._auto_register() diff --git a/shiny/session/__init__.py b/shiny/session/__init__.py index 8aecb2038..354014b66 100644 --- a/shiny/session/__init__.py +++ b/shiny/session/__init__.py @@ -3,7 +3,7 @@ """ from ._session import Session, Inputs, Outputs -from ._utils import ( # noqa: F401 +from ._utils import ( get_current_session, session_context as session_context, require_active_session, diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 1f401d5cc..fa0d954b9 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -190,8 +190,8 @@ def __init__( # query information about the request, like headers, cookies, etc. self.http_conn: HTTPConnection = conn.get_http_conn() - self.input: Inputs = Inputs(dict()) - self.output: Outputs = Outputs(self, self.ns, dict(), dict()) + self.input: Inputs = Inputs({}) + self.output: Outputs = Outputs(self, self.ns, {}, {}) self.user: str | None = None self.groups: list[str] | None = None @@ -677,7 +677,6 @@ def _send_message_sync(self, message: dict[str, object]) -> None: def _send_error_response(self, message_str: str) -> None: print("_send_error_response: " + message_str) - pass # ========================================================================== # Flush @@ -1064,7 +1063,7 @@ def set_renderer(renderer: RendererT) -> RendererT: if not isinstance(renderer, Renderer): raise TypeError( "`@output` must be applied to a `@render.xx` function.\n" - + "In other words, `@output` must be above `@render.xx`." + "In other words, `@output` must be above `@render.xx`." ) # Get the (possibly namespaced) output id diff --git a/shiny/types.py b/shiny/types.py index 89a22ac68..61551833e 100644 --- a/shiny/types.py +++ b/shiny/types.py @@ -14,12 +14,11 @@ from typing import TYPE_CHECKING, Any, BinaryIO, Literal, NamedTuple, Optional, Protocol -from htmltools import TagChild - from ._docstring import add_example from ._typing_extensions import NotRequired, TypedDict if TYPE_CHECKING: + from htmltools import TagChild from matplotlib.figure import Figure @@ -90,8 +89,6 @@ class SafeException(Exception): generate an error that is OK to be displayed to the user. """ - pass - @add_example() class SilentException(Exception): @@ -113,8 +110,6 @@ class SilentException(Exception): * :class:`~shiny.types.SilentCancelOutputException` """ - pass - @add_example() class SilentCancelOutputException(Exception): @@ -129,8 +124,6 @@ class SilentCancelOutputException(Exception): * :class:`~shiny.types.SilentException` """ - pass - class SilentOperationInProgressException(SilentException): # Throw a silent exception to indicate that an operation is in progress diff --git a/shiny/ui/_input_task_button.py b/shiny/ui/_input_task_button.py index 76229e915..bc1d60a0e 100644 --- a/shiny/ui/_input_task_button.py +++ b/shiny/ui/_input_task_button.py @@ -3,7 +3,7 @@ __all__ = ("input_task_button",) from functools import partial -from typing import Callable, Optional, TypeVar, cast, overload +from typing import TYPE_CHECKING, Callable, Optional, TypeVar, cast, overload from htmltools import HTML, Tag, TagAttrValue, TagChild, css, tags @@ -12,11 +12,13 @@ from .._docstring import add_example from .._namespaces import resolve_id from .._typing_extensions import ParamSpec -from ..reactive._extended_task import ExtendedTask from ..reactive._reactives import effect from ._html_deps_py_shiny import spin_dependency from ._html_deps_shinyverse import components_dependencies +if TYPE_CHECKING: + from ..reactive._extended_task import ExtendedTask + P = ParamSpec("P") R = TypeVar("R") diff --git a/shiny/ui/_input_update.py b/shiny/ui/_input_update.py index e7c8a7760..332a11a81 100644 --- a/shiny/ui/_input_update.py +++ b/shiny/ui/_input_update.py @@ -20,11 +20,9 @@ import json import re -from datetime import date from typing import TYPE_CHECKING, Literal, Mapping, Optional, cast, overload from htmltools import TagChild, TagList, tags -from starlette.requests import Request from starlette.responses import JSONResponse, Response from .._docstring import add_example, doc_format, no_example @@ -41,6 +39,10 @@ from ._utils import JSEval, _session_on_flush_send_msg, extract_js_keys if TYPE_CHECKING: + from datetime import date + + from starlette.requests import Request + from .. import Session @@ -751,10 +753,10 @@ def selectize_choices_json(request: Request) -> Response: if isinstance(search_fields[0], list): search_fields = search_fields[0] - if set(search_fields).difference(set(["label", "value", "optgroup"])): + if set(search_fields).difference({"label", "value", "optgroup"}): raise ValueError( "The selectize.js searchFields option must contain some combination of: " - + "'label', 'value', and 'optgroup'" + "'label', 'value', and 'optgroup'" ) # i.e. valueField (defaults to 'value') diff --git a/shiny/ui/_layout_columns.py b/shiny/ui/_layout_columns.py index d2de2d38e..e63de30bb 100644 --- a/shiny/ui/_layout_columns.py +++ b/shiny/ui/_layout_columns.py @@ -201,7 +201,8 @@ def validate_col_width( if len(list(y)) > n_kids: warn( - f"More column widths than children at breakpoint '{break_name}', extra widths will be ignored." + f"More column widths than children at breakpoint '{break_name}', extra widths will be ignored.", + stacklevel=2, ) return y @@ -262,9 +263,7 @@ def row_heights_attrs( # We use classes to activate CSS variables at the right breakpoints. Note: Mobile # row height is derived from xs or defaults to auto in the CSS, so we don't need the # class to activate it - classes = [ - f"bslib-grid--row-heights--{brk}" for brk in x_complete.keys() if brk != "xs" - ] + classes = [f"bslib-grid--row-heights--{brk}" for brk in x_complete if brk != "xs"] # Create CSS variables, treating numeric values as fractional units, passing strings css_vars: Dict[str, str] = {} diff --git a/shiny/ui/_modal.py b/shiny/ui/_modal.py index c24d164ab..57a11d2dd 100644 --- a/shiny/ui/_modal.py +++ b/shiny/ui/_modal.py @@ -7,15 +7,17 @@ "modal_remove", ) -from typing import Literal, Optional +from typing import TYPE_CHECKING, Literal, Optional from htmltools import HTML, Tag, TagAttrs, TagAttrValue, TagChild, div, tags from .._docstring import add_example from ..session import require_active_session -from ..session._session import Session from ..types import MISSING, MISSING_TYPE +if TYPE_CHECKING: + from ..session._session import Session + @add_example(ex_dir="../api-examples/modal") def modal_button(label: TagChild, icon: TagChild = None, **kwargs: TagAttrValue) -> Tag: @@ -129,16 +131,13 @@ def modal( ) # jQuery plugin doesn't work in Bootstrap 5, but vanilla JS doesn't work in Bootstrap 4 :sob: - js = "\n".join( - [ - "if (window.bootstrap && !window.bootstrap.Modal.VERSION.match(/^4\\. /)) {", - " var modal=new bootstrap.Modal(document.getElementById('shiny-modal'))", - " modal.show()", - "} else {", - " $('#shiny-modal').modal().focus()", - "}", - ] - ) + js = """\ +if (window.bootstrap && !window.bootstrap.Modal.VERSION.match(/^4\\. /)) { + var modal=new bootstrap.Modal(document.getElementById('shiny-modal')) + modal.show() +} else { + $('#shiny-modal').modal().focus() +}""" backdrop = None if easy_close else "static" keyboard = None if easy_close else "false" diff --git a/shiny/ui/_notification.py b/shiny/ui/_notification.py index 33593dce2..00804a1a1 100644 --- a/shiny/ui/_notification.py +++ b/shiny/ui/_notification.py @@ -4,13 +4,13 @@ from typing import TYPE_CHECKING, Any, Literal, Optional -from htmltools import TagChild - from .._docstring import add_example, no_example from .._utils import rand_hex from ..session import require_active_session if TYPE_CHECKING: + from htmltools import TagChild + from .. import Session diff --git a/shiny/ui/_output.py b/shiny/ui/_output.py index 2b21ae193..a509076dc 100644 --- a/shiny/ui/_output.py +++ b/shiny/ui/_output.py @@ -195,7 +195,7 @@ def output_image( height = f"{height}px" if isinstance(height, (float, int)) else height style = None if inline else css(width=width, height=height) - args: dict[str, str] = dict() + args: dict[str, str] = {} id_resolved = resolve_id(id) diff --git a/shiny/ui/_plot_output_opts.py b/shiny/ui/_plot_output_opts.py index d9c542b8b..c1e387d7d 100644 --- a/shiny/ui/_plot_output_opts.py +++ b/shiny/ui/_plot_output_opts.py @@ -44,7 +44,7 @@ def format_opt_names( opts: ClickOpts | DblClickOpts | HoverOpts | BrushOpts, prefix: str, ) -> dict[str, str]: - new_opts: dict[str, str] = dict() + new_opts: dict[str, str] = {} for key, value in opts.items(): new_key = f"data-{prefix}-" + re.sub("([A-Z])", "-\\1", key).lower() new_value = str(value) diff --git a/shiny/ui/_progress.py b/shiny/ui/_progress.py index 5f5ad764c..f18a682da 100644 --- a/shiny/ui/_progress.py +++ b/shiny/ui/_progress.py @@ -2,17 +2,18 @@ __all__ = ("Progress",) -from types import TracebackType from typing import TYPE_CHECKING, Optional, Type from warnings import warn from .._docstring import add_example from .._utils import rand_hex from ..session import require_active_session -from ..session._session import UpdateProgressMessage if TYPE_CHECKING: + from types import TracebackType + from .. import Session + from ..session._session import UpdateProgressMessage @add_example() @@ -95,7 +96,9 @@ def set( """ if self._closed: - warn("Attempting to set progress, but progress already closed.") + warn( + "Attempting to set progress, but progress already closed.", stacklevel=2 + ) return None self.value = value @@ -164,7 +167,10 @@ def close(self) -> None: Removes the progress panel. Future calls to set and close will be ignored. """ if self._closed: - warn("Attempting to close progress, but progress already closed.") + warn( + "Attempting to close progress, but progress already closed.", + stacklevel=2, + ) return None self._session._send_progress("close", {"id": self._id, "style": self._style}) diff --git a/shiny/ui/_sidebar.py b/shiny/ui/_sidebar.py index bff18727e..2baec5b6a 100644 --- a/shiny/ui/_sidebar.py +++ b/shiny/ui/_sidebar.py @@ -138,7 +138,7 @@ def _as_open( raise ValueError( f"""`open` must be one of {SidebarOpen._values_str()}, """ - + "or a dictionary with keys `desktop` and `mobile` using these values." + "or a dictionary with keys `desktop` and `mobile` using these values." ) @@ -321,9 +321,9 @@ def max_height_mobile(self) -> Optional[str]: if max_height_mobile is not None and self.open().mobile != "always": warnings.warn( "The `shiny.ui.sidebar(max_height_mobile=)` argument only applies to " - + "the sidebar when `open` is `'always'` on mobile, but " - + f"`open` is `'{self.open().mobile}'`. " - + "The `max_height_mobile` argument will be ignored.", + "the sidebar when `open` is `'always'` on mobile, but " + f"`open` is `'{self.open().mobile}'`. " + "The `max_height_mobile` argument will be ignored.", # `stacklevel=2`: Refers to the caller of `.max_height_mobile` property method stacklevel=2, ) diff --git a/shiny/ui/_utils.py b/shiny/ui/_utils.py index aa9d80a95..d526ad3ff 100644 --- a/shiny/ui/_utils.py +++ b/shiny/ui/_utils.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Dict, List, Optional, cast, overload +from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast, overload from htmltools import ( HTMLDependency, @@ -12,10 +12,12 @@ tags, ) -from .._typing_extensions import TypeGuard from ..session import Session, require_active_session from ..types import MISSING, MISSING_TYPE +if TYPE_CHECKING: + from .._typing_extensions import TypeGuard + def shiny_input_label(id: str, label: TagChild = None) -> Tag: cls = "control-label" + ("" if label else " shiny-label-null") diff --git a/shiny/ui/_valuebox.py b/shiny/ui/_valuebox.py index f374554a4..90d07c0a1 100644 --- a/shiny/ui/_valuebox.py +++ b/shiny/ui/_valuebox.py @@ -290,7 +290,7 @@ def value_box_theme( """e.g. `"primary"`, `"danger"`, `"purple"`, etc.""" ) - if not (name.startswith("bg-") or name.startswith("text-")): + if not (name.startswith(("bg-", "text-"))): name = "bg-" + name return ValueBoxTheme(class_=name, bg=bg, fg=fg) diff --git a/tests/playwright/conftest.py b/tests/playwright/conftest.py index 6707dd25e..95d5a3b92 100644 --- a/tests/playwright/conftest.py +++ b/tests/playwright/conftest.py @@ -10,9 +10,9 @@ from contextlib import contextmanager from pathlib import PurePath from time import sleep -from types import TracebackType from typing import ( IO, + TYPE_CHECKING, Any, Callable, Generator, @@ -39,7 +39,13 @@ "retry_with_timeout", ) -from playwright.sync_api import BrowserContext, Page + +if TYPE_CHECKING: + from types import TracebackType + + from playwright.sync_api import BrowserContext, Page + + from shiny._typing_extensions import Self # Make a single page fixture that can be used by all tests @@ -49,7 +55,7 @@ def session_page(browser: BrowserContext) -> Page: return browser.new_page() -@pytest.fixture(scope="function") +@pytest.fixture() # By going to `about:blank`, we _reset_ the page to a known state before each test. # It is not perfect, but it is faster than making a new page for each test. # This must be done before each test @@ -149,7 +155,7 @@ def close(self) -> None: sleep(0.5) self.proc.terminate() - def __enter__(self) -> ShinyAppProc: + def __enter__(self) -> Self: return self def __exit__( diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index 1d3e5490e..fde8d762d 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -3,7 +3,6 @@ from __future__ import annotations import json -import pathlib import re import sys import time @@ -22,6 +21,9 @@ ) from shiny.types import MISSING, MISSING_TYPE +if typing.TYPE_CHECKING: + import pathlib + """ Questions: * `_DateBase` is signaled as private, but `InputDateRange` will have two fields of `date_start` and `date_end`. Due to how the init selectors are created, they are not `InputDate` instances. Should we make `_DateBase` public? @@ -497,7 +499,6 @@ class InputPassword( # *, # width: Optional[str] = None, # placeholder: Optional[str] = None, - ... def __init__(self, page: Page, id: str) -> None: super().__init__( @@ -1685,7 +1686,7 @@ def slow_move(x: float, y: float, delay: float = sleep_time) -> None: values_found_txt = ", ".join([f'"{key}"' for key in key_arr]) raise ValueError( f"Could not find value '{value}' when moving slider from {error_msg_direction}\n" - + f"Values found:\n{values_found_txt}{trail_txt}" + f"Values found:\n{values_found_txt}{trail_txt}" ) def _grid_bb(self, *, timeout: Timeout = None) -> FloatRect: diff --git a/tests/playwright/deploys/plotly/app.py b/tests/playwright/deploys/plotly/app.py index bf90aa3f7..d36e1429f 100644 --- a/tests/playwright/deploys/plotly/app.py +++ b/tests/playwright/deploys/plotly/app.py @@ -1,5 +1,5 @@ # App altered from: https://github.com/rstudio/py-shiny/blob/main/shiny/api-examples/data_frame/app.py -import pandas # noqa: F401 (this line needed for Shinylive to load plotly.express) +import pandas as pd # noqa: F401 (this line needed for Shinylive to load plotly.express) import plotly.express as px import plotly.graph_objs as go from shinywidgets import output_widget, render_widget diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 296c64f31..8b3ebc0a4 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -174,7 +174,7 @@ def on_console_msg(msg: ConsoleMessage) -> None: app_name = os.path.basename(os.path.dirname(ex_app_path)) short_app_path = f"{os.path.basename(os.path.dirname(os.path.dirname(ex_app_path)))}/{app_name}" - if short_app_path in app_hard_wait.keys(): + if short_app_path in app_hard_wait: # Apps are constantly invalidating and will not stabilize # Instead, wait for specific amount of time page.wait_for_timeout(app_hard_wait[short_app_path]) @@ -193,7 +193,7 @@ def on_console_msg(msg: ConsoleMessage) -> None: error_lines = [ line for line in error_lines - if not any([error_txt in line for error_txt in app_allow_external_errors]) + if not any(error_txt in line for error_txt in app_allow_external_errors) ] # Remove any app specific errors that are allowed @@ -208,11 +208,11 @@ def on_console_msg(msg: ConsoleMessage) -> None: app_allowable_errors = [app_allowable_errors] app_allowable_errors = ( # Remove ^INFO lines - ["INFO:"] + *["INFO:"], # Remove any known errors caused by external packages - + app_allow_external_errors + *app_allow_external_errors, # Remove any known errors allowed by the app - + app_allowable_errors + *app_allowable_errors, ) # If there is an array of allowable errors, remove them from errors. Ex: `PlotnineWarning` @@ -220,7 +220,7 @@ def on_console_msg(msg: ConsoleMessage) -> None: line for line in error_lines if len(line.strip()) > 0 - and not any([error_txt in line for error_txt in app_allowable_errors]) + and not any(error_txt in line for error_txt in app_allowable_errors) ] if len(error_lines) > 0: print("\nshort_app_path: " + short_app_path) @@ -237,10 +237,8 @@ def on_console_msg(msg: ConsoleMessage) -> None: line for line in console_errors if not any( - [ - error_txt in line - for error_txt in app_allow_js_errors[short_app_path] - ] + error_txt in line + for error_txt in app_allow_js_errors[short_app_path] ) ] assert len(console_errors) == 0, ( diff --git a/tests/playwright/shiny/TODO/navbar/app.py b/tests/playwright/shiny/TODO/navbar/app.py index d1a750dbc..eca2ba3f5 100644 --- a/tests/playwright/shiny/TODO/navbar/app.py +++ b/tests/playwright/shiny/TODO/navbar/app.py @@ -1,7 +1,11 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from shiny import App, ui -from shiny.types import NavSetArg + +if TYPE_CHECKING: + from shiny.types import NavSetArg my_sidebar = ui.sidebar("Sidebar content", open="open", title="Sidebar title") diff --git a/tests/playwright/shiny/bugs/0648-update-slider-datetime-value/test_update_slider_datetime_value.py b/tests/playwright/shiny/bugs/0648-update-slider-datetime-value/test_update_slider_datetime_value.py index b569d0de7..d8e398386 100644 --- a/tests/playwright/shiny/bugs/0648-update-slider-datetime-value/test_update_slider_datetime_value.py +++ b/tests/playwright/shiny/bugs/0648-update-slider-datetime-value/test_update_slider_datetime_value.py @@ -1,11 +1,13 @@ from __future__ import annotations -from typing import Optional +from typing import TYPE_CHECKING, Optional -from conftest import ShinyAppProc from controls import InputActionButton, InputSlider, OutputTextVerbatim from playwright.sync_api import Page, expect +if TYPE_CHECKING: + from conftest import ShinyAppProc + def test_slider_app(page: Page, local_app: ShinyAppProc) -> None: def check_case( diff --git a/tests/playwright/shiny/bugs/0666-sidebar/test_sidebar_colors.py b/tests/playwright/shiny/bugs/0666-sidebar/test_sidebar_colors.py index e376d16ed..0a72b89d0 100644 --- a/tests/playwright/shiny/bugs/0666-sidebar/test_sidebar_colors.py +++ b/tests/playwright/shiny/bugs/0666-sidebar/test_sidebar_colors.py @@ -1,10 +1,14 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from colors import bg_color, fg_color -from conftest import ShinyAppProc from controls import Sidebar, _expect_class_value from playwright.sync_api import Page, expect +if TYPE_CHECKING: + from conftest import ShinyAppProc + def test_colors_are_rgb() -> None: assert bg_color.startswith("rgb(") diff --git a/tests/playwright/shiny/bugs/0676-row-selection/app.py b/tests/playwright/shiny/bugs/0676-row-selection/app.py index 1c8fee0fd..cea93bd41 100644 --- a/tests/playwright/shiny/bugs/0676-row-selection/app.py +++ b/tests/playwright/shiny/bugs/0676-row-selection/app.py @@ -7,11 +7,11 @@ from shiny import App, Inputs, Outputs, Session, render, ui df = pd.DataFrame( - dict( - id=["one", "two", "three"], - fname=["Alice", "Bob", "Charlie"], - lname=["Smith", "Jones", "Brown"], - ) + { + "id": ["one", "two", "three"], + "fname": ["Alice", "Bob", "Charlie"], + "lname": ["Smith", "Jones", "Brown"], + } ).set_index( # type: ignore "id", drop=False, diff --git a/tests/playwright/shiny/bugs/0676-row-selection/test_0676_row_selection.py b/tests/playwright/shiny/bugs/0676-row-selection/test_0676_row_selection.py index 9f83665ec..8362f3937 100644 --- a/tests/playwright/shiny/bugs/0676-row-selection/test_0676_row_selection.py +++ b/tests/playwright/shiny/bugs/0676-row-selection/test_0676_row_selection.py @@ -1,8 +1,12 @@ from __future__ import annotations -from conftest import ShinyAppProc +from typing import TYPE_CHECKING + from playwright.sync_api import Page, expect +if TYPE_CHECKING: + from conftest import ShinyAppProc + def test_row_selection(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) diff --git a/tests/playwright/shiny/bugs/0696-resolve-id/app.py b/tests/playwright/shiny/bugs/0696-resolve-id/app.py index 5e0af336e..8dd4c7b3e 100644 --- a/tests/playwright/shiny/bugs/0696-resolve-id/app.py +++ b/tests/playwright/shiny/bugs/0696-resolve-id/app.py @@ -17,7 +17,9 @@ from shiny import App, Inputs, Outputs, Session, module, reactive, render, ui from shiny.session import session_context -from shiny.types import ImgData + +if typing.TYPE_CHECKING: + from shiny.types import ImgData pandas_df = pd.DataFrame( { @@ -31,7 +33,7 @@ img_path = pathlib.Path(__file__).parent / "imgs" penguin_imgs = [str(img_path / img) for img in os.listdir(img_path)] assert len(penguin_imgs) > 0 -letters = [letter for letter in "abcdefghijklmnopqrstuvwxyz"][: len(penguin_imgs)] +letters = list("abcdefghijklmnopqrstuvwxyz")[: len(penguin_imgs)] input_keys = ( "input_action_button", diff --git a/tests/playwright/shiny/bugs/0696-resolve-id/check.py b/tests/playwright/shiny/bugs/0696-resolve-id/check.py index 60801f94e..0d2e7919e 100644 --- a/tests/playwright/shiny/bugs/0696-resolve-id/check.py +++ b/tests/playwright/shiny/bugs/0696-resolve-id/check.py @@ -48,7 +48,7 @@ class ModState(NamedTuple): navset_tab: str -blacklist = set(["awesome_component"]) +blacklist = {"awesome_component"} x_input_keys = ("x_" + key for key in x_input_keys) diff --git a/tests/playwright/shiny/bugs/0696-resolve-id/mod_state.py b/tests/playwright/shiny/bugs/0696-resolve-id/mod_state.py index 4a0ad4210..94fe831be 100644 --- a/tests/playwright/shiny/bugs/0696-resolve-id/mod_state.py +++ b/tests/playwright/shiny/bugs/0696-resolve-id/mod_state.py @@ -1,9 +1,12 @@ from __future__ import annotations import datetime +from typing import TYPE_CHECKING from controls import OutputTextVerbatim -from playwright.sync_api import Page + +if TYPE_CHECKING: + from playwright.sync_api import Page def expect_state( diff --git a/tests/playwright/shiny/bugs/0696-resolve-id/test_0696_resolve_id.py b/tests/playwright/shiny/bugs/0696-resolve-id/test_0696_resolve_id.py index 299bbb264..f5cc68c66 100644 --- a/tests/playwright/shiny/bugs/0696-resolve-id/test_0696_resolve_id.py +++ b/tests/playwright/shiny/bugs/0696-resolve-id/test_0696_resolve_id.py @@ -4,9 +4,9 @@ import datetime import os from pathlib import Path +from typing import TYPE_CHECKING import pytest -from conftest import ShinyAppProc from controls import ( DownloadButton, DownloadLink, @@ -33,11 +33,14 @@ OutputUi, ) from mod_state import expect_default_mod_state, expect_mod_state -from playwright.sync_api import Page from examples.example_apps import reruns, reruns_delay from shiny._utils import guess_mime_type +if TYPE_CHECKING: + from conftest import ShinyAppProc + from playwright.sync_api import Page + img_path = Path(__file__).parent / "imgs" penguin_imgs = [str(img_path / img) for img in os.listdir(img_path)] diff --git a/tests/playwright/shiny/components/data_frame/test_data_frame.py b/tests/playwright/shiny/components/data_frame/test_data_frame.py index bddcc3059..6f710e73a 100644 --- a/tests/playwright/shiny/components/data_frame/test_data_frame.py +++ b/tests/playwright/shiny/components/data_frame/test_data_frame.py @@ -14,22 +14,22 @@ data_frame_app = create_example_fixture("dataframe") -@pytest.fixture +@pytest.fixture() def grid(page: Page) -> Locator: return page.locator("#grid") -@pytest.fixture +@pytest.fixture() def grid_container(page: Page, grid: Locator) -> Locator: return grid.locator("> div > div.shiny-data-grid") -@pytest.fixture +@pytest.fixture() def summary(page: Page, grid: Locator) -> Locator: return grid.locator("div.shiny-data-grid-summary") -@pytest.fixture +@pytest.fixture() def scroll_to_end(page: Page, grid_container: Locator) -> Callable[[], None]: def do(): grid_container.locator("tbody tr:first-child td:first-child").click() diff --git a/tests/playwright/shiny/components/layout_columns/test_layout_columns.py b/tests/playwright/shiny/components/layout_columns/test_layout_columns.py index 38f421c22..c3b0dd429 100644 --- a/tests/playwright/shiny/components/layout_columns/test_layout_columns.py +++ b/tests/playwright/shiny/components/layout_columns/test_layout_columns.py @@ -1,9 +1,11 @@ from __future__ import annotations -from typing import TypeVar +from typing import TYPE_CHECKING, TypeVar from conftest import ShinyAppProc, create_doc_example_core_fixture -from playwright.sync_api import Page + +if TYPE_CHECKING: + from playwright.sync_api import Page T = TypeVar("T") diff --git a/tests/playwright/shiny/components/nav/app.py b/tests/playwright/shiny/components/nav/app.py index ffe36f163..6b49b1dda 100644 --- a/tests/playwright/shiny/components/nav/app.py +++ b/tests/playwright/shiny/components/nav/app.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import Any, Callable, List - -from htmltools import Tag +from typing import TYPE_CHECKING, Any, Callable, List from shiny import App, ui -from shiny.types import NavSetArg -from shiny.ui import Sidebar + +if TYPE_CHECKING: + from htmltools import Tag + + from shiny.types import NavSetArg + from shiny.ui import Sidebar # TODO-karan; Make test that uses sidebar / no sidebar (where possible) # TODO-karan; Make test that has/does not have a header & footer (where possible) diff --git a/tests/playwright/shiny/components/nav/test_nav.py b/tests/playwright/shiny/components/nav/test_nav.py index 546f81090..d6a80859d 100644 --- a/tests/playwright/shiny/components/nav/test_nav.py +++ b/tests/playwright/shiny/components/nav/test_nav.py @@ -1,9 +1,9 @@ from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING import pytest -from conftest import ShinyAppProc from controls import ( LayoutNavSetBar, LayoutNavSetCardPill, @@ -14,7 +14,10 @@ LayoutNavsetTab, LayoutNavSetUnderline, ) -from playwright.sync_api import Page + +if TYPE_CHECKING: + from conftest import ShinyAppProc + from playwright.sync_api import Page @pytest.mark.skip_browser("webkit") diff --git a/tests/playwright/shiny/implicit-register/app.py b/tests/playwright/shiny/implicit-register/app.py index eb88220a1..7bb6d7ecd 100644 --- a/tests/playwright/shiny/implicit-register/app.py +++ b/tests/playwright/shiny/implicit-register/app.py @@ -1,11 +1,11 @@ from shiny import App, Inputs, Outputs, Session, render, ui -scenarios = dict( - out1="The following output should be empty", - out2='The following output should have the word "One"', - out3='The following output should have the word "Two"', - out4='The following output should also have the word "Two"', -) +scenarios = { + "out1": "The following output should be empty", + "out2": 'The following output should have the word "One"', + "out3": 'The following output should have the word "Two"', + "out4": 'The following output should also have the word "Two"', +} app_ui = ui.page_fluid( [ diff --git a/tests/playwright/shiny/inputs/input_file/app.py b/tests/playwright/shiny/inputs/input_file/app.py index af92f9c55..d825a0b75 100644 --- a/tests/playwright/shiny/inputs/input_file/app.py +++ b/tests/playwright/shiny/inputs/input_file/app.py @@ -1,9 +1,13 @@ +from __future__ import annotations + import typing import pandas as pd from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui -from shiny.types import FileInfo + +if typing.TYPE_CHECKING: + from shiny.types import FileInfo app_ui = ui.page_fluid( ui.input_file("file1", "Choose CSV File", accept=[".csv"], multiple=False), diff --git a/tests/playwright/shiny/inputs/input_file/test_input_file.py b/tests/playwright/shiny/inputs/input_file/test_input_file.py index 953c1ea35..be11c3120 100644 --- a/tests/playwright/shiny/inputs/input_file/test_input_file.py +++ b/tests/playwright/shiny/inputs/input_file/test_input_file.py @@ -1,9 +1,13 @@ from __future__ import annotations -from conftest import ShinyAppProc +from typing import TYPE_CHECKING + from controls import InputFile, OutputTable, OutputTextVerbatim from playwright.sync_api import FilePayload, Page, expect +if TYPE_CHECKING: + from conftest import ShinyAppProc + def test_input_file_kitchen(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) diff --git a/tests/playwright/shiny/inputs/input_radio_checkbox_group/test_input_radio_checkbox_group_app.py b/tests/playwright/shiny/inputs/input_radio_checkbox_group/test_input_radio_checkbox_group_app.py index 24f5b265b..cafadac14 100644 --- a/tests/playwright/shiny/inputs/input_radio_checkbox_group/test_input_radio_checkbox_group_app.py +++ b/tests/playwright/shiny/inputs/input_radio_checkbox_group/test_input_radio_checkbox_group_app.py @@ -1,9 +1,14 @@ from __future__ import annotations -from conftest import ShinyAppProc +from typing import TYPE_CHECKING + +import pytest from controls import InputCheckboxGroup, InputRadioButtons, PatternOrStr from playwright.sync_api import Page, expect +if TYPE_CHECKING: + from conftest import ShinyAppProc + def test_input_checkbox_group_kitchen(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) @@ -87,35 +92,32 @@ def test_locator_debugging(page: Page, local_app: ShinyAppProc) -> None: timeout = 100 # Non-existent div - try: - not_exist = InputRadioButtons(page, "does-not-exist") + not_exist = InputRadioButtons(page, "does-not-exist") + with pytest.raises(AssertionError) as e: not_exist.expect_choices(["a", "b", "c"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '1'" in str(e) - assert "Actual value: 0" in str(e) + + assert "expected to have count '1'" in str(e.value) + assert "Actual value: 0" in str(e.value) check1 = InputCheckboxGroup(page, "check1") # Make sure it works check1.expect_choices(["red", "green", "blue"]) # Too many - try: + with pytest.raises(AssertionError) as e: check1.expect_choices(["red", "green", "blue", "test_value"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '4'" in str(e) - assert "Actual value: 3" in str(e) + assert "expected to have count '4'" in str(e.value) + assert "Actual value: 3" in str(e.value) # Not enough - try: + with pytest.raises(AssertionError) as e: check1.expect_choices(["red", "green"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '2'" in str(e) - assert "Actual value: 3" in str(e) + assert "expected to have count '2'" in str(e.value) + assert "Actual value: 3" in str(e.value) # Wrong value - try: + with pytest.raises(AssertionError) as e: check1.expect_choices(["red", "green", "test_value"], timeout=timeout) - except AssertionError as e: - assert "attribute 'test_value'" in str(e) - assert "Actual value: blue" in str(e) + assert "attribute 'test_value'" in str(e.value) + assert "Actual value: blue" in str(e.value) def test_locator_existance(page: Page, local_app: ShinyAppProc) -> None: @@ -124,12 +126,13 @@ def test_locator_existance(page: Page, local_app: ShinyAppProc) -> None: timeout = 100 # Non-existent div - try: - not_exist = InputCheckboxGroup(page, "does-not-exist") + not_exist = InputCheckboxGroup(page, "does-not-exist") + with pytest.raises(AssertionError) as e: not_exist.set(["green"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '1'" in str(e) - assert "Actual value: 0" in str(e) + + print(str(e.value)) + assert "expected to have count '1'" in str(e.value) + assert "Actual value: 0" in str(e.value) check1 = InputCheckboxGroup(page, "check1") @@ -140,17 +143,16 @@ def test_locator_existance(page: Page, local_app: ShinyAppProc) -> None: check1.expect_selected(["green"]) # Different value - try: + with pytest.raises(AssertionError) as e: check1.set(["test_value"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '1'" in str(e) - assert "Actual value: 0" in str(e) + assert "expected to have count '1'" in str(e.value) + assert "Actual value: 0" in str(e.value) # Extra value - try: + with pytest.raises(AssertionError) as e: check1.set(["blue", "test_value"], timeout=timeout) - except AssertionError as e: - assert "expected to have count '1'" in str(e) - assert "Actual value: 0" in str(e) + + assert "expected to have count '1'" in str(e.value) + assert "Actual value: 0" in str(e.value) check1.expect_selected(["green"]) diff --git a/tests/playwright/shiny/inputs/input_slider/test_input_slider_app.py b/tests/playwright/shiny/inputs/input_slider/test_input_slider_app.py index bb109e7f1..58968f982 100644 --- a/tests/playwright/shiny/inputs/input_slider/test_input_slider_app.py +++ b/tests/playwright/shiny/inputs/input_slider/test_input_slider_app.py @@ -1,6 +1,6 @@ -import re import time +import pytest from conftest import ShinyAppProc from controls import InputSlider, InputSliderRange, OutputTextVerbatim from playwright.sync_api import Page @@ -54,10 +54,8 @@ def test_slider_range(page: Page, local_app: ShinyAppProc) -> None: new_val = ("605", "840") s1.set(new_val, max_err_values=1000) - try: + with pytest.raises(ValueError, match="tuple entries cannot"): s1.expect_value((MISSING, MISSING)) # type: ignore - except ValueError as e: - assert re.search("tuple entries cannot", str(e)) s1.expect_value((new_val[0], MISSING)) s1.expect_value((MISSING, new_val[1])) s1.expect_value(new_val) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index f90ce3d5b..767f1c5a2 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -1,10 +1,13 @@ from __future__ import annotations import time +from typing import TYPE_CHECKING -from conftest import ShinyAppProc from controls import InputNumeric, InputTaskButton, OutputText -from playwright.sync_api import Page + +if TYPE_CHECKING: + from conftest import ShinyAppProc + from playwright.sync_api import Page def click_extended_task_button( diff --git a/tests/playwright/shiny/inputs/input_task_button2/test_input_task_button2.py b/tests/playwright/shiny/inputs/input_task_button2/test_input_task_button2.py index 85da00177..77a6cc825 100644 --- a/tests/playwright/shiny/inputs/input_task_button2/test_input_task_button2.py +++ b/tests/playwright/shiny/inputs/input_task_button2/test_input_task_button2.py @@ -1,8 +1,12 @@ from __future__ import annotations -from conftest import ShinyAppProc +from typing import TYPE_CHECKING + from controls import InputTaskButton, OutputText -from playwright.sync_api import Page + +if TYPE_CHECKING: + from conftest import ShinyAppProc + from playwright.sync_api import Page def click_extended_task_button( diff --git a/tests/playwright/shiny/inputs/test_input_dark_mode.py b/tests/playwright/shiny/inputs/test_input_dark_mode.py index d5c793566..607e59c8e 100644 --- a/tests/playwright/shiny/inputs/test_input_dark_mode.py +++ b/tests/playwright/shiny/inputs/test_input_dark_mode.py @@ -1,8 +1,12 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputActionButton, InputDarkMode, LayoutNavSetBar -from playwright.sync_api import Page + +if TYPE_CHECKING: + from playwright.sync_api import Page app = create_doc_example_core_fixture("input_dark_mode") diff --git a/tests/playwright/shiny/inputs/test_input_slider.py b/tests/playwright/shiny/inputs/test_input_slider.py index 5da4cfaac..3bd655de6 100644 --- a/tests/playwright/shiny/inputs/test_input_slider.py +++ b/tests/playwright/shiny/inputs/test_input_slider.py @@ -1,3 +1,4 @@ +import pytest from conftest import ShinyAppProc, create_doc_example_core_fixture from controls import InputSlider, OutputTextVerbatim from playwright.sync_api import Page, expect @@ -49,13 +50,12 @@ def test_input_slider_kitchen(page: Page, slider_app: ShinyAppProc) -> None: # e # ), "Error message should contain the list of first 15 valid values" - try: + with pytest.raises(ValueError) as e: obs.set("not-a-number", timeout=800, max_err_values=4) - except ValueError as e: - values_found = '"10", "11", "12", "13", ...' - assert values_found in str( - e - ), "Error message should contain the list of first 4 valid values" + values_found = '"10", "11", "12", "13", ...' + assert values_found in str( + e + ), "Error message should contain the list of first 4 valid values" def test_input_slider_output(page: Page, template_app: ShinyAppProc) -> None: diff --git a/tests/playwright/shiny/inputs/test_input_switch.py b/tests/playwright/shiny/inputs/test_input_switch.py index 39034bf2f..0fc16a042 100644 --- a/tests/playwright/shiny/inputs/test_input_switch.py +++ b/tests/playwright/shiny/inputs/test_input_switch.py @@ -9,7 +9,6 @@ def test_input_switch_kitchen(page: Page, app: ShinyAppProc) -> None: page.goto(app.url) somevalue = InputSwitch(page, "somevalue") - somevalue.expect_label expect(somevalue.loc_label).to_have_text("Some value") somevalue.expect_label("Some value") diff --git a/tests/playwright/shiny/plot-sizing/app.py b/tests/playwright/shiny/plot-sizing/app.py index 646fe7fe4..83a0c0c93 100644 --- a/tests/playwright/shiny/plot-sizing/app.py +++ b/tests/playwright/shiny/plot-sizing/app.py @@ -111,7 +111,7 @@ def plot_with_mpl(fig_size: tuple[float, float] | None) -> object: return fig def plot_with_sns(fig_size: tuple[float, float] | None) -> object: - kwargs = dict() + kwargs = {} if fig_size: kwargs["height"] = fig_size[1] / dpi kwargs["aspect"] = fig_size[0] / fig_size[1] diff --git a/tests/playwright/shiny/session/flush/app.py b/tests/playwright/shiny/session/flush/app.py index a6591e31d..d4aef1026 100644 --- a/tests/playwright/shiny/session/flush/app.py +++ b/tests/playwright/shiny/session/flush/app.py @@ -91,8 +91,8 @@ def call_a( ): def _(): with reactive.isolate(): - all_vals.set(all_vals.get() + (f"a-{suffix}",)) - vals.set(vals.get() + (f"a-{suffix}",)) + all_vals.set((*all_vals.get(), f"a-{suffix}")) + vals.set((*vals.get(), f"a-{suffix}")) return _ @@ -102,12 +102,12 @@ def call_b( ): async def _(): with reactive.isolate(): - all_vals.set(all_vals.get() + (f"bx-{suffix}",)) - vals.set(vals.get() + (f"bx-{suffix}",)) + all_vals.set((*all_vals.get(), f"bx-{suffix}")) + vals.set((*vals.get(), f"bx-{suffix}")) await asyncio.sleep(0) with reactive.isolate(): - all_vals.set(all_vals.get() + (f"by-{suffix}",)) - vals.set(vals.get() + (f"by-{suffix}",)) + all_vals.set((*all_vals.get(), f"by-{suffix}")) + vals.set((*vals.get(), f"by-{suffix}")) return _ @@ -117,8 +117,8 @@ def call_c( ): def _(): with reactive.isolate(): - all_vals.set(all_vals.get() + (f"c-{suffix}",)) - vals.set(vals.get() + (f"c-{suffix}",)) + all_vals.set((*all_vals.get(), f"c-{suffix}")) + vals.set((*vals.get(), f"c-{suffix}")) return _ diff --git a/tests/pytest/test_display_decorator.py b/tests/pytest/test_display_decorator.py index 63e014b28..469848002 100644 --- a/tests/pytest/test_display_decorator.py +++ b/tests/pytest/test_display_decorator.py @@ -1,5 +1,5 @@ # pyright: reportUnusedExpression=false -# flake8: noqa +# ruff: noqa: B018 from __future__ import annotations import contextlib diff --git a/tests/pytest/test_modules.py b/tests/pytest/test_modules.py index 1b91fc7d7..a1522e35f 100644 --- a/tests/pytest/test_modules.py +++ b/tests/pytest/test_modules.py @@ -39,7 +39,7 @@ def test_module_ui(): assert get_id(y, 2) == "outer-out2" -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_session_scoping(): sessions: Dict[str, Union[Session, None, str]] = {} diff --git a/tests/pytest/test_output_transformer.py b/tests/pytest/test_output_transformer.py index 0ae494b0c..a50dbe002 100644 --- a/tests/pytest/test_output_transformer.py +++ b/tests/pytest/test_output_transformer.py @@ -116,20 +116,16 @@ def test_renderer( def test_output_transformer_pos_args(): - try: + with pytest.raises(TypeError, match="must have 2 positional parameters"): @output_transformer # pyright: ignore[reportArgumentType] async def TestTransformer( _meta: TransformerMetadata, ): ... - raise RuntimeError() - except TypeError as e: - assert "must have 2 positional parameters" in str(e) - def test_output_transformer_limits_positional_arg_count(): - try: + with pytest.raises(TypeError, match="more than 2 positional"): @output_transformer async def TestTransformer( @@ -138,13 +134,9 @@ async def TestTransformer( y: str, ): ... - raise RuntimeError() - except TypeError as e: - assert "more than 2 positional" in str(e) - def test_output_transformer_does_not_allow_args(): - try: + with pytest.raises(TypeError, match="No variadic positional parameters"): @output_transformer async def TestTransformer( @@ -153,14 +145,9 @@ async def TestTransformer( *args: str, ): ... - raise RuntimeError() - - except TypeError as e: - assert "No variadic positional parameters" in str(e) - def test_output_transformer_kwargs_have_defaults(): - try: + with pytest.raises(TypeError, match="did not have a default value"): @output_transformer async def TestTransformer( @@ -170,11 +157,6 @@ async def TestTransformer( y: str, ): ... - raise RuntimeError() - - except TypeError as e: - assert "did not have a default value" in str(e) - def test_output_transformer_result_does_not_allow_args(): @output_transformer @@ -187,17 +169,16 @@ async def TestTransformer( def render_fn_sync(*args: str): return " ".join(args) - try: + with pytest.raises( + TypeError, match="Expected `params` to be of type `TransformerParams`" + ): TestTransformer( render_fn_sync, "X", # pyright: ignore[reportArgumentType] ) - raise RuntimeError() - except TypeError as e: - assert "Expected `params` to be of type `TransformerParams`" in str(e) -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_renderer_handler_or_transform_fn_can_be_async(): @output_transformer async def AsyncTransformer( diff --git a/tests/pytest/test_poll.py b/tests/pytest/test_poll.py index 46b5b298c..60a79fb4a 100644 --- a/tests/pytest/test_poll.py +++ b/tests/pytest/test_poll.py @@ -52,7 +52,7 @@ async def __aexit__( self._on_ended_callbacks.invoke() -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_poll(): async with OnEndedSessionCallbacks(): poll_invocations = 0 @@ -126,7 +126,7 @@ def _(): assert (poll_invocations, value_invocations) == (6, 4) -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_poll_errors(): async with OnEndedSessionCallbacks(): @@ -194,7 +194,7 @@ def _(): assert invocations == 3 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_file_reader(): tmpfile = tempfile.NamedTemporaryFile(delete=False) try: @@ -240,7 +240,7 @@ def read_file(): os.unlink(tmpfile.name) -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_file_reader_error(): async with OnEndedSessionCallbacks(): tmpfile1 = tempfile.NamedTemporaryFile(delete=False) diff --git a/tests/pytest/test_reactives.py b/tests/pytest/test_reactives.py index 4edaa4841..d2dd3d3aa 100644 --- a/tests/pytest/test_reactives.py +++ b/tests/pytest/test_reactives.py @@ -1,5 +1,7 @@ """Tests for `shiny.reactive`.""" +from __future__ import annotations + import asyncio from typing import List @@ -14,7 +16,7 @@ from .mocktime import MockTime -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_flush_runs_newly_invalidated(): """ Make sure that a flush will also run any calcs that were invalidated during the @@ -43,7 +45,7 @@ def o1(): assert o1._exec_count == 1 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_flush_runs_newly_invalidated_async(): """ Make sure that a flush will also run any calcs that were invalidated during the @@ -75,7 +77,7 @@ async def o1(): # ====================================================================== # Setting Value to same value doesn't invalidate downstream # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_reactive_value_same_no_invalidate(): v = Value(1) @@ -94,7 +96,7 @@ def o(): # ====================================================================== # Intializing reactive.Value to MISSING, and unsetting # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_reactive_value_unset(): v = Value[int]() @@ -134,7 +136,7 @@ def o(): # ====================================================================== # reactive.Value.is_set() invalidates dependents only when set state changes # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_reactive_value_is_set(): v = Value[int]() v_is_set: bool = False @@ -177,7 +179,7 @@ def o(): # ====================================================================== # Recursive calls to calcs # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_recursive_calc(): v = Value(5) @@ -199,7 +201,7 @@ def o(): assert v() == 0 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_recursive_async_calc(): v = Value(5) @@ -226,7 +228,7 @@ async def o(): # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_async_sequential(): x: Value[int] = Value(1) results: list[int] = [] @@ -274,7 +276,7 @@ async def _(): # ====================================================================== # isolate() # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_isolate_basic_without_context(): # isolate() works with calc and Value; allows executing without a reactive context. v = Value(1) @@ -294,7 +296,7 @@ def get_r(): assert get_r() == 11 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_isolate_prevents_dependency(): v = Value(1) @@ -331,7 +333,7 @@ def o(): # ====================================================================== # async isolate # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_isolate_async_basic_value(): async def f(): return 123 @@ -340,7 +342,7 @@ async def f(): assert await f() == 123 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_isolate_async_basic_without_context(): # async isolate works with calc and Value; allows executing without a reactive # context. @@ -358,7 +360,7 @@ async def get_r(): assert await get_r() == 11 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_isolate_async_prevents_dependency(): v = Value(1) @@ -395,7 +397,7 @@ async def o(): # ====================================================================== # Priority for effects # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_effect_priority(): v = Value(1) results: list[int] = [] @@ -446,7 +448,7 @@ def o4(): # Same as previous, but with async -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_async_effect_priority(): v = Value(1) results: list[int] = [] @@ -499,7 +501,7 @@ async def o4(): # ====================================================================== # Destroying effects # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_effect_destroy(): v = Value(1) results: list[int] = [] @@ -536,7 +538,7 @@ def o2(): # ====================================================================== # Error handling # ====================================================================== -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_error_handling(): vals: List[str] = [] @@ -583,7 +585,7 @@ def _(): assert vals == ["o1-1", "r", "o2"] -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_calc_error_rethrow(): # Make sure calcs re-throw errors. vals: List[str] = [] @@ -622,7 +624,7 @@ def _(): # Invalidating dependents # ====================================================================== # For https://github.com/posit-dev/py-shiny/issues/26 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_dependent_invalidation(): trigger = Value(0) v = Value(0) @@ -663,7 +665,7 @@ def r(): # ------------------------------------------------------------ # req() pauses execution in @effect() and @calc() # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_req(): n_times = 0 @@ -714,7 +716,7 @@ def _(): assert val == 1 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_invalidate_later(): mock_time = MockTime() with mock_time(): @@ -745,7 +747,7 @@ def obs1(): assert obs1._exec_count == 12 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_invalidate_later_invalidation(): mock_time = MockTime() with mock_time(): @@ -773,7 +775,7 @@ def obs1(): assert obs1._exec_count == 2 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_mock_time(): mock_time = MockTime() @@ -798,7 +800,7 @@ async def add_result_later(delay: float, msg: str): # ------------------------------------------------------------ # @reactive.event() works as expected # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_event_decorator(): n_times = 0 @@ -909,7 +911,7 @@ def _(): # ------------------------------------------------------------ # @event() works as expected with async # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_event_async_decorator(): n_times = 0 @@ -1027,7 +1029,7 @@ async def _(): # ------------------------------------------------------------ # @event() handles silent exceptions in event function # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_event_silent_exception(): n_times = 0 x = Value[bool]() @@ -1057,7 +1059,7 @@ def _(): # ------------------------------------------------------------ # @event() handles silent exceptions in event function, async # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_event_silent_exception_async(): n_times = 0 x = Value[bool]() @@ -1093,7 +1095,7 @@ async def _(): # ------------------------------------------------------------ # @event() throws runtime errors if passed wrong type # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_event_type_check(): with pytest.raises(TypeError): # Should complain about missing argument to @event(). @@ -1146,7 +1148,7 @@ async def _(): ... # ------------------------------------------------------------ # @output() throws runtime errors if passed wrong type # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_output_type_check(): conn = MockConnection() session = App(ui.TagList(), None)._create_session(conn) @@ -1194,7 +1196,7 @@ def _(): ... # ------------------------------------------------------------ # @effect()'s .suspend()/.resume() works as expected # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_effect_pausing(): a = Value(float(1)) @@ -1276,7 +1278,7 @@ def _(): # ------------------------------------------------------------ # @effect()'s .suspend()/.resume() works as expected (with async) # ------------------------------------------------------------ -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_effect_async_pausing(): a = Value(float(1)) @@ -1355,7 +1357,7 @@ def _(): assert obsB._exec_count == 3 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_observer_async_suspended_resumed_observers_run_at_most_once(): a = Value(1) diff --git a/tests/pytest/test_renderer.py b/tests/pytest/test_renderer.py index 652461c7b..ab6ff9e7f 100644 --- a/tests/pytest/test_renderer.py +++ b/tests/pytest/test_renderer.py @@ -8,7 +8,7 @@ from shiny.render.renderer import Renderer, ValueFn -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_renderer_works(): # No args works class test_renderer(Renderer[str]): @@ -30,7 +30,7 @@ def txt_no_paren() -> str: assert val == "Hello World! Hello World!" -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_renderer_works_with_args(): # No args works class test_renderer_with_args(Renderer[str]): diff --git a/tests/pytest/test_shinysession.py b/tests/pytest/test_shinysession.py index 18017ba44..de3580f81 100644 --- a/tests/pytest/test_shinysession.py +++ b/tests/pytest/test_shinysession.py @@ -49,7 +49,7 @@ def test_input_nonexistent(): assert "y" not in input -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_input_nonexistent_deps(): # Make sure that `"x" in input` causes a reactive dependency to be created. input = Inputs({}) diff --git a/tests/pytest/test_sidebar.py b/tests/pytest/test_sidebar.py index f2f11605d..66c4597a6 100644 --- a/tests/pytest/test_sidebar.py +++ b/tests/pytest/test_sidebar.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import Literal +from typing import TYPE_CHECKING, Literal import pytest from htmltools import Tag, TagAttrValue from shiny import ui -from shiny.ui._sidebar import SidebarOpenSpec, SidebarOpenValue + +if TYPE_CHECKING: + from shiny.ui._sidebar import SidebarOpenSpec, SidebarOpenValue _s = ui.sidebar("Sidebar!") _m = "Body" @@ -25,38 +27,27 @@ def test_panel_main_and_panel_sidebar(): ui.layout_sidebar(_s) ui.layout_sidebar(_s, None) - try: + with pytest.raises(ValueError) as e: ui.layout_sidebar(_s, _s) - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "multiple `sidebar()` objects" in str(e) + assert "multiple `sidebar()` objects" in str(e.value) - try: + with pytest.raises(ValueError) as e: ui.layout_sidebar(None, _ps) # pyright: ignore[reportArgumentType] - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "not being supplied with a `sidebar()` object." in str(e) + assert "not being supplied with a `sidebar()` object" in str(e.value) - try: + with pytest.raises(ValueError) as e: ui.layout_sidebar(_s, _pm) - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "is not being used with `panel_sidebar()`" in str(e) + assert "is not being used with `panel_sidebar()`" in str(e.value) - try: + with pytest.raises(ValueError, match="not being supplied as the second argument"): ui.layout_sidebar(_ps, None, _pm) - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "not being supplied as the second argument" in str(e) - try: + with pytest.raises(ValueError) as e: ui.layout_sidebar(_ps, _pm, None, "42") - raise AssertionError("Should have raised ValueError") - except ValueError as e: - assert "Unexpected extra legacy `*args`" in str(e) + assert "Unexpected extra legacy `*args`" in str(e.value) @pytest.mark.parametrize( - "open_value, expected", + ("open_value", "expected"), [ ("closed", {"desktop": "closed", "mobile": "closed"}), ("open", {"desktop": "open", "mobile": "open"}), diff --git a/tests/pytest/test_utils.py b/tests/pytest/test_utils.py index ac99d4ab6..31a0d8a58 100644 --- a/tests/pytest/test_utils.py +++ b/tests/pytest/test_utils.py @@ -19,7 +19,9 @@ def test_randomness(): pub2 = random.randint(0, 100000000) with private_seed(): priv2 = random.randint(0, 100000000) - assert pub != priv and priv != pub2 and pub2 != priv2 + assert pub != priv + assert priv != pub2 + assert pub2 != priv2 # By setting the same seed, we should get the same randomness random.seed(0) @@ -93,7 +95,7 @@ def mutate_registrations(): assert cb4.exec_count == 1 # Registered during previous invoke(), was called -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_async_callbacks(): class AsyncMockCallback: def __init__(self): diff --git a/tests/pytest/test_utils_async.py b/tests/pytest/test_utils_async.py index dd03f98d5..f91f7e5b2 100644 --- a/tests/pytest/test_utils_async.py +++ b/tests/pytest/test_utils_async.py @@ -1,5 +1,7 @@ """Tests for `shiny.utils` async-related functions.""" +from __future__ import annotations + import asyncio import contextvars from typing import Iterator, List @@ -142,7 +144,7 @@ async def inner(): asyncio.run(create_task_wrapper2()) -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid(): state = 0 @@ -165,7 +167,7 @@ async def test_task() -> int: assert await fut == 100 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_throw(): async def test_task_throw(): raise ValueError("boom") @@ -175,7 +177,7 @@ async def test_task_throw(): await fut -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_throw_later(): state = 0 @@ -191,7 +193,7 @@ async def test_task_throw_later(): await fut -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_cancel(): state = 0 @@ -208,7 +210,7 @@ async def test_task_cancel(): assert state == 1 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_self_cancel(): state = 0 @@ -231,7 +233,7 @@ async def test_task_cancel(): assert state == 1 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_self_cancel2(): state = 0 @@ -250,7 +252,7 @@ async def test_task_cancel(): assert state == 1 -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_coro_hybrid_context(): test = contextvars.ContextVar("test", default=False)