Commit 420dba33 by Gabe Mulley

support adding processors via django config

parent 2a9ab21e
...@@ -11,7 +11,8 @@ from eventtracking.tracker import Tracker ...@@ -11,7 +11,8 @@ from eventtracking.tracker import Tracker
from eventtracking.locator import ThreadLocalContextLocator from eventtracking.locator import ThreadLocalContextLocator
DJANGO_SETTING_NAME = 'TRACKING_BACKENDS' DJANGO_BACKEND_SETTING_NAME = 'TRACKING_BACKENDS'
DJANGO_PROCESSOR_SETTING_NAME = 'TRACKING_PROCESSORS'
class DjangoTracker(Tracker): class DjangoTracker(Tracker):
...@@ -22,13 +23,13 @@ class DjangoTracker(Tracker): ...@@ -22,13 +23,13 @@ class DjangoTracker(Tracker):
def __init__(self): def __init__(self):
backends = self.create_backends_from_settings() backends = self.create_backends_from_settings()
super(DjangoTracker, self).__init__(backends, ThreadLocalContextLocator()) processors = self.create_processors_from_settings()
super(DjangoTracker, self).__init__(backends, ThreadLocalContextLocator(), processors)
def create_backends_from_settings(self): def create_backends_from_settings(self):
""" """
Expects the Django setting `setting_name` (defaults to Expects the Django setting "TRACKING_BACKENDS" to be defined and point
"TRACKING_BACKENDS") to be defined and point to a dictionary of to a dictionary of backend engine configurations.
backend engine configurations.
Example:: Example::
...@@ -47,46 +48,71 @@ class DjangoTracker(Tracker): ...@@ -47,46 +48,71 @@ class DjangoTracker(Tracker):
}, },
} }
""" """
config = getattr(settings, DJANGO_SETTING_NAME, {}) config = getattr(settings, DJANGO_BACKEND_SETTING_NAME, {})
backends = {} backends = {}
for name, values in config.iteritems(): for name, values in config.iteritems():
# Ignore empty values to turn-off default tracker backends # Ignore empty values to turn-off default tracker backends
if values and 'ENGINE' in values: if values and 'ENGINE' in values:
engine = values['ENGINE'] backend = self.instantiate_from_dict(values)
options = values.get('OPTIONS', {})
backend = self.instantiate_backend_from_name(engine, options)
backends[name] = backend backends[name] = backend
return backends return backends
def instantiate_backend_from_name(self, name, options): def instantiate_from_dict(self, values):
""" """
Instantiate an event tracker backend from the full module path to Constructs an object given a dictionary containing an "ENGINE" key
the backend class. Useful when setting backends from configuration which contains the full module path to the class, and an "OPTIONS"
files. key which contains a dictionary that will be passed in to the
constructor as keyword args.
""" """
# Parse backend name
name = values['ENGINE']
options = values.get('OPTIONS', {})
# Parse the name
parts = name.split('.') parts = name.split('.')
module_name = '.'.join(parts[:-1]) module_name = '.'.join(parts[:-1])
class_name = parts[-1] class_name = parts[-1]
# Get and verify the backend class # Get the class
try: try:
module = import_module(module_name) module = import_module(module_name)
cls = getattr(module, class_name) cls = getattr(module, class_name)
except (ValueError, AttributeError, TypeError, ImportError): except (ValueError, AttributeError, TypeError, ImportError):
raise ValueError('Cannot find event tracker backend %s' % name) raise ValueError('Cannot find class %s' % name)
backend = cls(**options) return cls(**options)
if not hasattr(backend, 'send') or not callable(backend.send):
raise ValueError('Backend %s does not have a callable "send" method.' % name) def create_processors_from_settings(self):
"""
Expects the Django setting "TRACKING_PROCESSORS" to be defined and
point to a list of backend engine configurations.
Example::
TRACKING_PROCESSORS = [
{
'ENGINE': 'some.arbitrary.Processor'
},
{
'ENGINE': 'some.arbitrary.OtherProcessor',
'OPTIONS': {
'user': 'foo'
}
},
]
"""
config = getattr(settings, DJANGO_PROCESSOR_SETTING_NAME, [])
processors = []
for values in config:
# Ignore empty values to turn-off default tracker backends
if values and 'ENGINE' in values:
processors.append(self.instantiate_from_dict(values))
return backend return processors
def override_default_tracker(): def override_default_tracker():
......
...@@ -137,6 +137,23 @@ class TestConfiguration(TestCase): ...@@ -137,6 +137,23 @@ class TestConfiguration(TestCase):
django.override_default_tracker() django.override_default_tracker()
self.assertTrue(isinstance(tracker.get_tracker(), tracker.Tracker)) self.assertTrue(isinstance(tracker.get_tracker(), tracker.Tracker))
@override_settings(TRACKING_PROCESSORS=[
{
'ENGINE': 'eventtracking.django.tests.test_configuration.NopProcessor'
}
])
def test_single_processor(self):
self.configure_tracker()
self.assertEquals(len(self.tracker.processors), 1)
self.assertTrue(isinstance(self.tracker.processors[0], NopProcessor))
@override_settings(TRACKING_PROCESSORS=[
{}
])
def test_missing_processor_engine(self):
self.configure_tracker()
self.assertEquals(len(self.tracker.processors), 0)
class TrivialFakeBackend(object): class TrivialFakeBackend(object):
"""A trivial fake backend without any options""" """A trivial fake backend without any options"""
...@@ -157,3 +174,10 @@ class FakeBackendWithOptions(TrivialFakeBackend): ...@@ -157,3 +174,10 @@ class FakeBackendWithOptions(TrivialFakeBackend):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(FakeBackendWithOptions, self).__init__() super(FakeBackendWithOptions, self).__init__()
self.option = kwargs.get('option', None) self.option = kwargs.get('option', None)
class NopProcessor(object):
"""Changes every event"""
def __call__(self, event):
pass
...@@ -41,6 +41,10 @@ class Tracker(object): ...@@ -41,6 +41,10 @@ class Tracker(object):
self.context_locator = context_locator or DefaultContextLocator() self.context_locator = context_locator or DefaultContextLocator()
self.processors = processors or [] self.processors = processors or []
for backend in backends.itervalues():
if not hasattr(backend, 'send') or not callable(backend.send):
raise ValueError('Backend %s does not have a callable "send" method.' % backend.__class__.__name__)
@property @property
def located_context(self): def located_context(self):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment