├── .github └── workflows │ ├── stale.yml │ └── test.yaml ├── .gitignore ├── README.rst ├── django_statsd ├── __about__.py ├── __init__.py ├── celery.py ├── database.py ├── json.py ├── middleware.py ├── redis.py ├── settings.py ├── templates.py ├── urls.py └── utils.py ├── docs ├── .gitignore ├── Makefile ├── _static │ └── .keep ├── _theme │ ├── LICENSE │ ├── flask_theme_support.py │ └── wolph │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ ├── flasky.css_t │ │ └── small_flask.css │ │ └── theme.conf ├── conf.py ├── django_statsd.rst ├── index.rst ├── make.bat ├── requirements.txt └── usage.rst ├── pytest.ini ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── requirements.txt ├── settings.py ├── test_app │ ├── __init__.py │ ├── models.py │ ├── urls.py │ └── views.py ├── test_prefix.py ├── urls.py └── views.py └── tox.ini /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues and pull requests 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * *' # Run every day at midnight 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v8 13 | with: 14 | days-before-stale: 30 15 | exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | concurrency: 8 | group: check-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build: 13 | name: test with ${{ matrix.py }} on ${{ matrix.os }} 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | py: ['3.8', '3.9', '3.10', '3.11', '3.12'] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | - name: Set up Python ${{ matrix.py }} 25 | uses: actions/setup-python@v4 26 | with: 27 | python-version: ${{ matrix.py }} 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install tox-gh>=1.2 32 | - name: Setup test suite 33 | run: tox -vv --notest 34 | - name: Run test suite 35 | run: tox --skip-pkg-install 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info.* 3 | .* 4 | dist 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | `django_statsd` is a middleware that uses `python-statsd` to log query 5 | and view durations to statsd. 6 | 7 | * Documentation 8 | - http://django-stats.readthedocs.org/en/latest/ 9 | * Source 10 | - https://github.com/WoLpH/django-statsd 11 | * Bug reports 12 | - https://github.com/WoLpH/django-statsd/issues 13 | * Package homepage 14 | - https://pypi.python.org/pypi/django-statsd 15 | * Python Statsd 16 | - https://github.com/WoLpH/python-statsd 17 | * Graphite 18 | - http://graphite.wikidot.com 19 | * Statsd 20 | - code: https://github.com/etsy/statsd 21 | - blog post: http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/ 22 | 23 | 24 | Install 25 | ======= 26 | 27 | To install simply execute `python setup.py install`. 28 | If you want to run the tests first, run `python setup.py test` 29 | 30 | 31 | Usage 32 | ===== 33 | 34 | To install, add the following to your ``settings.py``: 35 | 36 | 1. ``django_statsd`` to the ``INSTALLED_APPS`` setting. 37 | 2. ``django_statsd.middleware.StatsdMiddleware`` to the **top** of your 38 | ``MIDDLEWARE`` 39 | 3. ``django_statsd.middleware.StatsdMiddlewareTimer`` to the **bottom** of your 40 | ``MIDDLEWARE`` 41 | 42 | Configuration 43 | ------------- 44 | You can configure ``django-statsd`` using the Django settings config: 45 | 46 | >>> # Settings 47 | ... STATSD_HOST = '127.0.0.1' 48 | ... STATSD_PORT = 12345 49 | 50 | The full list of configurations is available in ReadTheDocs_. 51 | 52 | .. _ReadTheDocs: https://django-stats.readthedocs.io/en/latest/django_statsd.html#module-django_statsd.settings 53 | 54 | 55 | Advanced Usage 56 | -------------- 57 | 58 | >>> def some_view(request): 59 | ... with request.timings('something_to_time'): 60 | ... # do something here 61 | ... pass 62 | >>> 63 | >>> def some_view(request): 64 | ... request.timings.start('something_to_time') 65 | ... # do something here 66 | ... request.timings.stop('something_to_time') 67 | -------------------------------------------------------------------------------- /django_statsd/__about__.py: -------------------------------------------------------------------------------- 1 | __package_name__ = 'django-statsd' 2 | __version__ = '2.7.0' 3 | __author__ = 'Rick van Hattem' 4 | __author_email__ = 'Rick.van.Hattem@Fawo.nl' 5 | __description__ = ( 6 | '''django-statsd is a Django app that submits query and view durations ''' 7 | '''to Etsy's statsd.''' 8 | ) 9 | __url__ = 'https://github.com/WoLpH/django-statsd' 10 | 11 | -------------------------------------------------------------------------------- /django_statsd/__init__.py: -------------------------------------------------------------------------------- 1 | from django_statsd.middleware import ( 2 | decr, 3 | incr, 4 | start, 5 | stop, 6 | with_, 7 | wrapper, 8 | named_wrapper, 9 | decorator, 10 | ) 11 | from django_statsd import redis, celery, json, templates 12 | 13 | __all__ = [ 14 | 'decr', 15 | 'incr', 16 | 'start', 17 | 'stop', 18 | 'with_', 19 | 'wrapper', 20 | 'named_wrapper', 21 | 'decorator', 22 | 'json', 23 | 'redis', 24 | 'celery', 25 | 'templates', 26 | ] 27 | -------------------------------------------------------------------------------- /django_statsd/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django_statsd import middleware, utils 3 | 4 | try: 5 | from celery import signals 6 | from celery.utils import dispatch 7 | 8 | counter = utils.get_counter('celery.status') 9 | 10 | def increment(signal): 11 | counter.increment(signal) 12 | 13 | def _increment(**kwargs): 14 | pass 15 | return _increment 16 | 17 | for signal in dir(signals): 18 | instance = getattr(signals, signal) 19 | if isinstance(instance, dispatch.Signal): 20 | instance.connect(increment(signal)) 21 | 22 | def start(**kwargs): 23 | middleware.StatsdMiddleware.start('celery') 24 | 25 | def stop(**kwargs): 26 | middleware.StatsdMiddleware.stop(kwargs.get('task').name) 27 | middleware.StatsdMiddleware.scope.timings = None 28 | 29 | def clear(**kwargs): 30 | middleware.StatsdMiddleware.scope.timings = None 31 | 32 | signals.task_prerun.connect(start) 33 | signals.task_postrun.connect(stop) 34 | signals.task_failure.connect(clear) 35 | 36 | except ImportError: 37 | pass 38 | -------------------------------------------------------------------------------- /django_statsd/database.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import django_statsd 3 | 4 | 5 | class TimingCursorWrapper(object): 6 | 7 | def execute(self, *args, **kwargs): 8 | with django_statsd.with_('sql.%s' % self.db.alias): 9 | return self.cursor.execute(*args, **kwargs) 10 | 11 | def executemany(self, *args, **kwargs): 12 | with django_statsd.with_('sql.%s' % self.db.alias): 13 | return self.cursor.executemany(*args, **kwargs) 14 | -------------------------------------------------------------------------------- /django_statsd/json.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import django_statsd 3 | 4 | try: 5 | import json 6 | 7 | if not hasattr(json, 'statsd_patched'): 8 | json.statsd_patched = True 9 | json.load = django_statsd.wrapper('json', json.load) 10 | json.loads = django_statsd.wrapper('json', json.loads) 11 | json.dump = django_statsd.wrapper('json', json.dump) 12 | json.dumps = django_statsd.wrapper('json', json.dumps) 13 | except ImportError: 14 | pass 15 | 16 | try: 17 | import cjson 18 | 19 | if not hasattr(json, 'statsd_patched'): 20 | cjson.statsd_patched = True 21 | cjson.encode = django_statsd.wrapper('cjson', cjson.encode) 22 | cjson.decode = django_statsd.wrapper('cjson', cjson.decode) 23 | except ImportError: 24 | pass 25 | -------------------------------------------------------------------------------- /django_statsd/middleware.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import collections 4 | import functools 5 | import logging 6 | import re 7 | import threading 8 | import time 9 | import warnings 10 | 11 | import statsd 12 | from django.core import exceptions 13 | 14 | from . import settings, utils 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | TAGS_LIKE_SUPPORTED = ['=', '_is_'] 19 | try: 20 | MAKE_TAGS_LIKE = settings.STATSD_TAGS_LIKE 21 | if MAKE_TAGS_LIKE is not None: 22 | if MAKE_TAGS_LIKE is True: 23 | MAKE_TAGS_LIKE = '_is_' 24 | elif MAKE_TAGS_LIKE not in TAGS_LIKE_SUPPORTED: 25 | MAKE_TAGS_LIKE = False 26 | warnings.warn( 27 | 'Unsupported `STATSD_TAGS_LIKE` setting. ' 28 | 'Please, choose from %r' % TAGS_LIKE_SUPPORTED 29 | ) 30 | 31 | except exceptions.ImproperlyConfigured: 32 | MAKE_TAGS_LIKE = False 33 | 34 | 35 | def is_ajax(request): 36 | ''' 37 | Recreating the old Django is_ajax function. Note that this is not 38 | guaranteed to be correct as it depends on jQuery style ajax 39 | requests 40 | ''' 41 | return request.headers.get('x-requested-with') == 'XMLHttpRequest' 42 | 43 | 44 | class WithTimer(object): 45 | 46 | def __init__(self, timer, key): 47 | self.timer = timer 48 | self.key = key 49 | 50 | def __enter__(self): 51 | self.timer.start(self.key) 52 | 53 | def __exit__( 54 | self, 55 | type_, 56 | value, 57 | traceback, 58 | ): 59 | self.timer.stop(self.key) 60 | 61 | 62 | class Client(object): 63 | class_ = statsd.Client 64 | 65 | def __init__(self, prefix='view'): 66 | if settings.STATSD_PREFIX: 67 | prefix = '%s.%s' % (settings.STATSD_PREFIX, prefix) 68 | self.prefix = prefix 69 | self.data = collections.defaultdict(int) 70 | 71 | def get_client(self, *args): 72 | args = [self.prefix] + list(args) 73 | prefix = '.'.join(a for a in args if a) 74 | return utils.get_client(prefix, class_=self.class_) 75 | 76 | def submit(self, *args): 77 | raise NotImplementedError( 78 | 'Subclasses must define a `submit` function') 79 | 80 | 81 | class Counter(Client): 82 | class_ = statsd.Counter 83 | 84 | def increment(self, key, delta=1): 85 | self.data[key] += delta 86 | 87 | def decrement(self, key, delta=1): 88 | self.data[key] -= delta 89 | 90 | def submit(self, *args): 91 | client = self.get_client(*args) 92 | for k, v in self.data.items(): 93 | if v: 94 | client.increment(k, v) 95 | 96 | 97 | class Timer(Client): 98 | class_ = statsd.Timer 99 | 100 | def __init__(self, prefix='view'): 101 | Client.__init__(self, prefix) 102 | self.starts = collections.defaultdict(collections.deque) 103 | self.data = collections.defaultdict(float) 104 | 105 | def start(self, key): 106 | self.starts[key].append(time.time()) 107 | 108 | def stop(self, key): 109 | assert self.starts[key], ('Unable to stop tracking %s, never ' 110 | 'started tracking it' % key) 111 | 112 | delta = time.time() - self.starts[key].pop() 113 | # Clean up when we're done 114 | if not self.starts[key]: 115 | del self.starts[key] 116 | 117 | self.data[key] += delta 118 | return delta 119 | 120 | def submit(self, *args): 121 | client = self.get_client(*args) 122 | for k in list(self.data.keys()): 123 | client.send(k, self.data.pop(k)) 124 | 125 | if settings.STATSD_DEBUG: 126 | assert not self.starts, ('Timer(s) %r were started but never ' 127 | 'stopped' % self.starts) 128 | 129 | def __call__(self, key): 130 | return WithTimer(self, key) 131 | 132 | 133 | class StatsdMiddleware: 134 | scope = threading.local() 135 | 136 | def __init__(self, get_response=None): 137 | self.get_response = get_response 138 | self.scope.timings = None 139 | self.scope.counter = None 140 | 141 | def __call__(self, request): 142 | # store the timings in the request so it can be used everywhere 143 | self.process_request(request) 144 | try: 145 | return self.process_response(request, self.get_response(request)) 146 | finally: 147 | self.cleanup(request) 148 | 149 | @classmethod 150 | def skip_view(cls, view_name): 151 | for pattern in settings.STATSD_VIEWS_TO_SKIP: 152 | if re.match(pattern, view_name): 153 | logger.debug("Skipping metric `{}`".format(view_name)) 154 | return True 155 | 156 | return False 157 | 158 | @classmethod 159 | def start(cls, prefix='view'): 160 | cls.scope.timings = Timer(prefix) 161 | cls.scope.timings.start('total') 162 | cls.scope.counter = Counter(prefix) 163 | cls.scope.counter.increment('hit') 164 | cls.scope.counter_codes = Counter(prefix) 165 | cls.scope.counter_codes.increment('hit') 166 | cls.scope.counter_site = Counter(prefix) 167 | cls.scope.counter_site.increment('hit') 168 | return cls.scope 169 | 170 | @classmethod 171 | def stop(cls, *key): 172 | if getattr(cls.scope, 'timings', None): 173 | cls.scope.timings.stop('total') 174 | cls.scope.timings.submit(*key) 175 | cls.scope.counter.submit(*key) 176 | cls.scope.counter_site.submit('site') 177 | 178 | def process_request(self, request): 179 | # store the timings in the request so it can be used everywhere 180 | request.statsd = self.start() 181 | if settings.STATSD_TRACK_MIDDLEWARE: 182 | self.scope.timings.start('process_request') 183 | self.view_name = None 184 | 185 | def process_view(self, request, view_func, view_args, view_kwargs): 186 | if settings.STATSD_TRACK_MIDDLEWARE: 187 | StatsdMiddleware.scope.timings.start('process_view') 188 | 189 | # View name is defined as module.view 190 | # (e.g. django.contrib.auth.views.login) 191 | self.view_name = view_func.__module__ 192 | 193 | # CBV specific 194 | if hasattr(view_func, '__name__'): 195 | self.view_name = '%s.%s' % (self.view_name, view_func.__name__) 196 | elif hasattr(view_func, '__class__'): 197 | self.view_name = '%s.%s' % ( 198 | self.view_name, view_func.__class__.__name__) 199 | 200 | if MAKE_TAGS_LIKE: 201 | self.view_name = self.view_name.replace('.', '_') 202 | self.view_name = 'view' + MAKE_TAGS_LIKE + self.view_name 203 | 204 | def process_response(self, request, response): 205 | if self.view_name and self.__class__.skip_view(self.view_name): 206 | return response 207 | 208 | self.scope.counter_codes.increment( 209 | f'{str(response.status_code // 100)}xx') 210 | self.scope.counter_codes.submit('http_codes') 211 | 212 | if settings.STATSD_TRACK_MIDDLEWARE: 213 | StatsdMiddleware.scope.timings.stop('process_response') 214 | if MAKE_TAGS_LIKE: 215 | method = f'method{MAKE_TAGS_LIKE}' 216 | method += request.method.lower().replace('.', '_') 217 | 218 | is_ajax_ = f'is_ajax_{MAKE_TAGS_LIKE}' 219 | is_ajax_ += str(is_ajax(request)).lower() 220 | 221 | if getattr(self, 'view_name', None): 222 | self.stop(method, self.view_name, is_ajax_) 223 | else: 224 | method = request.method.lower() 225 | if is_ajax(request): 226 | method += '_ajax' 227 | if getattr(self, 'view_name', None): 228 | self.stop(method, self.view_name) 229 | self.cleanup(request) 230 | return response 231 | 232 | def process_exception(self, request, exception): 233 | if settings.STATSD_TRACK_MIDDLEWARE: 234 | StatsdMiddleware.scope.timings.stop('process_exception') 235 | self.scope.counter_codes.increment('5xx') 236 | self.scope.counter_codes.submit('http_codes') 237 | 238 | def process_template_response(self, request, response): 239 | if settings.STATSD_TRACK_MIDDLEWARE: 240 | StatsdMiddleware.scope.timings.stop('process_template_response') 241 | return response 242 | 243 | def cleanup(self, request): 244 | self.scope.timings = None 245 | self.scope.counter = None 246 | self.view_name = None 247 | request.statsd = None 248 | 249 | 250 | class StatsdMiddlewareTimer: 251 | 252 | def __init__(self, get_response): 253 | self.get_response = get_response 254 | 255 | def __call__(self, request): 256 | self.process_request(request) 257 | return self.process_response(request, self.get_response(request)) 258 | 259 | def process_request(self, request): 260 | if settings.STATSD_TRACK_MIDDLEWARE: 261 | StatsdMiddleware.scope.timings.stop('process_request') 262 | 263 | def process_view(self, request, view_func, view_args, view_kwargs): 264 | if settings.STATSD_TRACK_MIDDLEWARE: 265 | StatsdMiddleware.scope.timings.stop('process_view') 266 | 267 | def process_response(self, request, response): 268 | if settings.STATSD_TRACK_MIDDLEWARE: 269 | StatsdMiddleware.scope.timings.start('process_response') 270 | return response 271 | 272 | def process_exception(self, request, exception): 273 | if settings.STATSD_TRACK_MIDDLEWARE: 274 | StatsdMiddleware.scope.timings.start('process_exception') 275 | 276 | def process_template_response(self, request, response): 277 | if settings.STATSD_TRACK_MIDDLEWARE: 278 | StatsdMiddleware.scope.timings.start('process_template_response') 279 | return response 280 | 281 | 282 | class DummyWith(object): 283 | 284 | def __enter__(self): 285 | pass 286 | 287 | def __exit__(self, type_, value, traceback): 288 | pass 289 | 290 | 291 | def start(key): 292 | if getattr(StatsdMiddleware.scope, 'timings', None): 293 | StatsdMiddleware.scope.timings.start(key) 294 | 295 | 296 | def stop(key): 297 | if getattr(StatsdMiddleware.scope, 'timings', None): 298 | return StatsdMiddleware.scope.timings.stop(key) 299 | 300 | 301 | def with_(key): 302 | if getattr(StatsdMiddleware.scope, 'timings', None): 303 | return StatsdMiddleware.scope.timings(key) 304 | else: 305 | return DummyWith() 306 | 307 | 308 | def incr(key, value=1): 309 | if getattr(StatsdMiddleware.scope, 'counter', None): 310 | StatsdMiddleware.scope.counter.increment(key, value) 311 | 312 | 313 | def decr(key, value=1): 314 | if getattr(StatsdMiddleware.scope, 'counter', None): 315 | StatsdMiddleware.scope.counter.decrement(key, value) 316 | 317 | 318 | def wrapper(prefix, f): 319 | @functools.wraps(f) 320 | def _wrapper(*args, **kwargs): 321 | with with_('%s.%s' % (prefix, f.__name__.lower())): 322 | return f(*args, **kwargs) 323 | 324 | return _wrapper 325 | 326 | 327 | def named_wrapper(name, f): 328 | @functools.wraps(f) 329 | def _wrapper(*args, **kwargs): 330 | with with_(name): 331 | return f(*args, **kwargs) 332 | 333 | return _wrapper 334 | 335 | 336 | def decorator(prefix): 337 | return lambda f: wrapper(prefix, f) 338 | -------------------------------------------------------------------------------- /django_statsd/redis.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import django_statsd 3 | 4 | try: 5 | import redis 6 | 7 | class StatsdRedis(redis.Redis): 8 | 9 | def execute_command(self, func_name, *args, **kwargs): 10 | with django_statsd.with_('redis.%s' % func_name.lower()): 11 | return origRedis.execute_command(self, func_name, *args, 12 | **kwargs) 13 | 14 | origRedis = None 15 | # NOTE issubclass is true if both are the same class 16 | if not issubclass(redis.Redis, StatsdRedis): 17 | origRedis = redis.Redis 18 | redis.Redis = StatsdRedis 19 | except ImportError: 20 | pass 21 | -------------------------------------------------------------------------------- /django_statsd/settings.py: -------------------------------------------------------------------------------- 1 | from django.core import exceptions 2 | from django.conf import settings 3 | 4 | 5 | def get_setting(key, default=None): 6 | try: 7 | value = getattr(settings, key, default) 8 | except exceptions.ImproperlyConfigured: 9 | value = default 10 | 11 | return value 12 | 13 | 14 | #: Enable tracking all requests using the middleware 15 | STATSD_TRACK_MIDDLEWARE = get_setting('STATSD_TRACK_MIDDLEWARE', False) 16 | 17 | #: Set the global statsd prefix if needed. Otherwise use the root 18 | STATSD_PREFIX = get_setting('STATSD_PREFIX') 19 | 20 | #: Enable warnings such as timers which are started but not finished. Defaults 21 | #: to DEBUG if not configured 22 | STATSD_DEBUG = get_setting('STATSD_DEBUG', get_setting('DEBUG')) 23 | 24 | #: Statsd disabled mode, avoids to send metrics to the real server. 25 | #: Useful for debugging purposes. 26 | STATSD_DISABLED = get_setting('STATSD_DISABLED', False) 27 | 28 | #: Enable creating tags as well as the bare version. This causes an ajax view 29 | #: to be stored both as the regular view name and as the ajax tag. Supported 30 | #: separators are _is_ and = 31 | STATSD_TAGS_LIKE = get_setting('STATSD_TAGS_LIKE') 32 | 33 | #: Statsd host, defaults to 127.0.0.1 34 | STATSD_HOST = get_setting('STATSD_HOST', '127.0.0.1') 35 | 36 | #: Statsd port, defaults to 8125 37 | STATSD_PORT = get_setting('STATSD_PORT', 8125) 38 | 39 | #: Statsd sample rate, lowering this decreases the (random) odds of actually 40 | #: submitting the data. Between 0 and 1 where 1 means always 41 | STATSD_SAMPLE_RATE = get_setting('STATSD_SAMPLE_RATE', 1.0) 42 | 43 | #: List of regexp to ignore views. 44 | STATSD_VIEWS_TO_SKIP = get_setting('STATSD_VIEWS_TO_SKIP', [ 45 | r'django.contrib.admin', 46 | ]) 47 | 48 | -------------------------------------------------------------------------------- /django_statsd/templates.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import django_statsd 3 | 4 | try: 5 | from coffin.template import loader 6 | if not hasattr(loader, 'statsd_patched'): 7 | loader.statsd_patched = True 8 | loader.render_to_string = django_statsd.named_wrapper( 9 | 'render_jinja', loader.render_to_string) 10 | 11 | except ImportError: 12 | pass 13 | 14 | try: 15 | from django.template import loader 16 | if not hasattr(loader, 'statsd_patched'): 17 | loader.statsd_patched = True 18 | loader.render_to_string = django_statsd.named_wrapper( 19 | 'render_django', loader.render_to_string) 20 | 21 | except ImportError: 22 | pass 23 | -------------------------------------------------------------------------------- /django_statsd/urls.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import django_statsd 3 | 4 | try: 5 | import httplib 6 | 7 | class StatsdHTTPConnection(httplib.HTTPConnection): 8 | 9 | def __init__(self, *args, **kwargs): 10 | origHTTPConnection.__init__(self, *args, **kwargs) 11 | 12 | def _get_host_name(self): 13 | hostname = self.host 14 | if self.port != 80: 15 | hostname += '-%d' % self.port 16 | 17 | hostname = hostname.replace('.', '-') 18 | return hostname 19 | 20 | def connect(self, *args, **kwargs): 21 | django_statsd.start('url.%s' % self._get_host_name()) 22 | return origHTTPConnection.connect(self, *args, **kwargs) 23 | 24 | def close(self, *args, **kwargs): 25 | if self.sock is not None: 26 | django_statsd.stop('url.%s' % self._get_host_name()) 27 | return origHTTPConnection.close(self, *args, **kwargs) 28 | 29 | def __del__(self, *args, **kwargs): 30 | if self.sock is not None: 31 | django_statsd.stop('url.%s' % self._get_host_name()) 32 | return origHTTPConnection.__del__(self, *args, **kwargs) 33 | 34 | origHTTPConnection = None 35 | # NOTE issubclass is true if both are the same class 36 | if not issubclass(httplib.HTTPConnection, StatsdHTTPConnection): 37 | origHTTPConnection = httplib.HTTPConnection 38 | httplib.HTTPConnection = StatsdHTTPConnection 39 | except ImportError: 40 | pass 41 | -------------------------------------------------------------------------------- /django_statsd/utils.py: -------------------------------------------------------------------------------- 1 | import statsd 2 | from . import settings 3 | 4 | 5 | def get_connection(host=None, port=None, sample_rate=None, disabled=None): 6 | if not host: 7 | host = settings.STATSD_HOST 8 | 9 | if not port: 10 | port = settings.STATSD_PORT 11 | 12 | if not sample_rate: 13 | sample_rate = settings.STATSD_SAMPLE_RATE 14 | 15 | if not disabled: 16 | disabled = settings.STATSD_DISABLED 17 | 18 | return statsd.Connection(host, port, sample_rate, disabled) 19 | 20 | 21 | def get_client(name, connection=None, class_=statsd.Client): 22 | if not connection: 23 | connection = get_connection() 24 | 25 | return class_(name, connection) 26 | 27 | 28 | def get_timer(name, connection=None): 29 | return get_client(name, connection, statsd.Timer) 30 | 31 | 32 | def get_counter(name, connection=None): 33 | return get_client(name, connection, statsd.Counter) 34 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/whatever.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/whatever.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/whatever" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/whatever" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-statsd/d351046ec820c472e59f0adec6c23ee70ce64620/docs/_static/.keep -------------------------------------------------------------------------------- /docs/_theme/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2012 Rick van Hattem. 4 | 5 | 6 | Original Projects: 7 | 8 | Copyright (c) 2010 Kenneth Reitz. 9 | Copyright (c) 2010 by Armin Ronacher. 10 | 11 | 12 | Some rights reserved. 13 | 14 | Redistribution and use in source and binary forms of the theme, with or 15 | without modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | * Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | * Redistributions in binary form must reproduce the above 22 | copyright notice, this list of conditions and the following 23 | disclaimer in the documentation and/or other materials provided 24 | with the distribution. 25 | 26 | * The names of the contributors may not be used to endorse or 27 | promote products derived from this software without specific 28 | prior written permission. 29 | 30 | We kindly ask you to only use these themes in an unmodified manner just 31 | for Flask and Flask-related products, not for unrelated projects. If you 32 | like the visual style and want to use it for your own projects, please 33 | consider making some larger changes to the themes (such as changing 34 | font faces, sizes, colors or margins). 35 | 36 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 40 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 41 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 42 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 43 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 44 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 45 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 46 | POSSIBILITY OF SUCH DAMAGE. 47 | -------------------------------------------------------------------------------- /docs/_theme/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/_theme/wolph/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 16 | {%- endblock %} 17 | -------------------------------------------------------------------------------- /docs/_theme/wolph/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; 18 | font-size: 17px; 19 | background-color: white; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | width: {{ page_width }}; 27 | margin: 30px auto 0 auto; 28 | } 29 | 30 | div.documentwrapper { 31 | float: left; 32 | width: 100%; 33 | } 34 | 35 | div.bodywrapper { 36 | margin: 0 0 0 {{ sidebar_width }}; 37 | } 38 | 39 | div.sphinxsidebar { 40 | width: {{ sidebar_width }}; 41 | } 42 | 43 | hr { 44 | border: 1px solid #B1B4B6; 45 | } 46 | 47 | div.body { 48 | background-color: #ffffff; 49 | color: #3E4349; 50 | padding: 0 30px 0 30px; 51 | } 52 | 53 | img.floatingflask { 54 | padding: 0 0 10px 10px; 55 | float: right; 56 | } 57 | 58 | div.footer { 59 | width: {{ page_width }}; 60 | margin: 20px auto 30px auto; 61 | font-size: 14px; 62 | color: #888; 63 | text-align: right; 64 | } 65 | 66 | div.footer a { 67 | color: #888; 68 | } 69 | 70 | div.related { 71 | display: none; 72 | } 73 | 74 | div.sphinxsidebar a { 75 | color: #444; 76 | text-decoration: none; 77 | border-bottom: 1px dotted #999; 78 | } 79 | 80 | div.sphinxsidebar a:hover { 81 | border-bottom: 1px solid #999; 82 | } 83 | 84 | div.sphinxsidebar { 85 | font-size: 14px; 86 | line-height: 1.5; 87 | } 88 | 89 | div.sphinxsidebarwrapper { 90 | padding: 0px 10px; 91 | } 92 | 93 | div.sphinxsidebarwrapper p.logo { 94 | padding: 0 0 20px 0; 95 | margin: 0; 96 | text-align: center; 97 | } 98 | 99 | div.sphinxsidebar h3, 100 | div.sphinxsidebar h4 { 101 | font-family: 'Garamond', 'Georgia', serif; 102 | color: #555; 103 | font-size: 24px; 104 | font-weight: normal; 105 | margin: 0 0 5px 0; 106 | padding: 0; 107 | } 108 | 109 | div.sphinxsidebar h4 { 110 | font-size: 20px; 111 | } 112 | 113 | div.sphinxsidebar h3 a { 114 | color: #444; 115 | } 116 | 117 | div.sphinxsidebar p.logo a, 118 | div.sphinxsidebar h3 a, 119 | div.sphinxsidebar p.logo a:hover, 120 | div.sphinxsidebar h3 a:hover { 121 | border: none; 122 | } 123 | 124 | div.sphinxsidebar p { 125 | color: #555; 126 | margin: 10px 0; 127 | } 128 | 129 | div.sphinxsidebar ul { 130 | margin: 10px 0; 131 | padding: 0; 132 | color: #000; 133 | } 134 | 135 | div.sphinxsidebar input[type="text"] { 136 | width: 160px!important; 137 | } 138 | div.sphinxsidebar input { 139 | border: 1px solid #ccc; 140 | font-family: 'Georgia', serif; 141 | font-size: 1em; 142 | } 143 | 144 | /* -- body styles ----------------------------------------------------------- */ 145 | 146 | a { 147 | color: #004B6B; 148 | text-decoration: underline; 149 | } 150 | 151 | a:hover { 152 | color: #6D4100; 153 | text-decoration: underline; 154 | } 155 | 156 | div.body h1, 157 | div.body h2, 158 | div.body h3, 159 | div.body h4, 160 | div.body h5, 161 | div.body h6 { 162 | font-family: 'Garamond', 'Georgia', serif; 163 | font-weight: normal; 164 | margin: 30px 0px 10px 0px; 165 | padding: 0; 166 | } 167 | 168 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 169 | div.body h2 { font-size: 180%; } 170 | div.body h3 { font-size: 150%; } 171 | div.body h4 { font-size: 130%; } 172 | div.body h5 { font-size: 100%; } 173 | div.body h6 { font-size: 100%; } 174 | 175 | a.headerlink { 176 | color: #ddd; 177 | padding: 0 4px; 178 | text-decoration: none; 179 | } 180 | 181 | a.headerlink:hover { 182 | color: #444; 183 | background: #eaeaea; 184 | } 185 | 186 | div.body p, div.body dd, div.body li { 187 | line-height: 1.4em; 188 | } 189 | 190 | div.admonition { 191 | background: #fafafa; 192 | margin: 20px -30px; 193 | padding: 10px 30px; 194 | border-top: 1px solid #ccc; 195 | border-bottom: 1px solid #ccc; 196 | } 197 | 198 | div.admonition tt.xref, div.admonition a tt { 199 | border-bottom: 1px solid #fafafa; 200 | } 201 | 202 | dd div.admonition { 203 | margin-left: -60px; 204 | padding-left: 60px; 205 | } 206 | 207 | div.admonition p.admonition-title { 208 | font-family: 'Garamond', 'Georgia', serif; 209 | font-weight: normal; 210 | font-size: 24px; 211 | margin: 0 0 10px 0; 212 | padding: 0; 213 | line-height: 1; 214 | } 215 | 216 | div.admonition p.last { 217 | margin-bottom: 0; 218 | } 219 | 220 | div.highlight { 221 | background-color: white; 222 | } 223 | 224 | dt:target, .highlight { 225 | background: #FAF3E8; 226 | } 227 | 228 | div.note { 229 | background-color: #eee; 230 | border: 1px solid #ccc; 231 | } 232 | 233 | div.seealso { 234 | background-color: #ffc; 235 | border: 1px solid #ff6; 236 | } 237 | 238 | div.topic { 239 | background-color: #eee; 240 | } 241 | 242 | p.admonition-title { 243 | display: inline; 244 | } 245 | 246 | p.admonition-title:after { 247 | content: ":"; 248 | } 249 | 250 | pre, tt { 251 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 252 | font-size: 0.9em; 253 | } 254 | 255 | img.screenshot { 256 | } 257 | 258 | tt.descname, tt.descclassname { 259 | font-size: 0.95em; 260 | } 261 | 262 | tt.descname { 263 | padding-right: 0.08em; 264 | } 265 | 266 | img.screenshot { 267 | -moz-box-shadow: 2px 2px 4px #eee; 268 | -webkit-box-shadow: 2px 2px 4px #eee; 269 | box-shadow: 2px 2px 4px #eee; 270 | } 271 | 272 | table.docutils { 273 | border: 1px solid #888; 274 | -moz-box-shadow: 2px 2px 4px #eee; 275 | -webkit-box-shadow: 2px 2px 4px #eee; 276 | box-shadow: 2px 2px 4px #eee; 277 | } 278 | 279 | table.docutils td, table.docutils th { 280 | border: 1px solid #888; 281 | padding: 0.25em 0.7em; 282 | } 283 | 284 | table.field-list, table.footnote { 285 | border: none; 286 | -moz-box-shadow: none; 287 | -webkit-box-shadow: none; 288 | box-shadow: none; 289 | } 290 | 291 | table.footnote { 292 | margin: 15px 0; 293 | width: 100%; 294 | border: 1px solid #eee; 295 | background: #fdfdfd; 296 | font-size: 0.9em; 297 | } 298 | 299 | table.footnote + table.footnote { 300 | margin-top: -15px; 301 | border-top: none; 302 | } 303 | 304 | table.field-list th { 305 | padding: 0 0.8em 0 0; 306 | } 307 | 308 | table.field-list td { 309 | padding: 0; 310 | } 311 | 312 | table.footnote td.label { 313 | width: 0px; 314 | padding: 0.3em 0 0.3em 0.5em; 315 | } 316 | 317 | table.footnote td { 318 | padding: 0.3em 0.5em; 319 | } 320 | 321 | dl { 322 | margin: 0; 323 | padding: 0; 324 | } 325 | 326 | dl dd { 327 | margin-left: 30px; 328 | } 329 | 330 | blockquote { 331 | margin: 0 0 0 30px; 332 | padding: 0; 333 | } 334 | 335 | ul, ol { 336 | margin: 10px 0 10px 30px; 337 | padding: 0; 338 | } 339 | 340 | pre { 341 | background: #eee; 342 | padding: 7px 30px; 343 | margin: 15px -30px; 344 | line-height: 1.3em; 345 | } 346 | 347 | dl pre, blockquote pre, li pre { 348 | margin-left: -60px; 349 | padding-left: 60px; 350 | } 351 | 352 | dl dl pre { 353 | margin-left: -90px; 354 | padding-left: 90px; 355 | } 356 | 357 | tt { 358 | background-color: #ecf0f3; 359 | color: #222; 360 | /* padding: 1px 2px; */ 361 | } 362 | 363 | tt.xref, a tt { 364 | background-color: #FBFBFB; 365 | border-bottom: 1px solid white; 366 | } 367 | 368 | a.reference { 369 | text-decoration: none; 370 | border-bottom: 1px dotted #004B6B; 371 | } 372 | 373 | a.reference:hover { 374 | border-bottom: 1px solid #6D4100; 375 | } 376 | 377 | a.footnote-reference { 378 | text-decoration: none; 379 | font-size: 0.7em; 380 | vertical-align: top; 381 | border-bottom: 1px dotted #004B6B; 382 | } 383 | 384 | a.footnote-reference:hover { 385 | border-bottom: 1px solid #6D4100; 386 | } 387 | 388 | a:hover tt { 389 | background: #EEE; 390 | } 391 | 392 | 393 | /* scrollbars */ 394 | 395 | ::-webkit-scrollbar { 396 | width: 6px; 397 | height: 6px; 398 | } 399 | 400 | ::-webkit-scrollbar-button:start:decrement, 401 | ::-webkit-scrollbar-button:end:increment { 402 | display: block; 403 | height: 10px; 404 | } 405 | 406 | ::-webkit-scrollbar-button:vertical:increment { 407 | background-color: #fff; 408 | } 409 | 410 | ::-webkit-scrollbar-track-piece { 411 | background-color: #eee; 412 | -webkit-border-radius: 3px; 413 | } 414 | 415 | ::-webkit-scrollbar-thumb:vertical { 416 | height: 50px; 417 | background-color: #ccc; 418 | -webkit-border-radius: 3px; 419 | } 420 | 421 | ::-webkit-scrollbar-thumb:horizontal { 422 | width: 50px; 423 | background-color: #ccc; 424 | -webkit-border-radius: 3px; 425 | } 426 | 427 | /* misc. */ 428 | 429 | .revsys-inline { 430 | display: none!important; 431 | } 432 | -------------------------------------------------------------------------------- /docs/_theme/wolph/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/_theme/wolph/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is execfile()d with the current directory set to its containing dir. 4 | # 5 | # Note that not all possible configuration values are present in this 6 | # autogenerated file. 7 | # 8 | # All configuration values have a default; values that are commented out 9 | # serve to show the default. 10 | 11 | import os 12 | import sys 13 | import datetime 14 | 15 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | # sys.path.insert(0, os.path.join(os.path.abspath(os.path.pardir), 'tests')) 21 | sys.path.insert(0, os.path.abspath(os.path.pardir)) 22 | from django_statsd import __about__ 23 | 24 | import django 25 | django.setup() 26 | 27 | # -- General configuration ----------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be extensions 33 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.doctest', 37 | 'sphinx.ext.intersphinx', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.viewcode', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix of source filenames. 46 | source_suffix = '.rst' 47 | 48 | # The encoding of source files. 49 | # source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = __about__.__package_name__.replace('-', ' ').capitalize() 56 | copyright = u'%s, %s' % ( 57 | datetime.date.today().year, 58 | __about__.__author__, 59 | ) 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = __about__.__version__ 67 | # The full version, including alpha/beta/rc tags. 68 | release = __about__.__version__ 69 | 70 | suppress_warnings = [ 71 | 'image.nonlocal_uri', 72 | ] 73 | 74 | needs_sphinx = '1.4' 75 | 76 | # The language for content autogenerated by Sphinx. Refer to documentation 77 | # for a list of supported languages. 78 | # language = None 79 | 80 | # There are two options for replacing |today|: either, you set today to some 81 | # non-false value, then it is used: 82 | # today = '' 83 | # Else, today_fmt is used as the format for a strftime call. 84 | # today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | exclude_patterns = ['_build'] 89 | 90 | # The reST default role (used for this markup: `text`) to use for all documents. 91 | # default_role = None 92 | 93 | # If true, '()' will be appended to :func: etc. cross-reference text. 94 | # add_function_parentheses = True 95 | 96 | # If true, the current module name will be prepended to all description 97 | # unit titles (such as .. function::). 98 | # add_module_names = True 99 | 100 | # If true, sectionauthor and moduleauthor directives will be shown in the 101 | # output. They are ignored by default. 102 | # show_authors = False 103 | 104 | # The name of the Pygments (syntax highlighting) style to use. 105 | pygments_style = 'sphinx' 106 | 107 | # A list of ignored prefixes for module index sorting. 108 | # modindex_common_prefix = [] 109 | 110 | 111 | # -- Options for HTML output --------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'wolph' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | # html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | html_theme_path = ['_theme'] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | # html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as html_title. 130 | # html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the top 133 | # of the sidebar. 134 | # html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon of the 137 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 138 | # pixels large. 139 | # html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) here, 142 | # relative to this directory. They are copied after the builtin static files, 143 | # so a file named "default.css" will overwrite the builtin "default.css". 144 | html_static_path = ['_static'] 145 | 146 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 147 | # using the given strftime format. 148 | # html_last_updated_fmt = '%b %d, %Y' 149 | 150 | # If true, SmartyPants will be used to convert quotes and dashes to 151 | # typographically correct entities. 152 | # html_use_smartypants = True 153 | 154 | # Custom sidebar templates, maps document names to template names. 155 | # html_sidebars = {} 156 | 157 | # Additional templates that should be rendered to pages, maps page names to 158 | # template names. 159 | # html_additional_pages = {} 160 | 161 | # If false, no module index is generated. 162 | # html_domain_indices = True 163 | 164 | # If false, no index is generated. 165 | # html_use_index = True 166 | 167 | # If true, the index is split into individual pages for each letter. 168 | # html_split_index = False 169 | 170 | # If true, links to the reST sources are added to the pages. 171 | # html_show_sourcelink = True 172 | 173 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 174 | # html_show_sphinx = True 175 | 176 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 177 | # html_show_copyright = True 178 | 179 | # If true, an OpenSearch description file will be output, and all pages will 180 | # contain a tag referring to it. The value of this option must be the 181 | # base URL from which the finished HTML is served. 182 | # html_use_opensearch = '' 183 | 184 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 185 | # html_file_suffix = None 186 | 187 | # Output file base name for HTML help builder. 188 | htmlhelp_basename = __about__.__package_name__ + '-doc' 189 | 190 | 191 | # -- Options for LaTeX output -------------------------------------------- 192 | 193 | latex_elements = { 194 | # The paper size ('letterpaper' or 'a4paper'). 195 | #'papersize': 'letterpaper', 196 | 197 | # The font size ('10pt', '11pt' or '12pt'). 198 | #'pointsize': '10pt', 199 | 200 | # Additional stuff for the LaTeX preamble. 201 | #'preamble': '', 202 | } 203 | 204 | # Grouping the document tree into LaTeX files. List of tuples 205 | # (source start file, target name, title, author, documentclass [howto/manual]). 206 | latex_documents = [( 207 | 'index', 208 | '%s.tex' % __about__.__package_name__, 209 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 210 | __about__.__author__, 211 | 'manual', 212 | )] 213 | 214 | # The name of an image file (relative to this directory) to place at the top of 215 | # the title page. 216 | # latex_logo = None 217 | 218 | # For "manual" documents, if this is true, then toplevel headings are parts, 219 | # not chapters. 220 | # latex_use_parts = False 221 | 222 | # If true, show page references after internal links. 223 | # latex_show_pagerefs = False 224 | 225 | # If true, show URL addresses after external links. 226 | # latex_show_urls = False 227 | 228 | # Documents to append as an appendix to all manuals. 229 | # latex_appendices = [] 230 | 231 | # If false, no module index is generated. 232 | # latex_domain_indices = True 233 | 234 | 235 | # -- Options for manual page output -------------------------------------- 236 | 237 | # One entry per manual page. List of tuples 238 | # (source start file, name, description, authors, manual section). 239 | man_pages = [( 240 | 'index', 241 | __about__.__package_name__, 242 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 243 | [__about__.__author__], 244 | 1, 245 | )] 246 | 247 | # If true, show URL addresses after external links. 248 | # man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ------------------------------------------ 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [( 257 | 'index', 258 | __about__.__package_name__, 259 | u'%s Documentation' % __about__.__package_name__.replace('-', ' ').capitalize(), 260 | __about__.__author__, 261 | __about__.__package_name__, 262 | __about__.__description__, 263 | 'Miscellaneous', 264 | )] 265 | 266 | # Documents to append as an appendix to all manuals. 267 | # texinfo_appendices = [] 268 | 269 | # If false, no module index is generated. 270 | # texinfo_domain_indices = True 271 | 272 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 273 | # texinfo_show_urls = 'footnote' 274 | 275 | 276 | # -- Options for Epub output --------------------------------------------- 277 | 278 | # Bibliographic Dublin Core info. 279 | epub_title = __about__.__package_name__.replace('-', ' ').capitalize() 280 | epub_author = __about__.__author__ 281 | epub_publisher = __about__.__author__ 282 | epub_copyright = copyright 283 | 284 | # The language of the text. It defaults to the language option 285 | # or en if the language is not set. 286 | # epub_language = '' 287 | 288 | # The scheme of the identifier. Typical schemes are ISBN or URL. 289 | # epub_scheme = '' 290 | 291 | # The unique identifier of the text. This can be a ISBN number 292 | # or the project homepage. 293 | # epub_identifier = '' 294 | 295 | # A unique identification for the text. 296 | # epub_uid = '' 297 | 298 | # A tuple containing the cover image and cover page html template filenames. 299 | # epub_cover = () 300 | 301 | # HTML files that should be inserted before the pages created by sphinx. 302 | # The format is a list of tuples containing the path and title. 303 | # epub_pre_files = [] 304 | 305 | # HTML files shat should be inserted after the pages created by sphinx. 306 | # The format is a list of tuples containing the path and title. 307 | # epub_post_files = [] 308 | 309 | # A list of files that should not be packed into the epub file. 310 | # epub_exclude_files = [] 311 | 312 | # The depth of the table of contents in toc.ncx. 313 | # epub_tocdepth = 3 314 | 315 | # Allow duplicate toc entries. 316 | # epub_tocdup = True 317 | 318 | 319 | # Example configuration for intersphinx: refer to the Python standard library. 320 | intersphinx_mapping = { 321 | 'python': ('http://docs.python.org/2', None), 322 | 'pythonutils': ('http://python-utils.readthedocs.io/en/latest/', None), 323 | 'numpy': ('http://docs.scipy.org/doc/numpy/', None), 324 | 'scipy': ('http://docs.scipy.org/doc/scipy/reference/', None), 325 | 'matplotlib': ('http://matplotlib.org/stable', None), 326 | 'django': ('http://docs.djangoproject.com/en/dev/', 327 | 'http://docs.djangoproject.com/en/dev/_objects/'), 328 | } 329 | 330 | -------------------------------------------------------------------------------- /docs/django_statsd.rst: -------------------------------------------------------------------------------- 1 | django_statsd Package 2 | ===================== 3 | 4 | :mod:`django_statsd` Package 5 | ---------------------------- 6 | 7 | .. automodule:: django_statsd.__init__ 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`settings` Module 13 | ---------------------- 14 | 15 | .. automodule:: django_statsd.settings 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`celery` Module 21 | -------------------- 22 | 23 | .. automodule:: django_statsd.celery 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`database` Module 29 | ---------------------- 30 | 31 | .. automodule:: django_statsd.database 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`json` Module 37 | ------------------ 38 | 39 | .. automodule:: django_statsd.json 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`middleware` Module 45 | ------------------------ 46 | 47 | .. automodule:: django_statsd.middleware 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`redis` Module 53 | ------------------- 54 | 55 | .. automodule:: django_statsd.redis 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`templates` Module 61 | ----------------------- 62 | 63 | .. automodule:: django_statsd.templates 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`urls` Module 69 | ------------------ 70 | 71 | .. automodule:: django_statsd.urls 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | :mod:`utils` Module 77 | ------------------- 78 | 79 | .. automodule:: django_statsd.utils 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Django Statsd Client 2 | ========================================= 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | usage 10 | 11 | django_statsd 12 | 13 | Indices and tables 14 | ================== 15 | 16 | * :ref:`genindex` 17 | * :ref:`search` 18 | 19 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\whatever.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\whatever.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[docs] 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include :: ../README.rst 3 | 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = 3 | django_statsd/*.py 4 | tests/*.py 5 | 6 | addopts = 7 | --cov django_statsd 8 | --cov-report term-missing 9 | tests 10 | 11 | pep8ignore = 12 | *.py W391 13 | docs/*.py ALL 14 | 15 | flakes-ignore = 16 | docs/*.py ALL 17 | 18 | DJANGO_SETTINGS_MODULE=tests.settings 19 | 20 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.rst 3 | 4 | [bdist_wheel] 5 | universal = 1 6 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import setuptools 4 | from setuptools.command.test import test as TestCommand 5 | 6 | # To prevent importing about and thereby breaking the coverage info we use this 7 | # exec hack 8 | about = {} 9 | with open('django_statsd/__about__.py') as fp: 10 | exec(fp.read(), about) 11 | 12 | 13 | if os.path.isfile('README.rst'): 14 | long_description = open('README.rst').read() 15 | else: 16 | long_description = ('See http://pypi.python.org/pypi/' + 17 | about['__package_name__']) 18 | 19 | 20 | class PyTest(TestCommand): 21 | def finalize_options(self): 22 | TestCommand.finalize_options(self) 23 | self.test_args = [] 24 | self.test_suite = True 25 | 26 | def run_tests(self): 27 | # import here, cause outside the eggs aren't loaded 28 | import pytest 29 | errno = pytest.main(self.test_args) 30 | sys.exit(errno) 31 | 32 | 33 | if __name__ == '__main__': 34 | setuptools.setup( 35 | name=about['__package_name__'], 36 | version=about['__version__'], 37 | author=about['__author__'], 38 | author_email=about['__author_email__'], 39 | description=about['__description__'], 40 | url=about['__url__'], 41 | license='BSD', 42 | packages=setuptools.find_packages(exclude=['tests']), 43 | install_requires=[ 44 | 'python-statsd>=1.7.2', 45 | ], 46 | extras_require={ 47 | 'docs': [ 48 | 'django>=1.11', 49 | 'mock', 50 | 'sphinx>=1.7.2', 51 | ], 52 | 'tests': [ 53 | 'mock', 54 | 'pytest', 55 | 'pytest-cache', 56 | 'pytest-cov', 57 | 'pytest-django', 58 | 'pytest-flakes', 59 | 'pytest-pep8', 60 | ], 61 | }, 62 | long_description=long_description, 63 | cmdclass={'test': PyTest}, 64 | classifiers=[ 65 | 'Development Status :: 5 - Production/Stable', 66 | 'Environment :: Web Environment', 67 | 'Framework :: Django', 68 | 'Framework :: Django :: 2.2', 69 | 'Framework :: Django :: 3.0', 70 | 'Intended Audience :: Developers', 71 | 'License :: OSI Approved :: BSD License', 72 | 'Operating System :: OS Independent', 73 | 'Programming Language :: Python', 74 | 'Programming Language :: Python :: 2', 75 | 'Programming Language :: Python :: 2.7', 76 | 'Programming Language :: Python :: 3', 77 | 'Programming Language :: Python :: 3.5', 78 | 'Programming Language :: Python :: 3.6', 79 | 'Programming Language :: Python :: 3.7', 80 | 'Programming Language :: Python :: 3.8', 81 | 'Programming Language :: Python :: 3 :: Only', 82 | 'Topic :: Internet :: WWW/HTTP', 83 | ], 84 | ) 85 | 86 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-statsd/d351046ec820c472e59f0adec6c23ee70ce64620/tests/__init__.py -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | -e.[tests] 2 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for tests project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', 15 | 'NAME': 'database.sqlite3', 16 | } 17 | } 18 | 19 | # Hosts/domain names that are valid for this site; required if DEBUG is False 20 | # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts 21 | ALLOWED_HOSTS = [] 22 | 23 | # Local time zone for this installation. Choices can be found here: 24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 25 | # although not all choices may be available on all operating systems. 26 | # In a Windows environment this must be set to your system time zone. 27 | TIME_ZONE = 'America/Chicago' 28 | 29 | # Language code for this installation. All choices can be found here: 30 | # http://www.i18nguy.com/unicode/language-identifiers.html 31 | LANGUAGE_CODE = 'en-us' 32 | 33 | SITE_ID = 1 34 | 35 | # If you set this to False, Django will make some optimizations so as not 36 | # to load the internationalization machinery. 37 | USE_I18N = True 38 | 39 | # If you set this to False, Django will not format dates, numbers and 40 | # calendars according to the current locale. 41 | USE_L10N = True 42 | 43 | # If you set this to False, Django will not use timezone-aware datetimes. 44 | USE_TZ = True 45 | 46 | # Absolute filesystem path to the directory that will hold user-uploaded files. 47 | # Example: "/var/www/example.com/media/" 48 | MEDIA_ROOT = '' 49 | 50 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 51 | # trailing slash. 52 | # Examples: "http://example.com/media/", "http://media.example.com/" 53 | MEDIA_URL = '' 54 | 55 | # Absolute path to the directory static files should be collected to. 56 | # Don't put anything in this directory yourself; store your static files 57 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 58 | # Example: "/var/www/example.com/static/" 59 | STATIC_ROOT = '' 60 | 61 | # URL prefix for static files. 62 | # Example: "http://example.com/static/", "http://static.example.com/" 63 | STATIC_URL = '/static/' 64 | 65 | # Additional locations of static files 66 | STATICFILES_DIRS = ( 67 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 68 | # Always use forward slashes, even on Windows. 69 | # Don't forget to use absolute paths, not relative paths. 70 | ) 71 | 72 | # List of finder classes that know how to find static files in 73 | # various locations. 74 | STATICFILES_FINDERS = ( 75 | 'django.contrib.staticfiles.finders.FileSystemFinder', 76 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 77 | ) 78 | 79 | # Make this unique, and don't share it with anybody. 80 | SECRET_KEY = '4f79$e&*4cfhk&k%uo*z0cjx&nvvayk-6wxkgf-apni5=q@!mz' 81 | 82 | # List of callables that know how to import templates from various sources. 83 | TEMPLATES = [ 84 | { 85 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 86 | 'DIRS': [], 87 | 'APP_DIRS': True, 88 | 'OPTIONS': { 89 | 'context_processors': [ 90 | 'django.template.context_processors.debug', 91 | 'django.template.context_processors.request', 92 | 'django.contrib.auth.context_processors.auth', 93 | 'django.contrib.messages.context_processors.messages', 94 | ], 95 | }, 96 | }, 97 | ] 98 | 99 | 100 | MIDDLEWARE = ( 101 | 'django_statsd.middleware.StatsdMiddleware', 102 | 'django.middleware.common.CommonMiddleware', 103 | 'django.contrib.sessions.middleware.SessionMiddleware', 104 | 'django.middleware.csrf.CsrfViewMiddleware', 105 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 106 | 'django.contrib.messages.middleware.MessageMiddleware', 107 | # Uncomment the next line for simple clickjacking protection: 108 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 109 | 'django_statsd.middleware.StatsdMiddlewareTimer', 110 | ) 111 | 112 | ROOT_URLCONF = 'tests.urls' 113 | 114 | # Python dotted path to the WSGI application used by Django's runserver. 115 | WSGI_APPLICATION = 'tests.wsgi.application' 116 | 117 | TEMPLATE_DIRS = ( 118 | # Put strings here, like "/home/html/django_templates" or 119 | # "C:/www/django/templates". Always use forward slashes, even on Windows. 120 | # Don't forget to use absolute paths, not relative paths. 121 | ) 122 | 123 | INSTALLED_APPS = ( 124 | 'django.contrib.auth', 125 | 'django.contrib.contenttypes', 126 | 'django.contrib.sessions', 127 | 'django.contrib.sites', 128 | 'django.contrib.messages', 129 | 'django.contrib.staticfiles', 130 | # Uncomment the next line to enable the admin: 131 | 'django.contrib.admin', 132 | # Uncomment the next line to enable admin documentation: 133 | # 'django.contrib.admindocs', 134 | 'django_statsd', 135 | 'tests.test_app', 136 | ) 137 | 138 | # A sample logging configuration. The only tangible logging 139 | # performed by this configuration is to send an email to 140 | # the site admins on every HTTP 500 error when DEBUG=False. 141 | # See http://docs.djangoproject.com/en/dev/topics/logging for 142 | # more details on how to customize your logging configuration. 143 | LOGGING = { 144 | 'version': 1, 145 | 'disable_existing_loggers': False, 146 | 'filters': { 147 | 'require_debug_false': { 148 | '()': 'django.utils.log.RequireDebugFalse' 149 | } 150 | }, 151 | 'handlers': { 152 | 'mail_admins': { 153 | 'level': 'ERROR', 154 | 'filters': ['require_debug_false'], 155 | 'class': 'django.utils.log.AdminEmailHandler' 156 | } 157 | }, 158 | 'loggers': { 159 | 'django.request': { 160 | 'handlers': ['mail_admins'], 161 | 'level': 'ERROR', 162 | 'propagate': True, 163 | }, 164 | } 165 | } 166 | 167 | STATSD_HOST = '127.0.0.1' 168 | STATSD_PORT = '8125' 169 | STATSD_TRACK_MIDDLEWARE = True 170 | STATSD_PREFIX = 'some_key_prefix' 171 | -------------------------------------------------------------------------------- /tests/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-statsd/d351046ec820c472e59f0adec6c23ee70ce64620/tests/test_app/__init__.py -------------------------------------------------------------------------------- /tests/test_app/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wolph/django-statsd/d351046ec820c472e59f0adec6c23ee70ce64620/tests/test_app/models.py -------------------------------------------------------------------------------- /tests/test_app/urls.py: -------------------------------------------------------------------------------- 1 | from django import urls 2 | from . import views 3 | 4 | urlpatterns = [ 5 | urls.path(r'', views.index, name='index'), 6 | ] 7 | -------------------------------------------------------------------------------- /tests/test_app/views.py: -------------------------------------------------------------------------------- 1 | from django import http 2 | import time 3 | 4 | 5 | def index(request, delay=None): 6 | if delay: 7 | time.sleep(float(delay)) 8 | 9 | return http.HttpResponse('Index page') 10 | -------------------------------------------------------------------------------- /tests/test_prefix.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from unittest import TestCase 3 | import mock 4 | from django_statsd import middleware 5 | 6 | 7 | class TestPrefix(TestCase): 8 | 9 | @mock.patch('statsd.Client') 10 | def test_prefix(self, mock_client): 11 | from django import test 12 | 13 | def get_keys(): 14 | return set(sum( 15 | [list(x[0][1]) for x in mock_client._send.call_args_list], 16 | [] 17 | )) 18 | 19 | middleware.StatsdMiddleware.start() 20 | middleware.StatsdMiddleware.stop() 21 | 22 | assert get_keys() >= set(( 23 | 'some_key_prefix.view.hit', 24 | 'some_key_prefix.view.site.hit', 25 | 'some_key_prefix.view.total', 26 | )) 27 | 28 | test.Client().get('/test_app/') 29 | assert get_keys() >= set([ 30 | 'some_key_prefix.view.get.tests.test_app.views.index.hit', 31 | 'some_key_prefix.view.get.tests.test_app.views.index.' 32 | 'process_request', 33 | 'some_key_prefix.view.get.tests.test_app.views.index.' 34 | 'process_response', 35 | 'some_key_prefix.view.get.tests.test_app.views.index.' 36 | 'process_view', 37 | 'some_key_prefix.view.get.tests.test_app.views.index.total', 38 | 'some_key_prefix.view.hit', 39 | 'some_key_prefix.view.site.hit', 40 | 'some_key_prefix.view.total', 41 | ]) 42 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django import urls 2 | 3 | urlpatterns = [ 4 | urls.path('test_app/', urls.include('tests.test_app.urls')), 5 | ] 6 | -------------------------------------------------------------------------------- /tests/views.py: -------------------------------------------------------------------------------- 1 | from django import http 2 | import time 3 | 4 | 5 | def index(request, delay=None): 6 | delay = float(request.GET.get('delay', 0)) 7 | if delay: 8 | time.sleep(float(delay)) 9 | 10 | return http.HttpResponse('Index page') 11 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py3{8,9,10,11,12,13,14}-django42, 4 | py3{10,11,12,13,14}-django50, 5 | flake8, 6 | docs 7 | 8 | skip_missing_interpreters = True 9 | usedevelop = True 10 | 11 | [gh] 12 | python = 13 | 3.12 = py312-django{42,50} 14 | 3.11 = py311-django{42,50} 15 | 3.10 = py310-django{42,50} 16 | 3.9 = py39-django42 17 | 3.8 = py38-django42 18 | 19 | [testenv] 20 | deps = 21 | setuptools 22 | django42: Django>=4.2,<4.3 # Added currently supported versions 23 | django50: Django>=5.0,<5.1 # Added currently supported versions 24 | -r{toxinidir}/tests/requirements.txt 25 | 26 | envlist = 27 | py3{8,9,10,11,12,13,14}-django{4.2,5.0}, 28 | 29 | commands = 30 | python setup.py test 31 | 32 | [testenv:flake8] 33 | deps = flake8 34 | commands = flake8 --ignore=W391 django_statsd {posargs} 35 | 36 | [testenv:docs] 37 | allowlist_externals = 38 | rm 39 | cd 40 | mkdir 41 | commands = 42 | rm -f docs/modules.rst 43 | mkdir -p docs/_static 44 | sphinx-apidoc -o docs/ django_statsd 45 | rm -f docs/modules.rst 46 | sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html {posargs} 47 | deps = -r{toxinidir}/docs/requirements.txt 48 | --------------------------------------------------------------------------------