Exemple #1
0
    def containing(cls, identifier):
        """
        Try to find the module that defines a name such as C{a.b.c} by trying
        to import C{a}, C{a.b}, and C{a.b.c}.

        @return:
          The name of the 'deepest' module (most commonly it would be C{a.b}
          in this example).
        @rtype:
          L{Module}
        """
        # In the code below we catch "Exception" rather than just ImportError
        # or AttributeError since importing and __getattr__ing can raise other
        # exceptions.
        identifier = DottedIdentifier(identifier)
        try:
            module = ModuleHandle(identifier[:1])
            result = module.module
        except Exception as e:
            raise ImportError(e)
        for part, prefix in zip(identifier, prefixes(identifier))[1:]:
            try:
                result = getattr(result, str(part))
            except Exception:
                try:
                    module = cls(prefix)
                    result = module.module
                except Exception as e:
                    raise ImportError(e)
            else:
                if isinstance(result, types.ModuleType):
                    module = cls(result)
        logger.debug("Imported %r to get %r", module, identifier)
        return module
Exemple #2
0
def initialize_comms():
    if in_jupyter():
        for target in pyflyby_comm_targets:
            _register_target(target)
        from ipykernel.comm import Comm
        comm = Comm(target_name=INIT_COMMS)
        msg = {"type": INIT_COMMS}
        logger.debug("Requesting frontend to (re-)initialize comms")
        comm.send(msg)
Exemple #3
0
 def action(m):
     bindir = os.path.dirname(os.path.realpath(sys.argv[0]))
     env = os.environ
     env['PATH'] = env['PATH'] + ":" + bindir
     fullcmd = "%s %s %s" % (
         command, m.input_content_filename, m.output_content_filename)
     logger.debug("Executing external command: %s", fullcmd)
     ret = subprocess.call(fullcmd, shell=True, env=env)
     logger.debug("External command returned %d", ret)
Exemple #4
0
def send_comm_message(target_name, msg):
    if in_jupyter():
        try:
            comm = comms[target_name]
        except KeyError:
            logger.debug("Comm with target_name " + target_name +
                         " hasn't been opened")
        else:
            # Help the frontend distinguish between multiple types
            # of custom comm messages
            msg["type"] = target_name
            comm.send(msg)
            logger.debug("Sending comm message for target " + target_name)
Exemple #5
0
    def _from_filenames(cls, filenames, _mandatory_filenames_deprecated=[]):
        """
        Load an import database from filenames.

        This function exists to support deprecated behavior.
        When we stop supporting the old behavior, we will delete this function.

        @type filenames:
          Sequence of L{Filename}s
        @param filenames:
          Filenames of files to read.
        @rtype:
          L{ImportDB}
        """
        if not isinstance(filenames, (tuple, list)):
            filenames = [filenames]
        filenames = [Filename(f) for f in filenames]
        logger.debug("ImportDB: loading [%s], mandatory=[%s]",
                     ', '.join(map(str, filenames)),
                     ', '.join(map(str, _mandatory_filenames_deprecated)))
        if "SUPPORT DEPRECATED BEHAVIOR":
            # Before 2014-10, pyflyby read the following:
            #   * known_imports from $PYFLYBY_PATH/known_imports/**/*.py or
            #     $PYFLYBY_KNOWN_IMPORTS_PATH/**/*.py,
            #   * mandatory_imports from $PYFLYBY_PATH/mandatory_imports/**/*.py or
            #     $PYFLYBY_MANDATORY_IMPORTS_PATH/**/*.py, and
            #   * forget_imports from $PYFLYBY_PATH/known_imports/**/__remove__.py
            # After 2014-10, pyflyby reads the following:
            #   * $PYFLYBY_PATH/**/*.py
            #     (with directives inside the file)
            # For backwards compatibility, for now we continue supporting the
            # old, deprecated behavior.
            blocks = []
            mandatory_imports_blocks = [
                Filename(f) for f in _mandatory_filenames_deprecated
            ]
            forget_imports_blocks = []
            for filename in filenames:
                if filename.base == "__remove__.py":
                    forget_imports_blocks.append(filename)
                elif "mandatory_imports" in str(filename).split("/"):
                    mandatory_imports_blocks.append(filename)
                else:
                    blocks.append(filename)
            return cls._from_code(blocks, mandatory_imports_blocks,
                                  forget_imports_blocks)
        else:
            return cls._from_code(filenames)
