From 4a28a2d89fc0263d1a46760b74e2913d49dba31f 2024-10-04 08:57:48 From: M Bussonnier Date: 2024-10-04 08:57:48 Subject: [PATCH] some more typechekcing --- diff --git a/IPython/testing/tests/test_tools.py b/IPython/testing/tests/test_tools.py index 178863c..e6d5bc2 100644 --- a/IPython/testing/tests/test_tools.py +++ b/IPython/testing/tests/test_tools.py @@ -32,7 +32,7 @@ def test_full_path_posix(): spath = "/foo" result = tt.full_path(spath, ["a.txt", "b.txt"]) assert result, ["/a.txt" == "/b.txt"] - result = tt.full_path(spath, "a.txt") + result = tt.full_path(spath, ["a.txt"]) assert result == ["/a.txt"] @@ -44,7 +44,7 @@ def test_full_path_win32(): spath = "c:\\foo" result = tt.full_path(spath, ["a.txt", "b.txt"]) assert result, ["c:\\a.txt" == "c:\\b.txt"] - result = tt.full_path(spath, "a.txt") + result = tt.full_path(spath, ["a.txt"]) assert result == ["c:\\a.txt"] diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index b030349..aa54443 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -36,7 +36,7 @@ from . import skipdoctest doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco @doctest_deco -def full_path(startPath,files): +def full_path(startPath: str, files: list[str]) -> list[str]: """Make full paths for all the listed files, based on startPath. Only the base part of startPath is kept, since this routine is typically @@ -49,7 +49,7 @@ def full_path(startPath,files): Initial path to use as the base for the results. This path is split using os.path.split() and only its first component is kept. - files : string or list + files : list One or more files. Examples @@ -61,13 +61,8 @@ def full_path(startPath,files): >>> full_path('/foo',['a.txt','b.txt']) ['/a.txt', '/b.txt'] - If a single file is given, the output is still a list:: - - >>> full_path('/foo','a.txt') - ['/a.txt'] """ - - files = list_strings(files) + assert isinstance(files, list) base = os.path.split(startPath)[0] return [ os.path.join(base,f) for f in files ] diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 46b3bb0..5623ca5 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -16,7 +16,20 @@ import warnings from string import Formatter from pathlib import Path -from typing import List, Dict, Tuple, Optional, cast, Sequence, Mapping, Any +from typing import ( + List, + Dict, + Tuple, + Optional, + cast, + Sequence, + Mapping, + Any, + Union, + Callable, + Iterator, + TypeVar, +) if sys.version_info < (3, 12): from typing_extensions import Self @@ -138,8 +151,13 @@ class SList(list): p = paths = property(get_paths) - def grep(self, pattern, prune = False, field = None): - """ Return all strings matching 'pattern' (a regex or callable) + def grep( + self, + pattern: Union[str, Callable[[Any], re.Match[str] | None]], + prune: bool = False, + field: Optional[int] = None, + ) -> Self: + """Return all strings matching 'pattern' (a regex or callable) This is case-insensitive. If prune is true, return all items NOT matching the pattern. @@ -154,7 +172,7 @@ class SList(list): a.grep('chm', field=-1) """ - def match_target(s): + def match_target(s: str) -> str: if field is None: return s parts = s.split() @@ -169,12 +187,12 @@ class SList(list): else: pred = pattern if not prune: - return SList([el for el in self if pred(match_target(el))]) + return type(self)([el for el in self if pred(match_target(el))]) else: - return SList([el for el in self if not pred(match_target(el))]) + return type(self)([el for el in self if not pred(match_target(el))]) - def fields(self, *fields): - """ Collect whitespace-separated fields from string list + def fields(self, *fields: List[str]) -> List[List[str]]: + """Collect whitespace-separated fields from string list Allows quick awk-like usage of string lists. @@ -209,8 +227,12 @@ class SList(list): return res - def sort(self,field= None, nums = False): - """ sort by specified fields (see fields()) + def sort( # type:ignore[override] + self, + field: Optional[List[str]] = None, + nums: bool = False, + ) -> Self: + """sort by specified fields (see fields()) Example:: @@ -236,7 +258,7 @@ class SList(list): dsu.sort() - return SList([t[1] for t in dsu]) + return type(self)([t[1] for t in dsu]) # FIXME: We need to reimplement type specific displayhook and then add this @@ -255,7 +277,7 @@ class SList(list): # print_slist = result_display.register(SList)(print_slist) -def indent(instr,nspaces=4, ntabs=0, flatten=False): +def indent(instr: str, nspaces: int = 4, ntabs: int = 0, flatten: bool = False) -> str: """Indent a string a given number of spaces or tabstops. indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. @@ -275,7 +297,7 @@ def indent(instr,nspaces=4, ntabs=0, flatten=False): Returns ------- - str|unicode : string indented by ntabs and nspaces. + str : string indented by ntabs and nspaces. """ if instr is None: @@ -292,7 +314,7 @@ def indent(instr,nspaces=4, ntabs=0, flatten=False): return outstr -def list_strings(arg): +def list_strings(arg: Union[str, List[str]]) -> List[str]: """Always return a list of strings, given a string or list of strings as input. @@ -316,7 +338,7 @@ def list_strings(arg): return arg -def marquee(txt='',width=78,mark='*'): +def marquee(txt: str = "", width: int = 78, mark: str = "*") -> str: """Return the input string centered in a 'marquee'. Examples @@ -343,7 +365,8 @@ def marquee(txt='',width=78,mark='*'): ini_spaces_re = re.compile(r'^(\s+)') -def num_ini_spaces(strng): + +def num_ini_spaces(strng: str) -> int: """Return the number of initial spaces in a string""" warnings.warn( "`num_ini_spaces` is Pending Deprecation since IPython 8.17." @@ -359,7 +382,7 @@ def num_ini_spaces(strng): return 0 -def format_screen(strng): +def format_screen(strng: str) -> str: """Format a string for screen printing. This removes some latex-type format codes.""" @@ -396,7 +419,7 @@ def dedent(text: str) -> str: return '\n'.join([first, rest]) -def wrap_paragraphs(text, ncols=80): +def wrap_paragraphs(text: str, ncols: int = 80) -> List[str]: """Wrap multiple paragraphs to fit a specified width. This is equivalent to textwrap.wrap, but with support for multiple @@ -428,7 +451,7 @@ def wrap_paragraphs(text, ncols=80): return out_ps -def strip_email_quotes(text): +def strip_email_quotes(text: str) -> str: """Strip leading email quotation characters ('>'). Removes any combination of leading '>' interspersed with whitespace that @@ -478,7 +501,7 @@ def strip_email_quotes(text): return text -def strip_ansi(source): +def strip_ansi(source: str) -> str: """ Remove ansi escape codes from text. @@ -519,7 +542,8 @@ class EvalFormatter(Formatter): In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello") Out[3]: 'll' """ - def get_field(self, name, args, kwargs): + + def get_field(self, name: str, args: Any, kwargs: Any) -> Tuple[Any, str]: v = eval(name, kwargs) return v, name @@ -606,11 +630,15 @@ class DollarFormatter(FullEvalFormatter): In [4]: f.format('$a or {b}', a=1, b=2) Out[4]: '1 or 2' """ - _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)") - def parse(self, fmt_string): - for literal_txt, field_name, format_spec, conversion \ - in Formatter.parse(self, fmt_string): - + + _dollar_pattern_ignore_single_quote = re.compile( + r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)" + ) + + def parse(self, fmt_string: str) -> Iterator[Tuple[Any, Any, Any, Any]]: # type: ignore + for literal_txt, field_name, format_spec, conversion in Formatter.parse( + self, fmt_string + ): # Find $foo patterns in the literal text. continue_from = 0 txt = "" @@ -627,14 +655,17 @@ class DollarFormatter(FullEvalFormatter): # Re-yield the {foo} style pattern yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion) - def __repr__(self): + def __repr__(self) -> str: return "" #----------------------------------------------------------------------------- # Utils to columnize a list of string #----------------------------------------------------------------------------- -def _col_chunks(l, max_rows, row_first=False): + +def _col_chunks( + l: List[int], max_rows: int, row_first: bool = False +) -> Iterator[List[int]]: """Yield successive max_rows-sized column chunks from l.""" if row_first: ncols = (len(l) // max_rows) + (len(l) % max_rows > 0) @@ -646,7 +677,7 @@ def _col_chunks(l, max_rows, row_first=False): def _find_optimal( - rlist: List[str], row_first: bool, separator_size: int, displaywidth: int + rlist: List[int], row_first: bool, separator_size: int, displaywidth: int ) -> Dict[str, Any]: """Calculate optimal info to columnize a list of string""" for max_rows in range(1, len(rlist) + 1): @@ -662,7 +693,10 @@ def _find_optimal( } -def _get_or_default(mylist, i, default=None): +T = TypeVar("T") + + +def _get_or_default(mylist: List[T], i: int, default: T) -> T: """return list item number, or default if don't exist""" if i >= len(mylist): return default @@ -740,9 +774,31 @@ def compute_item_matrix( ) nrow, ncol = info["max_rows"], info["num_columns"] if row_first: - return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info) + return ( + [ + [ + _get_or_default( + items, r * ncol + c, default=empty + ) # type:ignore[misc] + for c in range(ncol) + ] + for r in range(nrow) + ], + info, + ) else: - return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info) + return ( + [ + [ + _get_or_default( + items, c * nrow + r, default=empty + ) # type:ignore[misc] + for c in range(ncol) + ] + for r in range(nrow) + ], + info, + ) def columnize( @@ -795,7 +851,9 @@ def columnize( return "\n".join(map(sjoin, fmatrix)) + "\n" -def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""): +def get_text_list( + list_: List[str], last_sep: str = " and ", sep: str = ", ", wrap_item_with: str = "" +) -> str: """ Return a string with a natural enumeration of items diff --git a/pyproject.toml b/pyproject.toml index d57dc85..840938b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,11 +150,21 @@ warn_redundant_casts = true module = [ "IPython.utils.text", ] +disallow_untyped_defs = true +check_untyped_defs = false +disallow_untyped_decorators = true + +[[tool.mypy.overrides]] +module = [ +] disallow_untyped_defs = false +ignore_errors = true +ignore_missing_imports = true +disallow_untyped_calls = false +disallow_incomplete_defs = false check_untyped_defs = false disallow_untyped_decorators = false - # gloabl ignore error [[tool.mypy.overrides]] module = [