Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for structuring module exceptions in Python3

Suppose I have a project with a folder structure like so.

/project
    __init__.py
    main.py
    /__helpers
        __init__.py
        helpers.py
        ...

The module helpers.py defines some exception and contains some method that raises that exception.

# /project/__helpers/helpers.py

class HelperException(Exception):
    pass

def some_function_that_raises():
    raise HelperException

On the other hand my main.py module defines its own exceptions and imports methods that may raise an exception from helpers.py.

# /projects/main.py

from project.__helpers.helpers import some_function_that_raises

class MainException(Exception):
    pass

Now, I do not want users to have to do from project.__helpers.helpers import HelperException if they want to catch that exception. It would make more sense to be able to import the exception from the public module that is raising it.

But I cannot just move HelperException to main.py, which would create a circular import.

What would be the best way to allow users to import all exceptions from main.py while those being raised in /__helpers?

like image 646
Olivier Melançon Avatar asked Jan 04 '18 23:01

Olivier Melançon


People also ask

Why is it best practice to have multiple Except?

Why is it best practice to have multiple except statements with each type of error labeled correctly? Ensure the error is caught so the program will terminate In order to know what type of error was thrown and the.

How do you handle exceptions in Python?

In Python, exceptions can be handled using a try statement. The critical operation which can raise an exception is placed inside the try clause. The code that handles the exceptions is written in the except clause. We can thus choose what operations to perform once we have caught the exception.

How many ways can you handle exceptions in Python?

When a Python code throws an exception, it has two options: handle the exception immediately or stop and quit.

What are the best practices for exception handling in Python?

Some of the best practices for exception handling in python are given below: Exceptions are better than returning error status codes. We have to handle exceptions in Python as the whole language core and standard libraries throw exceptions. Elegantly handled exceptions are any day preferable to error codes and trace backs.

Can exceptions be used for flow control in Python?

But yes, exceptions can and are used for flow control in Python. It might be a bad practice to do so without abstracting over the mechanics (like is done for the iterator protocol by using next method and for statement), though.

How do I create my own exceptions in Python?

Python also allows you to create your own exceptions by deriving classes from the standard built-in exceptions. Here is an example related to RuntimeError. Here, a class is created that is subclassed from RuntimeError. This is useful when you need to display more specific information when an exception is caught.

How to differentiate application-level exceptions from other Python exceptions?

To differentiate application-level exceptions from other python exceptions, we create a specialized class, which inherits from python’s Exception class. Throw it when there is an application-level error, and catch it in the main code.


1 Answers

Here is the solution I came up with.

The idea is basically to put all the exceptions in one file from which they can be imported and then import them all in main.py. To make everything clean and explicit, we finally define the __all__ attribute of the module.

Here is the new file structure

/project
    __init__.py
    main.py
    /__helpers
        __init__.py
        exceptions.py
        helpers.py
        ...

Here is the exceptions.py file.

# /project/__helpers/exceptions.py

class MainException(Exception):
    pass

# Note that this also allows us to inherit between exceptions
class HelperException(MainException):
    pass

Then we can import exceptions from that file without risk of circular dependency.

And finally we define __all__ in main.py to make it explicit that the exceptions are to be imported.

# /projects/main.py

from project.__helpers.helpers import some_function_that_raises
from project.__helpers.exceptions import MainException, HelperException

__all__ = ['MainException', 'HelperException', ...]

Just a reminder the __all__ attribute defines what will be imported if one was to do from project import *. So this both extends the desired behavior to import star and makes it explicit that we want the exceptions to be imported from that file.

Also note that some IDEs will even treat 'HelperException' being in __all__ as a reference to HelperException and will not bother you from having an unused import. This is what leads me to think this is the correct approach.

like image 89
Olivier Melançon Avatar answered Oct 27 '22 15:10

Olivier Melançon