Skip to content

Commit

Permalink
Merge branch '23-management-command' into merge-0.5
Browse files Browse the repository at this point in the history
Conflicts:
	watchman/checks.py
  • Loading branch information
mwarkentin committed Jan 25, 2015
2 parents e31c76a + 6d41566 commit 1453b48
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 19 deletions.
17 changes: 17 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
History
-------
0.5.0 (2015-0?-??)
++++++++++++++++++

* Add `watchman` management command
* Exit code of `0` if all checks pass, `1` otherwise
* Print json stacktrace to stdout if check fails
* Handles `--verbosity` option to print all status checks
* `-c`, `--checks`, `-s`, `--skips` options take comma-separated list of python paths to run / skip
* Improve identifiability of emails sent from a django-watchman endpoint
* From: watchman@example.com
* Subject: django-watchman email check
* Body: This is an automated test of the email system.
* Add `X-DJANGO-WATCHMAN: True` custom header
* Remove `email_status` from default checks
* Refactor `utils.get_checks` to allow reuse in management command
* `get_checks` now performs the optional check inclusion / skipping
* `status` refactored to pull `check_list` / `skip_list` from GET params and pass them to `get_checks`

0.4.0 (2014-09-08)
++++++++++++++++++
Expand Down
5 changes: 2 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,9 @@ the ``WATCHMAN_CHECKS`` setting. In ``settings.py``::
'another.module.path.to.callable',
)

Checks now have the same contract as context processors: they consume a
``request`` and return a ``dict`` whose keys are applied to the JSON response::
Checks take no arguments, and must return a ``dict`` whose keys are applied to the JSON response::

def my_check(request):
def my_check():
return {'x': 1}

In the absence of any checks, a 404 is thrown, which is then handled by the
Expand Down
50 changes: 50 additions & 0 deletions tests/test_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_django-watchman
------------
Tests for `django-watchman` management commands.
"""

from __future__ import unicode_literals

import unittest

from django.core.management import call_command
from django.utils.six import StringIO


class TestWatchman(unittest.TestCase):

def setUp(self):
pass

def test_successful_management_command_outputs_nothing(self):
out = StringIO()
call_command('watchman', stdout=out)
self.assertEquals('', out.getvalue())

def test_successful_management_command_outputs_check_status_with_verbosity_2(self):
out = StringIO()
call_command('watchman', stdout=out, verbosity='2')
self.assertIn('caches', out.getvalue())

def test_successful_management_command_outputs_check_status_with_verbosity_3(self):
out = StringIO()
call_command('watchman', stdout=out, verbosity='3')
self.assertIn('caches', out.getvalue())

def test_successful_management_command_supports_check_list(self):
out = StringIO()
call_command('watchman', stdout=out, checks='watchman.checks.caches', verbosity='3')
self.assertIn('caches', out.getvalue())
self.assertNotIn('databases', out.getvalue())

def test_successful_management_command_supports_skip_list(self):
out = StringIO()
call_command('watchman', stdout=out, skips='watchman.checks.email', verbosity='3')
self.assertIn('caches', out.getvalue())
self.assertIn('databases', out.getvalue())
self.assertNotIn('email', out.getvalue())
59 changes: 59 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_django-watchman
------------
Tests for `django-watchman` utils module.
"""

from __future__ import unicode_literals

import unittest

from watchman.utils import get_checks


class TestWatchman(unittest.TestCase):

def assertListsEqual(self, l1, l2):
try:
# Python 3.4
self.assertCountEqual(l1, l2)
except AttributeError:
# Python 2.7
self.assertItemsEqual(l1, l2)

def setUp(self):
pass

def test_get_checks_returns_all_available_checks_by_default(self):
checks = [check.__name__ for check in get_checks()]
expected_checks = ['caches', 'databases', 'storage']
self.assertListsEqual(checks, expected_checks)

def test_get_checks_with_check_list_returns_union(self):
check_list = ['watchman.checks.caches']
checks = [check.__name__ for check in get_checks(check_list=check_list)]
expected_checks = ['caches']
self.assertListsEqual(checks, expected_checks)

def test_get_checks_with_skip_list_returns_difference(self):
skip_list = ['watchman.checks.caches']
checks = [check.__name__ for check in get_checks(skip_list=skip_list)]
expected_checks = ['databases', 'storage']
self.assertListsEqual(checks, expected_checks)

