Django:未捕获用户定义的异常

时间:2013-09-05 16:07:38

标签: python django try-catch

我的Django应用程序中有一个用户定义的异常:

class TemplateMatchError(Exception):
    ...

我有一段代码可以在常规try ... except中捕获此异常:

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

我注意到在生产中,当DEBUG=True时,没有捕获到此错误,如果引发错误,我的浏览器会显示黄色的Django堆栈跟踪页面。 DEBUG=False时,会捕获异常。

我对这种行为感到惊讶,因为这意味着调试设置会改变普通python try...except的行为。这是一个正确的解释,如果是这样,为什么Django会这样工作?

更新:根据评论我在下面发布实际追溯(名称与上面的玩具示例不同):

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

这是我的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

有问题的序列化程序类:

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'

4 个答案:

答案 0 :(得分:1)

问题是models.py抛出了一个不同的异常类,虽然名称是相同的。

我的settings.py没有指定有问题的models.py所在应用的完整路径。指定完整路径后,匹配的异常类和捕获的异常。感谢所有提供了很好提示的人。

答案 1 :(得分:0)

有关查看异常捕获的更多代码是有帮助的。根据您的展示,有几点需要注意:

  1. 我假设TemplateMatchError是您最初称为MyError
  2. 的内容
  3. 您的代码似乎不确定template.match如何返回否定结果。在serializers.py中,似乎期望nil / false返回值,但函数本身会引发异常,而不是返回虚假内容。
  4. 您显示的代码段有不良缩进,这可能导致无法捕获错误。
  5. 如你所示:

    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
    

    我认为你的意思是:

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

    希望有所帮助。

答案 2 :(得分:0)

你确定它是从你引发的同一模块导入的同一类«TemplateMatchError»并且你试图捕获。

如果这是两个具有相同名称但从不同模块导入的类,则python不会将它们作为相同的异常进行特征,然后永远不会输入你的catch块。

答案 3 :(得分:0)

以这种方式修改代码,以便在非常接近的点验证假设。

    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

以最佳调试方式启动测试服务器 python manage.py runserver --nothreading --noreload
当您看到调试器提示符(Pdb)时,请放置这些命令以便一步一步地重复它:

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

但是我认为其中一个断言会失败,如果DEBUG = True,你会知道更多,没有调试器。