update to lastest 097ca7f146e6fec7fb598382dfecce8762c8c6cc 2017/05/31 (#4969)

This commit is contained in:
Ludovic Hautier 2017-06-22 11:45:26 +02:00
parent 0ce2b24bea
commit 5b6e52e816
18 changed files with 702 additions and 36 deletions

View File

@ -14,5 +14,5 @@ Portions of this are from commonware:
https://github.com/jsocol/commonware/blob/master/LICENSE
.. |Build Status| image:: https://travis-ci.org/andymckay/django-statsd.svg?branch=master
:target: https://travis-ci.org/andymckay/django-statsd
.. |Build Status| image:: https://travis-ci.org/django-statsd/django-statsd.svg?branch=master
:target: https://travis-ci.org/django-statsd/django-statsd

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import
from django_statsd.clients import statsd
import time

View File

@ -1,6 +1,10 @@
import socket
from django.utils.importlib import import_module
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from django.conf import settings
_statsd = None
@ -22,7 +26,7 @@ def get_client():
# host = socket.gethostbyaddr(host)[2][0]
port = get('STATSD_PORT', 8125)
prefix = get('STATSD_PREFIX', None)
return import_module(client).StatsClient(host, port, prefix)
return import_module(client).StatsClient(host=host, port=port, prefix=prefix)
if not _statsd:
_statsd = get_client()

View File

@ -22,4 +22,5 @@ class StatsClient(StatsClient):
def gauge(self, stat, value, rate=1, delta=False):
"""Set a gauge value."""
log.info('Gauge: %s, %s%s, %s' % (stat, '' if not delta else 'diff ', value , rate))
log.info('Gauge: %s, %s%s, %s' % (
stat, '' if not delta else 'diff ', value, rate))

View File

@ -1,12 +1,20 @@
import inspect
import time
from django.conf import settings
from django.http import Http404
from django_statsd.clients import statsd
class GraphiteMiddleware(object):
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
class MiddlewareMixin(object):
pass
class GraphiteMiddleware(MiddlewareMixin):
def process_response(self, request, response):
statsd.incr('response.%s' % response.status_code)
@ -21,7 +29,7 @@ class GraphiteMiddleware(object):
statsd.incr('response.auth.500')
class GraphiteRequestTimingMiddleware(object):
class GraphiteRequestTimingMiddleware(MiddlewareMixin):
"""statsd's timing data per view."""
def process_view(self, request, view_func, view_args, view_kwargs):
@ -48,8 +56,9 @@ class GraphiteRequestTimingMiddleware(object):
data = dict(module=request._view_module, name=request._view_name,
method=request.method)
statsd.timing('view.{module}.{name}.{method}'.format(**data), ms)
statsd.timing('view.{module}.{method}'.format(**data), ms)
statsd.timing('view.{method}'.format(**data), ms)
if getattr(settings, 'STATSD_VIEW_TIMER_DETAILS', True):
statsd.timing('view.{module}.{method}'.format(**data), ms)
statsd.timing('view.{method}'.format(**data), ms)
class TastyPieRequestTimingMiddleware(GraphiteRequestTimingMiddleware):

View File

@ -35,6 +35,6 @@ def model_delete(sender, **kwargs):
instance._meta.object_name,
))
if getattr(settings, 'STATSD_MODEL_SIGNALS', True):
if getattr(settings, 'STATSD_MODEL_SIGNALS', False):
post_save.connect(model_save)
post_delete.connect(model_delete)

View File

@ -58,7 +58,7 @@ def times_summary(stats):
for stat in stats:
timings[stat[0].split('|')[0]].append(stat[2])
for stat, v in timings.iteritems():
for stat, v in timings.items():
if not v:
continue
v.sort()

View File

@ -1,5 +1,9 @@
from django.conf import settings
from django.utils.importlib import import_module
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
patches = getattr(settings, 'STATSD_PATCHES', [])

View File

