Add CoreFoundationEventLoop directly to avoid pulling in ALL of PyObjC
This commit is contained in:
parent
dae3e49783
commit
da90e64dac
4 changed files with 190 additions and 0 deletions
3
src/corefoundationasyncio/__init__.py
Normal file
3
src/corefoundationasyncio/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from .eventloop import CoreFoundationEventLoop
|
||||||
|
|
||||||
|
__all__ = ('CoreFoundationEventLoop',)
|
182
src/corefoundationasyncio/eventloop.py
Normal file
182
src/corefoundationasyncio/eventloop.py
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copied from https://github.com/alberthier/corefoundationasyncio/blob/5061b9b7daa8bcd40d54d58432d84dcc0a339ca6/corefoundationasyncio.py
|
||||||
|
# This module is copied, rather than simply installed, because the PyPI version of this module depends on *all* of PyObjC even though it only needs pyobjc-framework-Cocoa.
|
||||||
|
# There's an open PR to fix this: httpsV//github.com/alberthier/corefoundationasyncio/pull/3
|
||||||
|
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from CoreFoundation import (
|
||||||
|
CFRunLoopGetCurrent,
|
||||||
|
CFRunLoopTimerCreate, CFRunLoopAddTimer, CFRunLoopRemoveTimer, CFAbsoluteTimeGetCurrent,
|
||||||
|
CFFileDescriptorCreate, CFFileDescriptorIsValid, CFFileDescriptorEnableCallBacks, CFFileDescriptorDisableCallBacks,
|
||||||
|
CFFileDescriptorCreateRunLoopSource, CFRunLoopAddSource, CFRunLoopRemoveSource,
|
||||||
|
kCFAllocatorDefault, kCFRunLoopDefaultMode, kCFRunLoopCommonModes,
|
||||||
|
kCFFileDescriptorReadCallBack, kCFFileDescriptorWriteCallBack
|
||||||
|
)
|
||||||
|
import PyObjCTools.AppHelper
|
||||||
|
|
||||||
|
class _TimerHandle(asyncio.TimerHandle):
|
||||||
|
|
||||||
|
def __init__(self, when, callback, args, loop, context):
|
||||||
|
super().__init__(when, callback, args, loop, context)
|
||||||
|
self.cf_runloop_timer = None
|
||||||
|
|
||||||
|
|
||||||
|
class _FDEntry:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.cf_fd = None
|
||||||
|
self.cf_source = None
|
||||||
|
self.callbacks = {}
|
||||||
|
|
||||||
|
|
||||||
|
class CoreFoundationEventLoop(asyncio.SelectorEventLoop):
|
||||||
|
"""
|
||||||
|
Event loop based on CoreFoundation's CFRunLoop.
|
||||||
|
This allows integration of Cocoa GUI apps with asyncio
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, console_app = False, *eventloop_args):
|
||||||
|
self._console_app = console_app
|
||||||
|
self._eventloop_args = eventloop_args
|
||||||
|
self._registered_fds = {}
|
||||||
|
self._runloop = CFRunLoopGetCurrent()
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Running and stopping the event loop.
|
||||||
|
|
||||||
|
def run_forever(self):
|
||||||
|
"""Run until stop() is called."""
|
||||||
|
self._check_closed()
|
||||||
|
if self.is_running():
|
||||||
|
raise RuntimeError('This event loop is already running')
|
||||||
|
if asyncio._get_running_loop() is not None:
|
||||||
|
raise RuntimeError('Cannot run the event loop while another loop is running')
|
||||||
|
self._set_coroutine_origin_tracking(self._debug)
|
||||||
|
self._thread_id = threading.get_ident()
|
||||||
|
|
||||||
|
old_agen_hooks = sys.get_asyncgen_hooks()
|
||||||
|
sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
|
||||||
|
finalizer=self._asyncgen_finalizer_hook)
|
||||||
|
try:
|
||||||
|
asyncio._set_running_loop(self)
|
||||||
|
if self._console_app:
|
||||||
|
PyObjCTools.AppHelper.runConsoleEventLoop(*self._eventloop_args)
|
||||||
|
else:
|
||||||
|
PyObjCTools.AppHelper.runEventLoop(*self._eventloop_args)
|
||||||
|
finally:
|
||||||
|
self._thread_id = None
|
||||||
|
asyncio._set_running_loop(None)
|
||||||
|
self._set_coroutine_origin_tracking(False)
|
||||||
|
sys.set_asyncgen_hooks(*old_agen_hooks)
|
||||||
|
|
||||||
|
def _process_events(self, event_list):
|
||||||
|
raise NotImplementedError("Not available in this implementation")
|
||||||
|
|
||||||
|
def _run_once(self):
|
||||||
|
raise NotImplementedError("Not available in this implementation")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
PyObjCTools.AppHelper.stopEventLoop()
|
||||||
|
|
||||||
|
# Methods scheduling callbacks. All these return Handles.
|
||||||
|
|
||||||
|
def call_at(self, when, callback, *args, context=None):
|
||||||
|
self._check_closed()
|
||||||
|
if self._debug:
|
||||||
|
self._check_thread()
|
||||||
|
self._check_callback(callback, 'call_at')
|
||||||
|
timerHandle = _TimerHandle(when, callback, args, self, context)
|
||||||
|
if timerHandle._source_traceback:
|
||||||
|
del timerHandle._source_traceback[-1]
|
||||||
|
self._add_callback(timerHandle)
|
||||||
|
return timerHandle
|
||||||
|
|
||||||
|
def _call_soon(self, callback, args, context):
|
||||||
|
handle = asyncio.Handle(callback, args, self, context)
|
||||||
|
self._add_callback(handle)
|
||||||
|
return handle
|
||||||
|
|
||||||
|
def _add_callback(self, handle):
|
||||||
|
assert isinstance(handle, asyncio.Handle), 'A Handle is required here'
|
||||||
|
is_timer = isinstance(handle, _TimerHandle)
|
||||||
|
if handle.cancelled():
|
||||||
|
return
|
||||||
|
def ontimeout(cf_timer, info):
|
||||||
|
if not handle.cancelled():
|
||||||
|
handle._run()
|
||||||
|
when = handle.when() if is_timer else self.time()
|
||||||
|
cf_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, when, 0, 0, 0, ontimeout, None)
|
||||||
|
CFRunLoopAddTimer(self._runloop, cf_timer, kCFRunLoopCommonModes)
|
||||||
|
if is_timer:
|
||||||
|
handle.cf_runloop_timer = cf_timer
|
||||||
|
handle._scheduled = True
|
||||||
|
|
||||||
|
def _timer_handle_cancelled(self, handle):
|
||||||
|
CFRunLoopRemoveTimer(self._runloop, handle.cf_runloop_timer, kCFRunLoopCommonModes)
|
||||||
|
|
||||||
|
def time(self):
|
||||||
|
return CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
|
# Ready-based callback registration methods.
|
||||||
|
# The add_*() methods return None.
|
||||||
|
# The remove_*() methods return True if something was removed,
|
||||||
|
# False if there was nothing to delete.
|
||||||
|
|
||||||
|
def _register_fd(self, fd, event, callback, args):
|
||||||
|
entry = self._registered_fds.get(fd)
|
||||||
|
if entry is not None:
|
||||||
|
CFFileDescriptorEnableCallBacks(entry.cf_fd, event)
|
||||||
|
entry.callbacks[event] = (callback, args)
|
||||||
|
else:
|
||||||
|
def fd_callback(file_desc, callback_types, entry):
|
||||||
|
if callback_types & kCFFileDescriptorReadCallBack:
|
||||||
|
(callback, args) = entry.callbacks[kCFFileDescriptorReadCallBack]
|
||||||
|
callback(*args)
|
||||||
|
if callback_types & kCFFileDescriptorWriteCallBack:
|
||||||
|
(callback, args) = entry.callbacks[kCFFileDescriptorWriteCallBack]
|
||||||
|
callback(*args)
|
||||||
|
if CFFileDescriptorIsValid(file_desc):
|
||||||
|
CFFileDescriptorEnableCallBacks(file_desc, callback_types)
|
||||||
|
|
||||||
|
entry = _FDEntry()
|
||||||
|
entry.cf_fd = CFFileDescriptorCreate(kCFAllocatorDefault, fd, 0, fd_callback, entry)
|
||||||
|
CFFileDescriptorEnableCallBacks(entry.cf_fd, event)
|
||||||
|
entry.callbacks[event] = (callback, args)
|
||||||
|
entry.cf_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, entry.cf_fd, 0)
|
||||||
|
self._registered_fds[fd] = entry
|
||||||
|
CFRunLoopAddSource(self._runloop, entry.cf_source, kCFRunLoopDefaultMode)
|
||||||
|
|
||||||
|
def _unregister_fd(self, fd, event):
|
||||||
|
entry = self._registered_fds.pop(fd, None)
|
||||||
|
if entry is None:
|
||||||
|
return False
|
||||||
|
cb = entry.callbacks.pop(event, None)
|
||||||
|
if cb is None:
|
||||||
|
return False
|
||||||
|
if len(entry.callbacks) != 0:
|
||||||
|
CFFileDescriptorDisableCallBacks(entry.cf_fd, event)
|
||||||
|
else:
|
||||||
|
CFRunLoopRemoveSource(self._runloop, entry.cf_source, kCFRunLoopDefaultMode)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _add_reader(self, fd, callback, *args):
|
||||||
|
self._check_closed()
|
||||||
|
self._register_fd(fd, kCFFileDescriptorReadCallBack, callback, args)
|
||||||
|
|
||||||
|
def _remove_reader(self, fd):
|
||||||
|
if self.is_closed():
|
||||||
|
return False
|
||||||
|
return self._unregister_fd(fd, kCFFileDescriptorReadCallBack)
|
||||||
|
|
||||||
|
def _add_writer(self, fd, callback, *args):
|
||||||
|
self._check_closed()
|
||||||
|
self._register_fd(fd, kCFFileDescriptorWriteCallBack, callback, args)
|
||||||
|
|
||||||
|
def _remove_writer(self, fd):
|
||||||
|
if self.is_closed():
|
||||||
|
return False
|
||||||
|
return self._unregister_fd(fd, kCFFileDescriptorWriteCallBack)
|
1
typings/corefoundationasyncio/__init__.pyi
Normal file
1
typings/corefoundationasyncio/__init__.pyi
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .eventloop import CoreFoundationEventLoop as CoreFoundationEventLoop
|
4
typings/corefoundationasyncio/eventloop.pyi
Normal file
4
typings/corefoundationasyncio/eventloop.pyi
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
class CoreFoundationEventLoop(asyncio.SelectorEventLoop):
|
||||||
|
def __init__(self, console_app: bool = ..., *eventloop_args) -> None: ...
|
Loading…
Reference in a new issue