Search code examples
pythonpython-3.xpython-importpython-module

Should I hide imports of dependencies in a python module from the __dir__ method?


I'm making a Python distribution package with a single import package which has multiple modules (One folder, multiple files). The package has dependencies which are imported in the modules. When a user imports a module from the import package they also get access to the dependencies which are imported outside functions. I have found a work around, but I'm not sure if it is a good idea.

Option A for the module joke.py

from markdown import markdown

def joke():
    return markdown("The Funniest Joke in the World")

Option B for the module joke.py

def joke():
    from markdown import markdown
    return markdown("The Funniest Joke in the World")

Usage of A in interactive mode

>>> import joke
>>> joke.joke()
'The Funniest Joke in the World'
>>> dir(joke)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'joke', 'markdown']
>>> joke.markdown("Not funny")
'<p>Not funny</p>'

Usage of B in interactive mode

>>> import joke
>>> dir(joke)
['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'joke', 'markdown']
>>> joke.markdown("Not funny")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'joke' has no attribute 'markdown' 

Question

I don't want to expose the dependencies to the users, which means option B is better. However, the code becomes messy if all imports needs to be inside classes or functions. Is there a better option for achieving the functionality in B without making the imports messy?

I have looked at some packages to see if I can locate any of theirs dependencies using dir() but feel a bit overwhelmed since I do not know which dependencies they have or how to find their dependencies.


Solution

  • Starting with Python 3.7 you can define a module-level __dir__ function which will be used for generating results for dir(module):

    def __dir__():
        return ['joke']
    

    This can be combined with __all__ which handles from module import * imports:

    __all__ = ['joke']
    
    def __dir__():
        return __all__
    

    Note that users will still be able to access these dependency packages, i.e. joke.markdown still works. If you really want to restrict this, you can also define a module-level __getattr__:

    def __getattr__(name):
        if name not in __all__:
            raise AttributeError(name)
        return globals()[name]