From 008777fa218ba08a05ec782fbeac2d3ddc6b1086 2023-03-13 14:22:09 From: Matthias Bussonnier Date: 2023-03-13 14:22:09 Subject: [PATCH] MAINT: refactor/please mypy. With this mypy should be happier, and some files should be properly typed. --- diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index c7fa22c..c65682e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -26,14 +26,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install mypy pyflakes flake8 + pip install mypy pyflakes flake8 types-decorator - name: Lint with mypy run: | set -e - mypy -p IPython.terminal - mypy -p IPython.core.magics - mypy -p IPython.core.guarded_eval - mypy -p IPython.core.completer + mypy IPython - name: Lint with pyflakes run: | set -e diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index e7aa6f3..15cf703 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -29,6 +29,8 @@ from traitlets import ( default, observe, ) +from typing import Any + class DisplayFormatter(Configurable): @@ -306,8 +308,8 @@ class BaseFormatter(Configurable): returned and this format type is not used. """ - format_type = Unicode('text/plain') - _return_type = str + format_type = Unicode("text/plain") + _return_type: Any = str enabled = Bool(True).tag(config=True) diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index 01b9c8a..10707d3 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -31,6 +31,8 @@ import sys import tokenize import warnings +from typing import List + from IPython.core.inputtransformer import (leading_indent, classic_prompt, ipy_prompt, @@ -319,17 +321,16 @@ class InputSplitter(object): # Private attributes # List with lines of input accumulated so far - _buffer = None + _buffer: List[str] # Command compiler - _compile = None + _compile: codeop.CommandCompiler # Boolean indicating whether the current block is complete _is_complete = None # Boolean indicating whether the current block has an unrecoverable syntax error _is_invalid = False - def __init__(self): - """Create a new InputSplitter instance. - """ + def __init__(self) -> None: + """Create a new InputSplitter instance.""" self._buffer = [] self._compile = codeop.CommandCompiler() self.encoding = get_input_encoding() @@ -483,7 +484,7 @@ class InputSplitter(object): return False try: - code_ast = ast.parse(u''.join(self._buffer)) + code_ast = ast.parse("".join(self._buffer)) except Exception: #print("Can't parse AST") # debug return False diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 4fa266a..9b0bc97 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -33,7 +33,7 @@ from logging import error from pathlib import Path from typing import Callable from typing import List as ListType, Dict as DictType, Any as AnyType -from typing import Optional, Tuple +from typing import Optional, Sequence, Tuple from warnings import warn from pickleshare import PickleShareDB @@ -1563,7 +1563,7 @@ class InteractiveShell(SingletonConfigurable): # Things related to object introspection #------------------------------------------------------------------------- @staticmethod - def _find_parts(oname: str) -> ListType[str]: + def _find_parts(oname: str) -> Tuple[bool, ListType[str]]: """ Given an object name, return a list of parts of this object name. @@ -1607,7 +1607,9 @@ class InteractiveShell(SingletonConfigurable): return parts_ok, parts - def _ofind(self, oname: str, namespaces: DictType[str, AnyType] = None): + def _ofind( + self, oname: str, namespaces: Optional[Sequence[Tuple[str, AnyType]]] = None + ): """Find an object in the available namespaces. self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic @@ -1627,7 +1629,7 @@ class InteractiveShell(SingletonConfigurable): isalias=False, found=False, obj=None, - namespace="", + namespace=None, parent=None, ) @@ -1710,14 +1712,12 @@ class InteractiveShell(SingletonConfigurable): ospace = 'Interactive' return OInfo( - **{ - "obj": obj, - "found": found, - "parent": parent, - "ismagic": ismagic, - "isalias": isalias, - "namespace": ospace, - } + obj=obj, + found=found, + parent=parent, + ismagic=ismagic, + isalias=isalias, + namespace=ospace, ) @staticmethod @@ -3110,7 +3110,7 @@ class InteractiveShell(SingletonConfigurable): shell_futures=True, *, transformed_cell: Optional[str] = None, - preprocessing_exc_tuple: Optional[Any] = None, + preprocessing_exc_tuple: Optional[AnyType] = None, cell_id=None, ) -> ExecutionResult: """Run a complete IPython cell asynchronously. @@ -3425,7 +3425,7 @@ class InteractiveShell(SingletonConfigurable): if mode == "exec": mod = Module([node], []) elif mode == "single": - mod = ast.Interactive([node]) + mod = ast.Interactive([node]) # type: ignore with compiler.extra_flags( getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) if self.autoawait diff --git a/IPython/core/magic_arguments.py b/IPython/core/magic_arguments.py index 568abd8..24dd541 100644 --- a/IPython/core/magic_arguments.py +++ b/IPython/core/magic_arguments.py @@ -251,7 +251,7 @@ class ArgMethodWrapper(ArgDecorator): """ - _method_name = None + _method_name: str def __init__(self, *args, **kwds): self.args = args diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 399a52b..706c736 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -46,7 +46,7 @@ from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import HtmlFormatter -from typing import Any +from typing import Any, Optional from dataclasses import dataclass @@ -55,7 +55,7 @@ class OInfo: ismagic: bool isalias: bool found: bool - namespace: str + namespace: Optional[str] parent: Any obj: Any @@ -173,7 +173,10 @@ def getsource(obj, oname='') -> Union[str,None]: oname_prefix = ('%s.' % oname) if oname else '' sources.append(''.join(('# ', oname_prefix, attrname))) if inspect.isfunction(fn): - sources.append(dedent(getsource(fn))) + _src = getsource(fn) + if _src: + # assert _src is not None, "please mypy" + sources.append(dedent(_src)) else: # Default str/repr only prints function name, # pretty.pretty prints module name too. @@ -797,9 +800,11 @@ class Inspector(Colorable): if obj.__doc__: ds += "\nDocstring:\n" + obj.__doc__ else: - ds = getdoc(obj) - if ds is None: + ds_or_None = getdoc(obj) + if ds_or_None is None: ds = '' + else: + ds = ds_or_None # store output in a dict, we initialize it here and fill it as we go out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None) @@ -1060,15 +1065,15 @@ def _render_signature(obj_signature, obj_name) -> str: pos_only = False kw_only = True for param in obj_signature.parameters.values(): - if param.kind == inspect._POSITIONAL_ONLY: + if param.kind == inspect.Parameter.POSITIONAL_ONLY: pos_only = True elif pos_only: result.append('/') pos_only = False - if param.kind == inspect._VAR_POSITIONAL: + if param.kind == inspect.Parameter.VAR_POSITIONAL: kw_only = False - elif param.kind == inspect._KEYWORD_ONLY and kw_only: + elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only: result.append('*') kw_only = False diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 0ac9f60..b55062d 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -449,7 +449,7 @@ class InteractiveShellTestCase(unittest.TestCase): self.assertEqual(found, info) found = ip._ofind("a.bar", [("locals", locals())]) - info = OInfo( + expected = OInfo( found=False, isalias=False, ismagic=False, @@ -457,7 +457,7 @@ class InteractiveShellTestCase(unittest.TestCase): obj=None, parent=a, ) - self.assertEqual(found, info) + assert found == expected def test_ofind_prefers_property_to_instance_level_attribute(self): class A(object): diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index e16971e..009268f 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -391,7 +391,12 @@ class TBTools(colorable.Colorable): return self.stb2text(tb_list) def structured_traceback( - self, etype, evalue, tb, tb_offset: Optional[int] = None, context=5, mode=None + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + context=5, ): """Return a list of traceback frames. @@ -435,7 +440,7 @@ class ListTB(TBTools): def structured_traceback( self, etype: type, - evalue: BaseException, + evalue: Optional[BaseException], etb: Optional[TracebackType] = None, tb_offset: Optional[int] = None, context=5, @@ -493,8 +498,11 @@ class ListTB(TBTools): exception = self.get_parts_of_chained_exception(evalue) if exception and not id(exception[1]) in chained_exc_ids: - chained_exception_message = self.prepare_chained_exception_message( - evalue.__cause__)[0] + chained_exception_message = ( + self.prepare_chained_exception_message(evalue.__cause__)[0] + if evalue is not None + else "" + ) etype, evalue, etb = exception # Trace exception to avoid infinite 'cause' loop chained_exc_ids.add(id(exception[1])) @@ -872,7 +880,7 @@ class VerboseTB(TBTools): ) return result - def prepare_header(self, etype, long_version=False): + def prepare_header(self, etype: str, long_version: bool = False): colors = self.Colors # just a shorthand + quicker name lookup colorsnormal = colors.Normal # used a lot exc = '%s%s%s' % (colors.excName, etype, colorsnormal) @@ -882,15 +890,25 @@ class VerboseTB(TBTools): pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable date = time.ctime(time.time()) - head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal, - exc, ' ' * (width - len(str(etype)) - len(pyver)), - pyver, date.rjust(width) ) - head += "\nA problem occurred executing Python code. Here is the sequence of function" \ - "\ncalls leading up to the error, with the most recent (innermost) call last." + head = "%s%s%s\n%s%s%s\n%s" % ( + colors.topline, + "-" * width, + colorsnormal, + exc, + " " * (width - len(etype) - len(pyver)), + pyver, + date.rjust(width), + ) + head += ( + "\nA problem occurred executing Python code. Here is the sequence of function" + "\ncalls leading up to the error, with the most recent (innermost) call last." + ) else: # Simplified header - head = '%s%s' % (exc, 'Traceback (most recent call last)'. \ - rjust(width - len(str(etype))) ) + head = "%s%s" % ( + exc, + "Traceback (most recent call last)".rjust(width - len(etype)), + ) return head @@ -911,7 +929,7 @@ class VerboseTB(TBTools): def format_exception_as_a_whole( self, etype: type, - evalue: BaseException, + evalue: Optional[BaseException], etb: Optional[TracebackType], number_of_lines_of_context, tb_offset: Optional[int], @@ -995,7 +1013,7 @@ class VerboseTB(TBTools): ) # Let's estimate the amount of code we will have to parse/highlight. - cf = etb + cf: Optional[TracebackType] = etb max_len = 0 tbs = [] while cf is not None: diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 5ff2983..f39f389 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -8,7 +8,7 @@ from os import walk, sep, fsdecode from IPython.core.display import DisplayObject, TextDisplayObject -from typing import Tuple, Iterable +from typing import Tuple, Iterable, Optional __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', 'FileLink', 'FileLinks', 'Code'] @@ -272,7 +272,9 @@ class IFrame(object): > """ - def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs): + def __init__( + self, src, width, height, extras: Optional[Iterable[str]] = None, **kwargs + ): if extras is None: extras = [] diff --git a/IPython/utils/tz.py b/IPython/utils/tz.py index b315d53..dbe1e8e 100644 --- a/IPython/utils/tz.py +++ b/IPython/utils/tz.py @@ -33,7 +33,9 @@ class tzUTC(tzinfo): def dst(self, d): return ZERO -UTC = tzUTC() + +UTC = tzUTC() # type: ignore[abstract] + def utc_aware(unaware): """decorator for adding UTC tzinfo to datetime's utcfoo methods""" diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 7c1be49..0000000 --- a/mypy.ini +++ /dev/null @@ -1,4 +0,0 @@ -[mypy] -python_version = 3.8 -ignore_missing_imports = True -follow_imports = silent diff --git a/pyproject.toml b/pyproject.toml index 48ea37f..60b027e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,32 @@ [build-system] requires = ["setuptools >= 51.0.0"] build-backend = "setuptools.build_meta" +[tool.mypy] +python_version = 3.8 +ignore_missing_imports = true +follow_imports = 'silent' +exclude = [ + 'test_\.+\.py', + 'IPython.utils.tests.test_wildcard', + 'testing', + 'tests', + 'PyColorize.py', + '_process_win32_controller.py', + 'IPython/core/application.py', + 'IPython/core/completerlib.py', + 'IPython/core/displaypub.py', + 'IPython/core/historyapp.py', + #'IPython/core/interactiveshell.py', + 'IPython/core/magic.py', + 'IPython/core/profileapp.py', + 'IPython/core/ultratb.py', + 'IPython/lib/deepreload.py', + 'IPython/lib/pretty.py', + 'IPython/sphinxext/ipython_directive.py', + 'IPython/terminal/ipapp.py', + 'IPython/utils/_process_win32.py', + 'IPython/utils/path.py', + 'IPython/utils/timing.py', + 'IPython/utils/text.py' + ] +