From: Ben Darnell Date: Sun, 12 Aug 2018 03:00:41 +0000 (-0400) Subject: ioloop,platform: Add type annotations X-Git-Tag: v6.0.0b1~33^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=afe7dc588fa7333f908ddd299c69b1b574a14e03;p=thirdparty%2Ftornado.git ioloop,platform: Add type annotations --- diff --git a/setup.cfg b/setup.cfg index 433b6d0d3..cde5d24f2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,9 @@ disallow_untyped_defs = True [mypy-tornado.gen] disallow_untyped_defs = True +[mypy-tornado.ioloop] +disallow_untyped_defs = True + [mypy-tornado.locale] disallow_untyped_defs = True @@ -31,6 +34,9 @@ disallow_untyped_defs = True [mypy-tornado.options] disallow_untyped_defs = True +[mypy-tornado.platform.*] +disallow_untyped_defs = True + [mypy-tornado.testing] disallow_untyped_defs = True @@ -45,12 +51,18 @@ check_untyped_defs = True [mypy-tornado.test.escape_test] check_untyped_defs = True +[mypy-tornado.test.asyncio_test] +check_untyped_defs = True + [mypy-tornado.test.concurrent_test] check_untyped_defs = True [mypy-tornado.test.gen_test] check_untyped_defs = True +[mypy-tornado.test.ioloop_test] +check_untyped_defs = True + [mypy-tornado.test.locale_test] check_untyped_defs = True diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 51b5dd1e4..c9dfbedb8 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -31,7 +31,7 @@ events. `IOLoop.add_timeout` is a non-blocking alternative to """ import asyncio -from concurrent.futures import ThreadPoolExecutor +import concurrent.futures import datetime import logging import numbers @@ -43,9 +43,28 @@ import random from tornado.concurrent import Future, is_future, chain_future, future_set_exc_info, future_add_done_callback # noqa: E501 from tornado.log import app_log -from tornado.util import Configurable, TimeoutError, unicode_type, import_object +from tornado.util import Configurable, TimeoutError, import_object -import typing # noqa +import typing +from typing import Union, Any, Type, Optional, Callable, TypeVar, Tuple, Awaitable +if typing.TYPE_CHECKING: + from typing import Dict, List # noqa: F401 + + from typing_extensions import Protocol +else: + Protocol = object + + +class _Selectable(Protocol): + def fileno(self) -> int: + pass + + def close(self) -> None: + pass + + +_T = TypeVar('_T') +_S = TypeVar('_S', bound=_Selectable) class IOLoop(Configurable): @@ -137,22 +156,22 @@ class IOLoop(Configurable): ERROR = 0x018 # In Python 3, _ioloop_for_asyncio maps from asyncio loops to IOLoops. - _ioloop_for_asyncio = dict() # type: typing.Dict[typing.Any, typing.Any] + _ioloop_for_asyncio = dict() # type: Dict[asyncio.AbstractEventLoop, IOLoop] @classmethod - def configure(cls, impl, **kwargs): + def configure(cls, impl: Union[None, str, Type[Configurable]], **kwargs: Any) -> None: if asyncio is not None: from tornado.platform.asyncio import BaseAsyncIOLoop - if isinstance(impl, (str, unicode_type)): + if isinstance(impl, str): impl = import_object(impl) - if not issubclass(impl, BaseAsyncIOLoop): + if isinstance(impl, type) and not issubclass(impl, BaseAsyncIOLoop): raise RuntimeError( "only AsyncIOLoop is allowed when asyncio is available") super(IOLoop, cls).configure(impl, **kwargs) @staticmethod - def instance(): + def instance() -> 'IOLoop': """Deprecated alias for `IOLoop.current()`. .. versionchanged:: 5.0 @@ -173,7 +192,7 @@ class IOLoop(Configurable): """ return IOLoop.current() - def install(self): + def install(self) -> None: """Deprecated alias for `make_current()`. .. versionchanged:: 5.0 @@ -188,7 +207,7 @@ class IOLoop(Configurable): self.make_current() @staticmethod - def clear_instance(): + def clear_instance() -> None: """Deprecated alias for `clear_current()`. .. versionchanged:: 5.0 @@ -203,8 +222,18 @@ class IOLoop(Configurable): """ IOLoop.clear_current() + @typing.overload + @staticmethod + def current() -> 'IOLoop': + pass + + @typing.overload # noqa: F811 @staticmethod - def current(instance=True): + def current(instance: bool=True) -> Optional['IOLoop']: + pass + + @staticmethod # noqa: F811 + def current(instance: bool=True) -> Optional['IOLoop']: """Returns the current thread's `IOLoop`. If an `IOLoop` is currently running or has been marked as @@ -235,12 +264,12 @@ class IOLoop(Configurable): except KeyError: if instance: from tornado.platform.asyncio import AsyncIOMainLoop - current = AsyncIOMainLoop(make_current=True) + current = AsyncIOMainLoop(make_current=True) # type: Optional[IOLoop] else: current = None return current - def make_current(self): + def make_current(self) -> None: """Makes this the `IOLoop` for the current thread. An `IOLoop` automatically becomes current for its thread @@ -260,7 +289,7 @@ class IOLoop(Configurable): raise NotImplementedError() @staticmethod - def clear_current(): + def clear_current() -> None: """Clears the `IOLoop` for the current thread. Intended primarily for use by test frameworks in between tests. @@ -274,7 +303,7 @@ class IOLoop(Configurable): if asyncio is None: IOLoop._current.instance = None - def _clear_current_hook(self): + def _clear_current_hook(self) -> None: """Instance method called when an IOLoop ceases to be current. May be overridden by subclasses as a counterpart to make_current. @@ -282,15 +311,15 @@ class IOLoop(Configurable): pass @classmethod - def configurable_base(cls): + def configurable_base(cls) -> Type[Configurable]: return IOLoop @classmethod - def configurable_default(cls): + def configurable_default(cls) -> Type[Configurable]: from tornado.platform.asyncio import AsyncIOLoop return AsyncIOLoop - def initialize(self, make_current=None): + def initialize(self, make_current: bool=None) -> None: if make_current is None: if IOLoop.current(instance=False) is None: self.make_current() @@ -301,7 +330,7 @@ class IOLoop(Configurable): raise RuntimeError("current IOLoop already exists") self.make_current() - def close(self, all_fds=False): + def close(self, all_fds: bool=False) -> None: """Closes the `IOLoop`, freeing any resources used. If ``all_fds`` is true, all file descriptors registered on the @@ -328,13 +357,20 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def add_handler(self, fd, handler, events): + @typing.overload + def add_handler(self, fd: int, handler: Callable[[int, int], None], events: int) -> None: + pass + + @typing.overload # noqa: F811 + def add_handler(self, fd: _S, handler: Callable[[_S, int], None], events: int) -> None: + pass + + def add_handler(self, fd: Union[int, _Selectable], # noqa: F811 + handler: Callable[..., None], events: int) -> None: """Registers the given handler to receive the given events for ``fd``. The ``fd`` argument may either be an integer file descriptor or - a file-like object with a ``fileno()`` method (and optionally a - ``close()`` method, which may be called when the `IOLoop` is shut - down). + a file-like object with a ``fileno()`` and ``close()`` method. The ``events`` argument is a bitwise or of the constants ``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``. @@ -347,7 +383,7 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def update_handler(self, fd, events): + def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: """Changes the events we listen for ``fd``. .. versionchanged:: 4.0 @@ -356,7 +392,7 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def remove_handler(self, fd): + def remove_handler(self, fd: Union[int, _Selectable]) -> None: """Stop listening for events on ``fd``. .. versionchanged:: 4.0 @@ -365,7 +401,7 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def start(self): + def start(self) -> None: """Starts the I/O loop. The loop will run until one of the callbacks calls `stop()`, which @@ -373,7 +409,7 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def _setup_logging(self): + def _setup_logging(self) -> None: """The IOLoop catches and logs exceptions, so it's important that log output be visible. However, python's default behavior for non-root loggers (prior to python @@ -389,7 +425,7 @@ class IOLoop(Configurable): logging.getLogger('tornado.application').handlers]): logging.basicConfig() - def stop(self): + def stop(self) -> None: """Stop the I/O loop. If the event loop is not currently running, the next call to `start()` @@ -402,7 +438,7 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def run_sync(self, func, timeout=None): + def run_sync(self, func: Callable, timeout: float=None) -> Any: """Starts the `IOLoop`, runs the given function, and stops the loop. The function must return either an awaitable object or @@ -432,42 +468,47 @@ class IOLoop(Configurable): If a timeout occurs, the ``func`` coroutine will be cancelled. """ - future_cell = [None] + future_cell = [None] # type: List[Optional[Future]] - def run(): + def run() -> None: try: result = func() if result is not None: from tornado.gen import convert_yielded result = convert_yielded(result) except Exception: - future_cell[0] = Future() - future_set_exc_info(future_cell[0], sys.exc_info()) + fut = Future() # type: Future[Any] + future_cell[0] = fut + future_set_exc_info(fut, sys.exc_info()) else: if is_future(result): future_cell[0] = result else: - future_cell[0] = Future() - future_cell[0].set_result(result) + fut = Future() + future_cell[0] = fut + fut.set_result(result) + assert future_cell[0] is not None self.add_future(future_cell[0], lambda future: self.stop()) self.add_callback(run) if timeout is not None: - def timeout_callback(): + def timeout_callback() -> None: # If we can cancel the future, do so and wait on it. If not, # Just stop the loop and return with the task still pending. # (If we neither cancel nor wait for the task, a warning # will be logged). + assert future_cell[0] is not None if not future_cell[0].cancel(): self.stop() timeout_handle = self.add_timeout(self.time() + timeout, timeout_callback) self.start() if timeout is not None: self.remove_timeout(timeout_handle) + assert future_cell[0] is not None if future_cell[0].cancelled() or not future_cell[0].done(): raise TimeoutError('Operation timed out after %s seconds' % timeout) return future_cell[0].result() - def time(self): + def time(self) -> float: """Returns the current time according to the `IOLoop`'s clock. The return value is a floating-point number relative to an @@ -482,7 +523,9 @@ class IOLoop(Configurable): """ return time.time() - def add_timeout(self, deadline, callback, *args, **kwargs): + def add_timeout(self, deadline: Union[float, datetime.timedelta], + callback: Callable[..., None], + *args: Any, **kwargs: Any) -> object: """Runs the ``callback`` at the time ``deadline`` from the I/O loop. Returns an opaque handle that may be passed to @@ -516,7 +559,8 @@ class IOLoop(Configurable): else: raise TypeError("Unsupported deadline %r" % deadline) - def call_later(self, delay, callback, *args, **kwargs): + def call_later(self, delay: float, callback: Callable[..., None], + *args: Any, **kwargs: Any) -> object: """Runs the ``callback`` after ``delay`` seconds have passed. Returns an opaque handle that may be passed to `remove_timeout` @@ -529,7 +573,8 @@ class IOLoop(Configurable): """ return self.call_at(self.time() + delay, callback, *args, **kwargs) - def call_at(self, when, callback, *args, **kwargs): + def call_at(self, when: float, callback: Callable[..., None], + *args: Any, **kwargs: Any) -> object: """Runs the ``callback`` at the absolute time designated by ``when``. ``when`` must be a number using the same reference point as @@ -545,7 +590,7 @@ class IOLoop(Configurable): """ return self.add_timeout(when, callback, *args, **kwargs) - def remove_timeout(self, timeout): + def remove_timeout(self, timeout: object) -> None: """Cancels a pending timeout. The argument is a handle as returned by `add_timeout`. It is @@ -554,7 +599,8 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def add_callback(self, callback, *args, **kwargs): + def add_callback(self, callback: Callable, + *args: Any, **kwargs: Any) -> None: """Calls the given callback on the next I/O loop iteration. It is safe to call this method from any thread at any time, @@ -569,7 +615,8 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def add_callback_from_signal(self, callback, *args, **kwargs): + def add_callback_from_signal(self, callback: Callable, + *args: Any, **kwargs: Any) -> None: """Calls the given callback on the next I/O loop iteration. Safe for use from a Python signal handler; should not be used @@ -577,7 +624,8 @@ class IOLoop(Configurable): """ raise NotImplementedError() - def spawn_callback(self, callback, *args, **kwargs): + def spawn_callback(self, callback: Callable, + *args: Any, **kwargs: Any) -> None: """Calls the given callback on the next IOLoop iteration. As of Tornado 6.0, this method is equivalent to `add_callback`. @@ -586,7 +634,8 @@ class IOLoop(Configurable): """ self.add_callback(callback, *args, **kwargs) - def add_future(self, future, callback): + def add_future(self, future: Union['Future[_T]', 'concurrent.futures.Future[_T]'], + callback: Callable[['Future[_T]'], None]) -> None: """Schedules a callback on the ``IOLoop`` when the given `.Future` is finished. @@ -601,7 +650,8 @@ class IOLoop(Configurable): future_add_done_callback( future, lambda future: self.add_callback(callback, future)) - def run_in_executor(self, executor, func, *args): + def run_in_executor(self, executor: Optional[concurrent.futures.Executor], + func: Callable[..., _T], *args: Any) -> Awaitable[_T]: """Runs a function in a ``concurrent.futures.Executor``. If ``executor`` is ``None``, the IO loop's default executor will be used. @@ -612,23 +662,24 @@ class IOLoop(Configurable): if executor is None: if not hasattr(self, '_executor'): from tornado.process import cpu_count - self._executor = ThreadPoolExecutor(max_workers=(cpu_count() * 5)) + self._executor = concurrent.futures.ThreadPoolExecutor( + max_workers=(cpu_count() * 5)) # type: concurrent.futures.Executor executor = self._executor c_future = executor.submit(func, *args) # Concurrent Futures are not usable with await. Wrap this in a # Tornado Future instead, using self.add_future for thread-safety. - t_future = Future() + t_future = Future() # type: Future[_T] self.add_future(c_future, lambda f: chain_future(f, t_future)) return t_future - def set_default_executor(self, executor): + def set_default_executor(self, executor: concurrent.futures.Executor) -> None: """Sets the default executor to use with :meth:`run_in_executor`. .. versionadded:: 5.0 """ self._executor = executor - def _run_callback(self, callback): + def _run_callback(self, callback: Callable[[], Any]) -> None: """Runs a callback with error handling. For use in subclasses. @@ -653,11 +704,11 @@ class IOLoop(Configurable): except Exception: app_log.error("Exception in callback %r", callback, exc_info=True) - def _discard_future_result(self, future): + def _discard_future_result(self, future: Future) -> None: """Avoid unhandled-exception warnings from spawned coroutines.""" future.result() - def split_fd(self, fd): + def split_fd(self, fd: Union[int, _Selectable]) -> Tuple[int, Union[int, _Selectable]]: """Returns an (fd, obj) pair from an ``fd`` parameter. We accept both raw file descriptors and file-like objects as @@ -673,12 +724,11 @@ class IOLoop(Configurable): .. versionadded:: 4.0 """ - try: - return fd.fileno(), fd - except AttributeError: + if isinstance(fd, int): return fd, fd + return fd.fileno(), fd - def close_fd(self, fd): + def close_fd(self, fd: Union[int, _Selectable]) -> None: """Utility method to close an ``fd``. If ``fd`` is a file-like object, we close it directly; otherwise @@ -691,10 +741,10 @@ class IOLoop(Configurable): .. versionadded:: 4.0 """ try: - try: - fd.close() - except AttributeError: + if isinstance(fd, int): os.close(fd) + else: + fd.close() except OSError: pass @@ -705,21 +755,22 @@ class _Timeout(object): # Reduce memory overhead when there are lots of pending callbacks __slots__ = ['deadline', 'callback', 'tdeadline'] - def __init__(self, deadline, callback, io_loop): + def __init__(self, deadline: float, callback: Callable[[], None], + io_loop: IOLoop) -> None: if not isinstance(deadline, numbers.Real): raise TypeError("Unsupported deadline %r" % deadline) self.deadline = deadline self.callback = callback - self.tdeadline = (deadline, next(io_loop._timeout_counter)) + self.tdeadline = (deadline, next(io_loop._timeout_counter)) # type: Tuple[float, int] # Comparison methods to sort by deadline, with object id as a tiebreaker # to guarantee a consistent ordering. The heapq module uses __le__ # in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons # use __lt__). - def __lt__(self, other): + def __lt__(self, other: '_Timeout') -> bool: return self.tdeadline < other.tdeadline - def __le__(self, other): + def __le__(self, other: '_Timeout') -> bool: return self.tdeadline <= other.tdeadline @@ -749,16 +800,17 @@ class PeriodicCallback(object): .. versionchanged:: 5.1 The ``jitter`` argument is added. """ - def __init__(self, callback, callback_time, jitter=0): + def __init__(self, callback: Callable[[], None], + callback_time: float, jitter: float=0) -> None: self.callback = callback if callback_time <= 0: raise ValueError("Periodic callback must have a positive callback_time") self.callback_time = callback_time self.jitter = jitter self._running = False - self._timeout = None + self._timeout = None # type: object - def start(self): + def start(self) -> None: """Starts the timer.""" # Looking up the IOLoop here allows to first instantiate the # PeriodicCallback in another thread, then start it using @@ -768,21 +820,21 @@ class PeriodicCallback(object): self._next_timeout = self.io_loop.time() self._schedule_next() - def stop(self): + def stop(self) -> None: """Stops the timer.""" self._running = False if self._timeout is not None: self.io_loop.remove_timeout(self._timeout) self._timeout = None - def is_running(self): + def is_running(self) -> bool: """Return True if this `.PeriodicCallback` has been started. .. versionadded:: 4.1 """ return self._running - def _run(self): + def _run(self) -> None: if not self._running: return try: @@ -792,12 +844,12 @@ class PeriodicCallback(object): finally: self._schedule_next() - def _schedule_next(self): + def _schedule_next(self) -> None: if self._running: self._update_next(self.io_loop.time()) self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) - def _update_next(self, current_time): + def _update_next(self, current_time: float) -> None: callback_time_sec = self.callback_time / 1000.0 if self.jitter: # apply jitter fraction diff --git a/tornado/netutil.py b/tornado/netutil.py index e844facfe..57e2b2b81 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -231,7 +231,7 @@ def add_accept_handler(sock: socket.socket, io_loop = IOLoop.current() removed = [False] - def accept_handler(fd: int, events: int) -> None: + def accept_handler(fd: socket.socket, events: int) -> None: # More connections may come in while we're handling callbacks; # to prevent starvation of other tasks we must limit the number # of connections we accept at a time. Ideally we would accept diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py index 9211e2146..f1b072c7b 100644 --- a/tornado/platform/asyncio.py +++ b/tornado/platform/asyncio.py @@ -19,22 +19,31 @@ the same event loop. Windows. Use the `~asyncio.SelectorEventLoop` instead. """ +import concurrent.futures import functools from tornado.gen import convert_yielded -from tornado.ioloop import IOLoop +from tornado.ioloop import IOLoop, _Selectable import asyncio +import typing +from typing import Any, TypeVar, Awaitable, Callable, Union, Optional +if typing.TYPE_CHECKING: + from typing import Set, Dict, Tuple # noqa: F401 + +_T = TypeVar('_T') + class BaseAsyncIOLoop(IOLoop): - def initialize(self, asyncio_loop, **kwargs): + def initialize(self, asyncio_loop: asyncio.AbstractEventLoop, # type: ignore + **kwargs: Any) -> None: self.asyncio_loop = asyncio_loop # Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler) - self.handlers = {} + self.handlers = {} # type: Dict[int, Tuple[Union[int, _Selectable], Callable]] # Set of fds listening for reads/writes - self.readers = set() - self.writers = set() + self.readers = set() # type: Set[int] + self.writers = set() # type: Set[int] self.closing = False # If an asyncio loop was closed through an asyncio interface # instead of IOLoop.close(), we'd never hear about it and may @@ -53,7 +62,7 @@ class BaseAsyncIOLoop(IOLoop): IOLoop._ioloop_for_asyncio[asyncio_loop] = self super(BaseAsyncIOLoop, self).initialize(**kwargs) - def close(self, all_fds=False): + def close(self, all_fds: bool=False) -> None: self.closing = True for fd in list(self.handlers): fileobj, handler_func = self.handlers[fd] @@ -68,7 +77,8 @@ class BaseAsyncIOLoop(IOLoop): del IOLoop._ioloop_for_asyncio[self.asyncio_loop] self.asyncio_loop.close() - def add_handler(self, fd, handler, events): + def add_handler(self, fd: Union[int, _Selectable], + handler: Callable[..., None], events: int) -> None: fd, fileobj = self.split_fd(fd) if fd in self.handlers: raise ValueError("fd %s added twice" % fd) @@ -82,7 +92,7 @@ class BaseAsyncIOLoop(IOLoop): fd, self._handle_events, fd, IOLoop.WRITE) self.writers.add(fd) - def update_handler(self, fd, events): + def update_handler(self, fd: Union[int, _Selectable], events: int) -> None: fd, fileobj = self.split_fd(fd) if events & IOLoop.READ: if fd not in self.readers: @@ -103,7 +113,7 @@ class BaseAsyncIOLoop(IOLoop): self.asyncio_loop.remove_writer(fd) self.writers.remove(fd) - def remove_handler(self, fd): + def remove_handler(self, fd: Union[int, _Selectable]) -> None: fd, fileobj = self.split_fd(fd) if fd not in self.handlers: return @@ -115,15 +125,15 @@ class BaseAsyncIOLoop(IOLoop): self.writers.remove(fd) del self.handlers[fd] - def _handle_events(self, fd, events): + def _handle_events(self, fd: int, events: int) -> None: fileobj, handler_func = self.handlers[fd] handler_func(fileobj, events) - def start(self): + def start(self) -> None: try: old_loop = asyncio.get_event_loop() except (RuntimeError, AssertionError): - old_loop = None + old_loop = None # type: ignore try: self._setup_logging() asyncio.set_event_loop(self.asyncio_loop) @@ -131,10 +141,11 @@ class BaseAsyncIOLoop(IOLoop): finally: asyncio.set_event_loop(old_loop) - def stop(self): + def stop(self) -> None: self.asyncio_loop.stop() - def call_at(self, when, callback, *args, **kwargs): + def call_at(self, when: float, callback: Callable[..., None], + *args: Any, **kwargs: Any) -> object: # asyncio.call_at supports *args but not **kwargs, so bind them here. # We do not synchronize self.time and asyncio_loop.time, so # convert from absolute to relative. @@ -142,10 +153,10 @@ class BaseAsyncIOLoop(IOLoop): max(0, when - self.time()), self._run_callback, functools.partial(callback, *args, **kwargs)) - def remove_timeout(self, timeout): - timeout.cancel() + def remove_timeout(self, timeout: object) -> None: + timeout.cancel() # type: ignore - def add_callback(self, callback, *args, **kwargs): + def add_callback(self, callback: Callable, *args: Any, **kwargs: Any) -> None: try: self.asyncio_loop.call_soon_threadsafe( self._run_callback, @@ -160,10 +171,11 @@ class BaseAsyncIOLoop(IOLoop): add_callback_from_signal = add_callback - def run_in_executor(self, executor, func, *args): + def run_in_executor(self, executor: Optional[concurrent.futures.Executor], + func: Callable[..., _T], *args: Any) -> Awaitable[_T]: return self.asyncio_loop.run_in_executor(executor, func, *args) - def set_default_executor(self, executor): + def set_default_executor(self, executor: concurrent.futures.Executor) -> None: return self.asyncio_loop.set_default_executor(executor) @@ -181,10 +193,10 @@ class AsyncIOMainLoop(BaseAsyncIOLoop): Closing an `AsyncIOMainLoop` now closes the underlying asyncio loop. """ - def initialize(self, **kwargs): + def initialize(self, **kwargs: Any) -> None: # type: ignore super(AsyncIOMainLoop, self).initialize(asyncio.get_event_loop(), **kwargs) - def make_current(self): + def make_current(self) -> None: # AsyncIOMainLoop already refers to the current asyncio loop so # nothing to do here. pass @@ -209,7 +221,7 @@ class AsyncIOLoop(BaseAsyncIOLoop): Now used automatically when appropriate; it is no longer necessary to refer to this class directly. """ - def initialize(self, **kwargs): + def initialize(self, **kwargs: Any) -> None: # type: ignore self.is_current = False loop = asyncio.new_event_loop() try: @@ -220,27 +232,27 @@ class AsyncIOLoop(BaseAsyncIOLoop): loop.close() raise - def close(self, all_fds=False): + def close(self, all_fds: bool=False) -> None: if self.is_current: self.clear_current() super(AsyncIOLoop, self).close(all_fds=all_fds) - def make_current(self): + def make_current(self) -> None: if not self.is_current: try: self.old_asyncio = asyncio.get_event_loop() except (RuntimeError, AssertionError): - self.old_asyncio = None + self.old_asyncio = None # type: ignore self.is_current = True asyncio.set_event_loop(self.asyncio_loop) - def _clear_current_hook(self): + def _clear_current_hook(self) -> None: if self.is_current: asyncio.set_event_loop(self.old_asyncio) self.is_current = False -def to_tornado_future(asyncio_future): +def to_tornado_future(asyncio_future: asyncio.Future) -> asyncio.Future: """Convert an `asyncio.Future` to a `tornado.concurrent.Future`. .. versionadded:: 4.1 @@ -252,7 +264,7 @@ def to_tornado_future(asyncio_future): return asyncio_future -def to_asyncio_future(tornado_future): +def to_asyncio_future(tornado_future: asyncio.Future) -> asyncio.Future: """Convert a Tornado yieldable object to an `asyncio.Future`. .. versionadded:: 4.1 @@ -285,7 +297,7 @@ class AnyThreadEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore .. versionadded:: 5.0 """ - def get_event_loop(self): + def get_event_loop(self) -> asyncio.AbstractEventLoop: try: return super().get_event_loop() except (RuntimeError, AssertionError): diff --git a/tornado/platform/caresresolver.py b/tornado/platform/caresresolver.py index f1e379b22..b23614b67 100644 --- a/tornado/platform/caresresolver.py +++ b/tornado/platform/caresresolver.py @@ -6,6 +6,10 @@ from tornado import gen from tornado.ioloop import IOLoop from tornado.netutil import Resolver, is_valid_ip +import typing +if typing.TYPE_CHECKING: + from typing import Generator, Any, List, Tuple, Dict # noqa: F401 + class CaresResolver(Resolver): """Name resolver based on the c-ares library. @@ -22,12 +26,12 @@ class CaresResolver(Resolver): .. versionchanged:: 5.0 The ``io_loop`` argument (deprecated since version 4.1) has been removed. """ - def initialize(self): + def initialize(self) -> None: self.io_loop = IOLoop.current() self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb) - self.fds = {} + self.fds = {} # type: Dict[int, int] - def _sock_state_cb(self, fd, readable, writable): + def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None: state = ((IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0)) if not state: @@ -40,7 +44,7 @@ class CaresResolver(Resolver): self.io_loop.add_handler(fd, self._handle_events, state) self.fds[fd] = state - def _handle_events(self, fd, events): + def _handle_events(self, fd: int, events: int) -> None: read_fd = pycares.ARES_SOCKET_BAD write_fd = pycares.ARES_SOCKET_BAD if events & IOLoop.READ: @@ -50,12 +54,14 @@ class CaresResolver(Resolver): self.channel.process_fd(read_fd, write_fd) @gen.coroutine - def resolve(self, host, port, family=0): + def resolve( + self, host: str, port: int, family: int=0, + ) -> 'Generator[Any, Any, List[Tuple[int, Any]]]': if is_valid_ip(host): addresses = [host] else: # gethostbyname doesn't take callback as a kwarg - fut = Future() + fut = Future() # type: Future[Tuple[Any, Any]] self.channel.gethostbyname(host, family, lambda result, error: fut.set_result((result, error))) result, error = yield fut @@ -74,5 +80,5 @@ class CaresResolver(Resolver): if family != socket.AF_UNSPEC and family != address_family: raise IOError('Requested socket family %d but got %d' % (family, address_family)) - addrinfo.append((address_family, (address, port))) - raise gen.Return(addrinfo) + addrinfo.append((typing.cast(int, address_family), (address, port))) + return addrinfo diff --git a/tornado/platform/interface.py b/tornado/platform/interface.py index d7a6d76ee..11afdb186 100644 --- a/tornado/platform/interface.py +++ b/tornado/platform/interface.py @@ -21,6 +21,6 @@ implementation from `tornado.platform.auto`. """ -def set_close_exec(fd): +def set_close_exec(fd: int) -> None: """Sets the close-on-exec bit (``FD_CLOEXEC``)for a file descriptor.""" raise NotImplementedError() diff --git a/tornado/platform/posix.py b/tornado/platform/posix.py index 54327746c..0ab27ca85 100644 --- a/tornado/platform/posix.py +++ b/tornado/platform/posix.py @@ -19,11 +19,11 @@ import fcntl import os -def set_close_exec(fd): +def set_close_exec(fd: int) -> None: flags = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) -def _set_nonblocking(fd): +def _set_nonblocking(fd: int) -> None: flags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py index 68646b300..bf73e4aab 100644 --- a/tornado/platform/twisted.py +++ b/tornado/platform/twisted.py @@ -39,6 +39,10 @@ from tornado.escape import utf8 from tornado import gen from tornado.netutil import Resolver +import typing +if typing.TYPE_CHECKING: + from typing import Generator, Any, List, Tuple # noqa: F401 + class TwistedResolver(Resolver): """Twisted-based asynchronous resolver. @@ -57,7 +61,7 @@ class TwistedResolver(Resolver): .. versionchanged:: 5.0 The ``io_loop`` argument (deprecated since version 4.1) has been removed. """ - def initialize(self): + def initialize(self) -> None: # partial copy of twisted.names.client.createResolver, which doesn't # allow for a reactor to be passed in. self.reactor = twisted.internet.asyncioreactor.AsyncioSelectorReactor() @@ -70,7 +74,9 @@ class TwistedResolver(Resolver): [host_resolver, cache_resolver, real_resolver]) @gen.coroutine - def resolve(self, host, port, family=0): + def resolve( + self, host: str, port: int, family: int=0, + ) -> 'Generator[Any, Any, List[Tuple[int, Any]]]': # getHostByName doesn't accept IP addresses, so if the input # looks like an IP address just return it immediately. if twisted.internet.abstract.isIPAddress(host): @@ -81,7 +87,7 @@ class TwistedResolver(Resolver): resolved_family = socket.AF_INET6 else: deferred = self.resolver.getHostByName(utf8(host)) - fut = Future() + fut = Future() # type: Future[Any] deferred.addBoth(fut.set_result) resolved = yield fut if isinstance(resolved, failure.Failure): @@ -99,17 +105,17 @@ class TwistedResolver(Resolver): raise Exception('Requested socket family %d but got %d' % (family, resolved_family)) result = [ - (resolved_family, (resolved, port)), + (typing.cast(int, resolved_family), (resolved, port)), ] - raise gen.Return(result) + return result if hasattr(gen.convert_yielded, 'register'): @gen.convert_yielded.register(Deferred) # type: ignore - def _(d): - f = Future() + def _(d: Deferred) -> Future: + f = Future() # type: Future[Any] - def errback(failure): + def errback(failure: failure.Failure) -> None: try: failure.raiseException() # Should never happen, but just in case diff --git a/tornado/platform/windows.py b/tornado/platform/windows.py index 86293a908..6d6ebaf04 100644 --- a/tornado/platform/windows.py +++ b/tornado/platform/windows.py @@ -12,7 +12,7 @@ SetHandleInformation.restype = ctypes.wintypes.BOOL HANDLE_FLAG_INHERIT = 0x00000001 -def set_close_exec(fd): +def set_close_exec(fd: int) -> None: success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 0) if not success: - raise ctypes.WinError() + raise ctypes.WinError() # type: ignore diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index fc0011cf4..b6badde78 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -19,6 +19,10 @@ from tornado.log import app_log from tornado.testing import AsyncTestCase, bind_unused_port, ExpectLog, gen_test from tornado.test.util import skipIfNonUnix, skipOnTravis +import typing +if typing.TYPE_CHECKING: + from typing import List # noqa: F401 + class TestIOLoop(AsyncTestCase): def test_add_callback_return_sequence(self): @@ -196,7 +200,7 @@ class TestIOLoop(AsyncTestCase): def test_timeout_with_arguments(self): # This tests that all the timeout methods pass through *args correctly. - results = [] + results = [] # type: List[int] self.io_loop.add_timeout(self.io_loop.time(), results.append, 1) self.io_loop.add_timeout(datetime.timedelta(seconds=0), results.append, 2) @@ -447,7 +451,9 @@ class TestIOLoopCurrentAsync(AsyncTestCase): class TestIOLoopFutures(AsyncTestCase): def test_add_future_threads(self): with futures.ThreadPoolExecutor(1) as pool: - self.io_loop.add_future(pool.submit(lambda: None), + def dummy(): + pass + self.io_loop.add_future(pool.submit(dummy), lambda future: self.stop(future)) future = self.wait() self.assertTrue(future.done()) @@ -590,8 +596,11 @@ class TestPeriodicCallbackMath(unittest.TestCase): now = pc._next_timeout + d return calls + def dummy(self): + pass + def test_basic(self): - pc = PeriodicCallback(None, 10000) + pc = PeriodicCallback(self.dummy, 10000) self.assertEqual(self.simulate_calls(pc, [0] * 5), [1010, 1020, 1030, 1040, 1050]) @@ -607,12 +616,12 @@ class TestPeriodicCallbackMath(unittest.TestCase): 1220, 1230, # then back on schedule. ] - pc = PeriodicCallback(None, 10000) + pc = PeriodicCallback(self.dummy, 10000) self.assertEqual(self.simulate_calls(pc, call_durations), expected) def test_clock_backwards(self): - pc = PeriodicCallback(None, 10000) + pc = PeriodicCallback(self.dummy, 10000) # Backwards jumps are ignored, potentially resulting in a # slightly slow schedule (although we assume that when # time.time() and time.monotonic() are different, time.time() @@ -632,7 +641,7 @@ class TestPeriodicCallbackMath(unittest.TestCase): random_times = [0.5, 1, 0, 0.75] expected = [1010, 1022.5, 1030, 1041.25] call_durations = [0] * len(random_times) - pc = PeriodicCallback(None, 10000, jitter=0.5) + pc = PeriodicCallback(self.dummy, 10000, jitter=0.5) def mock_random(): return random_times.pop(0) @@ -643,11 +652,11 @@ class TestPeriodicCallbackMath(unittest.TestCase): class TestIOLoopConfiguration(unittest.TestCase): def run_python(self, *statements): - statements = [ + stmt_list = [ 'from tornado.ioloop import IOLoop', 'classname = lambda x: x.__class__.__name__', ] + list(statements) - args = [sys.executable, '-c', '; '.join(statements)] + args = [sys.executable, '-c', '; '.join(stmt_list)] return native_str(subprocess.check_output(args)).strip() def test_default(self): diff --git a/tornado/testing.py b/tornado/testing.py index 8c6e79ffe..2c0f24647 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -156,7 +156,7 @@ class AsyncTestCase(unittest.TestCase): self.__running = False self.__failure = None # type: Optional[_ExcInfoTuple] self.__stop_args = None # type: Any - self.__timeout = None + self.__timeout = None # type: Optional[object] # It's easy to forget the @gen_test decorator, but if you do # the test will silently be ignored because nothing will consume