[docs]classg_async:"""Instead of calling the function, schedule an idle handler at a given priority. This requires the async'ed method to be called from within the GTK main loop. Otherwise the method is executed directly. If a function's first argument is "self", it's considered a method. Calling the async function from outside the gtk main loop will yield immediate execution. A function can also be a generator. The generator will be fully executed. If run in the main loop, an empty iterator will be returned. A generator is "single" by default. Because of the nature of generators the first invocation will run till completion. """def__init__(self,single:bool=False,timeout:int=0,priority:int=GLib.PRIORITY_DEFAULT_IDLE,)->None:self.single=singleself.timeout=timeoutself.priority=prioritydefsource(self,func):timeout=self.timeouts=GLib.Timeout(timeout)iftimeout>0elseGLib.Idle()s.set_callback(func)s.priority=self.priorityreturnsdef__call__(self,func):is_method=inspect.getfullargspec(func).args[:1]==["self"]is_generator=inspect.isgeneratorfunction(func)source_attr=f"__g_async__{func.__name__}"@functools.wraps(func)defwrapper(*args,**kwargs):# execute directly if we're not in the main loopifGLib.main_depth()==0:returnfunc(*args,**kwargs)elifis_generator:# We can only run one generator at a timeholder=args[0]ifis_methodelsefuncsource=getattr(holder,source_attr,0)ifsource:returniterator=func(*args,**kwargs)defasync_wrapper(*_args):try:next(iterator)exceptException:delattr(holder,source_attr)returnGLib.SOURCE_REMOVEreturnGLib.SOURCE_CONTINUEsource=self.source(async_wrapper)setattr(holder,source_attr,source)source.attach()return()elifself.single:# Idle handlers should be registered per instanceholder=args[0]ifis_methodelsefuncsource=getattr(holder,source_attr,0)ifsource:returndefasync_wrapper(*_args):log.debug("async: %s%s%s",func,args,kwargs)try:func(*args,**kwargs)finally:delattr(holder,source_attr)returnGLib.SOURCE_REMOVEsource=self.source(async_wrapper)setattr(holder,source_attr,source)source.attach()else:defasync_wrapper(*_args):log.debug("async: %s%s%s",func,args,kwargs)func(*args,**kwargs)returnGLib.SOURCE_REMOVEself.source(async_wrapper).attach()returnwrapper
[docs]defnonrecursive(func):"""Enforce a function or method is not executed recursively: >>> class A(object): ... @nonrecursive ... def a(self, x=1): ... print(x) ... self.a(x+1) >>> A().a() 1 >>> A().a() 1 """m=threading.Lock()defwrapper(*args,**kwargs):"""Decorate function with a mutex that prohibits recursive execution."""ifm.acquire(False):try:returnfunc(*args,**kwargs)finally:m.release()returnwrapper