Exemple #6
0
def in_jupyter():
    from IPython.core.getipython import get_ipython
    ip = get_ipython()
    if ip is None:
        logger.debug("get_ipython() doesn't exist. Comm targets can only"
                     "be added in an Jupyter notebook/lab/console environment")
        return False
    else:
        try:
            ip.kernel.comm_manager
        except AttributeError:
            logger.debug("Comm targets can only be added in Jupyter "
                         "notebook/lab/console environment")
            return False
        else:
            return True
Exemple #7
0
 def unadvise(self):
     if self._wrapped is None:
         return
     cur = self._container.get(self._name, _UNSET)
     if cur is self._wrapped:
         from pyflyby._log import logger
         logger.debug("unadvising %s", self._qname)
         if self._previous is _UNSET:
             del self._container[self._name]
         else:
             self._container[self._name] = self._previous
     elif cur == self._previous:
         pass
     else:
         from pyflyby._log import logger
         logger.debug("%s seems modified; not unadvising it", self._name)
     self._wrapped = None
Exemple #8
0
    def clear_default_cache(cls):
        """
        Clear the class cache of default ImportDBs.

        Subsequent calls to ImportDB.get_default() will not reuse previously
        cached results.  Existing ImportDB instances are not affected by this
        call.
        """
        if cls._default_cache:
            if logger.debug_enabled:
                allpyfiles = set()
                for tup in cls._default_cache:
                    if tup[0] != 2:
                        continue
                    for tup2 in tup[1:]:
                        for f in tup2:
                            assert isinstance(f, Filename)
                            if f.ext == '.py':
                                allpyfiles.add(f)
                nfiles = len(allpyfiles)
                logger.debug("ImportDB: Clearing default cache of %d files",
                             nfiles)
            cls._default_cache.clear()
Exemple #9
0
    def add_module(self, module):
        """
        Adds ``module`` and recreates the DocIndex with the updated set of
        modules.

        :return:
          Whether anything was added.
        """
        module = ModuleHandle(module)
        for prefix in module.ancestors:
            if prefix in self.modules:
                # The module, or a prefix of it, was already added.
                return False

        for existing_module in sorted(self.modules):
            if existing_module.startswith(module):
                # This supersedes an existing module.
                assert existing_module != module
                self.modules.remove(existing_module)

        logger.debug("Expanding docindex to include %r", module)
        self.modules.add(module)
        del self.docindex
        return True
Exemple #10
0
 def advise(self, hook, once=False):
     from pyflyby._log import logger
     self._previous = self._container.get(self._name, _UNSET)
     if once and getattr(self._previous, "__aspect__", None):
         # TODO: check that it's the same hook - at least check the name.
         logger.debug("already advised %s", self._qname)
         return None
     logger.debug("advising %s", self._qname)
     assert self._previous is _UNSET or self._previous == self._original
     assert self._wrapped is None
     # Create the wrapped function.
     wrapped = FunctionWithGlobals(hook, __original__=self._original)
     wrapped.__joinpoint__ = self._joinpoint
     wrapped.__original__ = self._original
     wrapped.__name__ = "%s__advised__%s" % (self._name, hook.__name__)
     wrapped.__doc__ = "%s.\n\nAdvice %s:\n%s" % (
         self._original.__doc__, hook.__name__, hook.__doc__)
     wrapped.__aspect__ = self
     if self._wrapper is not None:
         wrapped = self._wrapper(wrapped)
     self._wrapped = wrapped
     # Install the wrapped function!
     self._container[self._name] = wrapped
     return self
