Skip to content

Commit fa7defc

Browse files
authored
Improve typing (#113)
1 parent f42c049 commit fa7defc

File tree

8 files changed

+65
-47
lines changed

8 files changed

+65
-47
lines changed

examples/demo.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@
3737
# Note: in this demo we listen to all events (using '*'). In general
3838
# you want to select one or more specific events to handle.
3939

40+
cursor_index = 0
41+
4042

4143
@canvas.add_event_handler("*")
4244
async def process_event(event):
45+
global cursor_index
46+
4347
if event["event_type"] not in ["pointer_move", "before_draw", "animate"]:
4448
print(event)
4549

@@ -62,10 +66,10 @@ async def process_event(event):
6266
elif event["key"] == "c":
6367
# Swap cursor
6468
shapes = list(rendercanvas.CursorShape)
65-
canvas.cursor_index = getattr(canvas, "cursor_index", -1) + 1
66-
if canvas.cursor_index >= len(shapes):
67-
canvas.cursor_index = 0
68-
cursor = shapes[canvas.cursor_index]
69+
cursor_index += 1
70+
if cursor_index >= len(shapes):
71+
cursor_index = 0
72+
cursor = shapes[cursor_index]
6973
canvas.set_cursor(cursor)
7074
print(f"Cursor: {cursor!r}")
7175
elif event["event_type"] == "close":

rendercanvas/_coreutils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def log_exception(kind):
4141
except Exception as err:
4242
# Store exc info for postmortem debugging
4343
exc_info = list(sys.exc_info())
44-
exc_info[2] = exc_info[2].tb_next # skip *this* function
44+
exc_info[2] = exc_info[2].tb_next # type: ignore | skip *this* function
4545
sys.last_type, sys.last_value, sys.last_traceback = exc_info
4646
# Show traceback, or a one-line summary
4747
msg = str(err)

rendercanvas/_events.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import time
66
from inspect import iscoroutinefunction
77
from collections import defaultdict, deque
8+
from typing import Callable
89

910
from ._coreutils import log_exception
1011
from ._enums import EventType
@@ -44,7 +45,7 @@ def _release(self):
4445
self._pending_events.clear()
4546
self._event_handlers.clear()
4647

47-
def add_handler(self, *args, order: float = 0):
48+
def add_handler(self, *args, order: float = 0) -> Callable:
4849
"""Register an event handler to receive events.
4950
5051
Arguments:
@@ -89,9 +90,11 @@ def my_handler(event):
8990
9091
"""
9192
order = float(order)
92-
decorating = not callable(args[0])
93-
callback = None if decorating else args[0]
94-
types = args if decorating else args[1:]
93+
callback = None
94+
types = args
95+
if len(args) > 0 and callable(args[0]):
96+
callback = args[0]
97+
types = args[1:]
9598

9699
if not types:
97100
raise TypeError("No event types are given to add_event_handler.")
@@ -101,11 +104,11 @@ def my_handler(event):
101104
if not (type == "*" or type in valid_event_types):
102105
raise ValueError(f"Adding handler with invalid event_type: '{type}'")
103106

104-
def decorator(_callback):
107+
def decorator(_callback: Callable):
105108
self._add_handler(_callback, order, *types)
106109
return _callback
107110

108-
if decorating:
111+
if callback is None:
109112
return decorator
110113
return decorator(callback)
111114

rendercanvas/_loop.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,14 @@ async def _loop_task(self):
139139
break
140140
elif self.__should_stop:
141141
# Close all remaining canvases. Loop will stop in a next iteration.
142+
# We store a flag on the canvas, that we only use here.
142143
for canvas in self.get_canvases():
143-
if not getattr(canvas, "_rc_closed_by_loop", False):
144-
canvas._rc_closed_by_loop = True
144+
try:
145+
closed_by_loop = canvas._rc_closed_by_loop # type: ignore
146+
except AttributeError:
147+
closed_by_loop = False
148+
if not closed_by_loop:
149+
canvas._rc_closed_by_loop = True # type: ignore
145150
canvas.close()
146151
del canvas
147152

rendercanvas/_scheduler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Scheduler:
4444
# don't affect the scheduling loop; they are just extra draws.
4545

4646
def __init__(
47-
self, canvas, events, *, update_mode="ondemand", min_fps=0, max_fps=30
47+
self, canvas, events, *, update_mode="ondemand", min_fps=0.0, max_fps=30.0
4848
):
4949
self.name = f"{canvas.__class__.__name__} scheduler"
5050

@@ -65,7 +65,7 @@ def __init__(
6565
def get_task(self):
6666
"""Get task. Can be called exactly once. Used by the canvas."""
6767
task = self.__scheduler_task
68-
self.__scheduler_task = None
68+
self.__scheduler_task = None # type: ignore
6969
assert task is not None
7070
return task
7171

@@ -102,7 +102,7 @@ def request_draw(self):
102102
self._draw_requested = True
103103

104104
async def __scheduler_task(self):
105-
"""The coro that reprsents the scheduling loop for a canvas."""
105+
"""The coro that represents the scheduling loop for a canvas."""
106106

107107
last_draw_time = 0
108108
last_tick_time = 0

rendercanvas/auto.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def select_backend():
4040
module = None
4141
failed_backends = {} # name -> error
4242

43+
backend_name = "none"
44+
reason = "no reason"
4345
for backend_name, reason in backends_generator():
4446
if "force" in reason.lower():
4547
return _load_backend(backend_name)
@@ -122,7 +124,7 @@ def get_env_var(*varnames):
122124
def backends_by_jupyter():
123125
"""Generate backend names that are appropriate for the current Jupyter session (if any)."""
124126
try:
125-
ip = get_ipython()
127+
ip = get_ipython() # type: ignore
126128
except NameError:
127129
return
128130
if not ip.has_trait("kernel"):

rendercanvas/base.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
from ._enums import (
1313
EventTypeEnum,
14-
UpdateMode,
1514
UpdateModeEnum,
1615
CursorShape,
1716
CursorShapeEnum,
@@ -45,7 +44,7 @@
4544
class BaseCanvasGroup:
4645
"""Represents a group of canvas objects from the same class, that share a loop."""
4746

48-
def __init__(self, default_loop):
47+
def __init__(self, default_loop: BaseLoop):
4948
self._canvases = weakref.WeakSet()
5049
self._loop = None
5150
self.select_loop(default_loop)
@@ -71,11 +70,11 @@ def select_loop(self, loop: BaseLoop) -> None:
7170
self._loop._unregister_canvas_group(self)
7271
self._loop = loop
7372

74-
def get_loop(self) -> BaseLoop:
73+
def get_loop(self) -> BaseLoop | None:
7574
"""Get the currently associated loop (can be None for canvases that don't run a scheduler)."""
7675
return self._loop
7776

78-
def get_canvases(self) -> List["BaseRenderCanvas"]:
77+
def get_canvases(self) -> List[BaseRenderCanvas]:
7978
"""Get a list of currently active (not-closed) canvases for this group."""
8079
return [canvas for canvas in self._canvases if not canvas.get_closed()]
8180

@@ -123,7 +122,7 @@ def select_loop(cls, loop: BaseLoop) -> None:
123122
def __init__(
124123
self,
125124
*args,
126-
size: Tuple[int] = (640, 480),
125+
size: Tuple[int, int] = (640, 480),
127126
title: str = "$backend",
128127
update_mode: UpdateModeEnum = "ondemand",
129128
min_fps: float = 0.0,
@@ -190,8 +189,8 @@ def _final_canvas_init(self):
190189
del self.__kwargs_for_later
191190
# Apply
192191
if not isinstance(self, WrapperRenderCanvas):
193-
self.set_logical_size(*kwargs["size"])
194-
self.set_title(kwargs["title"])
192+
self.set_logical_size(*kwargs["size"]) # type: ignore
193+
self.set_title(kwargs["title"]) # type: ignore
195194

196195
def __del__(self):
197196
# On delete, we call the custom destroy method.
@@ -202,15 +201,15 @@ def __del__(self):
202201
# Since this is sometimes used in a multiple inheritance, the
203202
# superclass may (or may not) have a __del__ method.
204203
try:
205-
super().__del__()
204+
super().__del__() # type: ignore
206205
except Exception:
207206
pass
208207

209208
# %% Implement WgpuCanvasInterface
210209

211210
_canvas_context = None # set in get_context()
212211

213-
def get_physical_size(self) -> Tuple[int]:
212+
def get_physical_size(self) -> Tuple[int, int]:
214213
"""Get the physical size of the canvas in integer pixels."""
215214
return self._rc_get_physical_size()
216215

@@ -368,7 +367,10 @@ def set_update_mode(
368367
max_fps (float): The maximum fps with update mode 'ondemand' and 'continuous'.
369368
370369
"""
371-
self.__scheduler.set_update_mode(update_mode, min_fps=min_fps, max_fps=max_fps)
370+
if self.__scheduler is not None:
371+
self.__scheduler.set_update_mode(
372+
update_mode, min_fps=min_fps, max_fps=max_fps
373+
)
372374

373375
def request_draw(self, draw_function: Optional[DrawFunction] = None) -> None:
374376
"""Schedule a new draw event.
@@ -463,7 +465,7 @@ def _draw_frame_and_present(self):
463465

464466
# %% Primary canvas management methods
465467

466-
def get_logical_size(self) -> Tuple[float]:
468+
def get_logical_size(self) -> Tuple[float, float]:
467469
"""Get the logical size (width, height) in float pixels.
468470
469471
The logical size can be smaller than the physical size, e.g. on HiDPI
@@ -485,12 +487,12 @@ def get_pixel_ratio(self) -> float:
485487
def close(self) -> None:
486488
"""Close the canvas."""
487489
# Clear the draw-function, to avoid it holding onto e.g. wgpu objects.
488-
self._draw_frame = None
490+
self._draw_frame = None # type: ignore
489491
# Clear the canvas context too.
490492
if hasattr(self._canvas_context, "_release"):
491493
# ContextInterface (and GPUCanvasContext) has _release()
492494
try:
493-
self._canvas_context._release()
495+
self._canvas_context._release() # type: ignore
494496
except Exception:
495497
pass
496498
self._canvas_context = None
@@ -541,12 +543,12 @@ def set_cursor(self, cursor: CursorShapeEnum) -> None:
541543
cursor = "default"
542544
if not isinstance(cursor, str):
543545
raise TypeError("Canvas cursor must be str.")
544-
cursor = cursor.lower().replace("_", "-")
545-
if cursor not in CursorShape:
546+
cursor_normed = cursor.lower().replace("_", "-")
547+
if cursor_normed not in CursorShape:
546548
raise ValueError(
547549
f"Canvas cursor {cursor!r} not known, must be one of {CursorShape}"
548550
)
549-
self._rc_set_cursor(cursor)
551+
self._rc_set_cursor(cursor_normed)
550552

551553
# %% Methods for the subclass to implement
552554

@@ -609,19 +611,19 @@ def _rc_present_bitmap(self, *, data, format, **kwargs):
609611
"""
610612
raise NotImplementedError()
611613

612-
def _rc_get_physical_size(self):
614+
def _rc_get_physical_size(self) -> Tuple[int, int]:
613615
"""Get the physical size (with, height) in integer pixels."""
614616
raise NotImplementedError()
615617

616-
def _rc_get_logical_size(self):
618+
def _rc_get_logical_size(self) -> Tuple[float, float]:
617619
"""Get the logical size (with, height) in float pixels."""
618620
raise NotImplementedError()
619621

620-
def _rc_get_pixel_ratio(self):
622+
def _rc_get_pixel_ratio(self) -> float:
621623
"""Get ratio between physical and logical size."""
622624
raise NotImplementedError()
623625

624-
def _rc_set_logical_size(self, width, height):
626+
def _rc_set_logical_size(self, width: float, height: float):
625627
"""Set the logical size. May be ignired when it makes no sense.
626628
627629
The default implementation does nothing.
@@ -644,18 +646,18 @@ def _rc_close(self):
644646
"""
645647
pass
646648

647-
def _rc_get_closed(self):
649+
def _rc_get_closed(self) -> bool:
648650
"""Get whether the canvas is closed."""
649651
return False
650652

651-
def _rc_set_title(self, title):
653+
def _rc_set_title(self, title: str):
652654
"""Set the canvas title. May be ignored when it makes no sense.
653655
654656
The default implementation does nothing.
655657
"""
656658
pass
657659

658-
def _rc_set_cursor(self, cursor):
660+
def _rc_set_cursor(self, cursor: str):
659661
"""Set the cursor shape. May be ignored.
660662
661663
The default implementation does nothing.
@@ -672,15 +674,16 @@ class WrapperRenderCanvas(BaseRenderCanvas):
672674
"""
673675

674676
_rc_canvas_group = None # No grouping for these wrappers
677+
_subwidget: BaseRenderCanvas
675678

676679
@classmethod
677680
def select_loop(cls, loop: BaseLoop) -> None:
678681
m = sys.modules[cls.__module__]
679682
return m.RenderWidget.select_loop(loop)
680683

681684
def add_event_handler(
682-
self, *args: str | EventHandlerFunction, order: float = 0
683-
) -> None:
685+
self, *args: EventTypeEnum | EventHandlerFunction, order: float = 0
686+
) -> Callable:
684687
return self._subwidget._events.add_handler(*args, order=order)
685688

686689
def remove_event_handler(self, callback: EventHandlerFunction, *types: str) -> None:
@@ -694,7 +697,7 @@ def get_context(self, context_type: str) -> object:
694697

695698
def set_update_mode(
696699
self,
697-
update_mode: UpdateMode,
700+
update_mode: UpdateModeEnum,
698701
*,
699702
min_fps: Optional[float] = None,
700703
max_fps: Optional[float] = None,
@@ -707,10 +710,10 @@ def request_draw(self, draw_function: Optional[DrawFunction] = None) -> None:
707710
def force_draw(self) -> None:
708711
self._subwidget.force_draw()
709712

710-
def get_physical_size(self) -> Tuple[int]:
713+
def get_physical_size(self) -> Tuple[int, int]:
711714
return self._subwidget.get_physical_size()
712715

713-
def get_logical_size(self) -> Tuple[float]:
716+
def get_logical_size(self) -> Tuple[float, float]:
714717
return self._subwidget.get_logical_size()
715718

716719
def get_pixel_ratio(self) -> float:

rendercanvas/utils/cube.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
import time
7+
from typing import Callable
78

89
import wgpu
910
import numpy as np
@@ -14,7 +15,7 @@
1415

1516
def setup_drawing_sync(
1617
canvas, power_preference="high-performance", limits=None, format=None
17-
):
18+
) -> Callable[[], None]:
1819
"""Setup to draw a rotating cube on the given canvas.
1920
2021
The given canvas must implement WgpuCanvasInterface, but nothing more.
@@ -220,7 +221,7 @@ def create_pipeline_layout(device):
220221

221222
def get_draw_function(
222223
canvas, device, render_pipeline, uniform_buffer, bind_groups, *, asynchronous
223-
):
224+
) -> Callable[[], None]:
224225
# Create vertex buffer, and upload data
225226
vertex_buffer = device.create_buffer_with_data(
226227
data=vertex_data, usage=wgpu.BufferUsage.VERTEX

0 commit comments

Comments
 (0)