@ -1,5 +1,8 @@
import django
from django.db.backends import util
try:
from django.db.backends import utils as util
except ImportError:
from django.db.backends import util
from django_statsd.patches.utils import wrap, patch_method
from django_statsd.clients import statsd
@ -33,6 +36,7 @@ def patched_execute(orig_execute, self, query, *args, **kwargs):
with statsd.timer(key(self.db, 'execute.%s' % _get_query_type(query))):
return orig_execute(self, query, *args, **kwargs)
def patched_executemany(orig_executemany, self, query, *args, **kwargs):
with statsd.timer(key(self.db, 'executemany.%s' % _get_query_type(query))):
return orig_executemany(self, query, *args, **kwargs)

View File

@ -1,10 +1,7 @@
try:
from django.conf.urls import patterns, url
except ImportError: # django < 1.4
from django.conf.urls.defaults import patterns, url
from django.conf.urls import url
import django_statsd.views
urlpatterns = patterns(
'',
url('^record$', 'django_statsd.views.record', name='django_statsd.record'),
)
urlpatterns = [
url('^record$', django_statsd.views.record, name='django_statsd.record'),
]

View File

@ -135,6 +135,7 @@ clients = {
@csrf_exempt
@require_http_methods(["GET", "POST"])
def record(request):
"""
This is a Django method you can link to in your URLs that process
@ -146,10 +147,11 @@ def record(request):
you need for imposing security on this method, so that not just anyone
can post to it.
"""
if 'client' not in request.REQUEST:
data = request.POST or request.GET
if 'client' not in data:
return http.HttpResponseBadRequest()
client = request.REQUEST['client']
client = data.get('client')
if client not in clients:
return http.HttpResponseBadRequest()

View File

@ -16,6 +16,12 @@ Credits:
Changes
-------
0.3.15:
- push from travis to pypi to keep files clean
- allow less statsd in the middleware
- fix to a specific statsd version
0.3.14:
- pypy testing support
@ -168,6 +174,14 @@ To get timings for your database or your cache, put in some monkeypatches::
'django_statsd.patches.cache',
]
You can change the host that stats are sent to with the `STATSD_HOST` setting::
STATSD_HOST = 'localhost'
Similarly, you can use the `STATSD_PORT`setting to customize the port number (which defaults to `8125`)::
STATSD_PORT = 8125
Toolbar integration
-------------------
@ -250,11 +264,11 @@ First, make sure you can record the timings in your Django site urls. This
could be done by pointing straight to the view or including the URL for
example::
from django_statsd.urls import urlpatterns as statsd_patterns
from django_statsd.urls import urlpatterns as statsd_patterns
urlpatterns = patterns('',
('^services/timing/', include(statsd_patterns)),
)
urlpatterns = [
url(r'^services/timing/', include(statsd_patterns)),
]
In this case the URL to the record view will be `/services/timing/record`.
@ -326,6 +340,13 @@ everyone not in INTERNAL_IPS::
STATSD_RECORD_GUARD = internal_only
STATSD_VIEW_TIMER_DETAILS (optional)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The middleware sends timing pings for the almost the same thing three times
when accessing a view: `module.name.method`, `module.method` and `method` by
default. Setting this to `False` just does the former.
Logging errors
~~~~~~~~~~~~~~
@ -341,9 +362,20 @@ do this by adding in the handler. For example in your logging configuration::
Testing
=======
You can run tests with the following command:
You need to install tox_ to run the tests.
You can run the full test matrix with:
DJANGO_SETTINGS_MODULE='django_statsd.test_settings' nosetests
tox
or choose a specific environment - let's say Python 3.4 and Django 1.11 - with:
tox -e py34-django111
You can list all the available environments with:
tox -l
.. _tox: http://tox.readthedocs.io/en/latest/index.html
Nose
====

View File

@ -1,5 +1,4 @@
mock
nose
unittest2
statsd>=2.0.0
django<1.7
pytest-django
statsd==3.2.1