Exemple #11
0
def import_module(module_name):
    module_name = str(module_name)
    logger.debug("Importing %r", module_name)
    try:
        result = __import__(module_name, fromlist=['dummy'])
        if result.__name__ != module_name:
            logger.debug("Note: import_module(%r).__name__ == %r", module_name,
                         result.__name__)
        return result
    except ImportError as e:
        # We got an ImportError.  Figure out whether this is due to the module
        # not existing, or whether the module exists but caused an ImportError
        # (perhaps due to trying to import another problematic module).
        # Do this by looking at the exception traceback.  If the previous
        # frame in the traceback is this function (because locals match), then
        # it should be the internal import machinery reporting that the module
        # doesn't exist.  Re-raise the exception as-is.
        # If some sys.meta_path or other import hook isn't compatible with
        # such a check, here are some things we could do:
        #   - Use pkgutil.find_loader() after the fact to check if the module
        #     is supposed to exist.  Note that we shouldn't rely solely on
        #     this before attempting to import, because find_loader() doesn't
        #     work with meta_path.
        #   - Write a memoized global function that compares in the current
        #     environment the difference between attempting to import a
        #     non-existent module vs a problematic module, and returns a
        #     function that uses the working discriminators.
        real_importerror1 = type(e) is ImportError
        real_importerror2 = (sys.exc_info()[2].tb_frame.f_locals is locals())
        m = re.match("^No module named (.*)$", str(e))
        real_importerror3 = (m and m.group(1) == module_name
                             or module_name.endswith("." + m.group(1)))
        logger.debug("import_module(%r): real ImportError: %s %s %s",
                     module_name, real_importerror1, real_importerror2,
                     real_importerror3)
        if real_importerror1 and real_importerror2 and real_importerror3:
            raise
        reraise(
            ErrorDuringImportError(
                "Error while attempting to import %s: %s: %s" %
                (module_name, type(e).__name__, e)), None,
            sys.exc_info()[2])
    except Exception as e:
        reraise(
            ErrorDuringImportError(
                "Error while attempting to import %s: %s: %s" %
                (module_name, type(e).__name__, e)), None,
            sys.exc_info()[2])
Exemple #12
0
def remove_comms():
    for target_name, comm in six.iteritems(comms):
        comm.close()
        logger.debug("Closing comm for " + target_name)
