Skip to content

Commit 1c8e9c3

Browse files
committed
Type hints for the public API
1 parent d8fd360 commit 1c8e9c3

File tree

2 files changed

+70
-51
lines changed

2 files changed

+70
-51
lines changed

rendercanvas/_loop.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
The base loop implementation.
33
"""
44

5+
from __future__ import annotations
6+
57
import signal
68
from inspect import iscoroutinefunction
9+
from typing import TYPE_CHECKING
710

811
from ._coreutils import logger, log_exception
912
from .utils.asyncs import sleep
1013
from .utils import asyncadapter
1114

15+
if TYPE_CHECKING:
16+
from typing import Any, Callable, List
17+
from base import BaseRenderCanvas
18+
1219

1320
HANDLED_SIGNALS = (
1421
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
@@ -74,7 +81,7 @@ def _unregister_canvas_group(self, canvas_group):
7481
# A CanvasGroup will call this when it selects a different loop.
7582
self.__canvas_groups.discard(canvas_group)
7683

77-
def get_canvases(self):
84+
def get_canvases(self) -> List[BaseRenderCanvas]:
7885
"""Get a list of currently active (not-closed) canvases."""
7986
canvases = []
8087
for canvas_group in self.__canvas_groups:
@@ -139,7 +146,7 @@ async def _loop_task(self):
139146
finally:
140147
self.__stop()
141148

142-
def add_task(self, async_func, *args, name="unnamed"):
149+
def add_task(self, async_func: Callable, *args: Any, name: str = "unnamed") -> None:
143150
"""Run an async function in the event-loop.
144151
145152
All tasks are stopped when the loop stops.
@@ -154,7 +161,7 @@ async def wrapper():
154161

155162
self._rc_add_task(wrapper, name)
156163

157-
def call_soon(self, callback, *args):
164+
def call_soon(self, callback: Callable, *args: Any) -> None:
158165
"""Arrange for a callback to be called as soon as possible.
159166
160167
The callback will be called in the next iteration of the event-loop,
@@ -171,7 +178,7 @@ async def wrapper():
171178

172179
self._rc_add_task(wrapper, "call_soon")
173180

174-
def call_later(self, delay, callback, *args):
181+
def call_later(self, delay: float, callback: Callable, *args: Any) -> None:
175182
"""Arrange for a callback to be called after the given delay (in seconds)."""
176183
if delay <= 0:
177184
return self.call_soon(callback, *args)
@@ -188,7 +195,7 @@ async def wrapper():
188195

189196
self._rc_add_task(wrapper, "call_later")
190197

191-
def run(self):
198+
def run(self) -> None:
192199
"""Enter the main loop.
193200
194201
This provides a generic API to start the loop. When building an application (e.g. with Qt)
@@ -229,7 +236,7 @@ def run(self):
229236
for sig, cb in prev_sig_handlers.items():
230237
signal.signal(sig, cb)
231238

232-
async def run_async(self):
239+
async def run_async(self) -> None:
233240
""" "Alternative to ``run()``, to enter the mainloop from a running async framework.
234241
235242
Only supported by the asyncio and trio loops.
@@ -250,7 +257,7 @@ async def run_async(self):
250257

251258
await self._rc_run_async()
252259

253-
def stop(self):
260+
def stop(self) -> None:
254261
"""Close all windows and stop the currently running event-loop.
255262
256263
If the loop is active but not running via our ``run()`` method, the loop

