Package: django-event-system
django-event-systemA great little utility to implement an event system for djangoAboutdjango-event-system utilizes gevent to build out an easy to use event system. This event system uses strings to track events and call event handlers.Why not just use django's built in signals?Unlike django signals, django-event-system utilizes a string based system for events. This allows developers to listen using regular expressions, and add a slightly nicer interface to interact and deal with events. For example you can create events likeevent::db::Model::created,event::db::Model::updated, andevent::db::Model::deletedand have a listener that listens forevent::db::Model::.*. This listener will handle all of the defined events.Installdjango-event-system requires python3 and django 1.7+. To install, just runpip install django-event-systemGetting startedTo get started add"events"to yourinstalled_appsinsettings.py. That's really it.Important note about gevent andmonkey_patchDjango event system does not usegevent's monkey patchutility anywhere. If you want to monkey patch (and I recommend you do), you need to do it yourself.The DispatcherThe Dispatcher is the central peice to django-event-system, every single event goes through the dispatcher. While there really isn't a need to use the dipsatcher directly, you can. The dispatcher manages the event queue, the handlers, and dispatching the event.Registering Event HandlersIn order to start using the dispatcher, you first need to define your handlers:fromeventsimportDispatcherDispatcher.RegisterHandler('event::name',handler)This will define a handler that will respond toevent::nameevents. The handler object must be a callable object (like a function or a class with__call__defined).While it is not necessary, I recommend that you use::rather than.in your event handlers because the string you pass into register handler gets compiled into regular expression. If you want to base events off of a class and its module, there is a utility calledGetPathFromClassyou can use.You can also, define a handler that responds to multiple events by using regular expressions:fromeventsimportDispatcherDispatcher.RegisterHandler('event::name(.*)',handler)This handler will respond to any event that has a tag that begins withevent::name. This handler will respond toevent::name::some_eventas well asevent::name:some::other::eventDispatching eventsTo Dispatch an event, all you need to do is callDispatcher.Dispatch('event::name'). This call will call every listener that is waiting for that event.If you need to pass arguments to an event handler, all you need to is:Dispatcher.Dispatch('event::name', *args, **kwargs)Clearing the queueBecause django-event-system is based off of gevent, its possible that the queue doesn't get cleared. If you want to force the queue to be cleared after a function, you should use theDispatcher.EnsureQueueIsCleareddecorator. This will run your function first and then clear the queue. If you don't want to use the wrapper, you can use theDispatcher.ClearQueuemethod instead.TheEventclassTheEventclass is really just here to stop you from making mistakes. All theEventclass does is build a string for your event and gives you aDispatchhelper.For example:fromeventsimportEventclassExampleEvent(Event):passThis makes an event that will always have the string<modulename>::ExampleEvent. To dispatch anExampleEvent, all you need to do isExampleEvent.Dispatch(). This will actually dispatch a<modulename>::ExampleEventevent.If you want to give your event classes a different name, you can just define theNameproperty on the class:fromeventsimportEventclassExampleEvent(Event):Name="ExampleEvent"passNowExampleEvent.Dispatch()will dispatch anExampleEventevent.If you want to pass data using anEventclass, use the__init__method to capture the input:fromeventsimportEventclassExampleEvent(Event):def__init__(self,name,value,something):self.name=nameself.value=valueself.something=somethingpasspassThen when dispatching:ExampleEvent.Dispatch('name', 'value, 'something'). This will pass anExampleEventobject to the handler withname,value, andsomethingdefined.TheEventListenerclassThis class sets up an event listener for you. If you use this class, you don't need to do anything more than define alistensForlist and ahandlemethod. Thehandlemethod will recieve the event that triggered the handler:fromeventsimportEventListenerfrommy.eventsimportExampleEventclassExampleEventListener(EventListener):listensFor=[ExampleEvent,]defhandle(self,event):# handle the event herepasspassThen, when you callExampleEvent.Dispatch,ExampleEventListener'shandlemethod will automatically get called, with event refering to theExampleEventthat triggered the handler.ThelistensForlist can contain bothEventobjects and strings. These will also be compiled into regular expressions. This allows you to have anEventListenerlisten for all events for example:fromeventsimportEventListenerclassExampleEventListener(EventListener):listensFor=['.*',]defhandle(self,event,*args,**kwargs):print("Event was dispatched: ",event)passpassTheObserverclassIn order to respond to model events, I implemented a Model observer class calledObserver. Using this model, you can respond to events such as creating, created, updating, updated, deleting, deleted a given model. And Observer has a method that corresponds to those events and recieves a Model object. To use a model Observer, simply inherit from theObserverclass and define aobservesmodel:fromeventsimportObserverfrommy.modelsimportExampleclassExampleModelObserver(Observer):observes=Exampledefcreating(self,eventName,example):print('creating a Example with name:',example.name)defcreated(self,eventName,example):print('created a Example with name:',example.name)defupdating(self,eventName,example):print('updating a Example with name:',example.name)defupdated(self,eventName,example):print('updated a Example with name:',example.name)defdeleting(self,eventName,example):print('deleting a Example with name:',example.name)defdeleted(self,eventName,example):print('deleted a Example with name:',example.name)When you preform an action on a Model that is being Observed, an event with the styleevents::db::path::to:ModelClass::*will be created. For example, the event for creating an Example object will be,events::db::my::models::Example::creating. You can define otherEventListenersto listen for these events. For example here is a listener that listens for any model that is created:fromeventsimportEventListenerclassExampleEventListener(EventListener):listensFor=['events::db::(.*)::created',]defhandle(self,event,example):print("An Example was created:",example)passpassOne thing to note is that only objects that have anObserverdefined will create events. If you want a Model to dispatch events without defining anObserver, you can useRegisterSignalsForto map the signals to Events:fromeventsimportRegisterSignalsForfrommy.modelsimportExampleRegisterSignalsFor(Example)Now the signals will dispatch events. I recommend doing this as early in your application as possible.Registering ObserversI recommend you keep all of  your app observers in an observer directory. Then inside that dir's__init__.pyimport all of your observers.In django, if you try to use Models before the application is ready, then you will get the infamousdjango.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.exception. To solve this, go toapps.pyin your django app and define areadymethod. Inside this method, import your observers like so:import myapp.observers. Then in the app's__init__.pyfile put this code:default_app_config = 'myapp.apps.MyAppConfig'. Wnen the models are ready, the observers will get registered! So youapps.pyshould look like this:fromdjango.appsimportAppConfigclassMyAppConfig(AppConfig):name='myapp'defready(self):importmyapp.observersand your__init__.pylooks like this:default_app_config='myapp.apps.MyAppConfigEvents for non observed modelsIf the Model doesn't have an observer, no events will be dispatched for that model. In order to make un-observed models dispatch events, use theRegisterSignalsForfunction. This function juust takes a model class and will register all the signal objects and map them to an event based on the model's name.Mapping Signals to EventsDjango commes with it's own signal ideas. If you want to listen for some event in django, you would use a signal. But, as these signals are object based, its more boiler plate to listen to multiple signals or to listen to a class of events.But django is based on signals, so in order to make it easier to just use events instead, django-event-system comes withSignalToEventto help convert a signal to an event. Basically, this just connects a listener to a signal and then dispatches an event. The way this works is like so:fromeventsimportSignalToEventfromsome.signalsimportsignalSignalToEvent(signal,event='event::from::signal')# ORSignalToEvent(signal,event=ExampleEvent)If you want to handle events from a specific sender, you can also define a sender on theSignalToEventcall:SignalToEvent(signal,event=ExampleEvent,sender=SomeSender)And finally, sometimes you just want to have more complex event dispatching. For that you can define a hook:defhook(*args,**kwargs):ifargs[0]==1:return'event::when::1'elifargs[1]==2:return'event::when::2'return'event::when::none'SignalToEvent(signal,hook=hook)the hook function must return an event or a string!Note:Do not callEvent.Dispatchinside of a hook function. If you do, the event will be dispatched twice!TestingIf you would like to test that certain events are raised during the course of your unit tests, django-event-system provide several utilies to make that easier.Dispatcher.MocksDispatchdecoratorThis decorator mocks and puts the Dispatcher into a test mode. In  side this environment, you are able to use a function likeDispatcher.Expect().Usage:fromevents.eventsimportDispatcherfromdjango.testimportTestCaseclassTestExample(TestCase):@Dispatcher.MocksDispatchdeftest_SignalToEvent(self):Dispatcher.Expect('some::event::to::be::fired')Dispatcher.Dispatch('some::event::to::be::fired')passpassDispatcher.Expect()This lets the dispatcher know you are looking for this event, but to not actually call the listeners on the event. If the event is never called, the test will fail and the message will tell you exactly what events where not raised.