Skip to content

Commit 39facb9

Browse files
Strict type checking.
1 parent 4cc6862 commit 39facb9

File tree

19 files changed

+215
-78
lines changed

19 files changed

+215
-78
lines changed

.github/workflows/test.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,21 @@ jobs:
2222
run: |
2323
sudo apt remove python3-pip
2424
python -m pip install --upgrade pip
25-
python -m pip install . black coverage codecov flake8 isort==5.6.4 mypy pytest readme_renderer types-contextvars
25+
python -m pip install . black coverage codecov flake8 isort==5.6.4 mypy pytest readme_renderer types-contextvars asyncssh
2626
pip list
2727
- name: Run Tests
2828
run: |
2929
flake8 prompt_toolkit
3030
coverage run -m pytest
3131
- name: Type Checker
3232
# Check wheather the imports were sorted correctly.
33-
# When this fails, please run ./tools/sort-imports.sh
33+
# When this fails, please run ./tools/sort-imports.sh
3434
run: |
35-
mypy prompt_toolkit
35+
mypy --strict prompt_toolkit
3636
isort -c --profile black prompt_toolkit examples tests setup.py
3737
black --check prompt_toolkit examples tests setup.py
3838
- name: Validate README.md
39-
# Ensure that the README renders correctly (required for uploading to PyPI).
39+
# Ensure that the README renders correctly (required for uploading to PyPI).
4040
run: |
4141
python -m readme_renderer README.rst > /dev/null
4242
- name: Run codecov

mypy.ini

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
[mypy]
2+
platform = win32
3+
4+
# --strict.
5+
check_untyped_defs = True
6+
disallow_any_generics = True
7+
disallow_incomplete_defs = True
8+
disallow_subclassing_any = True
9+
disallow_untyped_calls = True
10+
disallow_untyped_decorators = True
11+
disallow_untyped_defs = True
212
ignore_missing_imports = True
313
no_implicit_optional = True
4-
platform = win32
14+
no_implicit_reexport = True
515
strict_equality = True
616
strict_optional = True
17+
warn_redundant_casts = True
18+
warn_return_any = True
19+
warn_unused_configs = True
20+
warn_unused_ignores = True

