Search code examples
pythonpy2app

Using pkgutil.iter_modules with py2app


In my game, I have a lot of different levels, in the following structure:

+ levels/
  - Level1.py
  - Level2.py
  - Level3.py
  - ...

Level1.py contains class Level1, and all the others likewise.

I could import each of my levels thusly:

from levels.Level1 import Level1

but since I have a lot of them, and I want to import them all in the same place, I wrote a load_levels function that sits in levels/__init__.py:

def load_levels(game,window):
  levels = {}
  for loader, mod_name, ispkg in pkgutil.iter_modules(path=__path__):
    #import and initialise level
  return levels

This works great, until I package the application up using py2app. Then pkgutil.iter_modules contains nothing, presumably because it cannot run within a zip directory (__path__ is ['/.../dist/main.app/Contents/Resources/lib/python35.zip/levels'])

What can I do here? Is there a way to configure py2app not to zip the directory, or pkgutil to work within the zip file?


Solution

  • The issue is not that pkgutil isn't working, but that modulegraph doesn't package any of my level files since it can't 'see' them at build time.

    To get around this, you can list levels as a package in setup.py, which will make sure all of its contents are bundled. They will not be compiled, however.

    If you want to achieve the same thing and ensure the files are compiled, one way around it is to make sure __all__ in __init__.py contains all the modules:

    for f in os.listdir(__path__):
      mod = os.path.splitext(f)[0]
      if mod not in ['__init__','__pycache__','level_base']:
        __all__.append(mod)
    

    and then add the module to includes in setup.py.

    The only problem now is that this code won't work from within the packaged app, since __path__ is no longer a real dir, so you will want to put it in a try/except block.