View File

@ -4,13 +4,13 @@ from setuptools import setup
setup(
# Because django-statsd was taken, I called this django-statsd-mozilla.
name='django-statsd-mozilla',
version='0.3.14',
version='0.3.16',
description='Django interface with statsd',
long_description=open('README.rst').read(),
author='Andy McKay',
author_email='andym@mozilla.com',
license='BSD',
install_requires=['statsd>=2.0.0'],
install_requires=['statsd >= 2.1.2, != 3.2 , <= 4.0'],
packages=['django_statsd',
'django_statsd/patches',
'django_statsd/clients',
@ -29,6 +29,8 @@ setup(
'Intended Audience :: Developers',
'Natural Language :: English',
'Operating System :: OS Independent',
'Framework :: Django'
'Framework :: Django',
'Programming Language :: Python',
'Programming Language :: Python :: 3'
]
)

0
tests/__init__.py Normal file
View File

34
tests/conftest.py Normal file
View File

@ -0,0 +1,34 @@
def pytest_configure():
from django.conf import settings
settings.configure(
DEBUG_PROPAGATE_EXCEPTIONS=True,
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:'
}
},
SITE_ID=1,
SECRET_KEY='not very secret in tests',
ROOT_URLCONF='django_statsd.urls',
INSTALLED_APPS=(
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.staticfiles',
'django_statsd',
'tests',
),
STATSD_CLIENT='django_statsd.clients.null',
STATSD_PREFIX=None,
METLOG=None,
)
try:
import django
django.setup()
except AttributeError:
pass

546
tests/test_django_statsd.py Normal file
View File

