lol forgot to update this for ages

This commit is contained in:
2023-12-02 16:57:04 +11:00
parent 667fb2bd4f
commit 64f5cf2c30
13 changed files with 209 additions and 112 deletions

53
chatlib/chatlib.py Normal file
View File

@@ -0,0 +1,53 @@
import asyncio
from traceback import format_exc
from .listener import Listener
from .timer import Timer
from . import utils
from .event import Event
event_queue=asyncio.Queue() #Contains Events. Everything in here must be an Event.
listeners=[] # These respond to events. They're executed by workers processing the event queue. They should all be Listeners
timers=[] # These just execute on a schedule. That schedule can be "as soon as it exits". Should all be Timers, which include the property of whether to wait for exit before starting the next call or not (rarely relevant, so defaults to "no" unless schedule is 0).
# Queue management
async def process_queue():
while True:
item=await event_queue.get()
asyncio.create_task(process_item(item))
async def process_item(item):
for listener in listeners:
terminate=False
if listener==item:
try: terminate=await listener(item)
except: utils.trace('explosions in',listener); format_exc()
if terminate: break
# Convenience functions for initialising.
def addlistener(match):
def __wrap__(funky):
listeners.append(Listener(match,funky))
return funky
return __wrap__
def addtimer(interval,start=None,block=None):
def __wrap__(funky):
a=Timer(funky,interval,block,start)
timers.append(a)
try: # Timers added after start will get run here, but timers added before start won't
asyncio.create_task(a.run())
except RuntimeError: pass
return funky
return __wrap__
def addevent(*args,**kwargs):
asyncio.create_task(event_queue.put(Event(*args,**kwargs)))
async def main():
# Any time that didn't get run before occurs here.
for timer in timers: asyncio.create_task(timer.run())
await process_queue()
def run():
asyncio.run(main())

8
chatlib/event.py Normal file
View File

@@ -0,0 +1,8 @@
from dataclasses import dataclass
@dataclass
class Event():
event_type:str=''
data:object=None
raw_data:dict=None
outbound:bool=False # This should be remapped as a data piece so it could instead be a direction/destination

26
chatlib/listener.py Normal file
View File

@@ -0,0 +1,26 @@
from asyncio import iscoroutinefunction
from .event import Event
class Listener():
def __init__(self,match,function):
self.match=match
self.function=function
@property
def matchstr(self):
return isinstance(self.match,str)
@property
def isasync(self):
return iscoroutinefunction(self.function)
async def __call__(self,*args,**kwargs):
if not self.isasync: return self.function(*args,**kwargs)
return await self.function(*args,**kwargs)
def __eq__(self,other):
if isinstance(other,Event):
if self.matchstr: return self.match==other.event_type
return self.match(other) # If it's not a string, assume it's a callable.
else: return super.__eq__(self,other)
def __str__(self): return self.function.__name_

54
chatlib/timer.py Normal file
View File

@@ -0,0 +1,54 @@
from traceback import format_exc
from asyncio import sleep,create_task
from datetime import datetime
from asyncio import iscoroutinefunction,get_event_loop
from . import utils
class Timer():
def __init__(self,function,interval,block=None,start=None,*args,**kwargs):
self.function=function
self.interval=interval
self.args=args
self.kwargs=kwargs
self.block=block
if block is None: self.block=(interval == 0)
self.start=start
self.scheduled=None
@property
def isasync(self):
return iscoroutinefunction(self.function)
"""
This mess of functions...
Operate and enqueue are just wrappers that do some useful things that I couldn't fit elsewhere. Iterate is the core that runs them in the right order according to blocking.
"""
async def enqueue(self,interval=None):
self.scheduled=create_task(self.iterate(interval))
async def operate(self,*args,**kwargs):
if not args: args=self.args
if not kwargs: kwargs=self.kwargs
try:
if not self.isasync: return self.function(*args,**kwargs)
return await self.function(*args,**kwargs)
except: utils.trace('explosions in',self); format_exc()
async def iterate(self,interval=None):
if interval is None: interval=self.interval
await sleep(interval)
await (self.operate,self.enqueue)[not self.block]()
await (self.operate,self.enqueue)[self.block]()
async def run(self):
slop=0
if self.start is not None:
ima=datetime.utcnow()
slop=(self.start-ima).total_seconds() # It'll be very slightly wrong but whatever
await self.enqueue(slop)
def __str__(self):
return self.function.__name__
def __del__(self):
self.scheduled.cancel()

5
chatlib/utils.py Normal file
View File

@@ -0,0 +1,5 @@
from datetime import datetime
tracing=False
def trace(*msg):
if tracing: print(datetime.now(),*msg)