Section 10.9. Creating Exceptions


10.9. *Creating Exceptions

Although the set of standard exceptions is fairly wide-ranging, it may be advantageous to create your own exceptions. One situation is where you would like additional information from what a standard or module-specific exception provides. We will present two examples, both related to IOError.

IOError is a generic exception used for input/output problems, which may arise from invalid file access or other forms of communication. Suppose we wanted to be more specific in terms of identifying the source of the problem. For example, for file errors, we want to have a FileError exception that behaves like IOError, but with a name that has more meaning when performing file operations.

Another exception we will look at is related to network programming with sockets. The exception generated by the socket module is called socket.error and is not a built-in exception. It is subclassed from the generic Exception exception. However, the exception arguments from socket.error closely resemble those of IOError exceptions, so we are going to define a new exception called NetworkError, which subclasses from IOError but contains at least the information provided by socket.error.

Like classes and object-oriented programming, we have not formally covered network programming at this stage, but skip ahead to Chapter 16 if you need to.

We now present a module called myexc.py with our newly customized exceptions FileError and NetworkError. The code is in Example 10.2.

Example 10.2. Creating Exceptions (myexc.py)

This module defines two new exceptions,FileError and NetworkError, as well as reimplements more diagnostic versions of open() [myopen()] and socket.connect() [myconnect()]. Also included is a test function [test()] that is run if this module is executed directly.

1   #!/usr/bin/env python 2 3   import os, socket, errno, types, tempfile 4 5   class NetworkError(IOError): 6       pass 7 8   class FileError(IOError): 9       pass 10 11  def updArgs(args, newarg=None): 12      if isinstance(args, IOError): 13          myargs = [] 14          myargs.extend([arg for arg in args]) 15      else: 16          myargs = list(args) 17 18      if newarg: 19          myargs.append(newarg) 20 21      return tuple(myargs) 22 23  def fileArgs(file, mode, args): 24      if args[0] == errno.EACCES and \ 25              'access' in dir(os): 26          perms = '' 27          permd = { 'r': os.R_OK, 'w': os.W_OK, 28              'x': os.X_OK} 29          pkeys = permd.keys() 30          pkeys.sort() 31          pkeys.reverse() 32 33          for eachPerm in 'rwx': 34              if os.access(file, permd[eachPerm]): 35                  perms += eachPerm 36              else: 37                  perms += '-' 38 39          if isinstance(args, IOError): 40              myargs = [] 41              myargs.extend([arg for arg in args]) 42         else: 43              myargs = list(args) 44 45          myargs[1] = "'%s' %s (perms: '%s')" % \ 46              (mode, myargs[1], perms) 47 48          myargs.append(args.filename) 49 50       else: 51           myargs = args 52 53      return tuple(myargs) 54 55  def myconnect(sock, host, port): 56      try: 57          sock.connect((host, port)) 58 59      except socket.error, args: 60          myargs = updArgs(args)    # conv inst2tuple 61          if len(myargs) == 1:       # no #s on some errs 62              myargs = (errno.ENXIO, myargs[0]) 63 64          raise NetworkError, \ 65              updArgs(myargs, host + ':' + str(port)) 66 67  def myopen(file, mode='r'): 68      try: 69          fo = open(file, mode) 70      except IOError, args: 71          raise FileError, fileArgs(file, mode, args) 72 73      return fo 74 75  def testfile(): 76 77      file = mktemp() 78      f = open(file, 'w') 79      f.close() 80 81      for eachTest in ((0, 'r'), (0100, 'r'), 82              (0400, 'w'), (0500, 'w')): 83          try: 84              os.chmod(file, eachTest[0]) 85             f = myopen(file, eachTest[1]) 86 87          except FileError, args: 88              print "%s: %s" % \ 89                  (args.__class__.__name__, args) 90         else: 91              print file, "opened ok... perm ignored" 92              f.close() 93 94      os.chmod(file, 0777)    # enable all perms 95      os.unlink(file) 96 97  def testnet(): 98      s = socket.socket(socket.AF_INET, 99          socket.SOCK_STREAM) 100 101     for eachHost in ('deli', 'www'): 102       try: 103          myconnect(s, 'deli', 8080) 104       except NetworkError, args: 105          print "%s: %s" % \ 106              (args.__class__.__name__, args) 107 108 if __name__ == '__main__': 109     testfile() 110     testnet()

