0

I'd like to dynamically load all modules placed into a given directory (modules) and run a specific method in each of them (.main()).

I ran across the following question: How to load all modules in a folder?

and ended up with the following code: my main.py

import modules

if __name__ == "__main__":
    for module in modules.__all__:
        module.main()

The __init__.py in the modules folder look like this:

from os.path import dirname, basename, isfile, join
import glob

modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

However, I get the below error:

'str' object has no attribute 'main'

I believe this is some kind of newbie question, but I cannot figure out how to do things.

E. Jaep
  • 2,095
  • 1
  • 30
  • 56
  • This answer is relevant: https://stackoverflow.com/questions/1707709/list-all-the-modules-that-are-part-of-a-python-package. You should be able to add your .main() to the end of the top answer – Dan Sep 24 '19 at 14:46
  • 1
    You missed some very important point that, sadly, is a bist lost in a long list of comments: "Add `from . import *` after setting `__all__` if you want submodules to be available" – bruno desthuilliers Sep 24 '19 at 14:47
  • @brunodesthuilliers I'm not quite sure I understand what you mean by adding 'from . import *` If I add it to the `__init__.py` file, the error about the module not being found remains. – E. Jaep Sep 24 '19 at 14:56

1 Answers1

0

You need to import the module first

import modules
import importlib

if __name__ == "__main__":
    for module in modules.__all__:
        importlib.import_module(module).main()
tomgalpin
  • 1,943
  • 1
  • 4
  • 12
  • The use of `__import__` should be reserved for when you _really_ need it - the recommanded solution is to use `importlib.import_module()` – bruno desthuilliers Sep 24 '19 at 14:37
  • 1
    surely this only works if the modules directory is on the path...? – Dan Sep 24 '19 at 14:38
  • 1
    Thanks, the main point i was trying to express was that the OP was simply passing the string rather than importing the module itself. Ive edited my answer to reflect the recommended solution. – tomgalpin Sep 24 '19 at 14:39
  • 2
    @tomgalpin that's better but still not ok. The imports should happen in `modules/__init__.py`, not in the calling code (`modules.__all__` should still be a list of strings, but those string should match the available module's attributes so if you have `modules.__all__ = ["foo"]` then `module.foo` should be valid (IOW `module` should have an attribute named "foo"). – bruno desthuilliers Sep 24 '19 at 14:44
  • Tweaking the code from @tomgalpin, I ended up with: `importlib.import_module('modules.'+module)` which is not really elegant but works – E. Jaep Sep 24 '19 at 14:48