Exemple #13
0
def fix_unused_and_missing_imports(codeblock,
                                   add_missing=True,
                                   remove_unused="AUTOMATIC",
                                   add_mandatory=True,
                                   db=None,
                                   params=None):
    r"""
    Check for unused and missing imports, and fix them automatically.

    Also formats imports.

    In the example below, C{m1} and C{m3} are unused, so are automatically
    removed.  C{np} was undefined, so an C{import numpy as np} was
    automatically added.

      >>> codeblock = PythonBlock(
      ...     'from foo import m1, m2, m3, m4\n'
      ...     'm2, m4, np.foo', filename="/tmp/foo.py")

      >>> print fix_unused_and_missing_imports(codeblock, add_mandatory=False)
      [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m1'
      [PYFLYBY] /tmp/foo.py: removed unused 'from foo import m3'
      [PYFLYBY] /tmp/foo.py: added 'import numpy as np'
      import numpy as np
      from foo import m2, m4
      m2, m4, np.foo

    @type codeblock:
      L{PythonBlock} or convertible (C{str})
    @rtype:
      L{PythonBlock}
    """
    codeblock = PythonBlock(codeblock)
    if remove_unused == "AUTOMATIC":
        fn = codeblock.filename
        remove_unused = not (fn and (fn.base == "__init__.py"
                                     or ".pyflyby" in str(fn).split("/")))
    elif remove_unused is True or remove_unused is False:
        pass
    else:
        raise ValueError("Invalid remove_unused=%r" % (remove_unused, ))
    params = ImportFormatParams(params)
    db = ImportDB.interpret_arg(db, target_filename=codeblock.filename)
    # Do a first pass reformatting the imports to get rid of repeated or
    # shadowed imports, e.g. L1 here:
    #   import foo  # L1
    #   import foo  # L2
    #   foo         # L3
    codeblock = reformat_import_statements(codeblock, params=params)

    filename = codeblock.filename
    transformer = SourceToSourceFileImportsTransformation(codeblock)
    missing_imports, unused_imports = scan_for_import_issues(
        codeblock, find_unused_imports=remove_unused, parse_docstrings=True)
    logger.debug("missing_imports = %r", missing_imports)
    logger.debug("unused_imports = %r", unused_imports)
    if remove_unused and unused_imports:
        # Go through imports to remove.  [This used to be organized by going
        # through import blocks and removing all relevant blocks from there,
        # but if one removal caused problems the whole thing would fail.  The
        # CPU cost of calling without_imports() multiple times isn't worth
        # that.]
        # TODO: don't remove unused mandatory imports.  [This isn't
        # implemented yet because this isn't necessary for __future__ imports
        # since they aren't reported as unused, and those are the only ones we
        # have by default right now.]
        for lineno, imp in unused_imports:
            try:
                imp = transformer.remove_import(imp, lineno)
            except NoSuchImportError:
                logger.error(
                    "%s: couldn't remove import %r",
                    filename,
                    imp,
                )
            except LineNumberNotFoundError as e:
                logger.error("%s: unused import %r on line %d not global",
                             filename, str(imp), e.args[0])
            else:
                logger.info("%s: removed unused '%s'", filename, imp)

    if add_missing and missing_imports:
        missing_imports.sort(key=lambda k: (k[1], k[0]))
        known = db.known_imports.by_import_as
        # Decide on where to put each import to be added.  Find the import
        # block with the longest common prefix.  Tie-break by preferring later
        # blocks.
        added_imports = set()
        for lineno, ident in missing_imports:
            import_as = ident.parts[0]
            try:
                imports = known[import_as]
            except KeyError:
                logger.warning(
                    "%s:%s: undefined name %r and no known import for it",
                    filename, lineno, import_as)
                continue
            if len(imports) != 1:
                logger.error("%s: don't know which of %r to use", filename,
                             imports)
                continue
            imp_to_add = imports[0]
            if imp_to_add in added_imports:
                continue
            transformer.add_import(imp_to_add, lineno)
            added_imports.add(imp_to_add)
            logger.info("%s: added %r", filename,
                        imp_to_add.pretty_print().strip())

    if add_mandatory:
        # Todo: allow not adding to empty __init__ files?
        mandatory = db.mandatory_imports.imports
        for imp in mandatory:
            try:
                transformer.add_import(imp)
            except ImportAlreadyExistsError:
                pass
            else:
                logger.info("%s: added mandatory %r", filename,
                            imp.pretty_print().strip())

    return transformer.output(params=params)
