Skip to content

Commit b150ca8

Browse files
authored
Upgrade to mypy 1.10.0 and ruff 0.4.2 (bokeh#13840)
* Upgrade to mypy 1.10 * Help out mypy with explicit type annotations * Upgrade to ruff 0.4.2 * Replace legacy string formatting with f-strings * Silence ruff's configuration warnings * Use py310 as ruff's target-version * Ignore B905 (zip() without strict= parameter) * Replace (A, B) with A | B in isinstance() * Deal with pre-PEP 701 f-strings * Use iterator instead of an array * Modernize and robustify customjs_for_hover.py
1 parent 2c551ef commit b150ca8

Some content is hidden

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

60 files changed

+166
-167
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ default_stages: [push]
66

77
repos:
88
- repo: https://github.com/astral-sh/ruff-pre-commit
9-
rev: v0.2.2
9+
rev: v0.4.2
1010
hooks:
1111
- id: ruff
1212
stages: [commit]

conda/environment-test-3.10.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies:
6363
# pip dependencies
6464
- pip
6565
- pip:
66-
- mypy >=1.8
66+
- mypy >=1.10
6767
# docs
6868
- pydata_sphinx_theme
6969
- requests-unixsocket >= 0.3.0
@@ -74,7 +74,7 @@ dependencies:
7474
- sphinxext-opengraph
7575
# tests
7676
- pandas-stubs >=2.2
77-
- ruff ==0.2.2
77+
- ruff ==0.4.2
7878
- types-boto
7979
- types-colorama
8080
- types-mock

conda/environment-test-3.11.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies:
6363
# pip dependencies
6464
- pip
6565
- pip:
66-
- mypy >=1.8
66+
- mypy >=1.10
6767
# docs
6868
- pydata_sphinx_theme
6969
- requests-unixsocket >= 0.3.0
@@ -74,7 +74,7 @@ dependencies:
7474
- sphinxext-opengraph
7575
# tests
7676
- pandas-stubs >=2.2
77-
- ruff ==0.2.2
77+
- ruff ==0.4.2
7878
- types-boto
7979
- types-colorama
8080
- types-mock

conda/environment-test-3.12.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies:
6363
# pip dependencies
6464
- pip
6565
- pip:
66-
- mypy >=1.8
66+
- mypy >=1.10
6767
# docs
6868
- pydata_sphinx_theme
6969
- requests-unixsocket >= 0.3.0
@@ -74,7 +74,7 @@ dependencies:
7474
- sphinxext-opengraph
7575
# tests
7676
- pandas-stubs >=2.2
77-
- ruff ==0.2.2
77+
- ruff ==0.4.2
7878
- types-boto
7979
- types-colorama
8080
- types-mock

conda/environment-test-minimal-deps.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ dependencies:
5353
# pip dependencies
5454
- pip
5555
- pip:
56-
- mypy >=1.8
56+
- mypy >=1.10
5757
# docs
5858
- pydata_sphinx_theme
5959
- requests-unixsocket >= 0.3.0
@@ -63,7 +63,7 @@ dependencies:
6363
- sphinx-favicon
6464
- sphinxext-opengraph
6565
# tests
66-
- ruff ==0.2.2
66+
- ruff ==0.4.2
6767
- types-boto
6868
- types-colorama
6969
- types-mock

examples/interaction/js_callbacks/customjs_for_hover.py

+11-15
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
1212
'''
1313

14-
1514
from bokeh.models import ColumnDataSource, CustomJS, HoverTool
1615
from bokeh.plotting import figure, show
1716

@@ -29,29 +28,26 @@
2928

3029
p = figure(width=400, height=400, tools="", toolbar_location=None, title='Hover over points')
3130

32-
source = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []})
31+
source = ColumnDataSource(dict(x0=[], y0=[], x1=[], y1=[]))
3332
sr = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='olive', alpha=0.6, line_width=3, source=source )
3433
cr = p.scatter(x, y, color='olive', size=30, alpha=0.4, hover_color='olive', hover_alpha=1.0)
3534

3635
# add a hover tool that sets the link data for a hovered circle
3736
code = """
38-
const links = %s
39-
const data = {'x0': [], 'y0': [], 'x1': [], 'y1': []}
40-
const indices = cb_data.index.indices
41-
for (let i = 0; i < indices.length; i++) {
42-
const start = indices[i]
43-
for (let j = 0; j < links[start].length; j++) {
44-
const end = links[start][j]
45-
data['x0'].push(circle.data.x[start])
46-
data['y0'].push(circle.data.y[start])
47-
data['x1'].push(circle.data.x[end])
48-
data['y1'].push(circle.data.y[end])
37+
const data = {x0: [], y0: [], x1: [], y1: []}
38+
const {indices} = cb_data.index
39+
for (const start of indices) {
40+
for (const end of links.get(start)) {
41+
data.x0.push(circle.data.x[start])
42+
data.y0.push(circle.data.y[start])
43+
data.x1.push(circle.data.x[end])
44+
data.y1.push(circle.data.y[end])
4945
}
5046
}
5147
segment.data = data
52-
""" % links
48+
"""
5349

54-
callback = CustomJS(args={'circle': cr.data_source, 'segment': sr.data_source}, code=code)
50+
callback = CustomJS(args=dict(circle=cr.data_source, segment=sr.data_source, links=links), code=code)
5551
p.add_tools(HoverTool(tooltips=None, callback=callback, renderers=[cr]))
5652

5753
show(p)

examples/models/trail.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def trail_map(data):
7070

7171
map_options = GMapOptions(lng=lon, lat=lat, zoom=13)
7272
plot = GMapPlot(width=800, height=800, map_options=map_options, api_key=API_KEY)
73-
plot.title.text = "%s - Trail Map" % name
73+
plot.title.text = f"{name} - Trail Map"
7474
plot.x_range = Range1d()
7575
plot.y_range = Range1d()
7676
plot.add_tools(PanTool(), WheelZoomTool(), ResetTool())
@@ -89,7 +89,7 @@ def trail_map(data):
8989

9090
def altitude_profile(data):
9191
plot = Plot(width=800, height=400)
92-
plot.title.text = "%s - Altitude Profile" % name
92+
plot.title.text = f"{name} - Altitude Profile"
9393
plot.y_range.range_padding = 0
9494

9595
xaxis = LinearAxis(axis_label="Distance (km)")

examples/server/app/server_auth/app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
5454
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
5555
logout = Button(label="logout")
56-
logout.js_on_event("button_click", CustomJS(code="window.location.href='%s'" % curdoc().session_context.logout_url))
56+
logout.js_on_event("button_click", CustomJS(code=f"window.location.href='{curdoc().session_context.logout_url}'"))
5757

5858
# Set up callbacks
5959
def update_title(attrname, old, new):

pyproject.toml

+7-6
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,14 @@ exclude_lines = [
139139
]
140140

141141
[tool.ruff]
142-
allowed-confusables = ["σ"]
143-
exclude = [
142+
lint.allowed-confusables = ["σ"]
143+
lint.exclude = [
144144
".git",
145145
'node_modules',
146146
]
147-
per-file-ignores = {"__init__.py" = ["F403"]}
148-
select = ["B", "COM", "E", "F", "RUF", "TID", "UP", "W"]
149-
ignore = [
147+
lint.per-file-ignores = {"__init__.py" = ["F403"]}
148+
lint.select = ["B", "COM", "E", "F", "RUF", "TID", "UP", "W"]
149+
lint.ignore = [
150150
'B005', # Using .strip() with multi-character strings is misleading the reader
151151
'B006', # Do not use mutable data structures for argument defaults
152152
'B007', # Loop control variable not used within the loop body
@@ -160,6 +160,7 @@ ignore = [
160160
'B023', # Functions defined inside a loop must not use variables redefined in the loop
161161
'B028', # No explicit `stacklevel` keyword argument found
162162
'B904', # Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
163+
'B905', # `zip()` without an explicit `strict=` parameter
163164
'E401', # Multiple imports on one line
164165
'E402', # Module level import not at top of file
165166
'E701', # Multiple statements on one line (colon)
@@ -171,7 +172,7 @@ ignore = [
171172
'TID252', # Prefer absolute imports over relative imports from parent modules
172173
]
173174
line-length = 165
174-
target-version = "py39"
175+
target-version = "py310"
175176

176177
# For now enable each typed module individually.
177178
[[tool.mypy.overrides]]

src/bokeh/application/application.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def add(self, handler: Handler) -> None:
165165
static_paths = {h.static_path() for h in self.handlers}
166166
static_paths.discard(None)
167167
if len(static_paths) > 1:
168-
raise RuntimeError("More than one static path requested for app: %r" % list(static_paths))
168+
raise RuntimeError(f"More than one static path requested for app: {list(static_paths)!r}")
169169
elif len(static_paths) == 1:
170170
self._static_path = static_paths.pop()
171171
else:

src/bokeh/client/connection.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def connect(self) -> None:
154154
def connected_or_closed() -> bool:
155155
# we should be looking at the same state here as the 'connected' property above, so connected
156156
# means both connected and that we did our initial message exchange
157-
return isinstance(self._state, (CONNECTED_AFTER_ACK, DISCONNECTED))
157+
return isinstance(self._state, CONNECTED_AFTER_ACK | DISCONNECTED)
158158
self._loop_until(connected_or_closed)
159159

160160
def close(self, why: str = "closed") -> None:
@@ -352,7 +352,7 @@ async def _pop_message(self) -> Message[Any] | None:
352352
try:
353353
message = await self._receiver.consume(fragment)
354354
if message is not None:
355-
log.debug("Received message %r" % message)
355+
log.debug(f"Received message {message!r}")
356356
return message
357357
except (MessageError, ProtocolError, ValidationError) as e:
358358
log.error("%r", e, exc_info=True)

src/bokeh/command/bootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def main(argv: Sequence[str]) -> None:
8989
9090
'''
9191
if len(argv) == 1:
92-
die("ERROR: Must specify subcommand, one of: %s" % nice_join([x.name for x in subcommands.all]))
92+
die(f"ERROR: Must specify subcommand, one of: {nice_join(x.name for x in subcommands.all)}")
9393

9494
parser = argparse.ArgumentParser(
9595
prog=argv[0],

src/bokeh/command/subcommands/file_output.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class Foo(FileOutputSubcommand):
8585
return ('files', Argument(
8686
metavar='DIRECTORY-OR-SCRIPT',
8787
nargs='+',
88-
help=("The app directories or scripts to generate %s for" % (output_type_name)),
88+
help=(f"The app directories or scripts to generate {output_type_name} for"),
8989
default=None,
9090
))
9191

src/bokeh/command/subcommands/serve.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -484,14 +484,14 @@ def get_login_url(request_handler):
484484
action = 'store',
485485
default = None,
486486
choices = (*LOGLEVELS, 'None'),
487-
help = "One of: %s" % nice_join(LOGLEVELS),
487+
help = f"One of: {nice_join(LOGLEVELS)}",
488488
)),
489489

