Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moved some general-purpose Python stuff to hospital #4

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions demo/django_doctor_demo/simple/healthchecks.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""Health checks."""
import doctor
from hospital import HealthCheck


class FakeHealthCheck(doctor.HealthCheck):
class FakeHealthCheck(HealthCheck):
"""Check that health checks are captured."""
def test_success(self):
"""Successful healthcheck is captured and passes."""
Expand Down
2 changes: 0 additions & 2 deletions doctor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
"""Django health check and test-that-it-works application."""
# Import API shortcuts.
from doctor.healthchecks import HealthCheck


#: django-doctor version information, as a tuple.
Expand Down
106 changes: 3 additions & 103 deletions doctor/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from django.views.decorators.cache import never_cache
from django.views.generic import TemplateView

from hospital.loading import HealthCheckLoader

from doctor.conf import TEMPLATE_CONTEXT
from doctor.services import load_service_classes
from doctor.utils import cleanse_dictionary
Expand Down Expand Up @@ -122,108 +124,6 @@ def force_server_error(request):
return HttpResponse('This should never show up.', content_type='text/plain')


class HealthCheckLoader(unittest.TestLoader):
"""Encapsulate HealthCheck loading.

This is a special TestLoader which makes sure instances are actually
health checks.

.. warning::

Since this loader can be called with arguments provided by users (GET
requests), **we have to make sure user input is safe**.
As an example, we can't accept to load health checks from any callable,
because this callable could be anything.

"""
def is_health_check(self, value):
"""Return True if ``value`` is an health check.

Proxy to :py:attr:``
Tests ``is_healthcheck`` attribute of ``value``.

"""

try:
return value.is_healthcheck
except AttributeError:
return False

def filter_suite(self, suite):
"""Return copy of TestSuite where only health checks remain."""
if isinstance(suite, unittest.TestSuite):
suite_copy = self.suiteClass()
for sub in suite:
if isinstance(sub, unittest.TestSuite):
suite_copy.addTest(self.filter_suite(sub))
else:
if self.is_health_check(sub):
suite_copy.addTest(sub)
elif self.is_health_check(suite):
suite_copy = suite.copy()
return suite_copy

def loadTestsFromModule(self, module):
suite_tree = super(HealthCheckLoader, self).loadTestsFromModule(module)
return self.filter_suite(suite_tree)

def loadTestsFromName(self, name, module=None):
"""Same as unittest.TestLoader.loadTestsFromName, but restricted
to health test objects, i.e. no callable allowed."""
parts = name.split('.')
if module:
parts.insert(0, module)
name = '.'.join(parts)
# First, retrieve a module.
module_obj = None
path_parts = []
while parts:
latest_part = parts.pop(0)
path_parts.append(latest_part)
path = '.'.join(path_parts)
try:
imported_obj = __import__(path, globals(), locals(), [], -1)
except ImportError as module_exception:
if not module_obj:
raise module_exception
else:
path_parts.pop() # Last part is not a module.
path = '.'.join(path_parts)
break
else:
if not module_obj:
module_obj = imported_obj
else:
module_obj = getattr(module_obj, latest_part)
# We got ``module_obj`` for ``path``.
# Let's retrieve members.
parts = name[len(path):].lstrip('.')
if not parts:
return self.loadTestsFromModule(module_obj)
else:
parts = parts.split('.')
class_name = parts[0]
try:
class_obj = getattr(module_obj, class_name)
except AttributeError as class_exception:
raise ImportError("Couldn't load '%s'" % name)
if not self.is_health_check(class_obj):
raise ImportError("'%s' is not a health check" % name)
try:
method_name = parts[1]
except IndexError:
return self.loadTestsFromTestCase(class_obj)
else:
try:
method_obj = getattr(class_obj, method_name)
except AttributeError:
raise AttributeError("'%s' is not a health check method" % name)
return unittest.TestSuite([class_obj(method_name)])

def loadTestsFromNames(self, names, module=None):
raise NotImplementedError()


class HealthCheckView(TemplateView):
"""Load a health check, run it and return result."""
#: Name of the health_check argument captured in urlpatterns.
Expand Down Expand Up @@ -268,7 +168,7 @@ def del_health_check(self):
health_check = property(get_health_check,
set_health_check,
del_health_check,
""":py:class:`doctor.HealthCheck` instance.""")
""":py:class:`hospital.HealthCheck` instance.""")

def get_health_check_result(self):
"""Getter for health_check_result, runs tests when first requested."""
Expand Down
7 changes: 5 additions & 2 deletions etc/buildout.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ installed = lib/buildout/.installed.cfg
parts-directory = lib/buildout/parts
# Package index, mirrors, allowed hosts and dependency links. Those options
# control locations where buildout looks for packages.
index = http://f.pypi.python.org/simple
index = https://simple.crate.io
find-links =
allow-hosts = *.python.org
allow-hosts =
*.crate.io
packages.crate-cdn.com
use-dependency-links = false
# Development.
develop =
Expand Down Expand Up @@ -67,3 +69,4 @@ z3c.recipe.scripts = 1.0.1
zc.recipe.egg = 1.3.2
zest.releaser = 3.43
django-nose = 1.1
hospital = 0.1
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
url='https://github.com/funkbit/django-doctor',
include_package_data=True,
packages=['doctor', 'doctor.services', 'doctor.management', 'doctor.management.commands'],
install_requires=['hospital'],
tests_require=['django>=1.4,<1.5'],
license='BSD',
classifiers = (
Expand Down