Source code for mkinit.dynamic_mkinit

"""
Dynamically generate the import exec
"""
from os.path import dirname, join, exists
import sys
import multiprocessing
import textwrap


[docs] def dynamic_init(modname, submodules=None, dump=False, verbose=False): """ Main entry point for dynamic mkinit. Dynamically import listed util libraries and their attributes. Create reload_subs function. Using __import__ like this is typically not considered good style However, it is better than import * and this will generate the good file text that can be used when the module is 'frozen" Note: Dynamic mkinit is for initial development and prototyping, and even then it is not recommended. For production it is strongly recommended to use static mkinit instead of dynamic mkinit. Example: >>> # The easiest way to use this in your code is to add these lines >>> # to the module __init__ file >>> from mkinit import dynamic_init >>> execstr = dynamic_init('mkinit') >>> print(execstr) >>> exec(execstr) # xdoc: +SKIP """ if verbose: print("[MKINIT] Running Dynamic Imports for modname=%r " % modname) # Get the module that will be imported into try: module = sys.modules[modname] except Exception: module = __import__(modname) if submodules is None: pkgpath = dirname(module.__file__) submodules = _find_local_submodule_names(pkgpath) imports = submodules # Import the modules _excecute_imports(module, modname, imports, verbose=verbose) # If developing do explicit import stars from_imports = _execute_fromimport_star(module, modname, imports, verbose=verbose) # If requested: print what the __init__ module should look like dump_requested = ( ("--dump-%s-init" % modname) in sys.argv or ("--print-%s-init" % modname) in sys.argv ) or dump overwrite_requested = ("--update-%s-init" % modname) in sys.argv if verbose: print("[MKINIT] Finished Dynamic Imports for modname=%r " % modname) initstr = _make_initstr(modname, imports, from_imports, withheader=False) if dump_requested: is_main_proc = multiprocessing.current_process().name == "MainProcess" if is_main_proc: _initstr = _make_initstr(modname, imports, from_imports) print(_indent(_initstr)) # Overwrite the __init__.py file with new explicit imports if overwrite_requested: is_main_proc = multiprocessing.current_process().name == "MainProcess" if is_main_proc: modpath = module.__path__[0] _autogen_write(modpath, _indent(initstr)) return initstr
[docs] def _indent(str_, indent=" "): return indent + str_.replace("\n", "\n" + indent)
[docs] def _excecute_imports(module, modname, imports, verbose=False): """ Module Imports """ # level: -1 is a the Python2 import strategy # level: 0 is a the Python3 absolute import if verbose: print("[MKINIT] EXECUTING %d IMPORT TUPLES" % (len(imports),)) level = 0 for name in imports: if level == -1: tmp = __import__(name, globals(), locals(), fromlist=[], level=level) elif level == 0: # FIXME: should support unicode. Maybe just a python2 thing tmp = __import__( modname, globals(), locals(), fromlist=[str(name)], level=level )
[docs] def _execute_fromimport_star( module, modname, imports, check_not_imported=False, verbose=False ): """ Effectively import * statements The dynamic_init must happen before any * imports otherwise it wont catch anything. """ if verbose: print("[MKINIT] EXECUTE %d FROMIMPORT STAR TUPLES." % (len(imports),)) from_imports = [] # Explicitly ignore these special functions (usually stdlib functions) # FIXME: find a better way to do this ignoreset = set( [ "print", "print_function", "absolute_import", "division", "zip", "map", "range", "list", "zip_longest", "filter", "filterfalse", "dirname", "realpath", "join", "exists", "normpath", "splitext", "expanduser", "relpath", "isabs", "commonprefix", "basename", "input", "reduce", ] ) for name in imports: # absname = modname + '.' + name child_module = sys.modules[modname + "." + name] # Check if the variable already belongs to the module varset = set(vars(module)) if check_not_imported else set() fromset = set() # set(fromlist) if fromlist is not None else set() def valid_attrname(attrname): """ Guess if the attrname is valid based on its name """ is_forced = attrname in fromset is_private = attrname.startswith("_") is_conflit = attrname in varset is_module = attrname in sys.modules # Isn't fool proof (next step is) is_ignore = attrname in ignoreset is_valid = not any((is_ignore, is_private, is_conflit, is_module)) return is_forced or is_valid if hasattr(child_module, "__all__"): from_imports.append((name, getattr(child_module, "__all__"))) continue allattrs = dir(child_module) fromlist_ = [attrname for attrname in allattrs if valid_attrname(attrname)] valid_fromlist_ = [] for attrname in fromlist_: attrval = getattr(child_module, attrname) try: # Disallow fromimport modules forced = attrname in fromset if not forced and getattr(attrval, "__name__") in sys.modules: if verbose > 1: print("[MKINIT] not importing: %r" % attrname) continue except AttributeError: pass if verbose > 1: print("[MKINIT] %s is importing: %r" % (modname, attrname)) valid_fromlist_.append(attrname) setattr(module, attrname, attrval) if verbose: print( "[MKINIT] name=%r, len(valid_fromlist_)=%d" % (name, len(valid_fromlist_)) ) from_imports.append((name, valid_fromlist_)) return from_imports
[docs] def _make_initstr(modname, imports, from_imports, withheader=True): """ Calls the other string makers """ header = _make_module_header() if withheader else "" import_str = _make_imports_str(imports, modname) fromimport_str = _make_fromimport_str(from_imports, modname) initstr = "\n".join( [ str_ for str_ in [ header, import_str, fromimport_str, ] if len(str_) > 0 ] ) return initstr
[docs] def _make_module_header(): return "\n".join( [ "# flake8:" + " noqa", # the plus prevents it from triggering on this file "from __future__ import absolute_import, division, print_function, unicode_literals", ] )
[docs] def _make_imports_str(imports, rootmodname="."): imports_fmtstr = "from {rootmodname} import %s".format(rootmodname=rootmodname) return "\n".join([imports_fmtstr % (name,) for name in imports])
[docs] def _make_fromimport_str(from_imports, rootmodname="."): if rootmodname == ".": # dot is already taken care of in fmtstr rootmodname = "" def _pack_fromimport(tup): name, fromlist = tup[0], tup[1] from_module_str = "from {rootmodname}.{name} import (".format( rootmodname=rootmodname, name=name ) newline_prefix = " " * len(from_module_str) if len(fromlist) > 0: rawstr = from_module_str + ", ".join(fromlist) + ",)" else: rawstr = "" # not sure why this isn't 76? >= maybe? packstr = "\n".join( textwrap.wrap( rawstr, break_long_words=False, width=79, initial_indent="", subsequent_indent=newline_prefix, ) ) return packstr from_str = "\n".join(map(_pack_fromimport, from_imports)) return from_str
[docs] def _find_local_submodule_names(pkgpath): # Automatically find the imports if they are not specified from mkinit import static_mkinit import_paths = dict(static_mkinit._find_local_submodules(pkgpath)) imports = list(import_paths.keys()) return imports
[docs] def _autogen_write(modpath, initstr): """ TODO: - [ ] : replace with code in mkinit/formatting.py """ # Get path to init file so we can overwrite it init_fpath = join(modpath, "__init__.py") print("attempting to update: %r" % init_fpath) assert exists(init_fpath) new_lines = [] editing = False updated = False with open(init_fpath, "r") as file_: lines = file_.readlines() for line in lines: if not editing: new_lines.append(line) if line.strip().startswith("# <AUTOGEN_INIT>"): new_lines.append("\n" + initstr + "\n # </AUTOGEN_INIT>\n") editing = True updated = True if line.strip().startswith("# </AUTOGEN_INIT>"): editing = False if updated: print("writing updated file: %r" % init_fpath) new_text = "".join(new_lines) with open(init_fpath, "w") as file_: file_.write(new_text) else: print("no write hook for file: %r" % init_fpath)