diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 68afcbf3..6476e0fa 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -56,6 +56,7 @@ jobs: ipyparallel: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc2cfd9d..b73f2cbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,18 +40,33 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.8.0" + rev: "v1.12.0" hooks: - id: mypy files: ipykernel - stages: [manual] - args: ["--install-types", "--non-interactive"] + args: [] additional_dependencies: [ "traitlets>=5.13", "ipython>=8.16.1", "jupyter_client>=8.5", "appnope", + "types-Pygments", + "types-colorama", + "types-decorator", + "types-psutil", + "types-pycurl", + "types-python-dateutil", + "PyQt5", + "anyio", + "cloudpickle", + "comm", + "debugpy", + "dill", + "nest_asyncio", + "numpy", + "packaging", + "trio", ] - repo: https://github.com/adamchainz/blacken-docs diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 1747d4ce..f21fa56f 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -9,14 +9,14 @@ import comm.base_comm import traitlets.config -from traitlets import Bool, Bytes, Instance, Unicode, default +from traitlets import Bool, Instance, Unicode, default from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel # this is the class that will be created if we do comm.create_comm -class BaseComm(comm.base_comm.BaseComm): # type:ignore[misc] +class BaseComm(comm.base_comm.BaseComm): """The base class for comms.""" kernel: Optional["Kernel"] = None @@ -50,18 +50,18 @@ class Comm(BaseComm, traitlets.config.LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) # type:ignore[assignment] - comm_id = Unicode() - primary = Bool(True, help="Am I the primary or secondary Comm?") + comm_id = Unicode() # type: ignore[assignment] + primary = Bool(True, help="Am I the primary or secondary Comm?") # type: ignore[assignment] - target_name = Unicode("comm") - target_module = Unicode( + target_name = Unicode("comm") # type: ignore[assignment] + target_module = Unicode( # type: ignore[assignment] None, allow_none=True, help="""requirejs module from which to load comm target.""", ) - topic = Bytes() + topic: bytes @default("kernel") def _default_kernel(self): @@ -90,7 +90,7 @@ def __init__( kernel = kwargs.pop("kernel", None) if target_name: kwargs["target_name"] = target_name - BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # type:ignore[call-arg] + BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # only re-add kernel if explicitly provided if had_kernel: kwargs["kernel"] = kernel diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index aaef027c..4cfc9fca 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -8,18 +8,19 @@ import comm.base_comm import traitlets import traitlets.config +from typing import Dict, Any from .comm import Comm logger = logging.getLogger("ipykernel.comm") -class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): # type:ignore[misc] +class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): """A comm manager.""" kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") - comms = traitlets.Dict() - targets = traitlets.Dict() + comms: Dict[Any, Any] + targets: Dict[str, Any] def __init__(self, **kwargs): """Initialize the manager.""" @@ -33,7 +34,7 @@ def comm_open(self, stream, ident, msg): # but we should let the base class create the comm with comm.create_comm in a major release content = msg["content"] comm_id = content["comm_id"] - target_name = content["target_name"] + target_name: str = content["target_name"] f = self.targets.get(target_name, None) comm = Comm( comm_id=comm_id, diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index cc19696d..2175b014 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -13,7 +13,7 @@ try: # available since ipyparallel 5.0.0 - from ipyparallel.serialize import serialize_object + from ipyparallel.serialize import serialize_object # type: ignore[import-not-found] except ImportError: # Deprecated since ipykernel 4.3.0 from ipykernel.serialize import serialize_object diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 8680793f..67d2f469 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -21,10 +21,12 @@ try: # This import is required to have the next ones working... - from debugpy.server import api # noqa: F401 + from debugpy.server import api # type: ignore[import-untyped]# noqa: F401 - from _pydevd_bundle import pydevd_frame_utils # isort: skip - from _pydevd_bundle.pydevd_suspended_frames import ( # isort: skip + from _pydevd_bundle import ( # type:ignore[import-not-found] + pydevd_frame_utils, + ) # isort: skip + from _pydevd_bundle.pydevd_suspended_frames import ( # type:ignore[import-not-found] # isort: skip SuspendedFramesManager, _FramesTracker, ) @@ -70,7 +72,7 @@ class _DummyPyDB: def __init__(self): """Init.""" - from _pydevd_bundle.pydevd_api import PyDevdAPI + from _pydevd_bundle.pydevd_api import PyDevdAPI # type: ignore[import-not-found] self.variable_presentation = PyDevdAPI.VariablePresentation() @@ -117,9 +119,10 @@ def __init__(self, event_callback, log): self.tcp_buffer = "" self._reset_tcp_pos() self.event_callback = event_callback - self.message_send_stream, self.message_receive_stream = create_memory_object_stream[dict]( - max_buffer_size=inf - ) + ( + self.message_send_stream, + self.message_receive_stream, + ) = create_memory_object_stream[dict[t.Any, t.Any]](max_buffer_size=inf) self.log = log def _reset_tcp_pos(self): @@ -342,9 +345,10 @@ def __init__( self.is_started = False self.event_callback = event_callback self.just_my_code = just_my_code - self.stopped_send_stream, self.stopped_receive_stream = create_memory_object_stream[dict]( - max_buffer_size=inf - ) + ( + self.stopped_send_stream, + self.stopped_receive_stream, + ) = create_memory_object_stream[dict[t.Any, t.Any]](max_buffer_size=inf) self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index baca4dcd..ce643c1b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -172,7 +172,7 @@ def _loop_wx(app): def loop_wx(kernel): """Start a kernel with wx event loop support.""" - import wx + import wx # type: ignore[import-not-found] # Wx uses milliseconds poll_interval = int(1000 * kernel._poll_interval) @@ -279,7 +279,7 @@ def _schedule_exit(delay): else: import asyncio - import nest_asyncio + import nest_asyncio # type: ignore [import-untyped] nest_asyncio.apply() @@ -515,19 +515,19 @@ def set_qt_api_env_from_gui(gui): os.environ["QT_API"] = "pyqt5" except ImportError: try: - import PySide2 # noqa: F401 + import PySide2 # type: ignore[import-not-found] #noqa: F401 os.environ["QT_API"] = "pyside2" except ImportError: os.environ["QT_API"] = "pyqt5" elif gui == "qt6": try: - import PyQt6 # noqa: F401 + import PyQt6 # type: ignore[import-not-found] #noqa: F401 os.environ["QT_API"] = "pyqt6" except ImportError: try: - import PySide6 # noqa: F401 + import PySide6 # type: ignore[import-not-found] # noqa: F401 os.environ["QT_API"] = "pyside6" except ImportError: diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 3317ecfe..6da8fe29 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -15,11 +15,11 @@ import warnings # Third-party -import gi +import gi # type: ignore[import-not-found] gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") -from gi.repository import GObject, Gtk # noqa: E402 +from gi.repository import GObject, Gtk # type: ignore[import-not-found] # noqa: E402 warnings.warn( "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index e87249ea..72148699 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -15,8 +15,8 @@ import warnings # Third-party -import gobject -import gtk +import gobject # type: ignore[import-not-found] +import gtk # type: ignore[import-not-found] warnings.warn( "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index b5c421a7..5d70368f 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -68,6 +68,7 @@ def call_handlers(self, msg): _raw_input = self.client.kernel._sys_raw_input prompt = msg["content"]["prompt"] print(prompt, end="", file=sys.__stdout__) + assert sys.__stdout__ is not None sys.__stdout__.flush() self.client.input(_raw_input()) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 8ca97470..c5fd5fa0 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -55,7 +55,7 @@ def _default_blocking_class(self): return BlockingInProcessKernelClient - def get_connection_info(self): + def get_connection_info(self): # type:ignore [override] """Get the connection info for the client.""" d = super().get_connection_info() d["kernel"] = self.kernel # type:ignore[assignment] @@ -100,8 +100,14 @@ def hb_channel(self): # Methods for sending specific messages # ------------------------------------- - async def execute( - self, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None + async def execute( # type:ignore[override] + self, + code: str, + silent: bool = False, + store_history: bool = True, + user_expressions=None, + allow_stdin=None, + stop_on_error: bool = True, ): """Execute code on the client.""" if allow_stdin is None: diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 114e231d..84adcb51 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -82,7 +82,9 @@ async def execute_request(self, stream, ident, parent): with self._redirected_io(): await super().execute_request(stream, ident, parent) - async def start(self, *, task_status: TaskStatus = TASK_STATUS_IGNORED) -> None: + async def start( + self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED + ) -> None: """Override registration of dispatchers for streams.""" if self.shell: self.shell.exit_now = False diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index 9f0fcc75..cb32a479 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -46,7 +46,7 @@ def _default_session(self): # -------------------------------------------------------------------------- async def start_kernel( # type: ignore[explicit-override, override] - self, *, task_status: TaskStatus = TASK_STATUS_IGNORED, **kwds: Any + self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED, **kwds: Any ) -> None: """Start the kernel.""" from ipykernel.inprocess.ipkernel import InProcessKernel @@ -65,7 +65,7 @@ async def restart_kernel( # type: ignore[explicit-override, override] now: bool = False, newports: bool = False, *, - task_status: TaskStatus = TASK_STATUS_IGNORED, + task_status: TaskStatus[None] = TASK_STATUS_IGNORED, **kw: Any, ) -> None: """Restart the kernel.""" diff --git a/ipykernel/inprocess/session.py b/ipykernel/inprocess/session.py index 0eaed2c6..c6839489 100644 --- a/ipykernel/inprocess/session.py +++ b/ipykernel/inprocess/session.py @@ -2,7 +2,7 @@ class Session(_Session): - async def recv(self, socket, copy=True): + async def recv(self, socket, copy=True): # type:ignore[override] return await socket.recv_multipart() def send( diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index edc77c28..6e0a883c 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -9,6 +9,7 @@ import zmq.asyncio from anyio import create_memory_object_stream from traitlets import HasTraits, Instance +from typing import Any # ----------------------------------------------------------------------------- # Dummy socket class @@ -32,12 +33,12 @@ def __init__(self, is_shell, *args, **kwargs): self.is_shell = is_shell self.on_recv = None if is_shell: - self.in_send_stream, self.in_receive_stream = create_memory_object_stream[dict]( - max_buffer_size=inf - ) - self.out_send_stream, self.out_receive_stream = create_memory_object_stream[dict]( - max_buffer_size=inf - ) + self.in_send_stream, self.in_receive_stream = create_memory_object_stream[ + dict[Any, Any] + ](max_buffer_size=inf) + self.out_send_stream, self.out_receive_stream = create_memory_object_stream[ + dict[Any, Any] + ](max_buffer_size=inf) def put(self, msg): self.in_send_stream.send_nowait(msg) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index db83d986..1a780f9b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -246,7 +246,9 @@ async def poll_stopped_queue(self): while True: await self.debugger.handle_stopped_event() - async def start(self, *, task_status: TaskStatus = TASK_STATUS_IGNORED) -> None: + async def start( + self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED + ) -> None: """Start the kernel.""" if self.shell: self.shell.exit_now = False @@ -641,7 +643,7 @@ def do_is_complete(self, code): def do_apply(self, content, bufs, msg_id, reply_metadata): """Handle an apply request.""" try: - from ipyparallel.serialize import serialize_object, unpack_apply_message + from ipyparallel.serialize import serialize_object, unpack_apply_message # type: ignore[import-not-found] except ImportError: from .serialize import serialize_object, unpack_apply_message diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 394f52a4..65c33196 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -261,7 +261,7 @@ def _bind_socket(self, s, port): raise return None - def write_connection_file(self): + def write_connection_file(self): # type:ignore[override] """write connection info to JSON file""" cf = self.abs_connection_file connection_info = dict( diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 99358f9b..8fabe9f9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -233,6 +233,14 @@ def _parent_header(self): ] _eventloop_set: Event = Event() + control_handlers: Dict[ + str, + t.Callable[[zmq.asyncio.Socket, list[bytes], str], t.Awaitable[t.Any]] + | + # I think this one should be deprecated, and we should check the handlers are + # coroutine functions. + t.Callable[[zmq.asyncio.Socket, list[bytes], str], t.Any], + ] def __init__(self, **kwargs): """Initialize the kernel.""" @@ -531,7 +539,9 @@ def pre_handler_hook(self): def post_handler_hook(self): """Hook to execute after calling message handler""" - async def start(self, *, task_status: TaskStatus = TASK_STATUS_IGNORED) -> None: + async def start( + self, *, task_status: TaskStatus[None] = TASK_STATUS_IGNORED + ) -> None: """Process messages on shell and control channels""" async with create_task_group() as tg: self.control_stop = threading.Event() diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 895a785c..0b97ae3d 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -99,7 +99,7 @@ def run(self): try: from _winapi import INFINITE, WAIT_OBJECT_0 # type:ignore[attr-defined] except ImportError: - from _subprocess import INFINITE, WAIT_OBJECT_0 + from _subprocess import INFINITE, WAIT_OBJECT_0 # type: ignore[import-not-found] # Build the list of handle to listen on. handles = [] diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 6f156594..7a9e32f0 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -11,7 +11,9 @@ # This registers a hook when it's imported try: - from ipyparallel.serialize import codeutil # noqa: F401 + from ipyparallel.serialize import ( # type: ignore[import-not-found] + codeutil, + ) # noqa: F401, except ImportError: pass from traitlets.log import get_logger @@ -74,7 +76,7 @@ def use_dill(): adds support for object methods and closures to serialization. """ # import dill causes most of the magic - import dill + import dill # type: ignore[import-untyped] # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle @@ -98,7 +100,7 @@ def use_cloudpickle(): adds support for object methods and closures to serialization. """ - import cloudpickle + import cloudpickle # type: ignore[import-untyped] global pickle # noqa: PLW0603 pickle = cloudpickle diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 22ba5396..26e55622 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -9,7 +9,7 @@ try: # available since ipyparallel 5.0.0 - from ipyparallel.serialize.canning import ( + from ipyparallel.serialize.canning import ( # type: ignore[import-not-found] CannedObject, can, can_sequence, @@ -18,7 +18,7 @@ uncan, uncan_sequence, ) - from ipyparallel.serialize.serialize import PICKLE_PROTOCOL + from ipyparallel.serialize.serialize import PICKLE_PROTOCOL # type: ignore[import-not-found] except ImportError: # Deprecated since ipykernel 4.3.0 from ipykernel.pickleutil import ( diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 45f738ac..71770394 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -50,7 +50,7 @@ async def trio_main(): async with trio.open_nursery() as nursery: # TODO This hack prevents the nursery from cancelling all child # tasks when an uncaught exception occurs, but it's ugly. - nursery._add_exc = log_nursery_exc + nursery._add_exc = log_nursery_exc # type: ignore [method-assign] builtins.GLOBAL_NURSERY = nursery # type:ignore[attr-defined] await trio.sleep_forever() @@ -65,7 +65,7 @@ async def loc(coro): self._cell_cancel_scope = trio.CancelScope() with self._cell_cancel_scope: return await coro - self._cell_cancel_scope = None # type:ignore[unreachable] + self._cell_cancel_scope = None return None return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 3f97e817..a78c3569 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -82,8 +82,11 @@ def publish( self, data, metadata=None, + source=None, + *, transient=None, update=False, + **kwargs, ): """Publish a display-data message @@ -402,7 +405,7 @@ def qtconsole(self, arg_s): # %qtconsole should imply bind_kernel for engines: # FIXME: move to ipyparallel Kernel subclass if "ipyparallel" in sys.modules: - from ipyparallel import bind_kernel + from ipyparallel import bind_kernel # type: ignore[import-not-found] bind_kernel() @@ -516,7 +519,7 @@ def _update_exit_now(self, change): # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no # interactive input being read; we provide event loop support in ipkernel - def enable_gui(self, gui): + def enable_gui(self, gui): # type:ignore[override] """Enable a given guil.""" from .eventloops import enable_gui as real_enable_gui @@ -635,11 +638,13 @@ def set_parent(self, parent): if hasattr(self, "_data_pub"): self.data_pub.set_parent(parent) try: - sys.stdout.set_parent(parent) # type:ignore[attr-defined] + stdout = sys.stdout + stdout.set_parent(parent) # type: ignore[union-attr] except AttributeError: pass try: - sys.stderr.set_parent(parent) # type:ignore[attr-defined] + stderr = sys.stderr + stderr.set_parent(parent) # type: ignore[union-attr] except AttributeError: pass diff --git a/pyproject.toml b/pyproject.toml index aeaeef46..abf47dea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,12 +131,53 @@ build = [ [tool.mypy] files = "ipykernel" strict = true -disable_error_code = ["no-untyped-def", "no-untyped-call", "import-not-found"] enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] -follow_imports = "normal" -pretty = true warn_unreachable = true +[[tool.mypy.overrides]] +module = [ + "ipykernel.inprocess.manager", + "ipykernel.inprocess.ipkernel", + "ipykernel.inprocess.blocking", + "ipykernel.inprocess.client", + "ipykernel.inprocess.session", + "ipykernel.datapub", + "ipykernel.embed", + "ipykernel.kernelapp", + "ipykernel.pickleutil", + "ipykernel.ipkernel", + "ipykernel.zmqshell", + "ipykernel._eventloop_macos", + "ipykernel.comm.comm", + "ipykernel.comm.manager", + "ipykernel.compiler", + "ipykernel.connect", + "ipykernel.control", + "ipykernel.debugger", + "ipykernel.displayhook", + "ipykernel.eventloops", + "ipykernel.gui.gtk3embed", + "ipykernel.gui.gtkembed", + "ipykernel.heartbeat", + "ipykernel.inprocess.channels", + "ipykernel.inprocess.socket", + "ipykernel.iostream", + "ipykernel.jsonutil", + "ipykernel.kernelbase", + "ipykernel.log", + "ipykernel.parentpoller", + "ipykernel.serialize", + "ipykernel.shellchannel", + "ipykernel.subshell", + "ipykernel.thread", + "ipykernel.trio_runner", + ] +disallow_untyped_defs = false +disallow_incomplete_defs = false +check_untyped_defs = true +disallow_untyped_calls = false + + [tool.pytest.ini_options] minversion = "6.0" xfail_strict = true