rendercanvas/base.py

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22
The base classes.
33
"""
44

5-
__all__ = ["BaseLoop", "BaseRenderCanvas", "WrapperRenderCanvas"]
5+
from __future__ import annotations
66

77
import sys
88
import weakref
99
import importlib
10-
from typing import Optional, Tuple
10+
from typing import TYPE_CHECKING
1111

1212
from ._events import EventEmitter, EventType # noqa: F401
1313
from ._loop import BaseLoop
1414
from ._scheduler import Scheduler, UpdateMode
1515
from ._coreutils import logger, log_exception, BaseEnum
1616

17+
if TYPE_CHECKING:
18+
from typing import Callable, List, Optional, Tuple
19+
20+
21+
__all__ = ["BaseLoop", "BaseRenderCanvas", "WrapperRenderCanvas"]
22+
1723

1824
# Notes on naming and prefixes:
1925
#
@@ -61,7 +67,7 @@ def _register_canvas(self, canvas, task):
6167
loop._register_canvas_group(self)
6268
loop.add_task(task, name="scheduler-task")
6369

64-
def select_loop(self, loop):
70+
def select_loop(self, loop: BaseLoop) -> None:
6571
"""Select the loop to use for this group of canvases."""
6672
if not (loop is None or isinstance(loop, BaseLoop)):
6773
raise TypeError("select_loop() requires a loop instance or None.")
@@ -74,11 +80,11 @@ def select_loop(self, loop):
7480
self._loop._unregister_canvas_group(self)
7581
self._loop = loop
7682

77-
def get_loop(self):
83+
def get_loop(self) -> BaseLoop:
7884
"""Get the currently associated loop (can be None for canvases that don't run a scheduler)."""
7985
return self._loop
8086

81-
def get_canvases(self):
87+
def get_canvases(self) -> List[BaseRenderCanvas]:
8288
"""Get a list of currently active (not-closed) canvases for this group."""
8389
return [canvas for canvas in self._canvases if not canvas.get_closed()]
8490

@@ -112,7 +118,7 @@ class BaseRenderCanvas:
112118
"""
113119

114120
@classmethod
115-
def select_loop(cls, loop):
121+
def select_loop(cls, loop: BaseLoop) -> None:
116122
"""Select the loop to run newly created canvases with.
117123
Can only be called when there are no live canvases of this class.
118124
"""
@@ -213,11 +219,11 @@ def __del__(self):
213219

214220
_canvas_context = None # set in get_context()
215221

216-
def get_physical_size(self):
222+
def get_physical_size(self) -> Tuple[int]:
217223
"""Get the physical size of the canvas in integer pixels."""
218224
return self._rc_get_physical_size()
219225

220-
def get_context(self, context_type):
226+
def get_context(self, context_type: str) -> object:
221227
"""Get a context object that can be used to render to this canvas.
222228
223229
The context takes care of presenting the rendered result to the canvas.
@@ -292,13 +298,13 @@ def get_context(self, context_type):
292298

293299
# %% Events
294300

295-
def add_event_handler(self, *args, **kwargs):
296-
return self._events.add_handler(*args, **kwargs)
301+
def add_event_handler(self, *args: Callable | str, order: float = 0) -> None:
302+
return self._events.add_handler(*args, order=order)
297303

298-
def remove_event_handler(self, *args, **kwargs):
299-
return self._events.remove_handler(*args, **kwargs)
304+
def remove_event_handler(self, callback: Callable, *types: str) -> None:
305+
return self._events.remove_handler(callback, *types)
300306

301-
def submit_event(self, event):
307+
def submit_event(self, event: dict) -> None:
302308
# Not strictly necessary for normal use-cases, but this allows
303309
# the ._event to be an implementation detail to subclasses, and it
304310
# allows users to e.g. emulate events in tests.
@@ -354,7 +360,13 @@ def _draw_frame(self):
354360
"""
355361
pass
356362

357-
def set_update_mode(self, update_mode, *, min_fps=None, max_fps=None):
363+
def set_update_mode(
364+
self,
365+
update_mode: UpdateMode,
366+
*,
367+
min_fps: Optional[float] = None,
368+
max_fps: Optional[float] = None,
369+
) -> None:
358370
"""Set the update mode for scheduling draws.
359371
360372
Arguments:
@@ -365,7 +377,7 @@ def set_update_mode(self, update_mode, *, min_fps=None, max_fps=None):
365377
"""
366378
self.__scheduler.set_update_mode(update_mode, min_fps=min_fps, max_fps=max_fps)
367379

368-
def request_draw(self, draw_function=None):
380+
def request_draw(self, draw_function: Optional[Callable] = None) -> None:
369381
"""Schedule a new draw event.
370382
371383
This function does not perform a draw directly, but schedules a draw at
@@ -388,7 +400,7 @@ def request_draw(self, draw_function=None):
388400
# storing it here, the gc can detect this case, and its fine. However,
389401
# this fails if we'd store _draw_frame on the scheduler!
390402

391-
def force_draw(self):
403+
def force_draw(self) -> None:
392404
"""Perform a draw right now.
393405
394406
In most cases you want to use ``request_draw()``. If you find yourself using
@@ -458,15 +470,15 @@ def _draw_frame_and_present(self):
458470

459471
# %% Primary canvas management methods
460472

461-
def get_logical_size(self):
473+
def get_logical_size(self) -> Tuple[float]:
462474
"""Get the logical size (width, height) in float pixels."""
463475
return self._rc_get_logical_size()
464476

465-
def get_pixel_ratio(self):
477+
def get_pixel_ratio(self) -> float:
466478
"""Get the float ratio between logical and physical pixels."""
467479
return self._rc_get_pixel_ratio()
468480

469-
def close(self):
481+
def close(self) -> None:
470482
"""Close the canvas."""
471483
# Clear the draw-function, to avoid it holding onto e.g. wgpu objects.
472484
self._draw_frame = None
@@ -483,7 +495,7 @@ def close(self):
483495
# Let the subclass clean up.
484496
self._rc_close()
485497

486-
def get_closed(self):
498+
def get_closed(self) -> bool:
487499
"""Get whether the window is closed."""
488500
return self._rc_get_closed()
489501

@@ -498,14 +510,14 @@ def is_closed(self):
498510
# These methods provide extra control over the canvas. Subclasses should
499511
# implement the methods they can, but these features are likely not critical.
500512

501-
def set_logical_size(self, width, height):
513+
def set_logical_size(self, width: float, height: float) -> None:
502514
"""Set the window size (in logical pixels)."""
503515
width, height = float(width), float(height)
504516
if width < 0 or height < 0:
505517
raise ValueError("Canvas width and height must not be negative")
506518
self._rc_set_logical_size(width, height)
507519

508-
def set_title(self, title):
520+
def set_title(self, title: str) -> None:
509521
"""Set the window title.
510522
511523
The words "$backend", "$loop", and "$fps" can be used as variables that
@@ -516,7 +528,7 @@ def set_title(self, title):
516528
title = title.replace("$" + k, v)
517529
self._rc_set_title(title)
518530

519-
def set_cursor(self, cursor):
531+
def set_cursor(self, cursor: CursorShape) -> None:
520532
"""Set the cursor shape for the mouse pointer.
521533
522534
See :obj:`rendercanvas.CursorShape`:
@@ -658,50 +670,50 @@ class WrapperRenderCanvas(BaseRenderCanvas):
658670
_rc_canvas_group = None # No grouping for these wrappers
659671

660672
@classmethod
661-
def select_loop(cls, loop):
673+
def select_loop(cls, loop: BaseLoop) -> None:
662674
m = sys.modules[cls.__module__]
663675
return m.RenderWidget.select_loop(loop)
664676

665-
def add_event_handler(self, *args, **kwargs):
666-
return self._subwidget._events.add_handler(*args, **kwargs)
677+
def add_event_handler(self, *args: Callable | str, order: float = 0) -> None:
678+
return self._subwidget._events.add_handler(*args, order=order)
667679

668-
def remove_event_handler(self, *args, **kwargs):
669-
return self._subwidget._events.remove_handler(*args, **kwargs)
680+
def remove_event_handler(self, callback: Callable, *types: str) -> None:
681+
return self._subwidget._events.remove_handler(callback, *types)
670682

671-
def submit_event(self, event):
683+
def submit_event(self, event: dict) -> None:
672684
return self._subwidget._events.submit(event)
673685

674-
def get_context(self, *args, **kwargs):
675-
return self._subwidget.get_context(*args, **kwargs)
686+
def get_context(self, context_type: str) -> object:
687+
return self._subwidget.get_context(context_type)
676688

677-
def request_draw(self, *args, **kwargs):
678-
return self._subwidget.request_draw(*args, **kwargs)
689+
def request_draw(self, draw_function: Optional[Callable] = None) -> None:
690+
return self._subwidget.request_draw(draw_function)
679691

680-
def force_draw(self):
692+
def force_draw(self) -> None:
681693
self._subwidget.force_draw()
682694

683-
def get_physical_size(self):
695+
def get_physical_size(self) -> Tuple[int]:
684696
return self._subwidget.get_physical_size()
685697

686-
def get_logical_size(self):
698+
def get_logical_size(self) -> Tuple[float]:
687699
return self._subwidget.get_logical_size()
688700

689-
def get_pixel_ratio(self):
701+
def get_pixel_ratio(self) -> float:
690702
return self._subwidget.get_pixel_ratio()
691703

692-
def set_logical_size(self, width, height):
704+
def set_logical_size(self, width: float, height: float) -> None:
693705
self._subwidget.set_logical_size(width, height)
694706

695-
def set_title(self, *args):
696-
self._subwidget.set_title(*args)
707+
def set_title(self, title: str) -> None:
708+
self._subwidget.set_title(title)
697709

698-
def set_cursor(self, *args):
699-
self._subwidget.set_cursor(*args)
710+
def set_cursor(self, cursor: CursorShape) -> None:
711+
self._subwidget.set_cursor(cursor)
700712

701-
def close(self):
713+
def close(self) -> None:
702714
self._subwidget.close()
703715

704-
def get_closed(self):
716+
def get_closed(self) -> bool:
705717
return self._subwidget.get_closed()
706718

707719
def is_closed(self):

0 commit comments

Comments
 (0)