Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
event-tracking
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
event-tracking
Commits
0b937da3
Commit
0b937da3
authored
Sep 18, 2013
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
support loading backends
parent
b5ca901f
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
240 additions
and
25 deletions
+240
-25
eventtracking/tests/test_track.py
+169
-20
eventtracking/track.py
+71
-5
No files found.
eventtracking/tests/test_track.py
View file @
0b937da3
...
...
@@ -11,14 +11,18 @@ from mock import patch
from
mock
import
sentinel
from
eventtracking
import
track
from
eventtracking.backends
import
BaseBackend
class
TestTrack
(
TestCase
):
# pylint: disable=missing-docstring
def
setUp
(
self
):
self
.
_mock_backend
=
MagicMock
()
self
.
addCleanup
(
track
.
BACKENDS
.
remove
,
self
.
_mock_backend
)
track
.
BACKENDS
.
append
(
self
.
_mock_backend
)
# Ensure all backends are removed after executing
self
.
addCleanup
(
track
.
configure
,
{})
self
.
_mock_backends
=
[]
self
.
_mock_backend
=
None
self
.
configure_mock_backends
(
1
)
self
.
_expected_timestamp
=
datetime
.
utcnow
()
self
.
_datetime_patcher
=
patch
(
'eventtracking.track.datetime'
)
...
...
@@ -26,6 +30,28 @@ class TestTrack(TestCase): # pylint: disable=missing-docstring
mock_datetime
=
self
.
_datetime_patcher
.
start
()
mock_datetime
.
utcnow
.
return_value
=
self
.
_expected_timestamp
# pylint: disable=maybe-no-member
def
configure_mock_backends
(
self
,
number_of_mocks
):
"""Ensure the tracking module has the requisite number of mock backends"""
config
=
{}
for
i
in
range
(
number_of_mocks
):
name
=
'mock{0}'
.
format
(
i
)
config
[
name
]
=
{
'ENGINE'
:
'eventtracking.tests.test_track.TrivialFakeBackend'
}
track
.
configure
(
config
)
self
.
_mock_backends
=
[]
for
i
in
range
(
number_of_mocks
):
backend
=
self
.
get_mock_backend
(
i
)
backend
.
send
=
MagicMock
()
self
.
_mock_backends
.
append
(
backend
)
self
.
_mock_backend
=
self
.
_mock_backends
[
0
]
def
get_mock_backend
(
self
,
index
):
"""Get the mock backend created by `configure_mock_backends`"""
return
track
.
BACKENDS
[
'mock{0}'
.
format
(
index
)]
def
test_event_simple_event_without_data
(
self
):
track
.
event
(
sentinel
.
event_type
)
...
...
@@ -36,7 +62,7 @@ class TestTrack(TestCase): # pylint: disable=missing-docstring
if
not
backend
:
backend
=
self
.
_mock_backend
backend
.
event
.
assert_called_once_with
(
backend
.
send
.
assert_called_once_with
(
{
'event_type'
:
event_type
,
'timestamp'
:
self
.
_expected_timestamp
,
...
...
@@ -60,26 +86,149 @@ class TestTrack(TestCase): # pylint: disable=missing-docstring
)
def
test_multiple_backends
(
self
):
another_backend
=
MagicMock
()
track
.
BACKENDS
.
append
(
another_backend
)
try
:
track
.
event
(
sentinel
.
event_type
)
self
.
configure_mock_backends
(
2
)
track
.
event
(
sentinel
.
event_type
)
self
.
assert_backend_called_with
(
sentinel
.
event_type
)
for
backend
in
self
.
_mock_backends
:
self
.
assert_backend_called_with
(
sentinel
.
event_type
,
backend
=
another_backend
)
finally
:
track
.
BACKENDS
.
remove
(
another_backend
)
sentinel
.
event_type
,
backend
=
backend
)
def
test_single_backend_failure
(
self
):
self
.
_mock_backend
.
event
.
side_effect
=
Exception
self
.
configure_mock_backends
(
2
)
self
.
get_mock_backend
(
0
)
.
send
.
side_effect
=
Exception
another_backend
=
MagicMock
()
track
.
BACKENDS
.
append
(
another_backend
)
track
.
event
(
sentinel
.
event_type
)
self
.
assert_backend_called_with
(
sentinel
.
event_type
,
backend
=
self
.
get_mock_backend
(
1
))
def
test_configure
(
self
):
track
.
configure
({
"fake"
:
{
'ENGINE'
:
'eventtracking.tests.test_track.TrivialFakeBackend'
}
})
fake_backend
=
track
.
BACKENDS
[
'fake'
]
self
.
assertTrue
(
isinstance
(
fake_backend
,
TrivialFakeBackend
))
def
test_ignore_no_engine
(
self
):
track
.
configure
({
"no_engine"
:
{
'OPTIONS'
:
{}
}
})
self
.
assertEquals
(
len
(
track
.
BACKENDS
),
0
)
def
test_configure_empty_engine
(
self
):
try
:
track
.
configure
({
"empty_engine"
:
{
'ENGINE'
:
''
}
})
self
.
fail
(
'Expected exception to be thrown when attempting to add a backend with an empty engine'
)
except
ValueError
:
pass
def
test_configure_invalid_package
(
self
):
try
:
track
.
event
(
sentinel
.
event_type
)
track
.
configure
({
"invalid_package"
:
{
'ENGINE'
:
'foo.BarBackend'
}
})
self
.
fail
(
'Expected exception to be thrown when attempting to add a backend from a non-existent package'
)
except
ValueError
:
pass
def
test_configure_no_package_invalid_class
(
self
):
try
:
track
.
configure
({
"no_package_invalid_class"
:
{
'ENGINE'
:
'BarBackend'
}
})
self
.
fail
(
'Expected exception to be thrown when attempting to add a non-existent backend class'
)
except
ValueError
:
pass
def
test_configure_invalid_class
(
self
):
try
:
track
.
configure
({
"invalid_class"
:
{
'ENGINE'
:
'eventtracking.tests.test_track.BarBackend'
}
})
self
.
fail
(
'Expected exception to be thrown when attempting to add a non-existent backend class'
)
except
ValueError
:
pass
def
test_configure_engine_not_a_backend
(
self
):
try
:
track
.
configure
({
"not_a_backend"
:
{
'ENGINE'
:
'eventtracking.tests.test_track.NotABackend'
}
})
self
.
fail
(
'Expected exception to be thrown when attempting to add a backend class that'
' does not subclass BaseBackend'
)
except
ValueError
:
pass
def
test_configure_engine_with_options
(
self
):
track
.
configure
({
'with_options'
:
{
'ENGINE'
:
'eventtracking.tests.test_track.FakeBackendWithOptions'
,
'OPTIONS'
:
{
'option'
:
sentinel
.
option_value
}
}
})
self
.
assertEquals
(
track
.
BACKENDS
[
'with_options'
]
.
option
,
sentinel
.
option_value
)
self
.
assert_backend_called_with
(
sentinel
.
event_type
,
backend
=
another_backend
)
finally
:
track
.
BACKENDS
.
remove
(
another_backend
)
def
test_configure_engine_missing_options
(
self
):
track
.
configure
({
'without_options'
:
{
'ENGINE'
:
'eventtracking.tests.test_track.FakeBackendWithOptions'
}
})
self
.
assertEquals
(
track
.
BACKENDS
[
'without_options'
]
.
option
,
None
)
def
test_configure_engine_with_extra_options
(
self
):
track
.
configure
({
'extra_options'
:
{
'ENGINE'
:
'eventtracking.tests.test_track.FakeBackendWithOptions'
,
'OPTIONS'
:
{
'option'
:
sentinel
.
option_value
,
'extra_option'
:
sentinel
.
extra_option_value
}
}
})
self
.
assertEquals
(
track
.
BACKENDS
[
'extra_options'
]
.
option
,
sentinel
.
option_value
)
class
TrivialFakeBackend
(
BaseBackend
):
"""A trivial fake backend without any options"""
def
send
(
self
,
event
):
pass
class
NotABackend
(
object
):
"""
A class that is not a backend
"""
pass
class
FakeBackendWithOptions
(
BaseBackend
):
"""A trivial fake backend with options"""
def
__init__
(
self
,
**
kwargs
):
super
(
FakeBackendWithOptions
,
self
)
.
__init__
()
self
.
option
=
kwargs
.
get
(
'option'
,
None
)
def
send
(
self
,
event
):
pass
eventtracking/track.py
View file @
0b937da3
...
...
@@ -16,11 +16,77 @@ Best Practices:
from
__future__
import
absolute_import
from
datetime
import
datetime
from
importlib
import
import_module
import
inspect
import
logging
from
eventtracking.backends
import
BaseBackend
__all__
=
[
'configure'
,
'event'
]
LOG
=
logging
.
getLogger
(
__name__
)
BACKENDS
=
[]
BACKENDS
=
{}
def
configure
(
config
):
"""
Configure event tracking. `config` is expected to be a dictionary of backend engines.
Example::
config = {
'default': {
'ENGINE': 'some.arbitrary.Backend',
'OPTIONS': {
'endpoint': 'http://something/event'
}
},
'anoter_engine': {
'ENGINE': 'some.arbitrary.OtherBackend',
'OPTIONS': {
'user': 'foo'
}
},
}
"""
BACKENDS
.
clear
()
for
name
,
values
in
config
.
iteritems
():
# Ignore empty values to turn-off default tracker backends
if
values
and
'ENGINE'
in
values
:
engine
=
values
[
'ENGINE'
]
options
=
values
.
get
(
'OPTIONS'
,
{})
BACKENDS
[
name
]
=
_instantiate_backend_from_name
(
engine
,
options
)
def
_instantiate_backend_from_name
(
name
,
options
):
"""
Instantiate an event tracker backend from the full module path to
the backend class. Useful when setting backends from configuration
files.
"""
# Parse backend name
parts
=
name
.
split
(
'.'
)
module_name
=
'.'
.
join
(
parts
[:
-
1
])
class_name
=
parts
[
-
1
]
# Get and verify the backend class
try
:
module
=
import_module
(
module_name
)
cls
=
getattr
(
module
,
class_name
)
if
not
inspect
.
isclass
(
cls
)
or
not
issubclass
(
cls
,
BaseBackend
):
raise
TypeError
except
(
ValueError
,
AttributeError
,
TypeError
,
ImportError
):
raise
ValueError
(
'Cannot find event track backend
%
s'
%
name
)
backend
=
cls
(
**
options
)
return
backend
def
event
(
event_type
,
data
=
None
):
...
...
@@ -38,10 +104,10 @@ def event(event_type, data=None):
'data'
:
data
or
{}
}
for
backend
in
BACKENDS
:
for
name
,
backend
in
BACKENDS
.
iteritems
()
:
try
:
backend
.
event
(
full_event
)
except
Exception
:
# pylint: disable=
W0703
backend
.
send
(
full_event
)
except
Exception
:
# pylint: disable=
broad-except
LOG
.
exception
(
'Unable to
commit event to backend: {0}'
.
format
(
backend
)
'Unable to
send event to backend: {0}'
.
format
(
name
)
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment