Exception Handling In DTML

Ever wondered if there was a way to stop Zope from barfing error messages all over your screen? Here it is.

A Bug's Life

As a developer, there are a few things you get used to pretty quickly: the long hours, the demanding customers, the low pay, the lack of a social life. However, if you're anything like most developers, there's one thing that probably never fails to get your goat, no matter how long you've been writing code.

Bugs.

It's sad but true - error-free code is still largely a Utopian dream. No matter how careful you are, no matter how deep your knowledge or how good your IDE, you're bound to slip up sooner or later. And when you do, and your routines decide to go AWOL, you need to take remedial action.

Most programming languages - including DTML - come with built-in exception handling capabilities. These exception handlers make it possible to catch errors and resolve them sensibly, thereby insulating the user from complex decisions and incomprehensible technical information. As a professional developer, you should always wrap your code in exception handling routines, if only to avoid embarrassing flameouts in front of your customers.

That's where this article comes in. Over the next few pages, I'm going to be looking at exception handling, with a special focus on exceptions in the Zope and DTML universe. I'll be explaining the most common error types you'll encounter, together with the DTML constructs you can use to catch and resolve them, and I'll include lots of examples so that you can see how it works in practice. So come on in, and let's get started.

Anatomy Class

Now, since I've been talking so much about errors, how about introducing you to one? Take a look at the following code, which deliberately introduces an exception in a DTML script,

<dtml-var CheckThisOut>

and then take a look at the output, which might awaken some unpleasant memories.

Before you start screaming, you should know that this is one of the more common errors Zope developers see. It's ugly, it's loud, it'll really spoil your morning, but it can be fixed fairly easily. I'll be showing you how shortly, but first, let's dissect this baby a little.

In the example above, Zope tries to access the value of the variable "CheckThisOut". Now, this variable does not exist in the Zope variable namespace. What does Zope do? Rather than quietly packing up and moving to a new neighbourhood, Zope decides to generate a KeyError exception. You can see this from the first few lines of the output, which describe the exception, together with the variable that caused it.

Zope Error
Zope has encountered an error while publishing this resource.

Error Type: KeyError
Error Value: CheckThisOut

There are also a couple of useful suggestions as to why the error might have occurred:

Troubleshooting Suggestions
* This resource may be trying to reference a nonexistent object or variable CheckThisOut.
* The URL may be incorrect.
* The parameters passed to this resource may be incorrect.
* A resource that this resource relies on may be encountering an error.

