Package: aioevproc
aioevprocIt is a minimal async/sync event processing framework. Has no dependencies and
uses nothing except pure Python 3.8.TL;DRDo not have much time? Seerecap on examplesandrecap on conditions. Now go and useaioevproc! :)ExamplesSimplest example for a single async handler, just echo the message text:fromaioevprocimportEventsProcessor,handler,EventclassEchoTelegramBot(EventsProcessor):@handler(lambdaevent:'message'ineventand'text'inevent['message'])asyncdefecho_message(self,event:Event)->None:awaitself.reply_to_message(text=event['message']['text'])A little bit more complex Telegram bot example, see theexplanation below:fromaioevprocimportEventsProcessor,handler,Eventfromcontextlibimportasynccontextmanager,contextmanagerclassTelegramBot(EventsProcessor):# synchronous middleware for any exception: log exception@handler()@contextmanagerdeflog_exception(self,event:Event)->Generator[None,None,None]:try:yieldexcept:logging.exception('Error!')# async middleware for any exception: send excuse message to the user@handler()@asynccontextmanagerdefsend_excuse_message(self,event:Event)->AsyncGenerator[None,None]:try:yieldexcept:awaitself.send_message('Sorry!')# synchronous handler for all updates: log message@handler()deflog_update_id(self,event:Event)->Literal[True]:logging.info(event['update_id'])returnTrue# call following handlers# async handler to check if user is admin for update with messages and cb@handler(lambdaevent:'message'ineventor'callback_query'inevent)asyncdefcheck_admin(self,event:Event)->bool:# the next handler will be called only if this returns Truereturnevent['message']['from_user']['id']inawaitself.get_admins()# async handler to echo updates containing a message@handler(lambdaevent:'message'ineventand'text'inevent['message'])asyncdefecho_message(self,event:Event)->None:# if the update contains a message then echo itawaitself.reply_to_message(text=event['message']['text'])# async handler to answer a callback query@handler(lambdaevent:'callback_query'inevent)asyncdefecho_message(self,event:Event)->None:# if the update does not contain a message but a callback query, answerawaitself.answer_callback_query(event['callback_query']['id'])What do the examples demonstrate?handlerdecorates methods ofEventsProcessorsubclasses. The method can be
one of: async function (likecheck_admin,handle_messageandecho_messagein the example above), sync function (log_update_id), async
context manager (send_excuse_message) or sync context manager
(log_exception).All of the handlers are called in the same order as they are declared in the
class body. Middlewares follow the same rule: they are entered in the order
of declaration and exited in the reversed order (in a recursive manner).Sync and async handlers may return a value: if it is not a truthy value then
none of the following handlers will be called and event processing will be
stopped at the handler whichdid notreturn truthy value.Please notice: if you return nothing from the sync/async handler method (means
you implicitlyreturn None) then none of the following handlers will be
called. This is an intended default behavior since usually an event requires
a single handler. None is a falsy (not truthy) value.ReturningTruefrom the handler is useful for logging purposes: the logging
method should not block further processing of the event. This is shown in
the example below (log_update_id) as well as the filtering use case for
admins: if the user is not an admin thencheck_adminwill returnFalseand no further processing will be done.Middlewares are based on context managers and are intended to be used for
exceptions handling. Also use them when some related actions are required
before and after the event is processed by other handlers: for example, for
measuring the execution time.Recap on examplesLet's sum up on theexamples:aioevprocsupports both sync and async handlers and middlewares.Every handler or middleware has to be a method ofEventsProcessorsubclass.If the handler does not return exactlyTruethen the following handlers are
not called.Middlewares are sync/async context managers.Handlers and middlewares are called in the same order as they are declared.How to use the handlers conditionsHandler usually has to be applied to certain types of events, not all. The
following handler will be applied only to updates containing a message:@handler(lambdaevent:'message'inevent)asyncdefhandle_event(self,event:Event)->None:passIf the condition check fails then the next handler condition will be checked:@handler(lambdaevent:False)defalways_skipped(self,event:Event)->Literal[False]:# this handler is never called since its predicate always evaluates to FalsereturnFalse# has no effect since this handler is not called# since previous handler condition check failed this one will be checked next@handler(lambdaevent:'edited_message'inevent)deflog_message_edit(self,event:Event)->None:passPlease notice: if the handler condition check failed then the handler's return
value does not affect the next handlers. The return value of the handler
affects the next handlers only if the handler itself is called (meaning
that its condition check is passed).You can specify multiple predicates in ahandlercall: this will make handler
to be called only ifallof the predicates evaluate to a truthy value
for the event. Example below shows the handers which will be applied only to
updates with text messages:@handler(lambdaevent:'message'inevent,lambdaevent:'text'inevent['message'],)asyncdefhandle_event(self,event:Event)->None:passThe predicates are evaluated in the same order as they are declared. So the
above pair of conditions is equivalent to'message' in event and 'text' in event['message']. This means that
specifying multiple predicates for a singlehandlercall implements AND
semantics (conditions conjunction).If you need to apply single handler ifanyof the conditions is true, use
multiplehandlercalls:@handler(lambdaevent:'message'inevent)@handler(lambdaevent:'callback_query'inevent)asyncdefhandle_event(self,event:Event)->None:passThis will apply the handler for either update with a message or update with a
callback query. This form implements OR semantics (conditions disjunction).Please notice: the implementation ofaioevprocchecks handlers predicates in
the same order as they are declared. First'message' in eventwill be
checked and after that the'callback_query' in eventpredicate will be
evaluated. This is a reversed order to how Python applies decorators: Python
applies the most inner decorator first. Butaioevprocapplies the most
outerhandlercall first since it is more intuitive.If you need a handler to be applied unconditionally then use justhandler()without arguments.Please notice: you cannot usehandler()without arguments on a handler with
any otherhandlercall with arguments since this has no sense:@handler()# will raise an AssertionError@handler(lambdaevent:'message'inevent)asyncdefhandle_event(self,event:Event)->None:passDon't forget toreturn Truefrom unconditionally applied handler to not ignore
all of the following handlers!Recap on conditionsLet's sum up onconditions:Singlehandlercall accepts multiple predicates as arguments. The handler
then will be called only ifallof the predicates are true (AND semantics).If a handler method (or middleware) is decorated with multiplehandlercalls then the handler will be called ifanyof thehandlers'
conditions is true (OR semantics).OR and AND semantics can be combined.If the handler's conditions check failed then the handler is skipped and the
next handlers' conditions are checked until the matching handler is found.All the conditions are checked in the same order as they are declared. The
most outerhandlerdecorator is applied first.Handler decorated withhandler()w/o arguments is applied unconditionally.Installationpip install aioevprocHow to run testsFrom project root directory:python -m unittest discover -s tests/unit