Lines 13

The Unix startup script and importation of the socket, os, errno, types, and tempfile modules help us start this module.

Lines 59

Believe it or not, these five lines make up our new exceptions. Not just one, but both of them. Unless new functionality is going to be introduced, creating a new exception is just a matter of subclassing from an already existing exception. In our case, that would be IOError. EnvironmentError, from which IOError is derived, would also work, but we wanted to convey that our exceptions were definitely I/O-related.

We chose IOError because it provides two arguments, an error number and an error string. File-related [uses open()] IOError exceptions even support a third argument that is not part of the main set of exception arguments, and that would be the filename. Special handling is done for this third argument, which lives outside the main tuple pair and has the name filename.

Lines 1121

The entire purpose of the updArgs() function is to "update" the exception arguments. What we mean here is that the original exception is going to provide us a set of arguments. We want to take these arguments and make them part of our new exception, perhaps embellishing or adding a third argument (which is not added if nothing is givenNone is a default argument, which we will study in the next chapter). Our goal is to provide the more informative details to the user so that if and when errors occur, the problems can be tracked down as quickly as possible.

Lines 2353

The fileArgs() function is used only by myopen() (see below). In particular, we are seeking error EACCES, which represents "permission denied." We pass all other IOError exceptions along without modification (lines 54 - 55). If you are curious about ENXIO, EACCES, and other system error numbers, you can hunt them down by starting at file /usr/include/sys/errno.h on a Unix system, or C:\Msdev\include\Errno.h if you are using Visual C++ on Windows.

In line 27, we are also checking to make sure that the machine we are using supports the os.access() function, which helps you check what kind of file permissions you have for any particular file. We do not proceed unless we receive both a permission error as well as the ability to check what kind of permissions we have. If all checks out, we set up a dictionary to help us build a string indicating the permissions we have on our file.

The Unix file system uses explicit file permissions for the user, group (more than one user can belong to a group), and other (any user other than the owner or someone in the same group as the owner) in read, write, and execute (`r', `w', `x') order. Windows supports some of these permissions.

Now it is time to build the permission string. If the file has a permission, its corresponding letter shows up in the string, otherwise a dash ( - ) appears. For example, a string of "rw-" means that you have read and write access to it. If the string reads "r-x", you have only read and execute access; "---" means no permission at all.

After the permission string has been constructed, we create a temporary argument list. We then alter the error string to contain the permission string, something that standard IOError exception does not provide. "Permission denied" sometimes seems silly if the system does not tell you what permissions you have to correct the problem. The reason, of course, is security. When intruders do not have permission to access something, the last thing you want them to see is what the file permissions are, hence the dilemma. However, our example here is merely an exercise, so we allow for the temporary "breach of security." The point is to verify whether or not the os.chmod()functions call affected file permissions the way they are supposed to.

The final thing we do is to add the filename to our argument list and return the set of arguments as a tuple.

Lines 5565

Our new myconnect() function simply wraps the standard socket method connect()to provide an IOError -type exception if the network connection fails. Unlike the general socket.error exception, we also provide the hostname and port number as an added value to the programmer.

For those new to network programming, a hostname and port number pair are analogous to an area code and telephone number when you are trying to contact someone. In this case, we are trying to contact a program running on the remote host, presumably a server of some sort; therefore, we require the host's name and the port number that the server is listening on.

When a failure occurs, the error number and error string are quite helpful, but it would be even more helpful to have the exact host-port combination as well, since this pair may be dynamically generated or retrieved from some database or name service. That is the value-add we are bestowing on our version of connect(). Another issue arises when a host cannot be found. There is no direct error number given to us by the socket.error exception, so to make it conform to the IOError protocol of providing an error number-error string pair, we find the closest error number that matches. We choose ENXIO.