def test_get_checks_with_matching_check_and_skip_list_returns_empty_list(self):
check_list, skip_list = ['watchman.checks.caches'], ['watchman.checks.caches']
checks = [check.__name__ for check in get_checks(check_list=check_list, skip_list=skip_list)]
expected_checks = []
self.assertListsEqual(checks, expected_checks)

def test_get_checks_with_check_and_skip_list(self):
check_list = ['watchman.checks.caches', 'watchman.checks.databases']
skip_list = ['watchman.checks.caches']
checks = [check.__name__ for check in get_checks(check_list=check_list, skip_list=skip_list)]
expected_checks = ['databases']
self.assertListsEqual(checks, expected_checks)
8 changes: 4 additions & 4 deletions watchman/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ def _check_storage():
return response


def caches(request):
def caches():
return {"caches": _check_caches(settings.CACHES)}


def databases(request):
def databases():
return {"databases": _check_databases(settings.DATABASES)}


def email(request):
def email():
return {"email": _check_email()}


def storage(request):
def storage():
return {"storage": _check_storage()}
Empty file added watchman/management/__init__.py
Empty file.
Empty file.
50 changes: 50 additions & 0 deletions watchman/management/commands/watchman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import absolute_import

import json
from optparse import make_option

from django.core.management.base import BaseCommand, CommandError

from watchman.utils import get_checks


class Command(BaseCommand):
help = 'Runs the default django-watchman checks'

option_list = BaseCommand.option_list + (
make_option(
'-c',
'--checks',
dest='checks',
help='A comma-separated list of watchman checks to run (full python dotted paths)'
),
make_option(
'-s',
'--skips',
dest='skips',
help='A comma-separated list of watchman checks to skip (full python dotted paths)'
),
)

def handle(self, *args, **options):
check_list = None
skip_list = None
verbosity = options['verbosity']
print_all_checks = verbosity == '2' or verbosity == '3'

checks = options['checks']
skips = options['skips']

if checks is not None:
check_list = checks.split(',')

if skips is not None:
skip_list = skips.split(',')

for check in get_checks(check_list=check_list, skip_list=skip_list):
if callable(check):
resp = json.dumps(check())
if '"ok": false' in resp:
raise CommandError(resp)
elif print_all_checks:
self.stdout.write(resp)
14 changes: 12 additions & 2 deletions watchman/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-

from watchman.settings import WATCHMAN_CHECKS

try: # try for Django 1.7+ first.
from django.utils.module_loading import import_string
except ImportError: # < Django 1.7
Expand Down Expand Up @@ -31,6 +34,13 @@ def import_string(dotted_path, error_prefix=''):
return attr


def get_checks(paths_to_checks):
for python_path in paths_to_checks:
def get_checks(check_list=None, skip_list=None):
checks_to_run = frozenset(WATCHMAN_CHECKS)

if check_list is not None:
checks_to_run &= frozenset(check_list)
if skip_list is not None:
checks_to_run -= frozenset(skip_list)

for python_path in checks_to_run:
yield import_string(python_path)
23 changes: 13 additions & 10 deletions watchman/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,29 @@
from django.http import Http404

from jsonview.decorators import json_view
from watchman.utils import get_checks
from watchman.settings import WATCHMAN_CHECKS
from watchman.decorators import token_required
from watchman.utils import get_checks


@token_required
@json_view
def status(request):
response = {}
available_checks = frozenset(WATCHMAN_CHECKS)

check_list = None
skip_list = None

if len(request.GET) > 0:
if 'check' in request.GET:
possible_filters = frozenset(request.GET.getlist('check'))
available_checks &= possible_filters
check_list = request.GET.getlist('check')
if 'skip' in request.GET:
skipped_filters = frozenset(request.GET.getlist('skip'))
available_checks -= skipped_filters
for func in get_checks(paths_to_checks=available_checks):
if callable(func):
response.update(func(request))
skip_list = request.GET.getlist('skip')

for check in get_checks(check_list=check_list, skip_list=skip_list):
if callable(check):
response.update(check())

if len(response) == 0:
raise Http404('No checks found')

return response

0 comments on commit 1453b48

Please sign in to comment.