@ -0,0 +1,546 @@
import json
import logging.config
import sys
import unittest
from django.conf import settings
from nose.exc import SkipTest
from nose import tools as nose_tools
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden
from django.test import TestCase
from django.test.client import RequestFactory
from django.utils.http import urlencode
import mock
from nose.tools import eq_
from django_statsd.clients import get_client, statsd
from django_statsd.patches import utils
from django_statsd.patches.db import (
patched_callproc,
patched_execute,
patched_executemany,
)
from django_statsd import middleware
cfg = {
'version': 1,
'formatters': {},
'handlers': {
'test_statsd_handler': {
'class': 'django_statsd.loggers.errors.StatsdHandler',
},
},
'loggers': {
'test.logging': {
'handlers': ['test_statsd_handler'],
},
},
}
@mock.patch.object(middleware.statsd, 'incr')
class TestIncr(TestCase):
def setUp(self):
self.req = RequestFactory().get('/')
self.res = HttpResponse()
def test_graphite_response(self, incr):
gmw = middleware.GraphiteMiddleware()
gmw.process_response(self.req, self.res)
assert incr.called
def test_graphite_response_authenticated(self, incr):
self.req.user = mock.Mock()
self.req.user.is_authenticated.return_value = True
gmw = middleware.GraphiteMiddleware()
gmw.process_response(self.req, self.res)
eq_(incr.call_count, 2)
def test_graphite_exception(self, incr):
gmw = middleware.GraphiteMiddleware()
gmw.process_exception(self.req, None)
assert incr.called
def test_graphite_exception_authenticated(self, incr):
self.req.user = mock.Mock()
self.req.user.is_authenticated.return_value = True
gmw = middleware.GraphiteMiddleware()
gmw.process_exception(self.req, None)
eq_(incr.call_count, 2)
@mock.patch.object(middleware.statsd, 'timing')
class TestTiming(unittest.TestCase):
def setUp(self):
self.req = RequestFactory().get('/')
self.res = HttpResponse()
def test_request_timing(self, timing):
func = lambda x: x
gmw = middleware.GraphiteRequestTimingMiddleware()
gmw.process_view(self.req, func, tuple(), dict())
gmw.process_response(self.req, self.res)
eq_(timing.call_count, 3)
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
'view.%s.GET' % func.__module__,
'view.GET']
for expected, (args, kwargs) in zip(names, timing.call_args_list):
eq_(expected, args[0])
def test_request_timing_exception(self, timing):
func = lambda x: x
gmw = middleware.GraphiteRequestTimingMiddleware()
gmw.process_view(self.req, func, tuple(), dict())
gmw.process_exception(self.req, self.res)
eq_(timing.call_count, 3)
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
'view.%s.GET' % func.__module__,
'view.GET']
for expected, (args, kwargs) in zip(names, timing.call_args_list):
eq_(expected, args[0])
def test_request_timing_tastypie(self, timing):
func = lambda x: x
gmw = middleware.TastyPieRequestTimingMiddleware()
gmw.process_view(self.req, func, tuple(), {
'api_name': 'my_api_name',
'resource_name': 'my_resource_name'
})
gmw.process_response(self.req, self.res)
eq_(timing.call_count, 3)
names = ['view.my_api_name.my_resource_name.GET',
'view.my_api_name.GET',
'view.GET']
for expected, (args, kwargs) in zip(names, timing.call_args_list):
eq_(expected, args[0])
def test_request_timing_tastypie_fallback(self, timing):
func = lambda x: x
gmw = middleware.TastyPieRequestTimingMiddleware()
gmw.process_view(self.req, func, tuple(), dict())
gmw.process_response(self.req, self.res)
eq_(timing.call_count, 3)
names = ['view.%s.%s.GET' % (func.__module__, func.__name__),
'view.%s.GET' % func.__module__,
'view.GET']
for expected, (args, kwargs) in zip(names, timing.call_args_list):
eq_(expected, args[0])
class TestClient(unittest.TestCase):
@mock.patch.object(settings, 'STATSD_CLIENT', 'statsd.client')
def test_normal(self):
eq_(get_client().__module__, 'statsd.client')
@mock.patch.object(settings, 'STATSD_CLIENT',
'django_statsd.clients.null')
def test_null(self):
eq_(get_client().__module__, 'django_statsd.clients.null')
@mock.patch.object(settings, 'STATSD_CLIENT',
'django_statsd.clients.toolbar')
def test_toolbar(self):
eq_(get_client().__module__, 'django_statsd.clients.toolbar')
@mock.patch.object(settings, 'STATSD_CLIENT',
'django_statsd.clients.toolbar')
def test_toolbar_send(self):
client = get_client()
eq_(client.cache, {})
client.incr('testing')
eq_(client.cache, {'testing|count': [[1, 1]]})
class TestMetlogClient(TestCase):
def check_metlog(self):
try:
from metlog.config import client_from_dict_config
return client_from_dict_config
except ImportError:
raise SkipTest("Metlog is not installed")
@nose_tools.raises(AttributeError)
def test_no_metlog(self):
with self.settings(STATSD_PREFIX='moz_metlog',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
get_client()
def _create_client(self):
client_from_dict_config = self.check_metlog()
# Need to load within the test in case metlog is not installed
from metlog.config import client_from_dict_config
METLOG_CONF = {
'logger': 'django-statsd',
'sender': {
'class': 'metlog.senders.DebugCaptureSender',
},
}
return client_from_dict_config(METLOG_CONF)
def test_get_client(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_PREFIX='moz_metlog',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
eq_(client.__module__, 'django_statsd.clients.moz_metlog')
def test_metlog_incr(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_PREFIX='moz_metlog',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
eq_(len(client.metlog.sender.msgs), 0)
client.incr('testing')
eq_(len(client.metlog.sender.msgs), 1)
msg = json.loads(client.metlog.sender.msgs[0])
eq_(msg['severity'], 6)
eq_(msg['payload'], '1')
eq_(msg['fields']['rate'], 1)
eq_(msg['fields']['name'], 'moz_metlog.testing')
eq_(msg['type'], 'counter')
def test_metlog_decr(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_PREFIX='moz_metlog',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
eq_(len(client.metlog.sender.msgs), 0)
client.decr('testing')
eq_(len(client.metlog.sender.msgs), 1)
msg = json.loads(client.metlog.sender.msgs[0])
eq_(msg['severity'], 6)
eq_(msg['payload'], '-1')
eq_(msg['fields']['rate'], 1)
eq_(msg['fields']['name'], 'moz_metlog.testing')
eq_(msg['type'], 'counter')
def test_metlog_timing(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_PREFIX='moz_metlog',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
eq_(len(client.metlog.sender.msgs), 0)
client.timing('testing', 512, rate=2)
eq_(len(client.metlog.sender.msgs), 1)
msg = json.loads(client.metlog.sender.msgs[0])
eq_(msg['severity'], 6)
eq_(msg['payload'], '512')
eq_(msg['fields']['rate'], 2)
eq_(msg['fields']['name'], 'moz_metlog.testing')
eq_(msg['type'], 'timer')
@nose_tools.raises(AttributeError)
def test_metlog_no_prefixes(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
client.incr('foo', 2)
def test_metlog_prefixes(self):
metlog = self._create_client()
with self.settings(METLOG=metlog,
STATSD_PREFIX='some_prefix',
STATSD_CLIENT='django_statsd.clients.moz_metlog'):
client = get_client()
eq_(len(client.metlog.sender.msgs), 0)
client.timing('testing', 512, rate=2)
client.incr('foo', 2)
client.decr('bar', 5)
eq_(len(client.metlog.sender.msgs), 3)
msg = json.loads(client.metlog.sender.msgs[0])
eq_(msg['severity'], 6)
eq_(msg['payload'], '512')
eq_(msg['fields']['rate'], 2)
eq_(msg['fields']['name'], 'some_prefix.testing')
eq_(msg['type'], 'timer')
msg = json.loads(client.metlog.sender.msgs[1])
eq_(msg['severity'], 6)
eq_(msg['payload'], '2')
eq_(msg['fields']['rate'], 1)
eq_(msg['fields']['name'], 'some_prefix.foo')
eq_(msg['type'], 'counter')
msg = json.loads(client.metlog.sender.msgs[2])
eq_(msg['severity'], 6)
eq_(msg['payload'], '-5')
eq_(msg['fields']['rate'], 1)
eq_(msg['fields']['name'], 'some_prefix.bar')
eq_(msg['type'], 'counter')
# This is primarily for Zamboni, which loads in the custom middleware
# classes, one of which, breaks posts to our url. Let's stop that.
@mock.patch.object(settings, 'MIDDLEWARE_CLASSES', [])
class TestRecord(TestCase):
urls = 'django_statsd.urls'
def setUp(self):
super(TestRecord, self).setUp()
self.url = reverse('django_statsd.record')
settings.STATSD_RECORD_GUARD = None
self.good = {'client': 'boomerang', 'nt_nav_st': 1,
'nt_domcomp': 3}
self.stick = {'client': 'stick',
'window.performance.timing.domComplete': 123,
'window.performance.timing.domInteractive': 456,
'window.performance.timing.domLoading': 789,
'window.performance.timing.navigationStart': 0,
'window.performance.navigation.redirectCount': 3,
'window.performance.navigation.type': 1}
def test_no_client(self):
response = self.client.get(self.url)
assert response.status_code == 400
def test_no_valid_client(self):
response = self.client.get(self.url, {'client': 'no'})
assert response.status_code == 400
def test_boomerang_almost(self):
response = self.client.get(self.url, {'client': 'boomerang'})
assert response.status_code == 400
def test_boomerang_minimum(self):
content = self.client.get(
self.url, {
'client': 'boomerang',
'nt_nav_st': 1,
}).content.decode()
assert content == 'recorded'
@mock.patch('django_statsd.views.process_key')
def test_boomerang_something(self, process_key):
content = self.client.get(self.url, self.good).content.decode()
assert content == 'recorded'
assert process_key.called
def test_boomerang_post(self):
assert self.client.post(self.url + '?' + urlencode(self.good), self.good).status_code == 405
def test_good_guard(self):
settings.STATSD_RECORD_GUARD = lambda r: None
response = self.client.get(self.url, self.good)
assert response.status_code == 200
def test_bad_guard(self):
settings.STATSD_RECORD_GUARD = lambda r: HttpResponseForbidden()
response = self.client.get(self.url, self.good)
assert response.status_code == 403
def test_stick_get(self):
assert self.client.get(self.url, self.stick).status_code == 405
@mock.patch('django_statsd.views.process_key')
def test_stick(self, process_key):
assert self.client.post(self.url, self.stick).status_code == 200
assert process_key.called
def test_stick_start(self):
data = self.stick.copy()
del data['window.performance.timing.navigationStart']
assert self.client.post(self.url, data).status_code == 400
@mock.patch('django_statsd.views.process_key')
def test_stick_missing(self, process_key):
data = self.stick.copy()
del data['window.performance.timing.domInteractive']
assert self.client.post(self.url, data).status_code == 200
assert process_key.called
def test_stick_garbage(self):
data = self.stick.copy()
data['window.performance.timing.domInteractive'] = '<alert>'
assert self.client.post(self.url, data).status_code == 400
def test_stick_some_garbage(self):
data = self.stick.copy()
data['window.performance.navigation.redirectCount'] = '<alert>'
assert self.client.post(self.url, data).status_code == 400
def test_stick_more_garbage(self):
data = self.stick.copy()
data['window.performance.navigation.type'] = '<alert>'
assert self.client.post(self.url, data).status_code == 400
@mock.patch.object(middleware.statsd, 'incr')
class TestErrorLog(TestCase):
def setUp(self):
logging.config.dictConfig(cfg)
self.log = logging.getLogger('test.logging')
def division_error(self):
try:
1 / 0
except:
return sys.exc_info()
def test_emit(self, incr):
self.log.error('blargh!', exc_info=self.division_error())
assert incr.call_args[0][0] == 'error.zerodivisionerror'
def test_not_emit(self, incr):
self.log.error('blargh!')
assert not incr.called
class TestPatchMethod(TestCase):
def setUp(self):
super(TestPatchMethod, self).setUp()
class DummyClass(object):
def sumargs(self, a, b, c=3, d=4):
return a + b + c + d
def badfn(self, a, b=2):
raise ValueError
self.cls = DummyClass
def test_late_patching(self):
"""
Objects created before patching should get patched as well.
"""
def patch_fn(original_fn, self, *args, **kwargs):
return original_fn(self, *args, **kwargs) + 10
obj = self.cls()
self.assertEqual(obj.sumargs(1, 2, 3, 4), 10)
utils.patch_method(self.cls, 'sumargs')(patch_fn)
self.assertEqual(obj.sumargs(1, 2, 3, 4), 20)
def test_doesnt_call_original_implicitly(self):
"""
Original fn must be called explicitly from patched to be
executed.
"""
def patch_fn(original_fn, self, *args, **kwargs):
return 10
with self.assertRaises(ValueError):
obj = self.cls()
obj.badfn(1, 2)
utils.patch_method(self.cls, 'badfn')(patch_fn)
self.assertEqual(obj.badfn(1, 2), 10)
def test_args_kwargs_are_honored(self):
"""
Args and kwargs must be honored between calls from the patched to
the original version.
"""
def patch_fn(original_fn, self, *args, **kwargs):
return original_fn(self, *args, **kwargs)
utils.patch_method(self.cls, 'sumargs')(patch_fn)
obj = self.cls()
self.assertEqual(obj.sumargs(1, 2), 10)
self.assertEqual(obj.sumargs(1, 1, d=1), 6)
self.assertEqual(obj.sumargs(1, 1, 1, 1), 4)
def test_patched_fn_can_receive_arbitrary_arguments(self):
"""
Args and kwargs can be received arbitrarily with no contraints on
the patched fn, even if the original_fn had a fixed set of
allowed args and kwargs.
"""
def patch_fn(original_fn, self, *args, **kwargs):
return args, kwargs
utils.patch_method(self.cls, 'badfn')(patch_fn)
obj = self.cls()
self.assertEqual(obj.badfn(1, d=2), ((1,), {'d': 2}))
self.assertEqual(obj.badfn(1, d=2), ((1,), {'d': 2}))
self.assertEqual(obj.badfn(1, 2, c=1, d=2), ((1, 2), {'c': 1, 'd': 2}))
class TestCursorWrapperPatching(TestCase):
example_queries = {
'select': 'select * from something;',
'insert': 'insert (1, 2) into something;',
'update': 'update something set a=1;',
}
def test_patched_callproc_calls_timer(self):
for operation, query in list(self.example_queries.items()):
with mock.patch.object(statsd, 'timer') as timer:
client = mock.Mock(executable_name='client_executable_name')
db = mock.Mock(executable_name='name', alias='alias', client=client)
instance = mock.Mock(db=db)
patched_callproc(lambda *args, **kwargs: None, instance, query)
self.assertEqual(timer.call_count, 1)
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.callproc.%s' % operation)
def test_patched_execute_calls_timer(self):
for operation, query in list(self.example_queries.items()):
with mock.patch.object(statsd, 'timer') as timer:
client = mock.Mock(executable_name='client_executable_name')
db = mock.Mock(executable_name='name', alias='alias', client=client)
instance = mock.Mock(db=db)
patched_execute(lambda *args, **kwargs: None, instance, query)
self.assertEqual(timer.call_count, 1)
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.execute.%s' % operation)
def test_patched_executemany_calls_timer(self):
for operation, query in list(self.example_queries.items()):
with mock.patch.object(statsd, 'timer') as timer:
client = mock.Mock(executable_name='client_executable_name')
db = mock.Mock(executable_name='name', alias='alias', client=client)
instance = mock.Mock(db=db)
patched_executemany(lambda *args, **kwargs: None, instance, query)
self.assertEqual(timer.call_count, 1)
self.assertEqual(timer.call_args[0][0], 'db.client_executable_name.alias.executemany.%s' % operation)
@mock.patch('django_statsd.patches.db.patched_callproc')
@mock.patch('django_statsd.patches.db.patched_executemany')
@mock.patch('django_statsd.patches.db.patched_execute')
@mock.patch('django.db.backends.utils.CursorWrapper')
def test_cursorwrapper_patching(self, CursorWrapper, execute, executemany, callproc):
from django_statsd.patches.db import patch
execute.__name__ = 'execute'
executemany.__name__ = 'executemany'
callproc.__name__ = 'callproc'
execute.return_value = 'execute'
executemany.return_value = 'executemany'
callproc.return_value = 'callproc'
patch()
self.assertEqual(CursorWrapper.execute(), 'execute')
self.assertEqual(CursorWrapper.executemany(), 'executemany')
self.assertEqual(CursorWrapper.callproc(), 'callproc')

32
tox.ini Normal file
View File

@ -0,0 +1,32 @@
[pytest]
addopts=--tb=short
[tox]
envlist =
{py27,py33,py34,py35,pypy,pypy3}-django18,
{py27,py34,py35,pypy,pypy3}-django{19,110},
{py27,py34,py35,py36,pypy,pypy3}-django111,
{py35,py36,pypy,pypy3}-djangomaster
[travis:env]
DJANGO =
1.8: django18
1.9: django19
1.10: django110
1.11: django111
master: djangomaster
[testenv]
commands = py.test
setenv =
PYTHONDONTWRITEBYTECODE=1
PYTHONWARNINGS=once
deps =
py27: -roptional.txt
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11
django111: Django>=1.11,<2.0
djangomaster: https://github.com/django/django/archive/master.tar.gz
-rrequirements.txt