Skip to content

Commit 97f8ae7

Browse files
authored
Add inline typing annotations; remove .pyi stubs (#193)
* Port annotations for utility modules and remove migrated stubs * Port checker and util_stream stub annotations inline * Port parser and google docscrape stubs to inline annotations * Remove remaining xdoctest type stub files * Reduce type ignores and tighten typing fallbacks * Fix CaptureStdout destructor safety on partial init * Fix Python 3.8/3.9 runtime unions in typing.cast * Make pytest override fallback independent of typing_extensions * Add return annotations for Returns-documented functions * Annotate Args-documented parameters across xdoctest * Add return annotations for Returns/Yields docstring functions * Fix ty errors * Fix test * Refactor away more any types * manual type fixes * manual type fixes * more manual type fixes * fix bad fstring * fix bad fstring * More type fixes * Fix ty checks * Fix new mypy errors * Removed unused six_ast_parse code * Fix incorrect underscore refactor * minor cleanup * replace more typing.Any * Fix more typing.Any locations * wip * Fix mypy errors
1 parent 87054b6 commit 97f8ae7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+926
-1284
lines changed

pyproject.toml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
requires = [ "setuptools>=41.0.1",]
33
build-backend = "setuptools.build_meta"
44

5-
[tool.mypy]
6-
ignore_missing_imports = true
7-
85
[tool.xcookie]
96
tags = [ "erotemic", "github", "purepy",]
107
mod_name = "xdoctest"
@@ -93,6 +90,19 @@ count = true
9390
quiet-level = 3
9491
ignore-words-list = ['wont', 'cant', 'ANS', 'doesnt', 'arent', 'ans', 'thats', 'datas', 'isnt', 'didnt', 'wasnt']
9592

93+
94+
[tool.mypy]
95+
ignore_missing_imports = true
96+
97+
[tool.ty.rules]
98+
unused-type-ignore-comment = "ignore"
99+
100+
[[tool.ty.overrides]]
101+
include = ["src/xdoctest/_tokenize.py"]
102+
[tool.ty.overrides.rules]
103+
unsupported-operator = "ignore"
104+
invalid-assignment = "ignore"
105+
96106
[tool.ruff]
97107
line-length = 80
98108
target-version = "py38"
@@ -114,3 +124,5 @@ indent-style = "space"
114124
skip-magic-trailing-comma = false
115125
line-ending = "auto"
116126
docstring-code-format = false
127+
128+

src/xdoctest/__main__.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
This should work even if the target module is unaware of xdoctest.
66
"""
77

8-
import sys
8+
from __future__ import annotations
99

10+
import sys
1011

1112
__tests__ = """
1213
Ignore:
@@ -17,7 +18,7 @@
1718
"""
1819

1920

20-
def main(argv=None):
21+
def main(argv: list[str] | None = None) -> int:
2122
"""
2223
Args:
2324
argv (List[str] | None):
@@ -44,9 +45,10 @@ def main(argv=None):
4445

4546
import argparse
4647
import textwrap
47-
from xdoctest import utils
4848
from os.path import exists
4949

50+
from xdoctest import utils
51+
5052
# FIXME: default values are reporting incorrectly or are missformated
5153
class RawDescriptionDefaultsHelpFormatter(
5254
argparse.RawDescriptionHelpFormatter,
@@ -86,8 +88,7 @@ class RawDescriptionDefaultsHelpFormatter(
8688
)
8789

8890
# The bulk of the argparse CLI is defined in the doctest example
89-
from xdoctest import doctest_example
90-
from xdoctest import runner
91+
from xdoctest import doctest_example, runner
9192

9293
runner._update_argparse_cli(parser.add_argument)
9394
doctest_example.DoctestConfig()._update_argparse_cli(parser.add_argument)
@@ -140,27 +141,33 @@ class RawDescriptionDefaultsHelpFormatter(
140141
options = ''
141142
pyproject_fpath = 'pyproject.toml'
142143
if exists(pyproject_fpath):
144+
toml_loader = None
143145
try:
144146
import tomllib
145147
except ImportError:
146148
try:
147-
import tomli as tomllib
149+
import tomli # type: ignore[unresolved-import]
148150
except ImportError:
149151
pass
152+
else:
153+
toml_loader = tomli
150154
else:
155+
toml_loader = tomllib
156+
157+
if toml_loader is not None:
151158
with open(pyproject_fpath, 'rb') as file:
152-
pyproject_settings = tomllib.load(file)
159+
pyproject_settings = toml_loader.load(file)
153160
try:
154161
options = pyproject_settings['tool']['xdoctest']['options']
155162
except KeyError:
156163
pass
157164
if exists('pytest.ini'):
158165
import configparser
159166

160-
parser = configparser.ConfigParser()
161-
parser.read('pytest.ini')
167+
config_parser = configparser.ConfigParser()
168+
config_parser.read('pytest.ini')
162169
try:
163-
options = parser.get('pytest', 'xdoctest_options')
170+
options = config_parser.get('pytest', 'xdoctest_options')
164171
except configparser.NoOptionError:
165172
pass
166173
ns['options'] = options

src/xdoctest/__main__.pyi

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/xdoctest/_tokenize.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# type: ignore
21
# Vendored from Python 3.11
32
"""Tokenization help for Python programs.
43
@@ -686,7 +685,7 @@ def error(message, filename=None, location=None):
686685

687686
def _generate_tokens_from_c_tokenizer(source):
688687
"""Tokenize a source reading Python code as unicode strings using the internal C tokenizer"""
689-
import _tokenize as c_tokenizer
688+
import _tokenize as c_tokenizer # type: ignore[unresolved-import]
690689
for info in c_tokenizer.TokenizerIter(source):
691690
tok, type, lineno, end_lineno, col_off, end_col_off, line = info
692691
yield TokenInfo(type, tok, (lineno, col_off), (end_lineno, end_col_off), line)

src/xdoctest/checker.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
representation of expression-based "got-strings".
3434
"""
3535

36+
from __future__ import annotations
37+
38+
import typing
39+
3640
import re
3741
import difflib
3842
from xdoctest import utils
@@ -66,7 +70,10 @@
6670

6771

6872
def check_got_vs_want(
69-
want, got_stdout, got_eval=constants.NOT_EVALED, runstate=None
73+
want: str,
74+
got_stdout: str,
75+
got_eval: typing.Any = constants.NOT_EVALED,
76+
runstate: directive.RuntimeState | None = None,
7077
):
7178
"""
7279
Determines to check against either got_stdout or got_eval, and then does
@@ -121,7 +128,7 @@ def check_got_vs_want(
121128
return flag
122129

123130

124-
def _strip_exception_details(msg):
131+
def _strip_exception_details(msg: str) -> str:
125132
"""
126133
Args:
127134
msg (str):
@@ -155,7 +162,7 @@ def _strip_exception_details(msg):
155162
return msg[start:end]
156163

157164

158-
def extract_exc_want(want):
165+
def extract_exc_want(want: str) -> str | None:
159166
"""
160167
Args:
161168
want (str): the message supplied by the user
@@ -176,7 +183,11 @@ def extract_exc_want(want):
176183
return exc_want
177184

178185

179-
def check_exception(exc_got, want, runstate=None):
186+
def check_exception(
187+
exc_got: str,
188+
want: str,
189+
runstate: directive.RuntimeState | None = None,
190+
) -> bool:
180191
"""
181192
Checks want against an exception
182193
@@ -200,6 +211,9 @@ def check_exception(exc_got, want, runstate=None):
200211
# print('exc_got = {!r}'.format(exc_got))
201212
# print('flag = {!r}'.format(flag))
202213

214+
if runstate is None:
215+
runstate = directive.RuntimeState()
216+
203217
if not flag and runstate['IGNORE_EXCEPTION_DETAIL']:
204218
exc_got1 = _strip_exception_details(exc_got)
205219
exc_want1 = _strip_exception_details(exc_want)
@@ -215,7 +229,11 @@ def check_exception(exc_got, want, runstate=None):
215229
return flag
216230

217231

218-
def check_output(got, want, runstate=None):
232+
def check_output(
233+
got: str,
234+
want: str,
235+
runstate: directive.RuntimeState | None = None,
236+
) -> bool:
219237
"""
220238
Does the actual comparison between `got` and `want` as long as the check is
221239
enabled.
@@ -243,14 +261,16 @@ def check_output(got, want, runstate=None):
243261
return False
244262

245263

246-
def _check_match(got, want, runstate):
264+
def _check_match(
265+
got: str, want: str, runstate: directive.RuntimeState | dict
266+
) -> bool:
247267
"""
248268
Does the actual comparison between `got` and `want`
249269
250270
Args:
251271
got (str): normalized text produced by the test
252272
want (str): normalized target to match against
253-
runstate (xdoctest.directive.RuntimeState | None): current state
273+
runstate (xdoctest.directive.RuntimeState): current state
254274
255275
Returns:
256276
bool: True if got matches want
@@ -265,7 +285,7 @@ def _check_match(got, want, runstate):
265285
return False
266286

267287

268-
def _ellipsis_match(got, want):
288+
def _ellipsis_match(got: typing.Any, want: typing.Any) -> bool:
269289
r"""
270290
The ellipsis matching algorithm taken directly from standard doctest.
271291
@@ -349,7 +369,11 @@ def _ellipsis_match(got, want):
349369
return True
350370

351371

352-
def normalize(got, want, runstate=None):
372+
def normalize(
373+
got: str,
374+
want: str,
375+
runstate: directive.RuntimeState | dict[str, object] | None = None,
376+
) -> tuple[str, str]:
353377
r"""
354378
Normalizes the got and want string based on the runtime state.
355379
@@ -452,11 +476,12 @@ def norm_repr(a, b):
452476

453477

454478
class ExtractGotReprException(AssertionError):
479+
orig_ex: Exception
455480
"""
456481
Exception used when we are unable to extract a string "got"
457482
"""
458483

459-
def __init__(self, msg, orig_ex):
484+
def __init__(self, msg: str, orig_ex: Exception) -> None:
460485
"""
461486
Args:
462487
msg (str): The exception message
@@ -467,12 +492,14 @@ def __init__(self, msg, orig_ex):
467492

468493

469494
class GotWantException(AssertionError):
495+
got: str
496+
want: str
470497
"""
471498
Exception used when the "got" output of a doctest differs from the expected
472499
"want" output.
473500
"""
474501

475-
def __init__(self, msg, got, want):
502+
def __init__(self, msg: str, got: str, want: str) -> None:
476503
"""
477504
Args:
478505
msg (str): The exception message
@@ -502,7 +529,11 @@ def _do_a_fancy_diff(self, runstate=None):
502529

503530
return False
504531

505-
def output_difference(self, runstate=None, colored=True):
532+
def output_difference(
533+
self,
534+
runstate: directive.RuntimeState | None = None,
535+
colored: bool = True,
536+
) -> str:
506537
"""
507538
Return a string describing the differences between the expected output
508539
for a given example (`example`) and the actual output (`got`).
@@ -548,12 +579,14 @@ def output_difference(self, runstate=None, colored=True):
548579
got_lines = got.splitlines(True)
549580
# Use difflib to find their differences.
550581
if runstate['REPORT_UDIFF']:
551-
diff = difflib.unified_diff(want_lines, got_lines, n=2)
552-
diff = list(diff)[2:] # strip the diff header
582+
diff = list(difflib.unified_diff(want_lines, got_lines, n=2))[
583+
2:
584+
] # strip the diff header
553585
kind = 'unified diff with -expected +actual'
554586
elif runstate['REPORT_CDIFF']:
555-
diff = difflib.context_diff(want_lines, got_lines, n=2)
556-
diff = list(diff)[2:] # strip the diff header
587+
diff = list(difflib.context_diff(want_lines, got_lines, n=2))[
588+
2:
589+
] # strip the diff header
557590
kind = 'context diff with expected followed by actual'
558591
elif runstate['REPORT_NDIFF']:
559592
# TODO: Is there a way to make Differ ignore whitespace if that
@@ -594,7 +627,9 @@ def output_difference(self, runstate=None, colored=True):
594627
text = 'Expected nothing\nGot nothing\n'
595628
return text
596629

597-
def output_repr_difference(self, runstate=None):
630+
def output_repr_difference(
631+
self, runstate: directive.RuntimeState | None = None
632+
) -> str:
598633
"""
599634
Constructs a repr difference with minimal normalization.
600635
@@ -625,7 +660,7 @@ def output_repr_difference(self, runstate=None):
625660
return '\n'.join(lines)
626661

627662

628-
def remove_blankline_marker(text):
663+
def remove_blankline_marker(text: str) -> str:
629664
r"""
630665
Args:
631666
text (str): input text

src/xdoctest/checker.pyi

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)