Django: User-defined exception not being caught

拥有回忆 提交于 2019-12-12 02:53:47

问题


I have a user-defined exception in my Django app:

class TemplateMatchError(Exception):
    ...

I have a segment of code that catches this exception in regular try ... except:

try:
  if template.match(text):
    return attrs
except TemplateMatchError as e:
  continue

I noticed that in production, when DEBUG=True, this error is not caught and if raised, my browser displays the yellow Django stack trace page. When DEBUG=False, the exception is caught.

I was surprised by this behavior since it means the debug setting changes the behavior of a normal python try...except. Is this a correct interpretation, and if so why does Django work this way?

UPDATE: As per the comments I'm posting actual traceback below (names are different from toy example above):

Environment:


Request Method: POST
Request URL: http://mysite.com/api/call/6/

Django Version: 1.4.2
Python Version: 2.7.3
Installed Applications:
('longerusername',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.sites',
 'django.contrib.admin',
 'south',
 'django_extensions',
 'django.contrib.staticfiles',
 'crispy_forms',
 'api',
 'rest_framework')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/compat.py" in view
  121.                 return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/csrf.py" in wrapped_view
  77.         return view_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/views.py" in dispatch
  327.             response = self.handle_exception(exc)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/views.py" in dispatch
  324.             response = handler(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/generics.py" in put
  469.         return self.update(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/mixins.py" in update
  129.         if serializer.is_valid():
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in is_valid
  479.         return not self.errors
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in errors
  471.                 ret = self.from_native(data, files)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in from_native
  867.         instance = super(ModelSerializer, self).from_native(data, files)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in from_native
  319.                 attrs = self.perform_validation(attrs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in perform_validation
  260.                     attrs = validate_method(attrs, source)
File "/home/uname/api/serializers.py" in validate_text
  68.                 if template.match(text):
File "/home/uname/api/models.py" in match
  135.             raise TemplateMatchError()

Exception Type: TemplateMatchError at /api/call/6/
Exception Value: Not a match

Here's my models.py:

import re
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User as AuthUser
from otalo.ao.models import User
from otalo.surveys.models import Survey

class XCall(models.Model):
    created_on = models.DateTimeField(auto_now_add=True)
    send_on = models.DateTimeField(default=datetime.now)
    auth_user = models.ForeignKey(AuthUser, related_name='calls')
    recipient = models.ForeignKey(User)
    text = models.CharField(max_length=4096)
    backup_calls = models.IntegerField(blank=True, null=True)

    '''
    '    Associate with the survey, since
    '    all the calls can be traced through it.
    '    This billing/auditing purposes.
    '
    '    This can be lazily created at the time of
    '    scheduling the call, so make it nullable
    '
    '''
    survey = models.ForeignKey(Survey, null=True, blank=True)


    def __unicode__(self):
        return unicode(self.auth_user) + '-' + unicode(self.recipient)

class TemplateMatchError(Exception):
    STD_MESSAGE = 'Not a match'
    def __init__(self, msg=None):
        if msg is not None:
            self.msg = TemplateMatchError.STD_MESSAGE + ': ' + msg
        else:
            self.msg = TemplateMatchError.STD_MESSAGE
    def __str__(self):
        return self.msg

class XTemplate(models.Model):

    VARTYPE_NUM = '_N_'
    VARTYPE_WORD = '_W_'
    VARTYPE_DATETIME = '_DT_'
    VARTYPE_DATE = '_D_'
    VARTYPE_TIME = '_T_'

    # add grouping for regexes for easy extraction
    VARTYPE_REGEXS = {  VARTYPE_NUM: r'(\d+(?:\.\d{1,2})?)', # supports decimals up to two precision points. Could be more but then
                                                            # the prompting would start to sound weird
                        VARTYPE_WORD: r'(\w+)',
                        # Match dates and times as words
                        # so that your match function can
                        # try to convert to datetime for more
                        # detailed error messages
                        VARTYPE_DATETIME: r'(\w+)',
                        VARTYPE_DATE: r'(\w+)',
                        VARTYPE_TIME: r'(\w+)',
                      } 

    DATE_FORMATS = {
                        VARTYPE_DATETIME: '%d-%m-%y %H:%M',
                        VARTYPE_DATE: '%d-%m-%y',
                        VARTYPE_TIME: '%H:%M'
                    }

    created_on = models.DateTimeField(auto_now_add=True)
    auth_user = models.ForeignKey(AuthUser, related_name='templates')
    '''
    '    Encodes the wildcards and their type.
    '    e.g. Your account number _N_ is of type _W_ expiring at time _D_
    '''
    text = models.CharField(max_length=4096)

    '''
    '    For common prompts like numbers and dates and times
    '''
    language = models.CharField(max_length=24)

    STATUS_PENDING = 0
    STATUS_ACTIVE = 1
    STATUS_INACTIVE = 2

    STATUSES = (
    (STATUS_PENDING, 'Pending'),
    (STATUS_ACTIVE, 'Active'),
    (STATUS_INACTIVE, 'Inactive'),
    )
    status = models.IntegerField(choices=STATUSES)

    '''
    '    Compare the inupt text to this template's text;
    '    return the match object if it matches, else throw an error
    '''
    def match(self, input):
        pattern = self.text

        # first convert the template pattern into a regular expression
        vars = [var.group() for var in re.finditer('_[A-Z]_', pattern)]
        re_pattern = pattern
        for vartype, regex in XTemplate.VARTYPE_REGEXS.iteritems():
            re_pattern = re_pattern.replace(vartype, regex)

        # now try an initial match of the structure of the input
        match = re.match(re_pattern, input)

        if match:
            # make sure words are in the wordlist
            # numbers are valid numbers
            # and dates are in the proper format
            vocab = [word.text for word in self.vocabulary.all()]
            vals = match.groups()
            for i in range(len(vars)):
                if i > len(vals):
                    raise TemplateMatchError('Missing a variable in input')
                var = vars[i]
                val = vals[i]
                if var == XTemplate.VARTYPE_NUM:
                    try:
                        float(val)
                    except ValueError as e:
                        raise TemplateMatchError('Invalid number')
                elif var == XTemplate.VARTYPE_WORD:
                    if val not in vocab:
                        raise TemplateMatchError('Word not in vocabulary')
                elif var == XTemplate.VARTYPE_DATETIME or var == XTemplate.VARTYPE_DATE or var == XTemplate.VARTYPE_TIME:
                    format = XTemplate.DATE_FORMATS[var]
                    try:
                        date = datetime.strptime(val, format)
                    except ValueError as e:
                        raise TemplateMatchError('Invalid date, time, or datetime format - ' + val)
            return match
        else:
            raise TemplateMatchError()

    def __unicode__(self):
        return self.text + '-' + unicode(self.auth_user)

class XWord(models.Model):
    text = models.CharField(max_length=128)
    template = models.ForeignKey(XTemplate, related_name='vocabulary')

    def __unicode__(self):
        return self.text

serializer class in question:

class CallSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='call-detail',
    )
    recipient = PhoneNumberField(read_only=False)
    status = SurveySerializer(source='survey', read_only=True)

    def validate_text(self, attrs, source):
        text = attrs['text']
        auth = self.context['request'].user
        templates = auth.templates.all()
        for template in templates:
            try:
                if template.match(text):
                    return attrs
            except TemplateMatchError as e:
                continue

        raise serializers.ValidationError("Call text does not match a registered template")

    class Meta:
        model = XCall
        fields = ('url', 'id', 'text', 'recipient', 'send_on', 'backup_calls', 'status')
        lookup_field= 'pk'

回答1:


The problem was that a different exception class was being thrown up by models.py, though the name was the same.

My settings.py did not specify the full path of the app where the models.py in question lived. After specifying the full path, the exception classes matched and the exception was caught. Thanks to all who provided great hints.




回答2:


It'd help to see more of your code around the exception catching. From what you have shown, there are a few things to look at:

  1. I'm assuming TemplateMatchError is what you originally called MyError
  2. Your code seems unsure of how template.match returns a negative result. In serializers.py, it seems to expect a nil/false return value but the function itself raises an exception rather than returning something falsey.
  3. The code segment as you've shown it has bad indentation, which could be causing the failure to catch the error.

As you've shown it:

try:
    template.match(text)
    # Do other stuff, presumably including this:
    try:
        somethingElse()
    except TemplateMatchError as e:
        #this won't catch exceptions from template.match(text)
        continue

How I think you mean it:

try:
    template.match(text)
except TemplateMatchError as e:
    # This will be caught
    continue

Hope that helps.




回答3:


are you sure that it is the same Class «TemplateMatchError» imported from the same module that you raise and you try to catch.

if this is two class with the same name but imported from diferent module, python won't trait them as the same Exception, an then never enter your catch block.




回答4:


Modify the code this way in order to verify assumptions at very near point.

    import api
    assert TemplateMatchError == api.models.TemplateMatchError
    try:
        if template.match(text):
            return attrs
    except TemplateMatchError as e:
        continue
    except Exception as e:
        assert isinstance(e, TemplateMatchError)
        import pdb; pdb.set_trace()
        pass   # if both asserts vere OK, but the exception is uncaught (not
        #        probable) you are here and see the the line debugger started
        raise   # continue by caugting in external frames

Start the testserver by the best way for debugging
python manage.py runserver --nothreading --noreload
When you see the debugger prompt (Pdb), put these commands in order to repeat it step by step:

l(ist) Enter
j(ump) <line number of the line 'try:'> Enter
b /home/uname/api/models.py:135 Enter  # breakpoint at that raise command
c(ontinue) Enter
s(tep) Enter  # press Enter five times to see steps, how the lines 
              # "except ... continue" are missed 
c(ontinue)  # to reraise and see an error page in the browser

However I think that one of asserts will fail, if DEBUG=True, and you will know more, without debugger.



来源:https://stackoverflow.com/questions/18641114/django-user-defined-exception-not-being-caught

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!