490490
('--log-format', Argument(
491491
metavar ='LOG-FORMAT',
492492
action = 'store',
493493
default = DEFAULT_LOG_FORMAT,
494-
help = "A standard Python logging format string (default: %r)" % DEFAULT_LOG_FORMAT.replace("%", "%%"),
494+
help = f"A standard Python logging format string (default: {DEFAULT_LOG_FORMAT.replace('%', '%%')!r})",
495495
)),
496496

497497
('--log-file', Argument(
@@ -653,7 +653,7 @@ class Serve(Subcommand):
653653
action = 'store',
654654
default = None,
655655
choices = SESSION_ID_MODES,
656-
help = "One of: %s" % nice_join(SESSION_ID_MODES),
656+
help = f"One of: {nice_join(SESSION_ID_MODES)}",
657657
)),
658658

659659
('--auth-module', Argument(
@@ -979,7 +979,7 @@ def show_callback() -> None:
979979

980980
for route in sorted(applications.keys()):
981981
url = f"{protocol}://{address_string}:{server.port}{server.prefix}{route}"
982-
log.info("Bokeh app running at: %s" % url)
982+
log.info(f"Bokeh app running at: {url}")
983983

984984
log.info("Starting Bokeh server with process id: %d" % os.getpid())
985985

src/bokeh/command/util.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ def build_single_handler_application(path: str, argv: list[str] | None = None) -
134134
warn(DIRSTYLE_MAIN_WARNING)
135135
handler = ScriptHandler(filename=path, argv=argv)
136136
else:
137-
raise ValueError("Expected a '.py' script or '.ipynb' notebook, got: '%s'" % path)
137+
raise ValueError(f"Expected a '.py' script or '.ipynb' notebook, got: '{path}'")
138138
else:
139-
raise ValueError("Path for Bokeh server application does not exist: %s" % path)
139+
raise ValueError(f"Path for Bokeh server application does not exist: {path}")
140140

141141
if handler.failed:
142142
raise RuntimeError(f"Error loading {path}:\n\n{handler.error}\n{handler.error_detail} ")
@@ -177,7 +177,7 @@ def build_single_handler_applications(paths: list[str], argvs: dict[str, list[st
177177

178178
if not route:
179179
if '/' in applications:
180-
raise RuntimeError("Don't know the URL path to use for %s" % (path))
180+
raise RuntimeError(f"Don't know the URL path to use for {path}")
181181
route = '/'
182182
applications[route] = application
183183

src/bokeh/core/property/container.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def _is_seq(cls, value: Any) -> bool:
112112

113113
@classmethod
114114
def _is_seq_like(cls, value: Any) -> bool:
115-
return (isinstance(value, (Container, Sized, Iterable))
115+
return (isinstance(value, Container | Sized | Iterable)
116116
and hasattr(value, "__getitem__") # NOTE: this is what makes it disallow set type
117117
and not isinstance(value, Mapping))
118118

@@ -291,7 +291,7 @@ def __init__(self, *type_params: TypeOrInst[Property[Any]], default: Init[T] = U
291291
def validate(self, value: Any, detail: bool = True) -> None:
292292
super().validate(value, detail)
293293

294-
if isinstance(value, (tuple, list)) and len(self.type_params) == len(value):
294+
if isinstance(value, tuple | list) and len(self.type_params) == len(value):
295295
if all(type_param.is_valid(item) for type_param, item in zip(self.type_params, value)):
296296
return
297297

src/bokeh/core/property/dataspec.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ def is_color_tuple_shape(cls, val):
583583
True, if the value could be a color tuple
584584
585585
"""
586-
return isinstance(val, tuple) and len(val) in (3, 4) and all(isinstance(v, (float, int)) for v in val)
586+
return isinstance(val, tuple) and len(val) in (3, 4) and all(isinstance(v, float | int) for v in val)
587587

588588
def prepare_value(self, cls, name, value):
589589
# Some explanation is in order. We want to accept tuples like

src/bokeh/core/property/descriptors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ def has_unstable_default(self, obj: HasProps) -> bool:
484484
@classmethod
485485
def is_unstable(cls, value: Any) -> TypeGuard[Callable[[], Any]]:
486486
from .instance import InstanceDefault
487-
return isinstance(value, (FunctionType, InstanceDefault))
487+
return isinstance(value, FunctionType | InstanceDefault)
488488

489489
def _get(self, obj: HasProps) -> T:
490490
""" Internal implementation of instance attribute access for the

src/bokeh/core/property/instance.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Object(Property[T]):
6969
_instance_type: type[T] | Callable[[], type[T]] | str
7070

7171
def __init__(self, instance_type: type[T] | Callable[[], type[T]] | str, default: Init[T] = Undefined, help: str | None = None):
72-
if not (isinstance(instance_type, (type, str)) or callable(instance_type)):
72+
if not (isinstance(instance_type, type | str) or callable(instance_type)):
7373
raise ValueError(f"expected a type, fn() -> type, or string, got {instance_type}")
7474

7575
if isinstance(instance_type, type):

src/bokeh/core/property/visual.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def validate(self, value: Any, detail: bool = True) -> None:
153153
import numpy as np
154154
import PIL.Image
155155

156-
if isinstance(value, (str, Path, PIL.Image.Image)):
156+
if isinstance(value, str | Path | PIL.Image.Image):
157157
return
158158

159159
if isinstance(value, np.ndarray):
@@ -174,7 +174,7 @@ def transform(self, value):
174174
return value
175175

176176
# tempfile doesn't implement IO interface (https://bugs.python.org/issue33762)
177-
if isinstance(value, (Path, BinaryIO, tempfile._TemporaryFileWrapper)):
177+
if isinstance(value, Path | BinaryIO | tempfile._TemporaryFileWrapper):
178178
value = PIL.Image.open(value)
179179

180180
if isinstance(value, PIL.Image.Image):

src/bokeh/core/property/wrappers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ def _patch(self, doc: Document, source: ColumnarDataSource, patches, setter: Set
513513

514514
for name, patch in patches.items():
515515
for ind, value in patch:
516-
if isinstance(ind, (int, slice)):
516+
if isinstance(ind, int | slice):
517517
self[name][ind] = value
518518
else:
519519
shape = self[name][ind[0]][tuple(ind[1:])].shape

src/bokeh/core/serialization.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def _encode_other(self, obj: Any) -> AnyRep:
460460
# avoid importing pandas here unless it is actually in use
461461
if uses_pandas(obj):
462462
import pandas as pd
463-
if isinstance(obj, (pd.Series, pd.Index, pd.api.extensions.ExtensionArray)):
463+
if isinstance(obj, pd.Series | pd.Index | pd.api.extensions.ExtensionArray):
464464
return self._encode_ndarray(transform_series(obj))
465465
elif obj is pd.NA:
466466
return None

src/bokeh/document/callbacks.py

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def add_session_callback(self, callback_obj: SessionCallback, callback: Callback
174174
if doc is None:
175175
raise RuntimeError("Attempting to add session callback to already-destroyed Document")
176176

177+
actual_callback: Callback
177178
if one_shot:
178179
@wraps(callback)
179180
def remove_then_invoke() -> None:

0 commit comments

Comments
 (0)