diff --git a/.gitignore b/.gitignore index 270eb98a3e..894a46681c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ venv*/ *.code-workspace .history .vscode + +# MonkeyType runtime type trace database +monkeytype.sqlite3 diff --git a/IPython/core/tbtools.py b/IPython/core/tbtools.py index dc21e71923..32f7bc9691 100644 --- a/IPython/core/tbtools.py +++ b/IPython/core/tbtools.py @@ -203,7 +203,7 @@ def _tokens_filename( ] else: name = util_path.compress_user( - py3compat.cast_unicode(file, util_path.fs_encoding) + py3compat.cast_unicode(file or "", util_path.fs_encoding) ) if lineno is None: return [ diff --git a/IPython/terminal/prompts.py b/IPython/terminal/prompts.py index 1de6cf235d..f5868d7f1e 100644 --- a/IPython/terminal/prompts.py +++ b/IPython/terminal/prompts.py @@ -1,6 +1,6 @@ """Terminal input and output prompts.""" -from pygments.token import Token +from pygments.token import _TokenType, Token import sys from IPython.core.displayhook import DisplayHook @@ -8,10 +8,14 @@ from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens from prompt_toolkit.shortcuts import print_formatted_text from prompt_toolkit.enums import EditingMode +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +if TYPE_CHECKING: + from IPython.terminal.interactiveshell import TerminalInteractiveShell class Prompts: - def __init__(self, shell): + def __init__(self, shell: "TerminalInteractiveShell"): self.shell = shell def vi_mode(self): @@ -84,7 +88,7 @@ def rewrite_prompt_tokens(self): (Token.Prompt, ('-' * (width - 2)) + '> '), ] - def out_prompt_tokens(self): + def out_prompt_tokens(self) -> List[Tuple[_TokenType, str]]: return [ (Token.OutPrompt, 'Out['), (Token.OutPromptNum, str(self.shell.execution_count - 1)), @@ -128,7 +132,7 @@ def write_output_prompt(self): else: sys.stdout.write(prompt_txt) - def write_format_data(self, format_dict, md_dict=None) -> None: + def write_format_data(self, format_dict: Dict[str, str], md_dict: Optional[Dict[Any, Any]]=None) -> None: assert self.shell is not None if self.shell.mime_renderers: diff --git a/IPython/terminal/shortcuts/filters.py b/IPython/terminal/shortcuts/filters.py index 013bbeaecf..04c55acefd 100644 --- a/IPython/terminal/shortcuts/filters.py +++ b/IPython/terminal/shortcuts/filters.py @@ -9,7 +9,7 @@ import re import signal import sys -from typing import Dict, Union +from typing import Optional, Dict, Union from collections.abc import Callable from prompt_toolkit.application.current import get_app @@ -40,7 +40,7 @@ def cursor_in_leading_ws(): return (not before) or before.isspace() -def has_focus(value: FocusableElement): +def has_focus(value: FocusableElement) -> Condition: """Wrapper around has_focus adding a nice `__name__` to tester function""" tester = has_focus_impl(value).func tester.__name__ = f"is_focused({value})" @@ -102,7 +102,7 @@ def all_quotes_paired(quote, buf): _following_text_cache: Dict[Union[str, Callable], Condition] = {} -def preceding_text(pattern: Union[str, Callable]): +def preceding_text(pattern: Union[str, Callable]) -> Condition: if pattern in _preceding_text_cache: return _preceding_text_cache[pattern] @@ -129,7 +129,7 @@ def _preceding_text(): return condition -def following_text(pattern): +def following_text(pattern: str) -> Condition: try: return _following_text_cache[pattern] except KeyError: @@ -284,7 +284,7 @@ def __call__(self): } -def eval_node(node: Union[ast.AST, None]): +def eval_node(node: Union[ast.AST, None]) -> Optional[Filter]: if node is None: return None if isinstance(node, ast.Expression): @@ -315,7 +315,7 @@ def eval_node(node: Union[ast.AST, None]): raise ValueError("Unhandled node", ast.dump(node)) -def filter_from_string(code: str): +def filter_from_string(code: str) -> Union[Condition, Filter]: expression = ast.parse(code, mode="eval") return eval_node(expression) diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 1b2ed16b99..36240a1be3 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -8,8 +8,10 @@ # Expose the unittest-driven decorators from .ipunittest import ipdoctest, ipdocstring +from _pytest.mark.structures import MarkDecorator +from typing import Optional -def skipif(skip_condition, msg=None): +def skipif(skip_condition: bool, msg: Optional[str]=None) -> MarkDecorator: """Make function raise SkipTest exception if skip_condition is true Parameters @@ -39,7 +41,7 @@ def skipif(skip_condition, msg=None): # A version with the condition set to true, common case just to attach a message # to a skip decorator -def skip(msg=None): +def skip(msg: Optional[str]=None) -> MarkDecorator: """Decorator factory - mark a test function for skipping from test suite. Parameters @@ -61,7 +63,7 @@ def skip(msg=None): return skipif(True, msg) -def onlyif(condition, msg): +def onlyif(condition: bool, msg: str) -> MarkDecorator: """The reverse from skipif, see skipif for details.""" return skipif(not condition, msg) @@ -69,7 +71,7 @@ def onlyif(condition, msg): # ----------------------------------------------------------------------------- # Utility functions for decorators -def module_not_available(module): +def module_not_available(module: str) -> bool: """Can module be imported? Returns true if module does NOT import. This is used to make a decorator to skip tests that require module to be diff --git a/IPython/testing/ipunittest.py b/IPython/testing/ipunittest.py index 2ce77a44ed..1f7a5b2996 100644 --- a/IPython/testing/ipunittest.py +++ b/IPython/testing/ipunittest.py @@ -122,7 +122,7 @@ class Doc2UnitTester: no attempt is made at turning it into a singleton, there is no need for that). """ - def __init__(self, verbose=False): + def __init__(self, verbose: bool=False): """New decorator. Parameters diff --git a/IPython/testing/plugin/pytest_ipdoctest.py b/IPython/testing/plugin/pytest_ipdoctest.py index 2c35c6fe10..25ad7f3302 100644 --- a/IPython/testing/plugin/pytest_ipdoctest.py +++ b/IPython/testing/plugin/pytest_ipdoctest.py @@ -36,6 +36,7 @@ from _pytest.compat import safe_getattr from _pytest.config import Config from _pytest.config.argparsing import Parser +from _pytest.fixtures import TopRequest try: from _pytest.fixtures import TopRequest as FixtureRequest @@ -300,7 +301,7 @@ def from_parent( # type: ignore name: str, runner: "IPDocTestRunner", dtest: "doctest.DocTest", - ): + ) -> "IPDoctestItem": # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) @@ -475,7 +476,7 @@ def _get_flag_lookup() -> Dict[str, int]: ) -def get_optionflags(parent): +def get_optionflags(parent: "IPDoctestModule") -> int: optionflags_str = parent.config.getini("ipdoctest_optionflags") flag_lookup_table = _get_flag_lookup() flag_acc = 0 @@ -484,7 +485,7 @@ def get_optionflags(parent): return flag_acc -def _get_continue_on_failure(config): +def _get_continue_on_failure(config: Config) -> bool: continue_on_failure = config.getvalue("ipdoctest_continue_on_failure") if continue_on_failure: # We need to turn off this if we use pdb since we should stop at diff --git a/IPython/testing/skipdoctest.py b/IPython/testing/skipdoctest.py index f440ea14b2..92583a5301 100644 --- a/IPython/testing/skipdoctest.py +++ b/IPython/testing/skipdoctest.py @@ -4,12 +4,13 @@ numpy and sympy if they're present. Since this decorator is used in core parts of IPython, it's in a separate module so that running IPython doesn't trigger those imports.""" +from typing import Any, Callable # Copyright (C) IPython Development Team # Distributed under the terms of the Modified BSD License. -def skip_doctest(f): +def skip_doctest(f: Any) -> Any: """Decorator - mark a function or method for skipping its doctest. This decorator allows you to mark a function whose docstring you wish to diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index d596a000f8..7eba9a8c2b 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -29,6 +29,8 @@ from . import decorators as dec from . import skipdoctest +from types import TracebackType +from typing import List, Optional, Tuple, Type # The docstring for full_path doctests differently on win32 (different path @@ -67,7 +69,7 @@ def full_path(startPath: str, files: list[str]) -> list[str]: return [ os.path.join(base,f) for f in files ] -def parse_test_output(txt): +def parse_test_output(txt: str) -> Tuple[int, int]: """Parse the output of a test run and return errors, failures. Parameters @@ -113,7 +115,7 @@ def parse_test_output(txt): parse_test_output.__test__ = False -def default_argv(): +def default_argv() -> List[str]: """Return a valid default argv for creating testing instances of ipython""" return [ @@ -126,7 +128,7 @@ def default_argv(): ] -def default_config(): +def default_config() -> Config: """Return a config object with good defaults for testing.""" config = Config() config.TerminalInteractiveShell.colors = "nocolor" @@ -139,7 +141,7 @@ def default_config(): return config -def get_ipython_cmd(as_string=False): +def get_ipython_cmd(as_string: bool=False) -> List[str]: """ Return appropriate IPython command line name. By default, this will return a list that can be used with subprocess.Popen, for example, but passing @@ -157,7 +159,7 @@ def get_ipython_cmd(as_string=False): return ipython_cmd -def ipexec(fname, options=None, commands=()): +def ipexec(fname: str, options: Optional[List[str]]=None, commands: Tuple[str, ...]=()) -> Tuple[str, str]: """Utility to call 'ipython filename'. Starts IPython with a minimal and safe configuration to make startup as fast @@ -215,8 +217,8 @@ def ipexec(fname, options=None, commands=()): return out, err -def ipexec_validate(fname, expected_out, expected_err='', - options=None, commands=()): +def ipexec_validate(fname: str, expected_out: str, expected_err: str='', + options: Optional[List[str]]=None, commands: Tuple[str, ...]=()): """Utility to call 'ipython filename' and validate output/error. This function raises an AssertionError if the validation fails. @@ -267,7 +269,7 @@ class TempFileMixin(unittest.TestCase): Meant as a mixin class for test cases.""" - def mktmp(self, src, ext='.py'): + def mktmp(self, src: str, ext: str='.py'): """Make a valid python temp file.""" fname = temp_pyfile(src, ext) if not hasattr(self, 'tmps'): @@ -318,7 +320,7 @@ class AssertPrints: abcd def """ - def __init__(self, s, channel='stdout', suppress=True): + def __init__(self, s: str, channel: str='stdout', suppress: bool=True): self.s = s if isinstance(self.s, (str, _re_type)): self.s = [self.s] @@ -331,7 +333,7 @@ def __enter__(self): self.tee = Tee(self.buffer, channel=self.channel) setattr(sys, self.channel, self.buffer if self.suppress else self.tee) - def __exit__(self, etype, value, traceback): + def __exit__(self, etype: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]): __tracebackhide__ = True try: diff --git a/IPython/utils/capture.py b/IPython/utils/capture.py index 39ce9ca55f..795e4e5a4b 100644 --- a/IPython/utils/capture.py +++ b/IPython/utils/capture.py @@ -7,6 +7,8 @@ import sys from io import StringIO +from types import TracebackType +from typing import Any, List, Optional, Type #----------------------------------------------------------------------------- # Classes and functions @@ -72,7 +74,7 @@ class CapturedIO: above in the same order, and can be invoked simply via ``c()``. """ - def __init__(self, stdout, stderr, outputs=None): + def __init__(self, stdout: StringIO, stderr: StringIO, outputs: Optional[List[Any]]=None): self._stdout = stdout self._stderr = stderr if outputs is None: @@ -83,14 +85,14 @@ def __str__(self): return self.stdout @property - def stdout(self): + def stdout(self) -> str: "Captured standard output" if not self._stdout: return '' return self._stdout.getvalue() @property - def stderr(self): + def stderr(self) -> str: "Captured standard error" if not self._stderr: return '' @@ -127,13 +129,13 @@ class capture_output: stderr = True display = True - def __init__(self, stdout=True, stderr=True, display=True): + def __init__(self, stdout: bool=True, stderr: bool=True, display: bool=True): self.stdout = stdout self.stderr = stderr self.display = display self.shell = None - def __enter__(self): + def __enter__(self) -> CapturedIO: from IPython.core.getipython import get_ipython from IPython.core.displaypub import CapturingDisplayPublisher from IPython.core.displayhook import CapturingDisplayHook @@ -162,7 +164,7 @@ def __enter__(self): return CapturedIO(stdout, stderr, outputs) - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType]): sys.stdout = self.sys_stdout sys.stderr = self.sys_stderr if self.display and self.shell: diff --git a/IPython/utils/decorators.py b/IPython/utils/decorators.py index c99807d5d9..358d02124b 100644 --- a/IPython/utils/decorators.py +++ b/IPython/utils/decorators.py @@ -16,20 +16,17 @@ #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- -from __future__ import annotations - -from collections.abc import Callable, Sequence -from typing import Any, TypeVar +from collections.abc import Sequence from IPython.utils.docs import GENERATING_DOCUMENTATION +from typing import Any, Callable -F = TypeVar("F", bound=Callable[..., Any]) #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- -def flag_calls(func: Callable[..., Any]) -> Callable[..., Any]: +def flag_calls(func): """Wrap a function to detect and flag when it gets called. This is a decorator which takes a function and wraps it in a function with @@ -41,23 +38,23 @@ def flag_calls(func: Callable[..., Any]) -> Callable[..., Any]: Testing for truth in wrapper.called allows you to determine if a call to func() was attempted and succeeded.""" - + # don't wrap twice if hasattr(func, 'called'): return func - def wrapper(*args: Any, **kw: Any) -> Any: - wrapper.called = False # type: ignore[attr-defined] - out = func(*args, **kw) - wrapper.called = True # type: ignore[attr-defined] + def wrapper(*args,**kw): + wrapper.called = False + out = func(*args,**kw) + wrapper.called = True return out - wrapper.called = False # type: ignore[attr-defined] + wrapper.called = False wrapper.__doc__ = func.__doc__ return wrapper -def undoc(func: F) -> F: +def undoc(func: Any) -> Any: """Mark a function or class as undocumented. This is found by inspecting the AST, so for now it must be used directly @@ -70,14 +67,14 @@ def sphinx_options( show_inheritance: bool = True, show_inherited_members: bool = False, exclude_inherited_from: Sequence[str] = tuple(), -) -> Callable[[F], F]: +) -> Callable: """Set sphinx options""" - def wrapper(func: F) -> F: + def wrapper(func): if not GENERATING_DOCUMENTATION: return func - func._sphinx_options = dict( # type: ignore[attr-defined] + func._sphinx_options = dict( show_inheritance=show_inheritance, show_inherited_members=show_inherited_members, exclude_inherited_from=exclude_inherited_from, diff --git a/IPython/utils/dir2.py b/IPython/utils/dir2.py index 3796c37b10..ea867b6404 100644 --- a/IPython/utils/dir2.py +++ b/IPython/utils/dir2.py @@ -4,14 +4,12 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import annotations - import inspect import types -from typing import Any, Callable +from typing import List -def safe_hasattr(obj: object, attr: str) -> bool: +def safe_hasattr(obj: object, attr: str): """In recent versions of Python, hasattr() only catches AttributeError. This catches all errors. """ @@ -22,7 +20,7 @@ def safe_hasattr(obj: object, attr: str) -> bool: return False -def dir2(obj: object) -> list[str]: +def dir2(obj: object) -> List[str]: """dir2(obj) -> list of strings Extended version of the Python builtin dir(), which does a few extra @@ -53,7 +51,7 @@ def dir2(obj: object) -> list[str]: return sorted(words) -def get_real_method(obj: object, name: str) -> Callable[..., Any] | None: +def get_real_method(obj: object, name: str): """Like getattr, but with a few extra sanity checks: - If obj is a class, ignore everything except class methods diff --git a/IPython/utils/io.py b/IPython/utils/io.py index 05889aba0c..c122ee26d9 100644 --- a/IPython/utils/io.py +++ b/IPython/utils/io.py @@ -17,6 +17,8 @@ from IPython.utils.decorators import undoc from .capture import CapturedIO, capture_output +from io import StringIO +from typing import Union class Tee: @@ -30,7 +32,7 @@ class Tee: # Inspired by: # http://mail.python.org/pipermail/python-list/2007-May/442737.html - def __init__(self, file_or_name, mode="w", channel='stdout'): + def __init__(self, file_or_name: Union[str, StringIO], mode: str="w", channel: str='stdout'): """Construct a new Tee object. Parameters @@ -113,7 +115,7 @@ def ask_yes_no(prompt, default=None, interrupt=None): return answers[ans] -def temp_pyfile(src, ext='.py'): +def temp_pyfile(src: str, ext: str='.py') -> str: """Make a temporary python file, return filename and filehandle. Parameters diff --git a/IPython/utils/ipstruct.py b/IPython/utils/ipstruct.py index ed112101a3..c26d2d250c 100644 --- a/IPython/utils/ipstruct.py +++ b/IPython/utils/ipstruct.py @@ -6,6 +6,7 @@ * Fernando Perez (original) * Brian Granger (refactoring to a dict subclass) """ +from typing import Any #----------------------------------------------------------------------------- # Copyright (C) 2008-2011 The IPython Development Team @@ -62,7 +63,7 @@ def __init__(self, *args, **kw): object.__setattr__(self, '_allownew', True) dict.__init__(self, *args, **kw) - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any): """Set an item with check for allownew. Examples @@ -85,7 +86,7 @@ def __setitem__(self, key, value): "can't create new attribute %s when allow_new_attr(False)" % key) dict.__setitem__(self, key, value) - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any): """Set an attr with protection of class members. This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to @@ -119,7 +120,7 @@ def __setattr__(self, key, value): except KeyError as e: raise AttributeError(e) from e - def __getattr__(self, key): + def __getattr__(self, key: str) -> Any: """Get an attr by calling :meth:`dict.__getitem__`. Like :meth:`__setattr__`, this method converts :exc:`KeyError` to diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 2e93d6976f..73370165d4 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -21,7 +21,7 @@ #----------------------------------------------------------------------------- fs_encoding = sys.getfilesystemencoding() -def _writable_dir(path): +def _writable_dir(path: str) -> bool: """Whether `path` is a directory, to which the user has write access.""" return os.path.isdir(path) and os.access(path, os.W_OK) @@ -153,7 +153,7 @@ class HomeDirError(Exception): pass -def get_home_dir(require_writable=False) -> str: +def get_home_dir(require_writable: bool=False) -> str: """Return the 'home' directory, as a unicode string. Uses os.path.expanduser('~'), and checks for writability. @@ -196,7 +196,7 @@ def get_home_dir(require_writable=False) -> str: raise HomeDirError('%s is not a writable dir, ' 'set $HOME environment variable to override' % homedir) -def get_xdg_dir(): +def get_xdg_dir() -> str | None: """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. This is only for non-OS X posix (Linux,Unix,etc.) systems. @@ -234,7 +234,7 @@ def get_xdg_cache_dir(): return None -def expand_path(s): +def expand_path(s: str) -> str: """Expand $VARS and ~names in a string, like a shell :Examples: @@ -336,7 +336,7 @@ def link_or_copy(src, dst): # linking, or 'src' and 'dst' are on different filesystems. shutil.copy(src, dst) -def ensure_dir_exists(path, mode=0o755): +def ensure_dir_exists(path: str, mode: int=0o755): """ensure that a directory exists If it doesn't exist, try to create it and protect against a race condition diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py index f503c79257..3342e5b5b2 100644 --- a/IPython/utils/py3compat.py +++ b/IPython/utils/py3compat.py @@ -8,6 +8,7 @@ import builtins as builtin_mod from .encoding import DEFAULT_ENCODING +from typing import Optional def decode(s: bytes, encoding: str | None = None) -> str: @@ -15,12 +16,12 @@ def decode(s: bytes, encoding: str | None = None) -> str: return s.decode(encoding, "replace") -def encode(u, encoding=None): +def encode(u: str, encoding: Optional[str]=None) -> bytes: encoding = encoding or DEFAULT_ENCODING return u.encode(encoding, "replace") -def cast_unicode(s, encoding=None): +def cast_unicode(s: str | bytes, encoding: Optional[str]=None) -> str: if isinstance(s, bytes): return decode(s, encoding) return s diff --git a/IPython/utils/strdispatch.py b/IPython/utils/strdispatch.py index bbaabe5af6..f49edd5449 100644 --- a/IPython/utils/strdispatch.py +++ b/IPython/utils/strdispatch.py @@ -6,6 +6,7 @@ # Our own modules from IPython.core.hooks import CommandChainDispatcher +from typing import Callable # Code begins class StrDispatch: @@ -25,7 +26,7 @@ def __init__(self): self.strs = {} self.regexs = {} - def add_s(self, s, obj, priority= 0 ): + def add_s(self, s: str, obj: Callable, priority: int= 0 ): """ Adds a target 'string' for dispatching """ chain = self.strs.get(s, CommandChainDispatcher()) diff --git a/IPython/utils/tempdir.py b/IPython/utils/tempdir.py index 0efdeb1067..7e7751d1e5 100644 --- a/IPython/utils/tempdir.py +++ b/IPython/utils/tempdir.py @@ -5,12 +5,15 @@ """ import os as _os +from io import BufferedWriter from pathlib import Path from tempfile import TemporaryDirectory +from types import TracebackType +from typing import Optional, Type class NamedFileInTemporaryDirectory: - def __init__(self, filename, mode, bufsize=-1, add_to_syspath=False, **kwds): + def __init__(self, filename: str, mode: str, bufsize: int=-1, add_to_syspath: bool=False, **kwds): """ Open a file named `filename` in a temporary directory. @@ -32,10 +35,10 @@ def cleanup(self): __del__ = cleanup - def __enter__(self): + def __enter__(self) -> BufferedWriter: return self.file - def __exit__(self, type, value, traceback): + def __exit__(self, type: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]): self.cleanup() @@ -49,11 +52,11 @@ class TemporaryWorkingDirectory(TemporaryDirectory): ... """ - def __enter__(self): + def __enter__(self) -> str: self.old_wd = Path.cwd() _os.chdir(self.name) return super(TemporaryWorkingDirectory, self).__enter__() - def __exit__(self, exc, value, tb): + def __exit__(self, exc: Optional[Type[BaseException]], value: Optional[BaseException], tb: Optional[TracebackType]) -> None: _os.chdir(self.old_wd) return super(TemporaryWorkingDirectory, self).__exit__(exc, value, tb) diff --git a/IPython/utils/terminal.py b/IPython/utils/terminal.py index 14ab7a7ce9..1b7f9e9130 100644 --- a/IPython/utils/terminal.py +++ b/IPython/utils/terminal.py @@ -34,7 +34,7 @@ def _term_clear(): -def toggle_set_term_title(val): +def toggle_set_term_title(val: bool): """Control whether set_term_title is active or not. set_term_title() allows writing to the console titlebar. In embedded diff --git a/IPython/utils/tokenutil.py b/IPython/utils/tokenutil.py index 0024b807fd..5ecdbb8b8d 100644 --- a/IPython/utils/tokenutil.py +++ b/IPython/utils/tokenutil.py @@ -9,7 +9,7 @@ from io import StringIO from keyword import iskeyword from tokenize import TokenInfo -from typing import NamedTuple +from typing import Callable, NamedTuple from collections.abc import Generator @@ -21,7 +21,7 @@ class Token(NamedTuple): line: str -def generate_tokens(readline) -> Generator[TokenInfo, None, None]: +def generate_tokens(readline: Callable) -> Generator[TokenInfo, None, None]: """wrap generate_tkens to catch EOF errors""" try: yield from tokenize.generate_tokens(readline)