Python: Best practice to report an error during closing a resource

Viewed 51

What is the best practice in Python for reporting an error which occurs when closing a resource?

In particular, if I implement __enter__ and __exit__, I can use

with my_library.connect(endpoint) as connection:
   connection.store_data("...")

But what should I do if closing my connection and persisting the changes fails, e.g. due to a network outage? I know that I could technically raise an error from within __exit__ but is this best practice/idiomatic in Python? Or should I, e.g., provide a separate persistChanges method, let the __exit__ swallow all errors, and then write in the documentation "if you don't call persistChanges you might lose your changes in error cases"?

My specific use case is: I am providing a Python API to other developers, and I wonder how to handle this case of "error on closing the resource" such that my API follows Python best practices/meets the expectations of Python devs using my library.

1 Answers

I would recommend making a custom error/warning class for your library. this can be very, very simple. There already exists a set of built-in exceptions that you can extend from. Based on your description above, I would recommend extending the RuntimeError like this:

class MyLibraryConnectionError(RuntimeError):
    pass

or, if you want to only throw a warning, using the ResourceWarning like this:

class MyLibraryConnectionWarning(ResourceWarning):
    pass

There is also the RuntimeWarning that could be extended for similar effect.

If you feel that ResourceWarning, RuntimeWarning, and RuntimeError don't accurately describe the exception, you can also just have them inherit directly from Exception or Warning, depending on whether you want them to only be flagged in Developer mode(Warning), or if you want the full exception functionality.

You can throw these like any other exception:

throw MyLibraryConnectionError("The underlying resource failed to close")
throw MyLibraryConnectionWarning("The underlying resource failed to close")

or even catch your dependency's thrown exception:

def __exit__(...):
    try:
        # potentially dangerous code with connections
        underlyingConnection.close()
    except TheErrorYouSeeThrown as e: # you can probably make this Exception instead of TheErrorYouSeeThrown. The more specific the better, so you don't accidentally catch random errors you didn't mean to.
        throw MyLibraryConnectionError(e.message) # or whatever this needs to be, depending on the thrown exception type

Then your users could implement like so:

try:
    with my_library.connect(endpoint) as connection:
        connection.store_data("...")
except my_library.MyLibraryConnectionError as e:
    # handle appropriately, or ignore
except Exception as e:
    # Handle other errors that happen, that your library doesn't cause.
Related