Source code for pydeps.target
"""
Abstracting the target for pydeps to work on.
"""
import json
import os
import re
import shutil
import sys
import tempfile
from contextlib import contextmanager
import logging
log = logging.getLogger(__name__)
[docs]
class Target(object):
"""The compilation target.
"""
is_pysource = False
is_module = False
is_dir = False
def __init__(self, path, **kwargs):
"""Initialise target
Args:
path: module path
Keyword Args:
use_calling_fname (bool): flag to use the `calling_fname` instead of `fname` in `self.get_src_fname`
"""
# log.debug("CURDIR: %s, path: %s, exists: %s", os.getcwd(), path, os.path.exists(path))
# print("Target::CURDIR: %s, path: %s, exists: %s" % (os.getcwd(), path, os.path.exists(path)))
self.calling_fname = path
self.calling_dir = os.getcwd()
self.exists = os.path.exists(path)
self.use_calling_fname = kwargs.get('use_calling_fname', False)
if self.exists:
self.path = os.path.realpath(path)
else: # pragma: nocover
print("No such file or directory:", repr(path), file=sys.stderr)
if os.path.exists(path + '.py'):
print("..did you mean:", path + '.py', '?', file=sys.stderr)
sys.exit(1)
self.is_dir = os.path.isdir(self.path)
self.is_module = self.is_dir and '__init__.py' in os.listdir(self.path)
self.is_pysource = os.path.splitext(self.path)[1] in ('.py', '.pyc', '.pyo', '.pyw')
self.fname = os.path.basename(self.path)
if self.is_dir:
self.dirname = self.fname
self.modname = self.fname
else:
self.dirname = os.path.dirname(self.path)
self.modname = os.path.splitext(self.fname)[0]
if self.is_pysource:
# we will work directly on the file (in-situ)
self.workdir = os.path.dirname(self.path)
else:
self.workdir = os.path.realpath(tempfile.mkdtemp())
self.syspath_dir = self.get_package_root()
# split path such that syspath_dir + relpath == path
self.relpath = self.path[len(self.syspath_dir):].lstrip(os.path.sep)
if self.is_dir:
self.modpath = self.relpath.replace(os.path.sep, '.')
else:
self.modpath = os.path.splitext(self.relpath)[0].replace(os.path.sep, '.')
for part in self.modpath.split('.'):
if not part.isidentifier():
print(
"Cannot analyze {!r}: {!r} is not a valid Python "
"module name.".format(self.calling_fname, part),
file=sys.stderr,
)
print(
"\nTechnical reason:\n"
" pydeps works by generating a synthetic 'dummy' module\n"
" (e.g. _dummy_<target>.py) that contains real Python\n"
" 'import <modname>' statements for every submodule of\n"
" the target, then runs Python's modulefinder on that\n"
" dummy to trace the import graph. The 'import' keyword\n"
" is part of Python's grammar and only accepts dotted\n"
" identifiers -- letters, digits, and underscores, and\n"
" not starting with a digit. A name like {!r} cannot\n"
" appear on the right-hand side of an import statement,\n"
" so modulefinder has nothing to trace and pydeps\n"
" produces an empty graph.\n"
"\nFix:\n"
" Rename the offending file or directory so that every\n"
" path component is a valid Python identifier (e.g.\n"
" replace '-' with '_'). If the file must keep its\n"
" on-disk name, point pydeps at a package whose name\n"
" is importable instead.".format(part),
file=sys.stderr,
)
sys.exit(1)
self.package_root = os.path.join(
self.syspath_dir,
self._path_parts(self.relpath)[0]
)
[docs]
def get_src_fname(self):
"""Return the source file name to use."""
log.debug("[get_src_fname] use_calling_name: %s", self.use_calling_fname)
return self.calling_fname if self.use_calling_fname else self.fname
[docs]
@contextmanager
def chdir_work(self):
try:
os.chdir(self.workdir)
sys.path.insert(0, self.syspath_dir)
yield
finally:
os.chdir(self.calling_dir)
if sys.path[0] == self.syspath_dir:
sys.path = sys.path[1:]
self.close()
[docs]
def get_package_root(self):
for d in self.get_parents():
if '__init__.py' not in os.listdir(d):
return d
raise Exception(
"do you have an __init__.py file at the "
"root of the drive..?") # pragma: nocover
[docs]
def get_parents(self):
def _parent_iter():
parts = self._path_parts(self.path)
for i in range(1, len(parts)):
yield os.path.join(*parts[:-i])
return list(_parent_iter())
def _path_parts(self, pth):
"""Return a list of all directories in the path ``pth``.
"""
res = re.split(r"[\\/]", pth)
if res and os.path.splitdrive(res[0]) == (res[0], ''):
res[0] += os.path.sep
return res
def __del__(self):
self.close()
[docs]
def close(self):
"""Clean up after ourselves.
"""
try:
# make sure we don't delete the user's source file if we're working
# on it in-situ.
if not self.is_pysource and hasattr(self, 'workdir'):
shutil.rmtree(self.workdir)
except OSError:
pass
def __repr__(self): # pragma: nocover
return json.dumps(
{k: v for k, v in self.__dict__.items() if not k.startswith('_')},
indent=4, sort_keys=True
)