Exemple #14
0
def _xreload_module(module, filename, force=False):
    """
    Reload a module in place, using livepatch.

    @type module:
      C{ModuleType}
    @param module:
      Module to reload.
    @param force:
      Whether to reload even if the module has not been modified since the
      previous load.  If C{False}, then do nothing.  If C{True}, then reload.
    """
    import linecache
    if not filename or not filename.endswith(".py"):
        # If there's no *.py source file for this module, then fallback to
        # built-in reload().
        return reload(module)
    # Compare mtime of the file with the load time of the module.  If the file
    # wasn't touched, we don't need to do anything.
    try:
        mtime = os.stat(filename).st_mtime
    except OSError:
        logger.info("Can't find %s", filename)
        return None
    if not force:
        try:
            old_loadtime = module.__loadtime__
        except AttributeError:
            # We only have a __loadtime__ attribute if we were the ones that
            # loaded it.  Otherwise, fall back to the process start time as a
            # conservative bound.
            old_loadtime = _PROCESS_START_TIME
        if old_loadtime > mtime:
            logger.debug(
                "NOT reloading %s (file %s modified %s ago but loaded %s ago)",
                module.__name__, filename, _format_age(mtime),
                _format_age(old_loadtime))
            return None
        # Keep track of previously imported source.  If the file's timestamp
        # was touched, but the content unchanged, we can avoid reloading.
        cached_lines = linecache.cache.get(filename,
                                           (None, None, None, None))[2]
    else:
        cached_lines = None
    # Re-read source for module from disk, and update the linecache.
    source = ''.join(linecache.updatecache(filename))
    # Skip reload if the content didn't change.
    if cached_lines is not None and source == ''.join(cached_lines):
        logger.debug(
            "NOT reloading %s (file %s touched %s ago but content unchanged)",
            module.__name__, filename, _format_age(mtime))
        return module
    logger.info("Reloading %s (modified %s ago) from %s", module.__name__,
                _format_age(mtime), filename)
    # Compile into AST.  We do this as a separate step from compiling to byte
    # code so that we can get the module docstring.
    astnode = compile(source, filename, "exec", ast.PyCF_ONLY_AST, 1)
    # Get the new docstring.
    try:
        doc = astnode.body[0].value.s
    except (AttributeError, IndexError):
        doc = None
    # Compile into code.
    code = compile(astnode, filename, "exec", 0, 1)
    # Execute the code.  We do so in a temporary namespace so that if this
    # fails, nothing changes.  It's important to set __name__ so that relative
    # imports work correctly.
    new_mod = types.ModuleType(module.__name__)
    new_mod.__file__ = filename
    new_mod.__doc__ = doc
    if hasattr(module, "__path__"):
        new_mod.__path__ = module.__path__
    MISSING = object()
    saved_mod = sys.modules.get(module.__name__, MISSING)
    try:
        # Temporarily put the temporary module in sys.modules, in case the
        # code references sys.modules[__name__] for some reason.  Normally on
        # success, we will revert this what that was there before (which
        # normally should be C{module}).  If an error occurs, we'll also
        # revert.  If the user has defined a __livepatch__ hook at the module
        # level, it's possible for result to not be the old module.
        sys.modules[module.__name__] = new_mod
        # *** Execute new code ***
        exec(code, new_mod.__dict__)
        # Normally C{module} is of type C{ModuleType}.  However, in some
        # cases, the module might have done a "proxy module" trick where the
        # module is replaced by a proxy object of some other type.  Regardless
        # of the actual type, we do the update as C{module} were of type
        # C{ModuleType}.
        assume_type = types.ModuleType
        # Livepatch the module.
        result = livepatch(module,
                           new_mod,
                           module.__name__,
                           assume_type=assume_type)
        sys.modules[module.__name__] = result
    except:
        # Either the module failed executing or the livepatch failed.
        # Revert to previous state.
        # Note that this isn't perfect because it's possible that the module
        # modified some global state in other modules.
        if saved_mod is MISSING:
            del sys.modules[module.__name__]
        else:
            sys.modules[module.__name__] = saved_mod
        raise
    # Update the time we last loaded the module.  We intentionally use mtime
    # here instead of time.time().  If we are on NFS, it's possible for the
    # filer's mtime and time.time() to not be synchronized.  We will be
    # comparing to mtime next time, so if we use only mtime, we'll be fine.
    module.__loadtime__ = mtime
    return module
Exemple #15
0
def action_ifchanged(m):
    if m.output_content.joined == m.input_content.joined:
        logger.debug("unmodified: %s", m.filename)
        raise AbortActions
