Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
asyncio_loop.py256 linesDownload Raw Back to event_loop
1# Urwid main loop code2#    Copyright (C) 2004-2012  Ian Ward3#    Copyright (C) 2008 Walter Mundt4#    Copyright (C) 2009 Andrew Psaltis5#6#    This library is free software; you can redistribute it and/or7#    modify it under the terms of the GNU Lesser General Public8#    License as published by the Free Software Foundation; either9#    version 2.1 of the License, or (at your option) any later version.10#11#    This library is distributed in the hope that it will be useful,12#    but WITHOUT ANY WARRANTY; without even the implied warranty of13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU14#    Lesser General Public License for more details.15#16#    You should have received a copy of the GNU Lesser General Public17#    License along with this library; if not, write to the Free Software18#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA19#20# Urwid web site: https://urwid.org/21 22"""Asyncio based urwid EventLoop implementation."""23 24from __future__ import annotations25 26import asyncio27import functools28import logging29import sys30import typing31 32from .abstract_loop import EventLoop, ExitMainLoop33 34if typing.TYPE_CHECKING:35    from collections.abc import Callable36    from concurrent.futures import Executor37 38    from typing_extensions import ParamSpec39 40    _Spec = ParamSpec("_Spec")41    _T = typing.TypeVar("_T")42 43__all__ = ("AsyncioEventLoop",)44 45 46class AsyncioEventLoop(EventLoop):47    """48    Event loop based on the standard library ``asyncio`` module.49 50    .. warning::51        Under Windows, AsyncioEventLoop globally enforces WindowsSelectorEventLoopPolicy52        as a side-effect of creating a class instance.53        Original event loop policy is restored in destructor method.54 55    .. note::56        If you make any changes to the urwid state outside of it57        handling input or responding to alarms (for example, from asyncio.Task58        running in background), and wish the screen to be59        redrawn, you must call :meth:`MainLoop.draw_screen` method of the60        main loop manually.61 62        A good way to do this:63            asyncio.get_event_loop().call_soon(main_loop.draw_screen)64    """65 66    def __init__(self, *, loop: asyncio.AbstractEventLoop | None = None, **kwargs) -> None:67        super().__init__()68        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)69 70        if sys.version_info[:2] < (3, 11):71            self._event_loop_policy_altered: bool = False72            self._original_event_loop_policy: asyncio.AbstractEventLoopPolicy | None = None73 74            if loop:75                self._loop: asyncio.AbstractEventLoop = loop76            else:77                self._original_event_loop_policy = asyncio.get_event_loop_policy()78                if sys.platform == "win32" and not isinstance(79                    self._original_event_loop_policy, asyncio.WindowsSelectorEventLoopPolicy80                ):81                    self.logger.debug("Set WindowsSelectorEventLoopPolicy as asyncio event loop policy")82                    asyncio.set_event_loop_policy(83                        asyncio.WindowsSelectorEventLoopPolicy()  # pylint: disable=deprecated-class84                    )85                    self._event_loop_policy_altered = True86                else:87                    self._event_loop_policy_altered = False88 89                self._loop = asyncio.get_event_loop()90 91        else:92            self._runner: asyncio.Runner | None = None93 94            if loop:95                self._loop: asyncio.AbstractEventLoop = loop96            else:97                try:98                    self._loop = asyncio.get_running_loop()99                except RuntimeError:100                    self._runner = asyncio.Runner(loop_factory=asyncio.SelectorEventLoop)101                    self._loop = self._runner.get_loop()102 103        self._exc: BaseException | None = None104 105        self._idle_asyncio_handle: asyncio.TimerHandle | None = None106        self._idle_handle: int = 0107        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}108 109    def __del__(self) -> None:110        if sys.version_info[:2] < (3, 11):111            if self._event_loop_policy_altered:112                asyncio.set_event_loop_policy(self._original_event_loop_policy)  # Restore default event loop policy113        elif self._runner is not None:114            self._runner.close()115 116    def _also_call_idle(self, callback: Callable[_Spec, _T]) -> Callable[_Spec, _T]:117        """118        Wrap the callback to also call _entering_idle.119        """120 121        @functools.wraps(callback)122        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T:123            if not self._idle_asyncio_handle:124                self._idle_asyncio_handle = self._loop.call_later(0, self._entering_idle)125            return callback(*args, **kwargs)126 127        return wrapper128 129    def _entering_idle(self) -> None:130        """131        Call all the registered idle callbacks.132        """133        try:134            for callback in self._idle_callbacks.values():135                callback()136        finally:137            self._idle_asyncio_handle = None138 139    def run_in_executor(140        self,141        executor: Executor | None,142        func: Callable[_Spec, _T],143        *args: _Spec.args,144        **kwargs: _Spec.kwargs,145    ) -> asyncio.Future[_T]:146        """Run callable in executor.147 148        :param executor: Executor to use for running the function. Default asyncio executor is used if None.149        :type executor: concurrent.futures.Executor | None150        :param func: function to call151        :type func: Callable152        :param args: arguments to function (positional only)153        :type args: object154        :param kwargs: keyword arguments to function (keyword only)155        :type kwargs: object156        :return: future object for the function call outcome.157        :rtype: asyncio.Future158        """159        return self._loop.run_in_executor(executor, functools.partial(func, *args, **kwargs))160 161    def alarm(self, seconds: float, callback: Callable[[], typing.Any]) -> asyncio.TimerHandle:162        """163        Call callback() a given time from now.  No parameters are164        passed to callback.165 166        Returns a handle that may be passed to remove_alarm()167 168        seconds -- time in seconds to wait before calling callback169        callback -- function to call from event loop170        """171        return self._loop.call_later(seconds, self._also_call_idle(callback))172 173    def remove_alarm(self, handle) -> bool:174        """175        Remove an alarm.176 177        Returns True if the alarm exists, False otherwise178        """179        existed = not handle.cancelled()180        handle.cancel()181        return existed182 183    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:184        """185        Call callback() when fd has some data to read.  No parameters186        are passed to callback.187 188        Returns a handle that may be passed to remove_watch_file()189 190        fd -- file descriptor to watch for input191        callback -- function to call when input is available192        """193        self._loop.add_reader(fd, self._also_call_idle(callback))194        return fd195 196    def remove_watch_file(self, handle: int) -> bool:197        """198        Remove an input file.199 200        Returns True if the input file exists, False otherwise201        """202        return self._loop.remove_reader(handle)203 204    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:205        """206        Add a callback for entering idle.207 208        Returns a handle that may be passed to remove_enter_idle()209        """210        # XXX there's no such thing as "idle" in most event loops; this fakes211        # it by adding extra callback to the timer and file watch callbacks.212        self._idle_handle += 1213        self._idle_callbacks[self._idle_handle] = callback214        return self._idle_handle215 216    def remove_enter_idle(self, handle: int) -> bool:217        """218        Remove an idle callback.219 220        Returns True if the handle was removed.221        """222        try:223            del self._idle_callbacks[handle]224        except KeyError:225            return False226        return True227 228    def _exception_handler(self, loop: asyncio.AbstractEventLoop, context):229        if exc := context.get("exception"):230            loop.stop()231 232            if self._idle_asyncio_handle:233                # clean it up to prevent old callbacks234                # from messing things up if loop is restarted235                self._idle_asyncio_handle.cancel()236                self._idle_asyncio_handle = None237 238            if not isinstance(exc, ExitMainLoop):239                # Store the exc_info so we can re-raise after the loop stops240                self._exc = exc241        else:242            loop.default_exception_handler(context)243 244    def run(self) -> None:245        """Start the event loop.246 247        Exit the loop when any callback raises an exception.248        If ExitMainLoop is raised, exit cleanly.249        """250        self._loop.set_exception_handler(self._exception_handler)251        self._loop.run_forever()252        if self._exc:253            exc = self._exc254            self._exc = None255            raise exc.with_traceback(exc.__traceback__)256 
codekingpro/portable-devtools · Team Ai