diff --git a/IPython/__init__.py b/IPython/__init__.py index 91cf9ae444..9c87dc51be 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -21,6 +21,7 @@ import sys import warnings +from typing import Any #----------------------------------------------------------------------------- # Setup everything @@ -91,7 +92,7 @@ def embed_kernel(module=None, local_ns=None, **kwargs): from ipykernel.embed import embed_kernel as real_embed_kernel real_embed_kernel(module=module, local_ns=local_ns, **kwargs) -def start_ipython(argv=None, **kwargs): +def start_ipython(argv: list[str] | None = None, **kwargs: Any) -> Any: """Launch a normal IPython instance (as opposed to embedded) `IPython.embed()` puts a shell in a particular calling scope, diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index 4eac709dd3..2eaaa52ce8 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -15,7 +15,7 @@ import inspect from functools import wraps -_asyncio_event_loop = None +_asyncio_event_loop: asyncio.AbstractEventLoop | None = None def get_asyncio_loop(): diff --git a/IPython/core/doctb.py b/IPython/core/doctb.py index 4fcf378d66..c7be8453a6 100644 --- a/IPython/core/doctb.py +++ b/IPython/core/doctb.py @@ -330,7 +330,10 @@ def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any: before = context - after if self.has_colors: base_style = theme_table[self._theme_name].as_pygments_style() - style = stack_data.style_with_executing_node(base_style, self.tb_highlight) + # stack_data ships without type annotations + style = stack_data.style_with_executing_node( # type: ignore[no-untyped-call] + base_style, self.tb_highlight + ) formatter = Terminal256Formatter(style=style) else: formatter = None diff --git a/IPython/core/history.py b/IPython/core/history.py index ae78b06852..5495971b82 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -84,16 +84,16 @@ class DummyDB: def execute(*args: typing.Any, **kwargs: typing.Any) -> list: return [] - def commit(self, *args, **kwargs): # type: ignore [no-untyped-def] + def commit(self, *args: typing.Any, **kwargs: typing.Any) -> None: pass - def __enter__(self, *args, **kwargs): # type: ignore [no-untyped-def] + def __enter__(self, *args: typing.Any, **kwargs: typing.Any) -> None: pass - def __exit__(self, *args, **kwargs): # type: ignore [no-untyped-def] + def __exit__(self, *args: typing.Any, **kwargs: typing.Any) -> None: pass - def close(self, *args, **kwargs): # type: ignore [no-untyped-def] + def close(self, *args: typing.Any, **kwargs: typing.Any) -> None: pass diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 2799d0ba29..8de4f8d85a 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -482,7 +482,9 @@ def _enable_html_pager_changed(self, change): if change['new']: warn("`enable_html_pager` is provisional since IPython 5.0 and might change in future versions.", ProvisionalWarning) - data_pub_class = None + # Not a traitlets trait: a plain class-attribute override point for + # subclasses that want to set a data_pub class. + data_pub_class: type | None = None exit_now = Bool(False) exiter = Instance(ExitAutocall) @@ -616,7 +618,7 @@ def profile(self): _post_execute = Dict() # Tracks any GUI loop loaded for pylab - pylab_gui_select = None + pylab_gui_select: str | None = None last_execution_succeeded = Bool(True, help='Did last executed command succeeded') diff --git a/IPython/core/macro.py b/IPython/core/macro.py index 11314d4a16..a00c4a0d6b 100644 --- a/IPython/core/macro.py +++ b/IPython/core/macro.py @@ -10,8 +10,6 @@ import re -from IPython.utils.encoding import DEFAULT_ENCODING - coding_declaration = re.compile(r"#\s*coding[:=]\s*([-\w.]+)") class Macro: @@ -23,17 +21,8 @@ class Macro: def __init__(self, code: str): """store the macro value, as a single string which can be executed""" - lines = [] - enc = None - for line in code.splitlines(): - coding_match = coding_declaration.match(line) - if coding_match: - enc = coding_match.group(1) - else: - lines.append(line) + lines = [line for line in code.splitlines() if not coding_declaration.match(line)] code = "\n".join(lines) - if isinstance(code, bytes): - code = code.decode(enc or DEFAULT_ENCODING) self.value = code + '\n' def __str__(self): diff --git a/IPython/core/profiledir.py b/IPython/core/profiledir.py index ccbdf01d01..915f931b99 100644 --- a/IPython/core/profiledir.py +++ b/IPython/core/profiledir.py @@ -160,9 +160,6 @@ def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> boo dst = Path(os.path.join(self.location, config_file)) if dst.exists() and not overwrite: return False - if path is None: - path = os.path.join(get_ipython_package_dir(), 'core', 'profile', 'default') - assert isinstance(path, Path) src = path / config_file shutil.copy(src, dst) return True diff --git a/IPython/core/tbtools.py b/IPython/core/tbtools.py index 2e0440f700..a74981a960 100644 --- a/IPython/core/tbtools.py +++ b/IPython/core/tbtools.py @@ -58,10 +58,6 @@ def get_line_number_of_frame(frame: types.FrameType) -> int: if the file is not available. """ filename = frame.f_code.co_filename - if filename is None: - print("No file....") - lines, first = inspect.getsourcelines(frame) - return first + len(lines) return count_lines_in_py_file(filename) @@ -295,7 +291,7 @@ class FrameInfo: # number of context lines to use context: int | None raw_lines: list[str] - _sd: stack_data.core.FrameInfo + _sd: stack_data.core.FrameInfo | stack_data.core.RepeatedFrames | None frame: Any @classmethod @@ -344,8 +340,10 @@ def __init__( @property def variables_in_executing_piece(self) -> list[Any]: + # callers only reach here once RepeatedFrames-backed instances have + # been filtered out (see doctb.py/ultratb.py format_record) if self._sd is not None: - return self._sd.variables_in_executing_piece # type:ignore[misc] + return self._sd.variables_in_executing_piece # type:ignore[misc,union-attr] else: return [] @@ -353,9 +351,11 @@ def variables_in_executing_piece(self) -> list[Any]: def lines(self) -> list[Any]: from executing.executing import NotOneValueFound + # callers only reach here once RepeatedFrames-backed instances have + # been filtered out (see doctb.py/ultratb.py format_record) assert self._sd is not None try: - return self._sd.lines # type: ignore[misc] + return self._sd.lines # type: ignore[misc,union-attr] except NotOneValueFound: class Dummy: @@ -369,8 +369,10 @@ def render(self, *, pygmented: bool) -> str: @property def executing(self) -> Any: + # callers only reach here once RepeatedFrames-backed instances have + # been filtered out (see doctb.py/ultratb.py format_record) if self._sd is not None: - return self._sd.executing + return self._sd.executing # type: ignore[union-attr] else: return None diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index e2cea4ef72..487f1a489a 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -175,13 +175,16 @@ def structured_traceback( """ # This is a workaround to get chained_exc_ids in recursive calls # etb should not be a tuple if structured_traceback is not recursive + # (see the recursive self.structured_traceback() call below), and can + # also be a pre-built list of frames per the docstring above; neither + # is expressible in the public `TracebackType | None` signature. if isinstance(etb, tuple): - etb, chained_exc_ids = etb + etb, chained_exc_ids = etb # type: ignore[unreachable] else: chained_exc_ids = set() elist: list[Any] if isinstance(etb, list): - elist = etb + elist = etb # type: ignore[unreachable] elif etb is not None: elist = self._extract_tb(etb) # type: ignore[assignment] else: @@ -1256,11 +1259,14 @@ def structured_traceback( context: int = 5, ) -> list[str]: # tb: TracebackType or tupleof tb types ? + # etype can be None when called as structured_traceback(*sys.exc_info()) + # with no active exception; etb can be a tuple for a chained exception. + # Neither is expressible in the public signature above. if etype is None: - etype, evalue, etb = sys.exc_info() + etype, evalue, etb = sys.exc_info() # type: ignore[unreachable] if isinstance(etb, tuple): # tb is a tuple if this is a chained exception. - self.tb = etb[0] + self.tb = etb[0] # type: ignore[unreachable] else: self.tb = etb return FormattedTB.structured_traceback( diff --git a/IPython/paths.py b/IPython/paths.py index 1a95848bfa..d86d480c5a 100644 --- a/IPython/paths.py +++ b/IPython/paths.py @@ -109,7 +109,7 @@ def get_ipython_module_path(module_str): return the_path -def locate_profile(profile='default'): +def locate_profile(profile: str = "default") -> str: """Find the path to the folder associated with a given profile. I.e. find $IPYTHONDIR/profile_whatever. diff --git a/IPython/utils/PyColorize.py b/IPython/utils/PyColorize.py index 9446a5eee1..da749b5801 100644 --- a/IPython/utils/PyColorize.py +++ b/IPython/utils/PyColorize.py @@ -435,13 +435,16 @@ def theme_name(self, value: str) -> None: @property def style(self) -> str: + # `style` was renamed `theme_name`; this always raises to catch + # leftover callers of the old name. Body kept only to satisfy the -> str + # return type. assert False - return self._theme_name + return self._theme_name # type: ignore[unreachable] @style.setter def style(self, val: str) -> None: assert False - assert val == val.lower() + assert val == val.lower() # type: ignore[unreachable] self._theme_name = val def format(self, raw: str, out: Any = None) -> str | None: diff --git a/IPython/utils/module_paths.py b/IPython/utils/module_paths.py index 1ec2e38e49..52637aaf10 100644 --- a/IPython/utils/module_paths.py +++ b/IPython/utils/module_paths.py @@ -69,7 +69,7 @@ def find_mod(module_name: str) -> str | None | importlib.abc.Loader: if module_path is None: # built-in/frozen modules use their own meta_path finder as loader if spec.loader is not None and spec.loader in sys.meta_path: # type: ignore[comparison-overlap] - return spec.loader + return spec.loader # type: ignore[unreachable] return None else: split_path = module_path.split(".") diff --git a/pyproject.toml b/pyproject.toml index a73f42f149..384c135333 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,8 +116,8 @@ exclude = [ 'IPython/core/debugger_backport.py', ] check_untyped_defs = true -# disallow_untyped_calls = true -# disallow_untyped_decorators = true +disallow_untyped_calls = true +disallow_untyped_decorators = true # ignore_errors = false # ignore_missing_imports = false disallow_incomplete_defs = true @@ -125,6 +125,7 @@ disallow_untyped_defs = true warn_redundant_casts = true warn_unused_ignores = true strict_equality = true +warn_unreachable = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] [[tool.mypy.overrides]]