Error handling in Capstone Project
While you are developing your project, it is nice to be able to see Python errors in your browser. But, most end-users probably would not want that. An important objective in designing a web site is to handle all errors that can occur on bad or unexpected user input. Here are some tools within Python that can be useful.
Contents |
Exception Handlers
In Python, when the code does something illegal, an exception is raised. The exception normally kills the program and prints out a stack trace. Here is an example. Consider the following form:
<html> <p> Please provide input below:</p> <p> <a href="myotherpage.html"> My Other Page </a> </p> <form action="scripts/mycalc.py/computeinterest" method="POST"> Deposit Amount: <input type="text" name="deposit"><br> Interest rate: <input type="text" name="interest"><br> Number of years: <input type="text" name="years"><br> <input type="submit"> </form> </html>
Now look at part of the Python code to process this:
def computeinterest(deposit, interest, years): fdeposit = float(deposit) finterest = float(interest) iyears = int(years) # compute the balance at the end balance = fdeposit * ((1+finterest/100.0)**iyears
Notice how the code is making an assumption that the user will enter numeric values for deposit, interest, and years? But on the web, there is no guarantee that the user will actually do that. So, your program must be prepared to handle bad input. Here is what you get in the browser if the user enters a non-numeric value "Thousand" for the deposit field:
MOD_PYTHON ERROR ProcessId: 1350 Interpreter: '127.0.1.1' ServerName: '127.0.1.1' DocumentRoot: '/var/www' URI: '/scripts/mycalc.py/computeinterest' Location: None Directory: '/var/www/scripts/' Filename: '/var/www/scripts/mycalc.py' PathInfo: '/computeinterest' Phase: 'PythonHandler' Handler: 'mod_python.publisher' Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/mod_python/importer.py", line 1537, in HandlerDispatch default=default_handler, arg=req, silent=hlist.silent) File "/usr/lib/python2.7/dist-packages/mod_python/importer.py", line 1229, in _process_target result = _execute_target(config, req, object, arg) File "/usr/lib/python2.7/dist-packages/mod_python/importer.py", line 1128, in _execute_target result = object(arg) File "/usr/lib/python2.7/dist-packages/mod_python/publisher.py", line 213, in handler published = publish_object(req, object) File "/usr/lib/python2.7/dist-packages/mod_python/publisher.py", line 425, in publish_object return publish_object(req,util.apply_fs_data(object, req.form, req=req)) File "/usr/lib/python2.7/dist-packages/mod_python/util.py", line 554, in apply_fs_data return object(**args) File "/var/www/scripts/mycalc.py", line 10, in computeinterest fdeposit = float(deposit) ValueError: could not convert string to float: Thousand MODULE CACHE DETAILS Accessed: Wed Nov 30 10:08:06 2011 Generation: 2 _mp_e469afbafae585bf69c4fe9c5afd30ef { FileName: '/var/www/scripts/mycalc.py' Instance: 3 [RELOAD] Generation: 3 Modified: Wed Nov 30 10:03:09 2011 Imported: Wed Nov 30 10:08:06 2011 }
The error is nice during debugging, but it is not a good feedback to the user. To handle this, it is useful to handle the possibility of an exception. One way is to surround the code that can generate an exception with try...except as below:
try: fdeposit = float(deposit) finterest = float(interest) iyears = int(years) except: return badInputErrorPage()
With try..except code, Python does the following. It attempts to execute the code within the try block (i.e., 3 lines that do the type conversion in the above example). If that finishes normally without an error, the code within the except block is ignored. On the other hand, if an exception occurs on any line within the try block, Python stops executing the try block and jumps to the first instruction in the except block. After the except block is completed, it resumes normally with the instructions that follow the try...except block.
In the example above, the function badInputErrorPage() can return a string object that contains the HTML page warning the user about the bad input. Here is an example of this function:
def badInputErrorPage(): output = ''' <HTML> <BODY> <p> The values for deposit, interest and balance should be numbers </p> <p> Please hit the back button in your browser and try again. </p> </BODY> </HTML> ''' return output
Here is a general rule on handling user input from the web:
ALWAYS check that the user input is proper. Not doing a check can lead to poor feedback to users. Even worse, it can also lead to your web site being compromised. For example, if you invoke "eval" on something entered by the user, then you are permitting an unknown user to really execute anything within your Python code. That is not a great idea without sanitizing the input.
Default parameter values in functions
It is also possible for a user to not supply a value. Here is an example where we have a checkbox with two options. The user can select none of the checkboxes, one of the two checkboxes, or both:
<html> <p> Please provide input below:</p> <form action="scripts/handleboxes.py/myhandler" method="POST"> <input type="checkbox" name="checker" value="Option 1"/> This is value 1 <br /> <input type="checkbox" name="checker" value="Option 2"/> This is value 2 <br /> <input type="submit"> </form> </html>
In this case, in the Python function "myhandler" in the file handleboxes.py, you would have a parameter called "checker". The mod_python module will send the following values to the variable, depending on which of the two checkboxes the user has selected:
- ['Option 1', 'Option 2']
- 'Option 1'
- 'Option 2'
- undefined
You can distinguish between the first one and the next two by checking the type. If the type(checker) is list, then more than one checkbox was selected. If the type(checker) is a string, then only one checkbox was selected. But, both of these type checks will give an exception if none of the checkboxes were selected because the variable checker would be undefined. You can handle the exception by using an exception handler as before. But, you can also do something like the following in myhandler:
def myhandler(checker = None): if (checker == None): ... no checkbox was selected ...
In general, Python permits the developer to designate default values for parameters if they are not specified by the caller. Here is another example:
def square(x = 2): return x*x Use of the above function: >>> print square(10) 100 >>> print square() 4
Notice that when no parameter value for x is supplied, Python used the default value of 2 for the parameter.
If the function has multiple parameters, the parameters with default values have to be at the end. For example, the following is illegal:
def addvalues(x = 2, y = 3, z): return x + y + z
The problem with above is that if the call is addvalues(10, 20), Python would not know whether 10 is the value of x or y.
Variable parameter lists
Occasionally, you may need to handle variable number of inputs from the user. For example, you have a form that is generated by Javascript and permits user to enter one or more zip codes and you are going to return back a table of temperatures for those zip code. Here is a stand-alone example that shows how to handle variable number of parameters taken straight from this web site: http://www.tutorialspoint.com/python/python_functions.htm:
def printinfo( arg1, *vartuple ): "This prints a variable passed arguments" print "Output is: " print arg1 for var in vartuple: print var return; # Now you can call printinfo function printinfo( 10 ); printinfo( 70, 60, 50 );
Python Debugging Tricks
Using Python Debugger pdb from Terminal interpreter
If you are having trouble figuring out what your code is doing, there are few things you can do:
- Execute the function that is giving the problem by itself from an interpreter.
- Write testers for each critical function in your code to make sure those functions work correctly. See the assignment where you used assert statements to test function correctness.
- Use print statements. The print statement output would show up in the interpreter, but will not be sent back to the web browser when using mod_python.
- When using the interpreter, you can also use the Python debugger module pdb. Here is an example code:
import pdb def foo(x, y): z = x + y pdb.set_trace() w = z + x return w
When the above function is called, you will get the following:
>>> foo(3, 4) > <stdin>(4)foo() (Pdb)
Notice the prompt (pdb). At this point, the program is paused right at the pdb.set_trace() instruction. You can now print the values of variables at that point (just use "print <variable>"), step to the next instruction including going into any function calls (step), step to the next instruction treating function calls as one instruction (next), or simply continue the program (continue). Here is an example:
>>> foo(3, 4) > <stdin>(4)foo() (Pdb) (Pdb) x 3 (Pdb) print y 4 (Pdb) print z 7 (Pdb) print w *** NameError: name 'w' is not defined (Pdb) step > <stdin>(5)foo() (Pdb) print w 10 (Pdb)
Alternative to print for mod_python
When debugging mod_python Python scripts, you will find that the output from print statements does not go back to your browser. As indicated above, you can use pdb instead, but you have to do that from a Terminal. If you want to use your browser to help debug the script, a simple way to send back the value of a variable to the browser is to raise an exception as follows:
raise Exception(str(variablename))
This will cause the script to halt with the above exception. In your browser, you should then see a strack trace of Python error, provided you have PythonDebug setting on in your Apache's python configuration file.
Here is a sample code snippet from the bank interest computation example:
balance = fdeposit * ((1+finterest/100.0)**iyears) raise Exception("balance = " + str(balance))
Here is what the browser displays when I entered 1000, 10, and 20 for deposit, interest rate, and years respectively.
File "/var/www/scripts/mycalc.py", line 32, in computeinterest raise Exception("balance = " + str(balance)) Exception: balance = 6727.49994933