def _find_local_submodules(pkgpath): """ Yields all children submodules in a package (non-recursively) Args: pkgpath (str): path to a package with an __init__.py file Example: >>> pkgpath = static.modname_to_modpath('mkinit') >>> import_paths = dict(_find_local_submodules(pkgpath)) >>> print('import_paths = {!r}'.format(import_paths)) """ # Find all the children modules in this package (non recursive) pkgname = static.modpath_to_modname(pkgpath, check=False) if pkgname is None: raise Exception('cannot import {!r}'.format(pkgpath)) # TODO: # DOES THIS NEED A REWRITE TO HANDLE THE CASE WHEN __init__ does not exist? try: # Hack to grab the root package a, b = static.split_modpath(pkgpath, check=False) root_pkgpath = join(a, b.replace('\\', '/').split('/')[0]) except ValueError: # Assume that the path is the root package if split_modpath fails root_pkgpath = pkgpath for sub_modpath in static.package_modpaths(pkgpath, with_pkg=True, recursive=False, check=False): sub_modname = static.modpath_to_modname(sub_modpath, check=False, relativeto=root_pkgpath) rel_modname = sub_modname[len(pkgname) + 1:] if not rel_modname or rel_modname.startswith('_'): # Skip private modules pass else: yield rel_modname, sub_modpath
def autogen_init( modpath_or_name, submodules=None, respect_all=True, options=None, dry=False, diff=False, recursive=False, ): """ Autogenerates imports for a package __init__.py file. Args: modpath_or_name (PathLike | str): path to or name of a package module. The path should reference the dirname not the __init__.py file. If specified by name, must be findable from the PYTHONPATH. submodules (List[str], default=None): if specified, then only these specific submodules are used in package generation. Otherwise, all non underscore prefixed modules are used. respect_all (bool, default=True): if False the `__all__` attribute is ignored while parsing. options (dict): formatting options; customizes how output is formatted. See `formatting._ensure_options` for defaults. dry (bool, default=False): if True, the autogenerated string is not written recursive (bool, default=False): if True, we will autogenerate init files for all subpackages. Notes: This will partially override the __init__ file. By default everything up to the last comment / __future__ import is preserved, and everything after is overriden. For more fine grained control, you can specify XML-like `# <AUTOGEN_INIT>` and `# </AUTOGEN_INIT>` comments around the volitle area. If specified only the area between these tags will be overwritten. To autogenerate a module on demand, its useful to keep a doctr comment in the __init__ file like this: python -m mkinit <your_module> Example: >>> init_fpath, new_text = autogen_init('mkinit', submodules=None, >>> respect_all=True, >>> dry=True) >>> assert 'autogen_init' in new_text """ logger.info( "Autogenerating __init__ for modpath_or_name={}".format(modpath_or_name) ) modpath = _rectify_to_modpath(modpath_or_name) if recursive: if submodules is not None: raise AssertionError("cannot specify submodules in recursive mode") all_init_fpaths = list( static.package_modpaths(modpath, with_pkg=True, with_mod=False) ) all_init_fpaths = sorted(all_init_fpaths, key=lambda x: x.count(os.sep)) for fpath in reversed(all_init_fpaths): autogen_init( fpath, submodules=None, respect_all=respect_all, options=options, dry=dry, diff=diff, recursive=False, ) else: initstr = static_init( modpath, submodules=submodules, respect_all=respect_all, options=options ) init_fpath, new_text = _insert_autogen_text(modpath, initstr) if dry: logger.info("(DRY) would write updated file: %r" % init_fpath) if diff: # Display difference try: with open(init_fpath, "r") as file: old_text = file.read() except Exception: old_text = "" display_text = difftext( old_text, new_text, colored=True, context_lines=3 ) print(display_text) else: print(new_text) return init_fpath, new_text else: logger.info("writing updated file: %r" % init_fpath) # print(new_text) with open(init_fpath, "w") as file_: file_.write(new_text)