~james-w/python-timeline-django/clean-env

« back to all changes in this revision

Viewing changes to timeline_django/hooks.py

  • Committer: Tarmac
  • Author(s): James Westby
  • Date: 2012-02-22 22:20:19 UTC
  • mfrom: (1.3.24 extra-hooks)
  • Revision ID: tarmac@server-5390-20120222222019-8o3oww5ptsg73ppi
[r=jml] Also log some interesting Django events to the timeline.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from datetime import timedelta
 
2
import threading
 
3
 
 
4
import django.core.signals
 
5
import django.db.backends.signals
 
6
import django.db.models.signals
 
7
 
 
8
 
 
9
class TimelineReceiver(object):
 
10
    """Converts django signals in to `Timeline` entries.
 
11
 
 
12
    To use this instantiate on instance, passing a callable that
 
13
    will return the timeline, and then call `connect_to_signals`.
 
14
    """
 
15
 
 
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"
 
22
 
 
23
    def __init__(self, timeline_factory):
 
24
        """Create a TimelineReceiver.
 
25
 
 
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
 
29
            to a signal.
 
30
        """
 
31
        self.timeline_factory = timeline_factory
 
32
        self._actions = threading.local()
 
33
 
 
34
    def _model_name(self, model_cls):
 
35
        return "%s.%s" % (model_cls.__module__, model_cls.__name__)
 
36
 
 
37
    def _action_name(self, category, sender):
 
38
        return "%s-%s" % (category, self._model_name(sender))
 
39
 
 
40
    def _handle_pre(self, category, sender, **kwargs):
 
41
        timeline = self.timeline_factory()
 
42
        if timeline is None:
 
43
            return
 
44
        action = timeline.start(category, self._model_name(sender),
 
45
                allow_nested=True)
 
46
        setattr(self._actions, self._action_name(category, sender), action)
 
47
 
 
48
    def _handle_post(self, category, sender, **kwargs):
 
49
        action_name = self._action_name(category, sender)
 
50
        action = getattr(self._actions, action_name, None)
 
51
        if action is None:
 
52
            raise AssertionError("post action called without pre action.")
 
53
        delattr(self._actions, action_name)
 
54
        action.finish()
 
55
 
 
56
    def pre_init(self, sender, **kwargs):
 
57
        self._handle_pre(self.INIT_CATEGORY, sender, **kwargs)
 
58
 
 
59
    def post_init(self, sender, **kwargs):
 
60
        self._handle_post(self.INIT_CATEGORY, sender, **kwargs)
 
61
 
 
62
    def pre_save(self, sender, **kwargs):
 
63
        self._handle_pre(self.SAVE_CATEGORY, sender, **kwargs)
 
64
 
 
65
    def post_save(self, sender, **kwargs):
 
66
        self._handle_post(self.SAVE_CATEGORY, sender, **kwargs)
 
67
 
 
68
    def pre_delete(self, sender, **kwargs):
 
69
        self._handle_pre(self.DELETE_CATEGORY, sender, **kwargs)
 
70
 
 
71
    def post_delete(self, sender, **kwargs):
 
72
        self._handle_post(self.DELETE_CATEGORY, sender, **kwargs)
 
73
 
 
74
    # TODO: m2m_changed
 
75
    # TODO: 'using' kwarg in to the detail
 
76
 
 
77
    def request_started(self, sender, **kwargs):
 
78
        self._handle_pre(self.REQUEST_CATEGORY, sender, **kwargs)
 
79
 
 
80
    def request_finished(self, sender, **kwargs):
 
81
        self._handle_post(self.REQUEST_CATEGORY, sender, **kwargs)
 
82
 
 
83
    def _do_instantaneous_action(self, category, detail):
 
84
        timeline = self.timeline_factory()
 
85
        if timeline is None:
 
86
            return
 
87
        action = timeline.start(category, detail)
 
88
        action.duration = timedelta()
 
89
 
 
90
    def got_request_exception(self, sender, **kwargs):
 
91
        self._do_instantaneous_action(self.REQUEST_EXCEPTION_CATEGORY,
 
92
                self._model_name(sender))
 
93
 
 
94
    def connection_created(self, sender, **kwargs):
 
95
        connection = kwargs.get('connection', None)
 
96
        if connection is not None:
 
97
            connection_name = connection.alias
 
98
        else:
 
99
            connection_name = "(unknown)"
 
100
        self._do_instantaneous_action(self.CONNECTION_CREATED_CATEGORY,
 
101
                connection_name)
 
102
 
 
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)