prompt_toolkit/application/application.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ def __init__(
216216
max_render_postpone_time: Union[float, int, None] = 0.01,
217217
refresh_interval: Optional[float] = None,
218218
terminal_size_polling_interval: Optional[float] = 0.5,
219-
on_reset: Optional[ApplicationEventHandler] = None,
220-
on_invalidate: Optional[ApplicationEventHandler] = None,
221-
before_render: Optional[ApplicationEventHandler] = None,
222-
after_render: Optional[ApplicationEventHandler] = None,
219+
on_reset: Optional["ApplicationEventHandler[_AppResult]"] = None,
220+
on_invalidate: Optional["ApplicationEventHandler[_AppResult]"] = None,
221+
before_render: Optional["ApplicationEventHandler[_AppResult]"] = None,
222+
after_render: Optional["ApplicationEventHandler[_AppResult]"] = None,
223223
# I/O.
224224
input: Optional[Input] = None,
225225
output: Optional[Output] = None,
@@ -769,7 +769,7 @@ def flush_input() -> None:
769769
# Store unprocessed input as typeahead for next time.
770770
store_typeahead(self.input, self.key_processor.empty_queue())
771771

772-
return result
772+
return cast(_AppResult, result)
773773

774774
async def _run_async2() -> _AppResult:
775775
self._is_running = True

prompt_toolkit/application/run_in_terminal.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
"""
22
Tools for running functions on the terminal above the current application or prompt.
33
"""
4+
import sys
45
from asyncio import Future, ensure_future
56
from typing import AsyncGenerator, Awaitable, Callable, TypeVar
67

78
from prompt_toolkit.eventloop import run_in_executor_with_context
89

910
from .current import get_app_or_none
1011

11-
try:
12-
from contextlib import asynccontextmanager # type: ignore
13-
except ImportError:
12+
if sys.version_info >= (3, 7):
13+
from contextlib import asynccontextmanager
14+
else:
1415
from prompt_toolkit.eventloop.async_context_manager import asynccontextmanager
1516

1617

prompt_toolkit/cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def __missing__(self, key: _K) -> _V:
101101
return result
102102

103103

104-
_F = TypeVar("_F", bound=Callable)
104+
_F = TypeVar("_F", bound=Callable[..., object])
105105

106106

107107
def memoized(maxsize: int = 1024) -> Callable[[_F], _F]:

prompt_toolkit/contrib/ssh/server.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
import asyncio
55
import traceback
6-
from typing import Awaitable, Callable, Optional, TextIO, cast
6+
from typing import Any, Awaitable, Callable, Optional, TextIO, cast
77

88
import asyncssh
99

@@ -15,25 +15,25 @@
1515
__all__ = ["PromptToolkitSSHSession", "PromptToolkitSSHServer"]
1616

1717

18-
class PromptToolkitSSHSession(asyncssh.SSHServerSession):
18+
class PromptToolkitSSHSession(asyncssh.SSHServerSession): # type: ignore
1919
def __init__(
2020
self, interact: Callable[["PromptToolkitSSHSession"], Awaitable[None]]
2121
) -> None:
2222
self.interact = interact
2323
self.interact_task: Optional[asyncio.Task[None]] = None
24-
self._chan = None
24+
self._chan: Optional[Any] = None
2525
self.app_session: Optional[AppSession] = None
2626

2727
# PipInput object, for sending input in the CLI.
2828
# (This is something that we can use in the prompt_toolkit event loop,
2929
# but still write date in manually.)
3030
self._input = create_pipe_input()
31-
self._output = None
31+
self._output: Optional[Vt100_Output] = None
3232

3333
# Output object. Don't render to the real stdout, but write everything
3434
# in the SSH channel.
3535
class Stdout:
36-
def write(s, data):
36+
def write(s, data: str) -> None:
3737
try:
3838
if self._chan is not None:
3939
self._chan.write(data.replace("\n", "\r\n"))
@@ -43,12 +43,13 @@ def write(s, data):
4343
def isatty(s) -> bool:
4444
return True
4545

46-
def flush(s):
46+
def flush(s) -> None:
4747
pass
4848

4949
@property
50-
def encoding(s):
51-
return self._chan._orig_chan.get_encoding()[0]
50+
def encoding(s) -> str:
51+
assert self._chan is not None
52+
return str(self._chan._orig_chan.get_encoding()[0])
5253

5354
self.stdout = cast(TextIO, Stdout())
5455

@@ -62,7 +63,7 @@ def _get_size(self) -> Size:
6263
width, height, pixwidth, pixheight = self._chan.get_terminal_size()
6364
return Size(rows=height, columns=width)
6465

65-
def connection_made(self, chan):
66+
def connection_made(self, chan: Any) -> None:
6667
self._chan = chan
6768

6869
def shell_requested(self) -> bool:
@@ -98,17 +99,17 @@ async def _interact(self) -> None:
9899
self._input.close()
99100

100101
def terminal_size_changed(
101-
self, width: int, height: int, pixwidth, pixheight
102+
self, width: int, height: int, pixwidth: object, pixheight: object
102103
) -> None:
103104
# Send resize event to the current application.
104105
if self.app_session and self.app_session.app:
105106
self.app_session.app._on_resize()
106107

107-
def data_received(self, data, datatype):
108+
def data_received(self, data: str, datatype: object) -> None:
108109
self._input.send_text(data)
109110

110111

111-
class PromptToolkitSSHServer(asyncssh.SSHServer):
112+
class PromptToolkitSSHServer(asyncssh.SSHServer): # type: ignore
112113
"""
113114
Run a prompt_toolkit application over an asyncssh server.
114115

prompt_toolkit/eventloop/async_context_manager.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""
22
@asynccontextmanager code, copied from Python 3.7's contextlib.
33
For usage in Python 3.6.
4+
Types have been added to this file, just enough to make Mypy happy.
45
"""
6+
# mypy: allow-untyped-defs
57
import abc
68
from functools import wraps
9+
from typing import TYPE_CHECKING, AsyncContextManager, AsyncIterator, Callable, TypeVar
710

811
import _collections_abc
912

@@ -26,7 +29,7 @@ async def __aexit__(self, exc_type, exc_value, traceback):
2629
@classmethod
2730
def __subclasshook__(cls, C):
2831
if cls is AbstractAsyncContextManager:
29-
return _collections_abc._check_methods(C, "__aenter__", "__aexit__")
32+
return _collections_abc._check_methods(C, "__aenter__", "__aexit__") # type: ignore
3033
return NotImplemented
3134

3235

@@ -95,7 +98,12 @@ async def __aexit__(self, typ, value, traceback):
9598
raise
9699

97100

98-
def asynccontextmanager(func):
101+
_T = TypeVar("_T")
102+
103+
104+
def asynccontextmanager(
105+
func: Callable[..., AsyncIterator[_T]]
106+
) -> Callable[..., AsyncContextManager[_T]]:
99107
"""@asynccontextmanager decorator.
100108
Typical usage:
101109
@asynccontextmanager
@@ -119,6 +127,6 @@ async def some_async_generator(<arguments>):
119127

120128
@wraps(func)
121129
def helper(*args, **kwds):
122-
return _AsyncGeneratorContextManager(func, args, kwds)
130+
return _AsyncGeneratorContextManager(func, args, kwds) # type: ignore
123131

124132
return helper

prompt_toolkit/eventloop/inputhook.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,17 @@
2828
import selectors
2929
import threading
3030
from asyncio import AbstractEventLoop, get_event_loop
31-
from selectors import BaseSelector
32-
from typing import Callable
31+
from selectors import BaseSelector, SelectorKey
32+
from typing import (
33+
TYPE_CHECKING,
34+
Any,
35+
Callable,
36+
List,
37+
Mapping,
38+
NamedTuple,
39+
Optional,
40+
Tuple,
41+
)
3342

3443
from prompt_toolkit.utils import is_windows
3544

@@ -40,6 +49,11 @@
4049
"InputHookContext",
4150
]
4251

52+
if TYPE_CHECKING:
53+
from _typeshed import FileDescriptor, FileDescriptorLike
54+
55+
_EventMask = int
56+
4357

4458
def new_eventloop_with_inputhook(
4559
inputhook: Callable[["InputHookContext"], None]
@@ -79,19 +93,25 @@ def __init__(
7993
self.inputhook = inputhook
8094
self._r, self._w = os.pipe()
8195

82-
def register(self, fileobj, events, data=None):
96+
def register(
97+
self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None
98+
) -> "SelectorKey":
8399
return self.selector.register(fileobj, events, data=data)
84100

85-
def unregister(self, fileobj):
101+
def unregister(self, fileobj: "FileDescriptorLike") -> "SelectorKey":
86102
return self.selector.unregister(fileobj)
87103

88-
def modify(self, fileobj, events, data=None):
104+
def modify(
105+
self, fileobj: "FileDescriptorLike", events: "_EventMask", data: Any = None
106+
) -> "SelectorKey":
89107
return self.selector.modify(fileobj, events, data=None)
90108

91-
def select(self, timeout=None):
109+
def select(
110+
self, timeout: Optional[float] = None
111+
) -> List[Tuple["SelectorKey", "_EventMask"]]:
92112
# If there are tasks in the current event loop,
93113
# don't run the input hook.
94-
if len(get_event_loop()._ready) > 0:
114+
if len(getattr(get_event_loop(), "_ready", [])) > 0:
95115
return self.selector.select(timeout=timeout)
96116

97117
ready = False
@@ -140,6 +160,7 @@ def input_is_ready() -> bool:
140160

141161
# Wait for the real selector to be done.
142162
th.join()
163+
assert result is not None
143164
return result
144165

145166
def close(self) -> None:
@@ -153,7 +174,7 @@ def close(self) -> None:
153174
self._r = self._w = -1
154175
self.selector.close()
155176

156-
def get_map(self):
177+
def get_map(self) -> Mapping["FileDescriptorLike", "SelectorKey"]:
157178
return self.selector.get_map()
158179

159180

prompt_toolkit/eventloop/win32.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def wait_for_handles(
4444
arrtype = HANDLE * len(handles)
4545
handle_array = arrtype(*handles)
4646

47-
ret = windll.kernel32.WaitForMultipleObjects(
47+
ret: int = windll.kernel32.WaitForMultipleObjects(
4848
len(handle_array), handle_array, BOOL(False), DWORD(timeout)
4949
)
5050

prompt_toolkit/formatted_text/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727

2828
if TYPE_CHECKING:
29+
from typing_extensions import TypeGuard
2930

3031
class MagicFormattedText(Protocol):
3132
"""
@@ -100,7 +101,7 @@ def to_formatted_text(
100101
return FormattedText(result)
101102

102103

103-
def is_formatted_text(value: object) -> bool:
104+
def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]":
104105
"""
105106
Check whether the input is valid formatted text (for use in assert
106107
statements).

0 commit comments

Comments
 (0)