# from .mf.mf_next import * # for debugging next version
import modulefinder
from modulefinder import (
ModuleFinder as NativeModuleFinder
)
from importlib.util import MAGIC_NUMBER
import marshal
import dis
from . import mfimp
HAVE_ARGUMENT = dis.HAVE_ARGUMENT
# from stdlib's modulefinder
_PY_SOURCE = mfimp.PY_SOURCE
_PY_COMPILED = mfimp.PY_COMPILED
_PKG_DIRECTORY = mfimp.PKG_DIRECTORY
# monkey-patch broken modulefinder._find_module
# (https://github.com/python/cpython/issues/84530)
# in Python 3.8-3.10
if hasattr(modulefinder, '_find_module'):
modulefinder._find_module = mfimp.find_module
[docs]class ModuleFinder(NativeModuleFinder):
[docs] def import_hook(self, name, caller=None, fromlist=None, level=-1):
self.msg(3, "import_hook: name(%s) caller(%s) fromlist(%s) level(%s)" % (name, caller, fromlist, level))
parent = self.determine_parent(caller, level=level)
q, tail = self.find_head_package(parent, name)
if q.shortname in ('__future__', 'future'): # [pydeps] the future package causes recursion overflow
return None
m = self.load_tail(q, tail)
if not fromlist:
return q
if m.__path__:
self.ensure_fromlist(m, fromlist)
return None
[docs] def load_module(self, fqname, fp, pathname, file_info):
# fqname = dotted module name we're loading
suffix, mode, kind = file_info
kstr = {
_PKG_DIRECTORY: 'PKG_DIRECTORY',
_PY_SOURCE: 'PY_SOURCE',
_PY_COMPILED: 'PY_COMPILED',
}.get(kind, 'unknown-kind')
self.msgin(2, "load_module(%s) fqname=%s, fp=%s, pathname=%s" % (kstr, fqname, fp and "fp", pathname))
if kind == _PKG_DIRECTORY:
module = self.load_package(fqname, pathname)
self.msgout(2, "load_module ->", module)
return module
if kind == _PY_SOURCE:
txt = fp.read()
txt += b'\n' if isinstance(txt, bytes) else '\n'
co = compile(
txt,
pathname,
'exec', # compile code block
dont_inherit=True # [pydeps] don't inherit future statements from current environment
)
elif kind == _PY_COMPILED:
# a .pyc file is a binary file containing only three things:
# 1. a four-byte magic number
# 2. a four byte modification timestamp, and
# 3. a Marshalled code object
# from: https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html
if fp.read(4) != MAGIC_NUMBER:
self.msgout(2, "raise ImportError: Bad magic number", pathname)
raise ImportError("Bad magic number in %s" % pathname)
fp.read(4) # skip modification timestamp
co = marshal.load(fp) # load marshalled code object.
else:
co = None
m = self.add_module(fqname)
m.__file__ = pathname
if co:
if self.replace_paths:
co = self.replace_paths_in_code(co)
m.__code__ = co
self.scan_code(co, m)
self.msgout(2, "load_module ->", m)
return m
[docs] def scan_code(self, co, m):
code = co.co_code # noqa
# if sys.version_info >= (3, 4):
# scanner = self.scan_opcodes
# elif sys.version_info >= (2, 5):
# scanner = self.scan_opcodes_25
# else:
# scanner = self.scan_opcodes_24
scanner = self.scan_opcodes
for what, args in scanner(co):
if what == "store":
name, = args
m.globalnames[name] = 1
elif what in ("import", "absolute_import"):
fromlist, name = args
have_star = 0
if fromlist is not None:
if "*" in fromlist:
have_star = 1
fromlist = [f for f in fromlist if f != "*"]
if what == "absolute_import":
level = 0
else:
level = -1
self._safe_import_hook(name, m, fromlist, level=level)
if have_star:
# We've encountered an "import *". If it is a Python module,
# the code has already been parsed and we can suck out the
# global names.
mm = None
if m.__path__:
# At this point we don't know whether 'name' is a
# submodule of 'm' or a global module. Let's just try
# the full name first.
mm = self.modules.get(m.__name__ + "." + name)
if mm is None:
mm = self.modules.get(name)
if mm is not None:
m.globalnames.update(mm.globalnames)
m.starimports.update(mm.starimports)
if mm.__code__ is None:
m.starimports[name] = 1
else:
m.starimports[name] = 1
elif what == "relative_import":
level, fromlist, name = args
if name:
self._safe_import_hook(name, m, fromlist, level=level)
else:
parent = self.determine_parent(m, level=level)
# m is still the caller here... [bp]
self._safe_import_hook(parent.__name__, m, fromlist, level=0)
else:
# We don't expect anything else from the generator.
raise RuntimeError(what)
for c in co.co_consts:
if isinstance(c, type(co)):
self.scan_code(c, m)