1
from datetime import timedelta
4
import django.core.signals
5
import django.db.backends.signals
6
import django.db.models.signals
9
class TimelineReceiver(object):
10
"""Converts django signals in to `Timeline` entries.
12
To use this instantiate on instance, passing a callable that
13
will return the timeline, and then call `connect_to_signals`.
16
INIT_CATEGORY = "model-init"
17
SAVE_CATEGORY = "model-save"
18
DELETE_CATEGORY = "model-delete"
19
REQUEST_CATEGORY = "request"
20
REQUEST_EXCEPTION_CATEGORY = "request-exception"
21
CONNECTION_CREATED_CATEGORY = "connection-created"
23
def __init__(self, timeline_factory):
24
"""Create a TimelineReceiver.
26
:param timeline_factory: a callable that takes no arguments,
27
and returns a `Timeline` object or `None`. This will
28
be called each time a `Timeline` is needed in reponse
31
self.timeline_factory = timeline_factory
32
self._actions = threading.local()
34
def _model_name(self, model_cls):
35
return "%s.%s" % (model_cls.__module__, model_cls.__name__)
37
def _action_name(self, category, sender):
38
return "%s-%s" % (category, self._model_name(sender))
40
def _handle_pre(self, category, sender, **kwargs):
41
timeline = self.timeline_factory()
44
action = timeline.start(category, self._model_name(sender),
46
setattr(self._actions, self._action_name(category, sender), action)
48
def _handle_post(self, category, sender, **kwargs):
49
action_name = self._action_name(category, sender)
50
action = getattr(self._actions, action_name, None)
52
raise AssertionError("post action called without pre action.")
53
delattr(self._actions, action_name)
56
def pre_init(self, sender, **kwargs):
57
self._handle_pre(self.INIT_CATEGORY, sender, **kwargs)
59
def post_init(self, sender, **kwargs):
60
self._handle_post(self.INIT_CATEGORY, sender, **kwargs)
62
def pre_save(self, sender, **kwargs):
63
self._handle_pre(self.SAVE_CATEGORY, sender, **kwargs)
65
def post_save(self, sender, **kwargs):
66
self._handle_post(self.SAVE_CATEGORY, sender, **kwargs)
68
def pre_delete(self, sender, **kwargs):
69
self._handle_pre(self.DELETE_CATEGORY, sender, **kwargs)
71
def post_delete(self, sender, **kwargs):
72
self._handle_post(self.DELETE_CATEGORY, sender, **kwargs)
75
# TODO: 'using' kwarg in to the detail
77
def request_started(self, sender, **kwargs):
78
self._handle_pre(self.REQUEST_CATEGORY, sender, **kwargs)
80
def request_finished(self, sender, **kwargs):
81
self._handle_post(self.REQUEST_CATEGORY, sender, **kwargs)
83
def _do_instantaneous_action(self, category, detail):
84
timeline = self.timeline_factory()
87
action = timeline.start(category, detail)
88
action.duration = timedelta()
90
def got_request_exception(self, sender, **kwargs):
91
self._do_instantaneous_action(self.REQUEST_EXCEPTION_CATEGORY,
92
self._model_name(sender))
94
def connection_created(self, sender, **kwargs):
95
connection = kwargs.get('connection', None)
96
if connection is not None:
97
connection_name = connection.alias
99
connection_name = "(unknown)"
100
self._do_instantaneous_action(self.CONNECTION_CREATED_CATEGORY,
103
def connect_to_signals(self):
104
"""Connect the callbacks to their corresponding signals."""
105
django.db.models.signals.pre_init.connect(self.pre_init, weak=False)
106
django.db.models.signals.post_init.connect(self.post_init, weak=False)
107
django.db.models.signals.pre_save.connect(self.pre_save, weak=False)
108
django.db.models.signals.post_save.connect(self.post_save, weak=False)
109
django.db.models.signals.pre_delete.connect(self.pre_delete, weak=False)
110
django.db.models.signals.post_delete.connect(self.post_delete, weak=False)
111
django.core.signals.request_started.connect(self.request_started, weak=False)
112
django.core.signals.request_finished.connect(self.request_finished, weak=False)
113
django.core.signals.got_request_exception.connect(self.got_request_exception, weak=False)
114
django.db.backends.signals.connection_created.connect(self.connection_created, weak=False)