"""Tools for writing automatic `__init__.py` files.
This allows modules to define a minimal `__init__.py` file with some
flexibility for delaying imports (for performance) and automatically
deducing the local modules. The function :func:`get_imports` does all
the work and assumes that the module has the following structure::
mod/__init__.py
_mod.py
mod1.py
...
mod2/__init__.py
...
...
_mod3/...
tests/...
These are treated as follows:
`__init__.py` :
Should be based on the following skeleton::
from mmf.utils.init import get_imports as __get_imports
__delay__ = set([]) # Add modules to delay here.
__all__, __doc__, __imports = __get_imports(globals())
exec(__imports) # Remove this to delay everything.
del __get_imports, __imports, __delay__
If you want to suppress loading of modules (typically for
performance) until they are explicitly requested, then add them
to the `__delay__` set. If you want to suppress the loading of
all sub modules, then add the string `'__all__'` to `__delay__`:
this will still import everything in `_mod.py`. If you want to
suppress this also, then don't execute `__imports` and directly
import the modules you want.
`_mod.py` :
This defines all the members directly defined in the module. The
following special attributes are used to control the behaviour
of `__imports` returned by :func:`get_imports`:
`__all__` :
If provided, then this is used to define the exported names.
Only these names will be imported into the module `mod` when
`__imports` is executed.
`__dict__` :
Used if `__all__` is not provided. This is consistent with the
default behaviour of `from mod_ import *`.
`__doc__` :
The docstring for the module is also obtained from the docstring
of this file. Note that some automatic documentation is added
to the bottom of this for the members that are explicitly
included.
`__delay__` :
If this list is defined, these modules are not explicitly
imported. May also contain the special symbol `'__all__'`
which suppress loading all submodules.
The file `_mod` is not intended to be exported on it's own,
which is why we give a "private" name. If you need access to
the private module, you can get it from
`sys.modules['mod._mod']` or set the module flag
:attr:`_INCLUDE_PRIVATE_MODS` to `True`. (This behaviour is
implemented by first importing `mod._mod` and then selectively
deleting or keeping the attribute `_mod`.
The module `_mod` will be reloaded if the top-level module is
reloaded. Thus calling ``reload(mod)`` will trigger a call to
``reload(mod._mod)`` even though this cannot be effected by the
user (since `_mod` is not in the `mod` namespace).
`mod1`, `mod2`, ... :
These are standard modules and will imported in the usual way as
`import mod1` etc. when `__imports` is executed unless included in
`__delay__`.
`_mod3` :
Names stating with an underscore are private and will be ignored
unless explicitly included in `__all__`. Also ignored are names
with '~' and '#' which represent temporary files.
`tests` :
These will be ignored.
Attributes
----------
_INCLUDE_PRIVATE_MOD : bool
Specifies whether or not the private module '_mod' is
included in the module or not. if `True`, then it is
accessible as `mod._mod` and the `__imports` will include::
import _mod
from _mod import *
If `False`, then only the second line is used and `_mod` is not
part of the module (however, it will be accessible from
`sys.modules['mod._mod']` with a fully qualified module name.
"""
from __future__ import division
__all__ = ['get_imports']
import sys
import os.path
import inspect
_INCLUDE_PRIVATE_MODS = False
[docs]def get_imports(globals):
"""Return `(__all__, __doc__, __imports)` describing the
dynamically generated list of imports. The `__init__.py` should
look like::
from mmf.utils.init import get_imports as __get_imports
__delay__ = set([])
__all__, __doc__, __imports = __get_imports(globals())
exec(__imports) # Remove this to delay everything.
del __get_imports, __imports, __delay__
Parameters
----------
globals : dict
Global names. Used so that :func:`__import__` will behave as
in the original module. Also, `__name__` and `__path__` are used.
Returns
-------
__all__ : [str]
List of imports to use when `from mod import *` is called.
__doc__ : str
Docstring for module.
__imports : str
This string should be executed in the context of the module to
effect the required imports.
Notes
-----
This assumes the following structure::
mod/__init__.py
_mod.py
mod1.py
...
mod2/__init__.py
...
...
_mod3/...
tests/...
These are treated as follows
`_mod.py` :
This defines all the members directly defined in the module. The
following special methods are used:
`__all__` :
If provided, then it is used.
`__dict__` :
Used if `__all__` is not provided. This is consistent with the
behaviour of `from mod_ import *` which is used.
`__doc__` :
The docstring for the module is also obtained from the docstring
of this file. Note that some automatic documentation is added
to the bottom of this for the members included.
`__delay__` :
If this list is defined, these modules are not explicitly
imported.
It is not intended to be exported on it's own, which is why we
give a "private" name. If you need access to the private
module, you need to get it from `sys.modules` (or set the
module flag :attr:`_INCLUDE_PRIVATE_MODS` to `True`).
This module will be reloaded if the top-level module is
reloaded. Thus calling ``reload(mod)`` will trigger a call to
``reload(mod._mod)`` even though this cannot be effected by the
user (since `_mod` is not in the `mod` namespace).
`mod1`, `mod2`, ... :
These will be imported unless included in `__delay__`.
`_mod3` :
Names stating with an underscore are private and will be ignored.
Also ignored are names with '~' and '#' which represent temporary files.
`tests` :
These will be ignored.
"""
name = globals['__name__']
path = globals['__path__']
__doc__ = globals['__doc__']
__delay__ = set(globals.get('__delay__', set([])))
modname = name.rsplit('.')[-1]
modfilename = '_' + modname
modfile = modfilename + ".py"
imports = []
# Process modfile
import_code = """
import %(modfilename)s
try:
if __is_reload: # This is not defined on first import
reload(%(modfilename)s)
except NameError:
__is_reload = True
from %(modfilename)s import *
if %(del_local_mod)s:
del %(modfilename)s"""
if os.path.exists(os.path.join(path[0], modfile)):
_m = __import__(modfilename, globals=globals)
__delay__.update(getattr(_m, '__delay__', []))
mod_doc = getattr(_m, '__doc__', "")
if __doc__ is None:
__doc__ = mod_doc
else:
__doc__ = "\n\n".join([__doc__, mod_doc])
# Make a copy here - we don't want to mutate the original!
__all__ = list(getattr(_m, '__all__',
getattr(_m, '__dict__', {}).keys()))
del_local_mod = True
if sys.argv[0].endswith('nosetests') or _INCLUDE_PRIVATE_MODS:
# Special case: do not delete the mod if using nosetests! See
# issue 11.
del_local_mod = False
imports.append(import_code % dict(
modfilename=modfilename,
del_local_mod=str(del_local_mod)))
if hasattr(sys.modules[name], modfilename):
delattr(sys.modules[name], modfilename)
if del_local_mod:
# Now remove _modfilename from the end of __module__ for classes so
# that the automatic documentation etc. points to the correct place.
_objs = [getattr(_m, _class_name, None) for _class_name in __all__]
#_classes = [_c for _c in _objs if inspect.isclass(_c)]
_tail = "." + modfilename
for _o in _objs:
if getattr(_o, '__module__', "").endswith(_tail):
_o.__module__ = _o.__module__[:-len(_tail)]
else:
if __doc__ is None:
__doc__ = ""
__all__ = []
# Process modules
modules = [_f for _f in os.listdir(path[0])
if _f != "tests"
and _f != "setup.py"
and not _f.startswith('_')
and not '#' in _f and not '~' in _f
and not _f == modfile
and (_f.endswith(".py") # Modules
or os.path.exists( # Directories
os.path.join(path[0], _f, "__init__.py")))
]
modules.sort()
for _f in modules:
if _f.endswith(".py"):
_m = _f[:-3]
else:
_m = _f
if _m in __all__:
# Fail if there are any name clashes.
raise ImportError("Symbol %s overridden by %s" % ( _m, _f))
else:
__all__.append(_m)
if _m not in __delay__ and '__all__' not in __delay__:
imports.append("import %s" % _m)
return __all__, __doc__, "\n".join(imports)