From the above suggestions, it's pretty clear that Zope's analysis is not totally incorrect - the error occurred because the "CheckThisOut" variable doesn't exist. And Zope also provides a "traceback" of the error, which gives developers with detailed debugging information on the steps that led to the error. A little analysis of this traceback data, and you'll quickly be able to identify the scripts involved in the error (this feature is particularly useful when dealing with complex errors that you can't locate in the first code pass-through).

Playing Catch

So that's what an error looks like. And now that you've seen what makes it tick, how about writing something to handle it?

Zope allows you to trap errors using the standard "try-catch" exception-handling combination...or, in the DTML world, <dtml-try> and <dtml-catch>. Here's what it looks like:

<dtml-try>
execute this block

<dtml-except err>
execute this block if exception "err" is raised

</dtml-try>

Take a look at this rewrite of the previous example, which incorporates a simple exception handler.

<dtml-try>
<dtml-var CheckThisOut>
<dtml-except>
<b>Something bad happened</b>
</dtml-try>

Now, view this DTML Document and you'll see that the ugly error screen has disappeared; it's been replaced by a simpler (though not very useful) error message.

The first part of the code introduces you to the <dtml-try> block; this tag instructs Zope to try - literally - to execute the statements within the enclosing block.

<dtml-try>
<dtml-var CheckThisOut>
...
</dtml-try>

The <dtml-except> tag sets up the exception handler, in the event that one is generated. This tag does the hard work of trapping the exception and suggesting an alternative path for Zope to proceed with.

<dtml-except>
<b>Something bad happened</b>

If an exception is generated, the <dtml-except> segment of the code will be triggered, and Zope's default error handling mechanism will be overridden by the instructions in that code segment - in this case, displaying the text "Something bad happened".

Being Verbose

Now, what you just saw was a very primitive exception handler, albeit one which isn't very useful on a production site. Ideally, you'd want the message generated by the exception handler to be a little more descriptive, and to contain some information on the source of the error.

DTML satisfies this requirement via two special, pre-defined variables - the "error_name" and "error_type" variables. Take a look:

<dtml-try>
<dtml-var CheckThisOut>
<dtml-except>
<b>Something's wrong here! But what?</b><br>
This might help:<br>
Error type - <i><dtml-var error_type></i>
<br>
Error value - <i><dtml-var error_value></i>
</dtml-try>

Here's the output:

Something's wrong here! But what?
This might help:
Error type - KeyError
Error value - CheckThisOut

And if you flip back a couple of pages to the very first code snippet in this article, you'll notice a remarkable similarity between the default Zope error output, and that of the script above...not surprising, considering that Zope's default error screen also uses the same two variables.

All For One...

You can also write specific exception handlers for different types of exceptions, by noting the exception type within the <dtml-except> tag. Consider the following handler, which only handles KeyError exceptions:

<dtml-try>
<dtml-var CheckThisOut>
<dtml-except KeyError>
Undefined variable - <i><dtml-var error_value></i>
</dtml-try>

Here's the output:

Undefined variable - CheckThisOut

Of course, this handler will now only restrict its activities to KeyError exceptions. All other errors will be routed via the default Zope error handler.

You can trap more than one exception, and handle each one in a different way, by using multiple <dtml-except> statements within a single <dtml-try> block.

<dtml-try>
execute this block

<dtml-except err1>
execute this block if exception "err1" is raised

<dtml-except err2>
execute this block if exception "err2" is raised

... and so on ...

<dtml-else>
execute this block if no exceptions are raised

</dtml-try>

If an exception is encountered while running the code within the <dtml-try> block, Zope stops execution of the <dtml-try> block at that point and begins checking each <dtml-except> block to see if there is a handler for the exception. If a handler is found, the code within the appropriate <dtml-except> block is executed; if not, control either moves to the parent <dtml-try> block, if one exists, or to the default handler.

Once the <dtml-try> block has been executed and assuming that the program has not been terminated, the lines following the <dtml-try> block are executed.

In case you're wondering, the <dtml-else> branch in the block above is executed only if no exceptions are raised. It's optional, though, so only include it if you need to.

Take a look at the next example, which demonstrates how this works:

<dtml-try>

<dtml-var alpha> divided by <dtml-var beta> is <dtml-var expr="_.int(alpha)/_.int(beta)" >.<br>

<dtml-except KeyError>
<b>No value assigned to the variable: <dtml-var error_value></b>

<dtml-except ZeroDivisionError>
<b>Division by zero</b>

<dtml-except ValueError>
<b>Illegal value: <dtml-var error_value></b>

<dtml-else>
<i>No errors encountered.</i>

</dtml-try>

Save this code in a DTML Document named "ExceptionallyYours", and test it by passing it values for the variables "alpha" and "beta" on the URL string, via the GET method (you can also use a form and submit values for these variables via POST). For example, if you access the URL

http://localhost/ExceptionallyYours?alpha=12&beta=1

you'll see

12 divided by 1 is 12.
No errors encountered.

But look what happens when you try

http://localhost/ExceptionallyYours

Since no values have been passed for the variables, Zope barfs with a KeyError, which is caught by the exception handler, and the following message is displayed:

No value assigned to the variable: alpha

What about if you pass an illegal value, like a very large integer? Try the following URL,

http://localhost/ExceptionallyYours?alpha=12732132132&beta=12

and then see what Zope says:

Illegal value: int() literal too large: 12732132132

Finally, try attempting division by zero,

http://localhost/ExceptionallyYours?alpha=12&beta=0

and watch as Zope generates a ZeroDivisionError, and an appropriate message.

Division by zero

If you'd like to save yourself some keystrokes, you also have the option of handling multiple exceptions within a single <dtml-except> block, by specifying a list of exception types separated by spaces. Consider the following rewrite of the previous example, which uses the same exception handler for both ZeroDivisionError and ValueError exceptions:

<dtml-try>

<dtml-var alpha> divided by <dtml-var beta> is <dtml-var expr="_.int(alpha)/_.int(beta)" >.<br>

<dtml-except KeyError>
<b>No value assigned to the variable: <dtml-var error_value></b>

<dtml-except ValueError ZeroDivisionError>
<b>Illegal value: <dtml-var error_value></b>

<dtml-else>
<i>No errors encountered.</i>

</dtml-try>

...And One For All

Now, the <dtml-try> statement can only deal with exceptions that it knows about. What about the ones the developer can't predict?

DTML also allows you to specify a catch-all exception handler, one which handles any type of exception generated - simply omit the exception name from the <dtml-except> statement. The following code snippet illustrates this technique:

<dtml-try>

<dtml-var alpha> divided by <dtml-var beta> is <dtml-var expr="_.int(alpha)/_.int(beta)" >.<br>

<dtml-except>
<b>Something bad happened. Call 911.</b>

</dtml-try>

You can even combine this with exception handlers for specific exceptions.

<dtml-try>

<dtml-var alpha> divided by <dtml-var beta> is <dtml-var expr="_.int(alpha)/_.int(beta)" >.<br>

<dtml-except KeyError>
<b>Missing variable.</b>

<dtml-except ValueError>
<b>Illegal value.</b>

<dtml-except>
<b>Something bad happened. Call 911.</b>

</dtml-try>

This way, when an exception occurs, Zope will first check to see if an exception handler has been defined for that exception type. If so, the appropriate handler is invoked; if not, the exception is routed to the catch-all handler, which displays a generic error message.

The Final Solution

Most of what you've just learned also applies to DTML's other exception-handling construct, the "try-finally" statement. The "try-finally" statement block differs from "try-except-else" in that it merely detects errors; it does not provide for a mechanism to resolve them. It is typically used to ensure that certain statements are always executed when an error (regardless of type) is encountered.

Here's what it looks like:

<dtml-try>
execute this block

<dtml-finally>
execute this block if exceptions are generated

</dtml-try>

If an exception is encountered when running the code within the <dtml-try> block, Zope will stop execution at that point; jump to the <dtml-finally> block; execute the statements within it; and then pass the exception upwards, to the parent <dtml-try> block, if one exists, or to the default handler, which terminates the program.

Take a look at the next example to see how this works:

<dtml-try>

<dtml-try>
<dtml-var CheckThisOut>

<dtml-except KeyError>
<b>No value assigned to the variable: <dtml-var error_value><br></b>

</dtml-try>

<dtml-finally>
<i>Goodbye!</i>

</dtml-try>

Here's the output:

No value assigned to the variable: CheckThisOut
Goodbye!

It's important to note that the code in the <dtml-finally> block - actually part of the outer <dtml-try> block - will be executed even if errors are encountered in the inner <dtml-try> block.

Raising The Bar

Thus far, you've been working with DTML's built-in exceptions, which can handle most logical or syntactical expressions. However, DTML also allows you to get creative with exceptions, by generating your own custom exceptions if the need arises.

This is accomplished via the <dtml-raise> tag, used to raise errors which can be detected and resolved by the <dtml-try> family of exception handlers. The <dtml-raise> tag needs to be passed an exception name, and an optional descriptive string. When the exception is raised, this exception name and description will be made available to the defined exception handler.

Consider the following script, which demonstrates:

<dtml-let name="'John Doe'"></dtml-let>

<dtml-if expr="name != 'neo'">

<dtml-raise type="AccessError">
Intruder detected. Closing all portals.
</dtml-raise>

</dtml-if>

In this case, Zope will raise the user-defined exception, AccessError, whenever the conditional expression specified evaluates as false. This exception may be caught by a <dtml-try> block, or by the default error handler (as demonstrated in the output screenshot below).

Pre-Packaged Python

There are a number of standard exceptions are built into Python (and, therefore, Zope and DTML). Here they are:

IOError - generated when an I/O operation fails;

ImportError - generated when a module import fails;

IndexError - generated when an attempt is made to access a non-existent element index;

KeyError - generated when an attempt is made to access a non-existent dictionary key;

MemoryError - generated when an out-of-memory error occurs;

NameError - generated when an attempt is made to access a non-existent variable;

SyntaxError - generated when the interpreter finds a syntax error;

TypeError - generated when an attempt is made to run an operation on an incompatible object type;

ZeroDivisionError - generated when an attempt is made to divide by zero.

For a complete list, take a look at http://www.python.org/doc/current/lib/module-exceptions.html

Endzone

And that just about covers it. In this article, I took you on a guided tour of exception handling in DTML, demonstrating how the <dtml-try> and <dtml-except> blocks can be used to trap and resolve errors in script execution. I showed you how the <dtml-except> statement could be used to handle different exceptions differently, or be set up to catch all errors in a generic manner.

I also explained a variant, the <dtml-finally> statement, and demonstrated how it could be nested within a set of <dtml-try> blocks to trigger specific code segments whenever an exception occurs. Finally, I showed you how to create your own custom exceptions via <dtml-raise>, and provided you with a quick look at some of the built-in exceptions that come with Zope.

In case you'd like to learn more about these topics, you should consider visiting the following links:

Exception handling in Zope, at http://www.zope.org/Documentation/ZopeBook/AdvDTML.stx

Exception handling in Python, at http://www.melonfire.com/community/columns/trog/article.php?id=84

Reference material for the various exception handling tags, at http://www.zope.org/Documentation/ZopeBook/AppendixA.stx

A complete list of Python exceptions, at http://www.python.org/doc/current/lib/module-exceptions.html

Until next time...be good!

Note: All examples in this article have been tested on Linux/i586 with Zope 2.5.0. Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!

This article was first published on21 Jun 2002.