Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
select_loop.py231 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"""Select based urwid EventLoop implementation."""23 24from __future__ import annotations25 26import contextlib27import heapq28import logging29import selectors30import time31import typing32from contextlib import suppress33from itertools import count34 35from .abstract_loop import EventLoop, ExitMainLoop36 37if typing.TYPE_CHECKING:38    from collections.abc import Callable, Iterator39    from concurrent.futures import Executor, Future40 41    from typing_extensions import Literal, ParamSpec42 43    _T = typing.TypeVar("_T")44    _Spec = ParamSpec("_Spec")45 46__all__ = ("SelectEventLoop",)47 48 49class SelectEventLoop(EventLoop):50    """51    Event loop based on :func:`selectors.DefaultSelector.select`52    """53 54    def __init__(self) -> None:55        super().__init__()56        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)57        self._alarms: list[tuple[float, int, Callable[[], typing.Any]]] = []58        self._watch_files: dict[int, Callable[[], typing.Any]] = {}59        self._idle_handle: int = 060        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}61        self._tie_break: Iterator[int] = count()62        self._did_something: bool = False63 64    def run_in_executor(65        self,66        executor: Executor,67        func: Callable[_Spec, _T],68        *args: _Spec.args,69        **kwargs: _Spec.kwargs,70    ) -> Future[_T]:71        """Run callable in executor.72 73        :param executor: Executor to use for running the function74        :type executor: concurrent.futures.Executor75        :param func: function to call76        :type func: Callable77        :param args: positional arguments to function78        :type args: object79        :param kwargs: keyword arguments to function80        :type kwargs: object81        :return: future object for the function call outcome.82        :rtype: concurrent.futures.Future83        """84        return executor.submit(func, *args, **kwargs)85 86    def alarm(87        self,88        seconds: float,89        callback: Callable[[], typing.Any],90    ) -> tuple[float, int, Callable[[], typing.Any]]:91        """92        Call callback() a given time from now.  No parameters are93        passed to callback.94 95        Returns a handle that may be passed to remove_alarm()96 97        seconds -- floating point time to wait before calling callback98        callback -- function to call from event loop99        """100        tm = time.time() + seconds101        handle = (tm, next(self._tie_break), callback)102        heapq.heappush(self._alarms, handle)103        return handle104 105    def remove_alarm(self, handle: tuple[float, int, Callable[[], typing.Any]]) -> bool:106        """107        Remove an alarm.108 109        Returns True if the alarm exists, False otherwise110        """111        try:112            self._alarms.remove(handle)113            heapq.heapify(self._alarms)114 115        except ValueError:116            return False117 118        return True119 120    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:121        """122        Call callback() when fd has some data to read.  No parameters123        are passed to callback.124 125        Returns a handle that may be passed to remove_watch_file()126 127        fd -- file descriptor to watch for input128        callback -- function to call when input is available129        """130        self._watch_files[fd] = callback131        return fd132 133    def remove_watch_file(self, handle: int) -> bool:134        """135        Remove an input file.136 137        Returns True if the input file exists, False otherwise138        """139        if handle in self._watch_files:140            del self._watch_files[handle]141            return True142        return False143 144    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:145        """146        Add a callback for entering idle.147 148        Returns a handle that may be passed to remove_idle()149        """150        self._idle_handle += 1151        self._idle_callbacks[self._idle_handle] = callback152        return self._idle_handle153 154    def remove_enter_idle(self, handle: int) -> bool:155        """156        Remove an idle callback.157 158        Returns True if the handle was removed.159        """160        try:161            del self._idle_callbacks[handle]162        except KeyError:163            return False164        return True165 166    def _entering_idle(self) -> None:167        """168        Call all the registered idle callbacks.169        """170        for callback in self._idle_callbacks.values():171            callback()172 173    def run(self) -> None:174        """175        Start the event loop.  Exit the loop when any callback raises176        an exception.  If ExitMainLoop is raised, exit cleanly.177        """178        with contextlib.suppress(ExitMainLoop):179            self._did_something = True180            while True:181                with suppress(InterruptedError):182                    self._loop()183 184    def _loop(self) -> None:185        """186        A single iteration of the event loop187        """188        tm: float | Literal["idle"] | None = None189 190        with selectors.DefaultSelector() as selector:191            for fd, callback in self._watch_files.items():192                selector.register(fd, selectors.EVENT_READ, callback)193 194            if self._alarms or self._did_something:195                timeout = 0.0196 197                if self._alarms:198                    timeout_ = self._alarms[0][0]199                    tm = timeout_200                    timeout = max(timeout, timeout_ - time.time())201 202                if self._did_something and (not self._alarms or (self._alarms and timeout > 0)):203                    timeout = 0.0204                    tm = "idle"205 206                self.logger.debug(f"Waiting for input: timeout={timeout!r}")207                ready = [event for event, _ in selector.select(timeout)]208 209            elif self._watch_files:210                self.logger.debug("Waiting for input: timeout")211                ready = [event for event, _ in selector.select()]212            else:213                ready = []214 215        if not ready:216            if tm == "idle":217                self.logger.debug("No input, entering IDLE")218                self._entering_idle()219                self._did_something = False220            elif tm is not None:221                # must have been a timeout222                tm, _tie_break, alarm_callback = heapq.heappop(self._alarms)223                self.logger.debug(f"No input in timeout, calling scheduled {alarm_callback!r}")224                alarm_callback()225                self._did_something = True226 227        self.logger.debug("Processing input")228        for record in ready:229            record.data()230            self._did_something = True231 
codekingpro/portable-devtools · Team Ai