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