Chapter 5 ■ Network Data aND Network errors
90
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
...
urllib.error.URLError: <urlopen error [Errno -2] Name or service not known>
So, depending on the protocol implementation you are using, you might have to deal only with exceptions
specific to that protocol, or you might have to deal with both protocol-specific exceptions and with raw socket errors.
Consult the documentation carefully if you are in doubt about the approach taken by a particular library. For the
major packages that I cover in subsequent chapters of this book, I have tried to provide insets that list the possible
exceptions to which each library can subject your code.
Of course, you can always fire up the library in question, provide it with a nonexistent hostname, or even run it
when disconnected from the network and see what kind of exception comes out.
When writing a network program, how should you handle all of the errors that can occur? Of course, this question
is not really specific to networking. All sorts of Python programs have to handle exceptions, and the techniques that I
discuss briefly in this chapter are applicable to many other kinds of programs. Your approach will differ whether you
are packaging up exceptions for processing by other programmers who call your API or whether you are intercepting
exceptions to report them appropriately to an end user.
Raising More Specific Exceptions
There are two approaches to delivering exceptions to the users of an API that you are writing. Of course, in many cases
you will be the only customer of a module or routine you are writing. However, it is still worthwhile to think of your
future self as a customer who will have forgotten nearly everything about this module and will very much appreciate
simplicity and clarity in its approach toward exceptions.
One option is not to handle network exceptions at all. They will then be visible for processing by the caller, who
can catch or report them as they choose. This approach is a good match for networking routines that are fairly low
level, where the caller can vividly picture why you are setting up a socket and why its setup or use might have run
into an error. Only if the mapping between API callables and low-level networking actions is clear will the developer
writing the calling code expect a network error.
The other approach is wrapping the network errors in an exception of your own. This can be much easier on
authors who know little about how you implement your routines because their code can now catch exceptions
specific to the operations your code performs without having to know the details of how you use sockets. Custom
exceptions also give you the opportunity to craft error messages that describe exactly what your library was trying to
accomplish when it ran afoul of the network.
If, for example, you write a little mycopy() method that copies a file from one remote machine to another, a
socket.error will not help the caller know whether the error was with the connection to the source or the destination
machine or whether it was some other problem altogether. In this case, it could be much better to define your own
exceptions—perhaps SourceError and DestinationError—which have a tight semantic relationship to your API. You
can always include the original socket error through raise...from exception chaining, in case some users of your API
will want to investigate further.
class DestinationError(Exception):
def str(self):
return '%s: %s' % (self.args[0], self.cause.strerror)
...
try:
host = sock.connect(address)
except socket.error as e:
raise DestinationError('Error connecting to destination') from e