# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import textwrap
import logging
from . import cli
log = logging.getLogger(__name__)
[docs]
def is_module(directory):
"""A directory is a module if it contains an ``__init__.py`` file.
"""
return os.path.isdir(directory) and '__init__.py' in os.listdir(directory)
[docs]
def is_pysource(fname):
"""A file name is a python source file iff it ends with '.py(w?)' and
doesn't start with a dot.
"""
return not fname.startswith('.') and fname.endswith(('.py', '.pyw'))
[docs]
def fname2modname(fname, package_root):
subpath = os.path.splitext(fname)[0][len(package_root):]
modname = subpath.lstrip(os.path.sep).replace(os.path.sep, '.')
return modname
[docs]
def python_sources_below(directory, package=True):
for root, dirs, files in os.walk(directory):
if package and '__init__.py' not in files:
continue
dotdirs = [d for d in dirs if d.startswith('.')]
for d in dotdirs:
dirs.remove(d)
if 'migrations' in dirs:
dirs.remove('migrations')
for fname in files:
if is_pysource(fname): # and fname not in args['exclude']:
if fname == '__init__.py':
yield os.path.abspath(root)
else:
yield os.path.abspath(os.path.join(root, fname))
[docs]
class DummyModule(object):
"""We create a file that imports the module to be investigated.
"""
def __init__(self, target, **args):
self._legal_mnames = {}
self.target = target
self.fname = '_dummy_' + target.modpath.replace('.', '_') + '.py'
self.absname = os.path.join(target.workdir, self.fname)
if target.is_module:
cli.verbose(1, "target is a PACKAGE")
with open(self.fname, 'w') as fp:
for fname in python_sources_below(target.package_root):
modname = fname2modname(fname, target.syspath_dir)
self.print_import(fp, modname)
elif target.is_dir:
# FIXME?: not sure what the intended semantics was here, as it is
# this will almost certainly not do the right thing...
cli.verbose(1, "target is a DIRECTORY")
log.debug('curdir: %r', os.getcwd())
log.debug('fname: %r', self.fname)
log.debug('target.dirname: %r', target.dirname)
with open(self.fname, 'w') as fp:
dirname = os.path.abspath(os.path.join(target.calling_dir, target.calling_fname))
for fname in os.listdir(dirname):
fname = os.path.join(dirname, fname)
log.debug("fname: %r", fname)
if is_pysource(fname):
self.print_import(fp, fname2modname(fname, ''))
elif is_module(fname):
log.debug("fname is a module: %r", fname)
for fnamea in python_sources_below(fname):
modname = fname2modname(fnamea, target.syspath_dir)
self.print_import(fp, modname)
else:
assert target.is_pysource
cli.verbose(1, "target is a FILE")
# if working on a single file, we don't need to create a dummy
# module, this also avoids problems with file names that are
# not importable (e.g. `foo.bar.py)
# self.fname = target.calling_fname
self.fname = target.fname
self.absname = target.package_root
# with open(self.fname, 'w') as fp:
# self.print_import(fp, target.modpath)
log.debug(
"dummy-filename: %r (%s)[module=%s, dir=%s, file=%s]",
self.fname, self.absname, target.is_module, target.is_dir, target.is_pysource
)
[docs]
def text(self):
"""Return the content of the dummy module.
"""
log.debug("Getting text from %r", self.fname)
# log.debug("sys.path: %r", sys.path)
if self.fname.endswith('.pyc') or self.fname.endswith('.pyo'):
return '<pyc file, no text>'
with open(self.fname) as fp:
return fp.read()
[docs]
def legal_module_name(self, name):
"""Legal module names are dotted strings where each part
is a valid Python identifier.
(and not a keyword, and support unicode identifiers in
Python3, ..)
"""
if name in self._legal_mnames:
return self._legal_mnames[name]
for part in name.split('.'):
try:
exec("%s = 42" % part, {}, {})
except: # pragma: nocover # noqa
self._legal_mnames[name] = False
return False
self._legal_mnames[name] = True
return True
[docs]
def print_import(self, fp, module):
if not self.legal_module_name(module):
log.warning("SKIPPING ILLEGAL MODULE_NAME: %s", module)
return
mparts = module.rsplit('.', 1)
# we're not executing the file in fp, so really not necessary to
# catch import errors
if len(mparts) == 1:
print(textwrap.dedent("""\
import {module}
""").format(module=module), file=fp)
else:
print(textwrap.dedent("""\
from {prefix} import {mname}
""").format(prefix=mparts[0], mname=mparts[1]), file=fp)