Exemple #16
0
    def get_default(cls, target_filename):
        """
        Return the default import library for the given target filename.

        This will read various .../.pyflyby files as specified by
        $PYFLYBY_PATH as well as older deprecated environment variables.

        Memoized.

        @param target_filename:
          The target filename for which to get the import database.  Note that
          the target filename itself is not read.  Instead, the target
          filename is relevant because we look for .../.pyflyby based on the
          target filename.
        @rtype:
          L{ImportDB}
        """
        # We're going to canonicalize target_filenames in a number of steps.
        # At each step, see if we've seen the input so far.  We do the cache
        # checking incrementally since the steps involve syscalls.  Since this
        # is going to potentially be executed inside the IPython interactive
        # loop, we cache as much as possible.
        # TODO: Consider refreshing periodically.  Check if files have
        # been touched, and if so, return new data.  Check file timestamps at
        # most once every 60 seconds.
        cache_keys = []
        target_filename = Filename(target_filename or ".")
        if target_filename.startswith("/dev"):
            target_filename = Filename(".")
        target_dirname = target_filename
        # TODO: with StatCache
        while True:
            cache_keys.append((1, target_dirname, os.getenv("PYFLYBY_PATH"),
                               os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"),
                               os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH")))
            try:
                return cls._default_cache[cache_keys[-1]]
            except KeyError:
                pass
            if target_dirname.isdir:
                break
            target_dirname = target_dirname.dir
        target_dirname = target_dirname.real
        if target_dirname != cache_keys[-1][0]:
            cache_keys.append((1, target_dirname, os.getenv("PYFLYBY_PATH"),
                               os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"),
                               os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH")))
            try:
                return cls._default_cache[cache_keys[-1]]
            except KeyError:
                pass
        DEFAULT_PYFLYBY_PATH = []
        etc_dir = _find_etc_dir()
        if etc_dir:
            DEFAULT_PYFLYBY_PATH.append(str(etc_dir))
        DEFAULT_PYFLYBY_PATH += [
            ".../.pyflyby",
            "~/.pyflyby",
        ]
        logger.debug("DEFAULT_PYFLYBY_PATH=%s", DEFAULT_PYFLYBY_PATH)
        filenames = _get_python_path("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH,
                                     target_dirname)
        mandatory_imports_filenames = ()
        if "SUPPORT DEPRECATED BEHAVIOR":
            PYFLYBY_PATH = _get_env_var("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH)
            # If the old deprecated environment variables are set, then heed
            # them.
            if os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"):
                # Use PYFLYBY_PATH as the default for
                # PYFLYBY_KNOWN_IMPORTS_PATH.  Note that the default is
                # relevant even though we only enter this code path when the
                # variable is set to anything, because the env var can
                # reference "-" to include the default.
                # Before pyflyby version 0.8, the default value would have
                # been
                #   [d/"known_imports" for d in PYFLYBY_PATH]
                # Instead of using that, we just use PYFLYBY_PATH directly as
                # the default.  This simplifies things and avoids need for a
                # "known_imports=>." symlink for backwards compatibility.  It
                # means that ~/.pyflyby/**/*.py (as opposed to only
                # ~/.pyflyby/known_imports/**/*.py) would be included.
                # Although this differs slightly from the old behavior, it
                # matches the behavior of the newer PYFLYBY_PATH; matching the
                # new behavior seems higher utility than exactly matching the
                # old behavior.  Files under ~/.pyflyby/mandatory_imports will
                # be included in known_imports as well, but that should not
                # cause any problems.
                default_path = PYFLYBY_PATH
                # Expand $PYFLYBY_KNOWN_IMPORTS_PATH.
                filenames = _get_python_path("PYFLYBY_KNOWN_IMPORTS_PATH",
                                             default_path, target_dirname)
                logger.debug(
                    "The environment variable PYFLYBY_KNOWN_IMPORTS_PATH is deprecated.  "
                    "Use PYFLYBY_PATH.")
            if os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH"):
                # Compute the "default" path.
                # Note that we still calculate the erstwhile default value,
                # even though it's no longer the defaults, in order to still
                # allow the "-" in the variable.
                default_path = [
                    os.path.join(d, "mandatory_imports") for d in PYFLYBY_PATH
                ]
                # Expand $PYFLYBY_MANDATORY_IMPORTS_PATH.
                mandatory_imports_filenames = _get_python_path(
                    "PYFLYBY_MANDATORY_IMPORTS_PATH", default_path,
                    target_dirname)
                logger.debug(
                    "The environment variable PYFLYBY_MANDATORY_IMPORTS_PATH is deprecated.  "
                    "Use PYFLYBY_PATH and write __mandatory_imports__=['...'] in your files."
                )
        cache_keys.append((2, filenames, mandatory_imports_filenames))
        try:
            return cls._default_cache[cache_keys[-1]]
        except KeyError:
            pass
        result = cls._from_filenames(filenames, mandatory_imports_filenames)
        for k in cache_keys:
            cls._default_cache[k] = result
        return result