3

I'm using Python 2.7.3 with Flask 0.10.1 and SQL-Alchemy 0.9.1.

In my view code, I'm building an object for database input, and my code depends on the proper execution of two try/except/else blocks. It works as expected for the first block, both when exceptions occur and when they do not. When there's an exception, I get a collection of errors. When there's not an exception, the data get added to the database and the sample counter increments. Here's that snippet:

        try:
            new_sample = Sample()
            new_sample.build(sample)
        except SampleBuildingError as e:
            result_response['errors'].append('{0}: {1}'.format(sample['sample_name'], e.value) )
        else:
            db.session.add(new_sample)
            num_added += 1

Further down in the view function, I have a try/except/else/finally block. The data are being committed to the database, so the try portion is clearly working, as is the finally block. However, it appears that the else block is not being executed:

try:
    db.session.commit()
except Exception as e:
    result_response['error'] = "Failed on the database input: {0}".format( str(e) )
else:
    result_response['success'] = "The samples were input to the database successfully. {0} samples entered into the database, with {1} errors".format( num_added, len(errors) )
finally:
    return jsonify(result_response)

When there's an exception, I get json back with an error key and the database error, as expected. However, when the database commit is succesfully, I get a json object with every expected key except the success key.

It appears that the else block is being skipped, but I can't figure out why. I've tried putting the word bogus in the second line of the else block to try to force an error, but Python doesn't complain!

Here's the full view function:

@app.route("/kapasubmit", methods=['POST'])
def kapasubmit():
    num_added = 0
    result_response = {'errors': []}
    samples = request.json['data']
    for sample in samples:
        sample_check = Sample.query.filter_by( sample_name = sample['sample_name'] ).first()
        if sample_check is None:
            try:
                new_sample = Sample()
                new_sample.build(sample)
            except SampleBuildingError as e:
                result_response['errors'].append('{0}: {1}'.format(sample['sample_name'], e.value) )
            else:
                db.session.add(new_sample)
                num_added += 1
        else:
            result_response['errors'].append('{0}: This is a duplicate sample'.format(sample['sample_name']) )

    if num_added > 0:
        try:
            db.session.commit()
        except Exception as e:
            result_response['error'] = "Failed on the database input: {0}".format( str(e) )
        else:
            result_response['success'] = "The samples were input to the database successfully. {0} samples entered into the database, with {1} errors".format( num_added, len(errors) )
            bogus
        finally:
            return jsonify(result_response)
    else:
        result_response['error'] = "No valid samples submitted for input"
        return jsonify(result_response)
DeeDee
  • 2,641
  • 2
  • 17
  • 21
  • I have made a quick test, `>>> try: ... pass ... except: ... pass ... else: ... print 'else' ... finally: ... print 'finally' ... else finally ` This seems to work. There has to be something else. – njzk2 Feb 27 '14 at 21:29
  • @njzk2 Yeah, it's puzzling as all get-out, because the first statement works exactly as it should. The second statement works except that it skips the else block. Baffling, especially to a Python (but not coding) newbie. – DeeDee Feb 27 '14 at 21:30
  • In this particular case, why not just put the statement into the try block. According to http://stackoverflow.com/questions/855759/python-try-else the only time you need the else is when there is an exception raising case that you do not want to be handled by your except: In this case the else is not needed. – sabbahillel Feb 27 '14 at 21:30
  • 1
    `len(errors)`: I don't see where `errors` is defined? – njzk2 Feb 27 '14 at 21:32
  • @sabbahillel That's a good suggestion. But now I'm wondering what's up, just for my knowledge. – DeeDee Feb 27 '14 at 21:33
  • @njzk2 I think you're on to something! I neglected to fix that after a refactor. That could be the source of the problem! – DeeDee Feb 27 '14 at 21:34
  • @njzk2 I fixed that bug and tested it, but it didn't change the behavior. – DeeDee Feb 27 '14 at 21:39
  • that has to be it. put a try except around your `.format` call and log stuff (also, if you put a log in the else block right after the else, i'm quite confident you'll see it) – njzk2 Feb 27 '14 at 21:40
  • At first I would have thought that the indentation might have linked the else to the preceding if but I see that you have an else for that if so you would have gotten a syntax error. What happens if you dummy the code to remove the first try except block. Do you still get the problem? – sabbahillel Feb 27 '14 at 21:44

2 Answers2

3

In case else block raises an exception, it gets ignored, since the interpreter has to execute return from the function. A demo:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print "division by zero!"
...     else:
...         print "result is", result
...         print 1/0
...     finally:
...         return 1
... 
>>> divide(2,1)
result is 2
1

1/0 doesn't cause any traceback.

Roman Bodnarchuk
  • 29,461
  • 12
  • 59
  • 75
  • that makes the use of `return` and `finally` quite dangerous, as there is no reason the result would be any different if there was an exception raised in the except block as well. – njzk2 Feb 27 '14 at 21:38
  • This is correct and helpful, thanks! @njzk2 spotted the error first, so I think I have to give him the check. – DeeDee Feb 27 '14 at 21:42
2

I think errors is not defined.

len(errors) would raise an exception, but not prevent the execution of the finally block, hence still returning some data, without having defined result_response['success']

njzk2
  • 38,969
  • 7
  • 69
  • 107
  • Thanks: I tested this with else: print 'in else', errors, 'out of else' finally: 'done' and the 'in else' 'done' got printed followed by the NameError errors not defined output. – sabbahillel Feb 27 '14 at 21:58
  • typo in the above comment, each string should have a separate print for it to be undestandable you might want to add a code snippet to illustrate your point. – sabbahillel Feb 28 '14 at 13:50