Table of Contents¶
django-issue Documentation¶
Overview¶
Sometimes things go wrong in production; if it is a repeating or ongoing error, it makes sense
to represent and track this in some way. This is the purpose of the Issue
class.
When an error is detected, an Issue
can be created to store details about it.
Possible advantages of this:
- When something goes wrong, corrective actions that are taken often should not be repeated.
- It gives admins an ability to view at a glance a history of actions taken by the system to address the issue.
Once an Issue
is created, it is often desirable to act on it. For this, django-issue provides
a Responder
model. A Responder
specifies a pattern to match against Issue`s; when a pattern matches
for an :class:`Issue
to a ‘Responder’, the Responder
executes some configured action.
How are Issues
created? They can be easily created by any bit of code.
Alternatively, you can use the Assertion
. The goal of an Assertion
is to provide a means for detecting when certain properties of your system no longer hold true.
Think of it as a cross between the classic assert
statement available in many programming langauge and traditional software monitoring systems like Nagios.
Philosophy¶
It’s often the case that you know how your system should behave (you built it). The problem is, your system doesn’t know how it’s supposed to behave. So when it missbehaves (due to bugs, unexpected edge-cases, user error, malware, a cat climbing on a keyboard), it doesn’t realize that something is amiss and continues on it’s way doing something terribly wrong. If the system had a more explicit notion of it’s expected behavior, then it could try to correct deviations, or at the very least, to escalate to a human and ask for help.
django-issue is an initial exploration into these ideas; how can we detect when things go wrong, represent the fact that they have gone wrong, and then respond to them?
Examples¶
Representing the fact that something is amiss¶
Suppose an error occurs in the middle of the night that needs to be addressed in the morning (but is not pressing enough to wake someone up). We could do something like this:
from isssue.models import Issue
try:
// a problem occurs
except ValueError as ve:
Issue.objects.create(name='That *impossible* edge case finally happened...', details=str(ve))
How do you know if something has gone wrong?¶
Enter the Assertion
class. Suppose you have a model that tracks a heartbeat from some external
service/software components. If, after some amount of time, the sytem does not receive a heartbeat, a human should be notified. Suppose you have an app called ‘heartbeat’ and your models.py file looks like this:
# Models.py
from datetime import datetime, timedelta
from django.db import models
class HeartbeatKeeper(model.Model):
last_heartbeat = models.DateTimeField(auto_now=True)
def check_for_recent_heartbeat(**kwargs):
"""
Returns (True, None) when all is well.
Returns (False, None) otherwise.
"""
delta = timedelta(minutes=30)
interval = (datetime.utcnow() - HeartbeatKeeper.objects.get().last_heartbeat)
return (interval < delta, None)
Now you create an Assertion
to call your check_for_recent_heartbeat()
function and create an Issue when it returns a tuple beginning with False:
Assertion.objects.create(target_function='heartbeat.models.check_for_recent_heartbeat', name='Check for heartbeat')
When the check_for_recent_heartbeat function returns a False tuple, then an Issue is created with the name ‘Check for heartbeat’).
There is a special type of a Assertion
called a ModelAssertion
.
A ModelAssertion
is designed to ensure that certain properties hold true for the models
in your database.
Suppose you have a Profile model for your Users. After 5 days of signing up, you want to be notified if the user hasn’t created a profile pic yet. You have an app, ‘profile’, and your models.py file looks like this:
# Models.py
from datetime import datetime, timedelta
from django.contrib.auth.models import Group, User
from django.db import models
class Profile(model.Model):
user = models.ForeignKey(User)
# Note: selfies are preferred
pic_url = models.URLField(null=True, blank=True)
def should_have_pic_by_now(record, **kwargs):
"""
Check if the specified user has a pic or still has time for one.
"""
interval = timedelta(days=5)
has_pic = record.pic_url is not None
date_joined = record.user.date_joined
okay = has_pic or (datetime.utcnow() - date_joined) < interval
return (okay, None)
Now you create an ModelAssertion
to call your check_for_recent_heartbeat()
function and create an Issue when it returns a tuple beginning with False:
from django.contrib.contenttypes.models import ContentType
from profile.models import Profile
ModelAssertion.objects.create(
target_function='profile.models.should_have_pic_by_now', name='Check for pic', model_type=ContentType.get_for_model(Profile))
Now whenever a Uer
account (and associated Profile
) is created, an Issue is created if the user does not set a profile pic within 5 days.
Addressing an ongoing problem¶
Now when this exception occurs, we will have a record in the database along with details about what happened and when. Now suppose we want an email notification when this happens. Well we could add the following:
from issue.models import Responder, ResponderAction
r = Responder.objects.create(watch_pattern='That \*impossible\* edge case finally happened')
ResponderAction.objects.create(responder=r, delay_sec=30, target_function='issue.actions.email',
function_kwargs={
'subject': 'Doh!',
'recipients': 'john.smith@example.com',
})
There is a helper function, build_responder()
for constructing a Responder
and one or more associated ResponderAction
from json:
from issue.builder import build_responder
build_responder({
'watch_pattern': 'That \'impossible\' edge case finally happened...',
'actions': [
{
'target_function': 'issue.actions.email',
'function_kwargs': {
'subject': 'Doh!',
'recipients': 'john.smith@example.com',
},
'delay_sec': 30,
},
{
'target_function': 'issue.actions.email',
'function_kwargs': {
'subject': 'Doh-2!',
'recipients': 'john.smith-boss@example.com',
},
'delay_sec': 1800,
},
]})
The delay_sec
may be ommitted; when this happens the ResponderAction will be executed as soon as the Responder matches against an Issue.
When do these checks happen?¶
Two management commands are provided, check_assertions and respond_to_issues which should be ran periodically.
Installation¶
To install the latest release, type:
pip install django-issue
To install the latest code directly from source, type:
pip install git+git://github.com/ambitioninc/django-issue.git
Contributing¶
Contributions and issues are most welcome! All issues and pull requests are handled through github on the ambitioninc repository. Also, please check for any existing issues before filing a new one. If you have a great idea but it involves big changes, please file a ticket before making a pull request! We want to make sure you don’t spend your time coding something that might not fit the scope of the project.
Running the tests¶
To get the source source code and run the unit tests, run:
git clone git://github.com/ambitioninc/django-issue.git
cd django-issue
virtualenv env
. env/bin/activate
python setup.py install
coverage run setup.py test
coverage report --fail-under=100
While 100% code coverage does not make a library bug-free, it significantly reduces the number of easily caught bugs! Please make sure coverage is at 100% before submitting a pull request!
Code Styling¶
Please arrange imports with the following style
# Standard library imports
import os
# Third party package imports
from mock import patch
from django.conf import settings
# Local package imports
from issue.version import __version__
Please follow Google’s python style guide wherever possible.
Building the docs¶
When in the project directory:
pip install -r requirements/docs.txt
python setup.py build_sphinx
open docs/_build/html/index.html
Release Checklist¶
Before a new release, please go through the following checklist:
Bump version in issue/version.py
Add a release note in docs/release_notes.rst
Git tag the version
Upload to pypi:
pip install wheel python setup.py sdist bdist_wheel upload
Vulnerability Reporting¶
For any security issues, please do NOT file an issue or pull request on github! Please contact security@ambition.com with the GPG key provided on Ambition’s website.
Release Notes¶
v3.1.2¶
- Fix django warning related to JSONField default value
v3.1.1¶
- Remove 1.10 from setup file
v3.1.0¶
- json field encoder (drop support for django 1.10)
v3.0.0¶
- Add tox to support more versions
- Upgrade to django’s JSONField
v2.0.0¶
- Remove python 2.7 support
- Remove python 3.4 support
- Remove Django 1.9 support
- Remove Django 1.10 support
- Add Django 2.0 support
v1.4.0¶
- Python 3.6 support
- Django 1.10 support
- Django 1.11 support
- Remove Django 1.8 support
v1.3.0¶
- Python 3.5 support, remove django 1.7 support
v1.2.0¶
- Django 1.9 support
v1.1.0¶
- Django 1.8 support and removal of 1.6 support
v1.0.5¶
- Additional tweak to the behavior of maybe_open_issue
v1.0.4¶
- Remove south as a dependency
v1.0.3¶
- Tweak to the behavior of reopen_issue
v1.0.2¶
- Tweak to the behavior of maybe_open_issue
v1.0.1¶
- Added a helper method, maybe_open_issue, to the IssueManager class
- This implements logic that’s begun to repeat in my Issue use cases
v1.0.0¶
- Added Django 1.7 compatability
ModelAssertion
andBaseAssertion
’s.check()
methods were renamed
to check_assertion()
v0.1¶
- This is the initial release of django-issue.
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line