0

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

4 回答 4

1

问题是models.py抛出了一个不同的异常类,尽管名字是一样的。

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

于 2013-09-12T13:42:29.220 回答
0

查看更多关于异常捕获的代码会有所帮助。从您所展示的内容来看,有几点需要注意:

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

正如你所展示的:

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

希望有帮助。

于 2013-09-09T08:37:05.597 回答
0

您确定它是从您提出并尝试捕获的同一模块导入的同一个类«TemplateMatchError»。

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

于 2013-09-09T13:12:44.570 回答
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,其中一个断言将失败,并且您将知道更多,而无需调试器。

于 2013-09-10T23:57:07.430 回答