Lines 6773

Like its sibling myconnect(), myopen()also wraps around an existing piece of code. Here, we have the open() function. Our handler catches only IOErrorexceptions. All others will pass through and on up to the next level (when no handler is found for them). Once an IOError is caught, we raise our own error and customized arguments as returned from fileArgs().

Lines 7595

We shall perform the file testing first, here using the testfile() function. In order to begin, we need to create a test file that we can manipulate by changing its permissions to generate permission errors. The tempfile module contains code to create temporary file names or temporary files themselves. We just need the name for now and use our new myopen() function to create an empty file. Note that if an error occurred here, there would be no handler, and our program would terminate fatallythe test program should not continue if we cannot even create a test file.

Our test uses four different permission configurations. A zero means no permissions at all, 0100 means execute-only, 0400 indicates read-only, and 0500 means read- and execute-only (0400 + 0100). In all cases, we will attempt to open a file with an invalid mode. The os.chmod()function is responsible for updating a file's permission modes. (Note: These permissions all have a leading zero in front, indicating that they are octal [base 8] numbers.)

If an error occurs, we want to display diagnostic information similar to the way the Python interpreter performs the same task when uncaught exceptions occur, and that is giving the exception name followed by its arguments. The __class__ special variable represents the class object from which an instance was created. Rather than displaying the entire class name here (myexc.FileError), we use the class object's __name__ variable to just display the class name (FileError), which is also what you see from the interpreter in an unhandled error situation. Then the arguments that we arduously put together in our wrapper functions follow.

If the file opened successfully, that means the permissions were ignored for some reason. We indicate this with a diagnostic message and close the file. Once all tests have been completed, we enable all permissions for the file and remove it with the os.unlink() function. (os.remove() is equivalent to os.unlink().)

Lines 97106

The next section of code (testnet()) tests our NetworkError exception. A socket is a communication endpoint with which to establish contact with another host. We create such an object, then use it in an attempt to connect to a host with no server to accept our connect request and a host not on our network.

Lines 108110

We want to execute our test*() functions only when invoking this script directly, and that is what the code here does. Most of the scripts given in this text utilize the same format.

Running this script on a Unix-flavored box, we get the following output:

$myexc.py FileError: [Errno 13] 'r' Permission denied (perms: '---'):  '/usr/tmp/@18908.1' FileError: [Errno 13] 'r' Permission denied (perms: '--x'):  '/usr/tmp/@18908.1' FileError: [Errno 13] 'w' Permission denied (perms: 'r--'):  '/usr/tmp/@18908.1' FileError: [Errno 13] 'w' Permission denied (perms: 'r-x'):  '/usr/tmp/@18908.1' NetworkError: [Errno 146] Connection refused: 'deli:8080' NetworkError: [Errno 6] host not found: 'www:8080'


The results are slightly different on a Win32 machine:

D:\python> python myexc.py C:\WINDOWS\TEMP\~-195619-1 opened ok... perms ignored C:\WINDOWS\TEMP\~-195619-1 opened ok... perms ignored FileError: [Errno 13] 'w' Permission denied (perms: 'r-x'):  'C:\\WINDOWS\\TEMP\\~-195619-1' FileError: [Errno 13] 'w' Permission denied (perms: 'r-x'):  'C:\\WINDOWS\\TEMP\\~-195619-1' NetworkError: [Errno 10061] winsock error: 'deli:8080' NetworkError: [Errno 6] host not found: 'www:8080'


You will notice that Windows does not support read permissions on files, which is the reason why the first two file open attempts succeeded. Your mileage may vary (YMMV) on your own machine and operating system.



Core Python Programming
Core Python Programming (2nd Edition)
ISBN: 0132269937
EAN: 2147483647
Year: 2004
Pages: 334
Authors: Wesley J Chun

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net