6

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?

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73

1 Answers1

3

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.

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73