Skip to content

Commit d4de56f

Browse files
committed
Use the stdlib-list package to filter out Python standard library modules.
1 parent 158c71f commit d4de56f

File tree

6 files changed

+132
-36
lines changed

6 files changed

+132
-36
lines changed

pydeps/depgraph.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
import pprint
88
import re
99
import enum
10+
import yaml
11+
1012
from . import colors
1113
import sys
14+
import logging
15+
log = logging.getLogger(__name__)
1216

1317
# we're normally not interested in imports of std python packages.
1418
PYLIB_PATH = {
@@ -40,6 +44,7 @@ def __init__(self, name, kind=imp.UNKNOWN, path=None, imports=(), exclude=False,
4044
print "changing __main__ =>", self.name
4145
else:
4246
self.name = name
47+
4348
self.kind = kind
4449
self.path = path # needed here..?
4550
self.imports = set(imports) # modules we import
@@ -58,12 +63,14 @@ def path_parts(self):
5863

5964
@property
6065
def in_degree(self):
61-
"Number of incoming arrows."
66+
"""Number of incoming arrows.
67+
"""
6268
return len(self.imports)
6369

6470
@property
6571
def out_degree(self):
66-
"Number of outgoing arrows."
72+
"""Number of outgoing arrows.
73+
"""
6774
return len(self.imported_by)
6875

6976
@property
@@ -107,11 +114,18 @@ def __repr__(self):
107114
return json.dumps(self.__json__(), indent=4)
108115

109116
def __iadd__(self, other):
117+
if self.name == other.name and self.imports == other.imports and self.bacon == other.bacon:
118+
return self
119+
log.debug("iadd lhs: %r", self)
120+
log.debug("iadd rhs: %r", other)
110121
assert self.name == other.name
111122
self.path = self.path or other.path
112123
self.kind = self.kind or other.kind
113124
self.imports |= other.imports
114125
self.imported_by |= other.imported_by
126+
self.bacon = min(self.bacon, other.bacon)
127+
self.excluded = self.excluded or other.excluded
128+
log.debug("iadd result: %r", self)
115129
return self
116130

117131
# def imported_modules(self, depgraph):
@@ -166,6 +180,7 @@ def get_colors(self, src, colorspace=None):
166180
return colorspace.color(src)
167181

168182
def _is_pylib(self, path):
183+
log.info('path %r in PYLIB_PATH %r => %s', path, PYLIB_PATH, path in PYLIB_PATH)
169184
return path in PYLIB_PATH
170185

171186
def proximity_metric(self, a, b):
@@ -194,8 +209,8 @@ def dissimilarity_metric(self, a, b):
194209
195210
Returns an int between 1 (default) and 4 (highly unrelated).
196211
"""
197-
if self._is_pylib(a) and self._is_pylib(b):
198-
return 1
212+
# if self._is_pylib(a) and self._is_pylib(b):
213+
# return 1
199214

200215
res = 4
201216
for an, bn, n in izip_longest(a.name_parts, b.name_parts, range(4)):
@@ -205,6 +220,10 @@ def dissimilarity_metric(self, a, b):
205220
return res
206221

207222
def _exclude(self, name):
223+
# excl = any(skip.match(name) for skip in self.skiplist)
224+
# if 'metar' in name:
225+
# print "Exclude?", name, excl
226+
# print [s.pattern for s in self.skiplist]
208227
return any(skip.match(name) for skip in self.skiplist)
209228

210229
def __init__(self, depgraf, types, **args):
@@ -215,15 +234,17 @@ def __init__(self, depgraf, types, **args):
215234
self.cyclerelations = set()
216235

217236
self.args = args
237+
218238
self.sources = {} # module_name -> Source
219239
self.skiplist = [re.compile(fnmatch.translate(arg)) for arg in args['exclude']]
240+
# print "SKPLIST:", self.skiplist[0].pattern
220241

221242
for name, imports in depgraf.items():
222-
self.verbose(4, "depgraph:", name, imports)
243+
log.debug("depgraph name=%r imports=%r", name, imports)
223244
src = Source(
224245
name=name,
225246
kind=imp(types.get(name, 0)),
226-
imports=imports.keys(),
247+
imports=imports.keys(), # XXX: throwing away .values(), which is abspath!
227248
args=args,
228249
exclude=self._exclude(name),
229250
)
@@ -252,9 +273,11 @@ def __init__(self, depgraf, types, **args):
252273
self.exclude_bacon(self.args['max_bacon'])
253274

254275
excluded = [v for v in self.sources.values() if v.excluded]
276+
# print "EXCLUDED:", excluded
255277
self.skip_count = len(excluded)
256278
self.verbose(1, "skipping", self.skip_count, "modules")
257279
for module in excluded:
280+
# print 'exclude:', module.name
258281
self.verbose(2, " ", module.name)
259282

260283
self.remove_excluded()
@@ -268,8 +291,10 @@ def verbose(self, n, *args):
268291

269292
def add_source(self, src):
270293
if src.name in self.sources:
294+
log.info("ADD-SOURCE[+=]\n%r", src)
271295
self.sources[src.name] += src
272296
else:
297+
log.info("ADD-SOURCE[=]\n%r", src)
273298
self.sources[src.name] = src
274299

275300
def __getitem__(self, item):
@@ -322,7 +347,8 @@ def traverse(node, path):
322347
traverse(src, [])
323348

324349
def connect_generations(self):
325-
"Traverse depth-first adding imported_by."
350+
"""Traverse depth-first adding imported_by.
351+
"""
326352
for src in self.sources.values():
327353
for _child in src.imports:
328354
if _child in self.sources:
@@ -355,17 +381,22 @@ def exclude_noise(self):
355381
if src.is_noise():
356382
self.verbose(2, "excluding", src, "because it is noisy:", src.degree)
357383
src.excluded = True
384+
print "Exluding noise:", src.name
358385
self._add_skip(src.name)
359386

360387
def exclude_bacon(self, limit):
361-
"Exclude models that are more than `limit` hops away from __main__."
388+
"""Exclude models that are more than `limit` hops away from __main__.
389+
"""
362390
for src in self.sources.values():
363391
if src.bacon > limit:
364392
src.excluded = True
393+
# print "Excluding bacon:", src.name
365394
self._add_skip(src.name)
366395

367396
def remove_excluded(self):
368-
"Remove all sources marked as excluded."
397+
"""Remove all sources marked as excluded.
398+
"""
399+
# print yaml.dump({k:v.__json__() for k,v in self.sources.items()}, default_flow_style=False)
369400
sources = self.sources.values()
370401
for src in sources:
371402
if src.excluded:
@@ -374,4 +405,5 @@ def remove_excluded(self):
374405
src.imported_by = [m for m in src.imported_by if not self._exclude(m)]
375406

376407
def _add_skip(self, name):
408+
# print 'add skip:', name
377409
self.skiplist.append(re.compile(fnmatch.translate(name)))

pydeps/mf27.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,10 +387,13 @@ def scan_opcodes_25(self, co,
387387
oparg_1, oparg_2, oparg_3 = unpack('<xHxHxH', code[:9])
388388
level = consts[oparg_1]
389389
if level == -1: # normal import
390+
# print "import", (consts[oparg_2], names[oparg_3])
390391
yield "import", (consts[oparg_2], names[oparg_3])
391392
elif level == 0: # absolute import
393+
# print "absolute_import", (consts[oparg_2], names[oparg_3])
392394
yield "absolute_import", (consts[oparg_2], names[oparg_3])
393395
else: # relative import
396+
# print "relative_import", (level, consts[oparg_2], names[oparg_3])
394397
yield "relative_import", (level, consts[oparg_2], names[oparg_3])
395398
code = code[9:]
396399
continue

pydeps/py2depgraph.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
2020
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2121
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
import json
2223
import os
2324
import pprint
2425
import sys
2526
from collections import defaultdict
2627

2728
import enum
29+
import yaml
30+
from .pystdlib import pystdlib
2831

2932
from . import depgraph
3033
from . import mf27
@@ -83,6 +86,7 @@ def __init__(self, fname, *args, **kwargs):
8386
self.include_pylib_all = kwargs.pop('pylib_all', False)
8487

8588
# include python std lib modules.
89+
# self.include_pylib = kwargs.pop('pylib', self.include_pylib_all)
8690
self.include_pylib = kwargs.pop('pylib', self.include_pylib_all)
8791

8892
self._depgraph = defaultdict(dict)
@@ -121,7 +125,8 @@ def _add_import(self, module):
121125
pass
122126
else:
123127
rpath = os.path.split(module.__file__)[0].lower()
124-
pylib_p = [rpath.startswith(pp) for pp in PYLIB_PATH]
128+
if 'site-packages' not in rpath:
129+
pylib_p = [rpath.startswith(pp) for pp in PYLIB_PATH]
125130
if self.include_pylib or not any(pylib_p):
126131
# if self._last_caller.__name__ != module.__name__:
127132
# self._depgraph[self._last_caller.__name__][module.__name__] = module.__file__
@@ -288,23 +293,42 @@ def py2dep(pattern, **kw):
288293

289294
# remove exclude so we don't pass it twice to modulefinder
290295
exclude = ['migrations'] + kw.pop('exclude', [])
296+
log.debug("Exclude: %r", exclude)
297+
log.debug("KW: %r", kw)
291298
mf = MyModuleFinder(path, exclude, **kw)
292299
mf.run_script(fname)
300+
log.debug("mf._depgraph:\n%s", json.dumps(dict(mf._depgraph), indent=4))
293301

294302
# remove dummy file and restore exclude argument
295303
os.unlink(fname)
296304
kw['exclude'] = exclude
297305

298-
if kw.get('verbose', 0) >= 4: # pragma: nocover
299-
print
300-
print "mf27._depgraph:", mf._depgraph
301-
print "mf27._types: ", mf._types
302-
print "mf27.modules: ", pprint.pformat(mf.modules)
303-
print
304-
print "last caller: ", mf._last_caller
305-
306-
log.debug("Returning depgraph.")
307-
return depgraph.DepGraph(mf._depgraph, mf._types, **kw)
306+
if kw.get('pylib'):
307+
mf_depgraph = mf._depgraph
308+
for k, v in mf._depgraph.items():
309+
log.debug('depgraph item: %r %r', k, v)
310+
# mf_modules = {k: os.path.abspath(v.__file__)
311+
# for k, v in mf.modules.items()}
312+
else:
313+
pylib = pystdlib()
314+
mf_depgraph = {}
315+
for k, v in mf._depgraph.items():
316+
log.debug('depgraph item: %r %r', k, v)
317+
if k in pylib:
318+
continue
319+
vals = {vk: vv for vk, vv in v.items() if vk not in pylib}
320+
mf_depgraph[k] = vals
321+
322+
# mf_modules = {k: os.path.abspath(v.__file__)
323+
# for k, v in mf.modules.items()
324+
# if k not in pylib}
325+
326+
log.debug("mf_depgraph:\n%s",
327+
yaml.dump(dict(mf_depgraph), default_flow_style=False))
328+
# log.error("mf._types:\n%s", yaml.dump(mf._types, default_flow_style=False))
329+
# log.debug("mf_modules:\n%s", yaml.dump(mf_modules, default_flow_style=False))
330+
331+
return depgraph.DepGraph(mf_depgraph, mf._types, **kw)
308332

309333

310334
def py2depgraph():

pydeps/pydeps.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@
77
import os
88
import pprint
99
import sys
10+
import textwrap
11+
1012
from .py2depgraph import py2dep
1113
from .depgraph2dot import dep2dot, cycles2dot
1214
from .dot import dot
1315
from . import __version__
1416
import logging
15-
logging.basicConfig(
16-
level=logging.CRITICAL,
17-
# level=logging.DEBUG,
18-
format='%(filename)s:%(lineno)d: %(levelname)s: %(message)s'
19-
)
2017

2118

2219
def _pydeps(**kw):
@@ -71,8 +68,21 @@ def parse_args(argv=()):
7168
_p.add_argument('--config', help="specify config file", metavar="FILE")
7269
_p.add_argument('--no-config', help="disable processing of config files", action='store_true')
7370
_p.add_argument('--version', action='store_true', help='print pydeps version')
71+
_p.add_argument('-L', '--log', help=textwrap.dedent('''
72+
set log-level to one of CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET.
73+
'''))
7474
_args, argv = _p.parse_known_args(argv)
7575

76+
if _args.log:
77+
loglevel = getattr(logging, _args.log)
78+
else:
79+
loglevel = None
80+
81+
logging.basicConfig(
82+
level=loglevel,
83+
format='%(filename)s:%(lineno)d: %(levelname)s: %(message)s'
84+
)
85+
7686
if _args.version:
7787
print "pydeps v" + __version__
7888
sys.exit(0)
@@ -136,7 +146,7 @@ def parse_args(argv=()):
136146
if _args.externals:
137147
return dict(
138148
T='svg', config=None, debug=False, display=None, exclude=[], externals=True,
139-
fname=_args.fname, format='svg', max_bacon=2, no_config=False, nodot=False,
149+
fname=_args.fname, format='svg', max_bacon=10, no_config=False, nodot=False,
140150
noise_level=200, noshow=True, output=None, pylib=False, pylib_all=False,
141151
show=False, show_cycles=False, show_deps=False, show_dot=False,
142152
show_raw_deps=False, verbose=0
@@ -173,8 +183,8 @@ def externals(pkgname):
173183
"""
174184
kw = dict(
175185
T='svg', config=None, debug=False, display=None, exclude=[], externals=True,
176-
format='svg', max_bacon=5, no_config=False, nodot=False,
177-
noise_level=200, noshow=True, output=None, pylib=False, pylib_all=False,
186+
format='svg', max_bacon=2**65, no_config=False, nodot=False,
187+
noise_level=2**65, noshow=True, output=None, pylib=False, pylib_all=True,
178188
show=False, show_cycles=False, show_deps=False, show_dot=False,
179189
show_raw_deps=False, verbose=0
180190
)

pydeps/pystdlib.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import sys
4+
import stdlib_list
5+
6+
7+
def pystdlib():
8+
"""Return a set of all module-names in the Python standard library.
9+
"""
10+
curver = '.'.join(str(x) for x in sys.version_info[:2])
11+
return (set(stdlib_list.stdlib_list(curver)) | {
12+
'_LWPCookieJar', '_MozillaCookieJar', '_abcoll', 'email._parseaddr',
13+
'email.base64mime', 'email.feedparser', 'email.quoprimime',
14+
'encodings', 'genericpath', 'ntpath', 'nturl2path', 'os2emxpath',
15+
'posixpath', 'sre_compile', 'sre_parse', 'unittest.case',
16+
'unittest.loader', 'unittest.main', 'unittest.result',
17+
'unittest.runner', 'unittest.signals', 'unittest.suite',
18+
'unittest.util', '_threading_local', 'sre_constants', 'strop',
19+
'repr', 'opcode', 'nt', 'encodings.aliases',
20+
'_bisect', '_codecs', '_collections', '_functools', '_hashlib',
21+
'_heapq', '_io', '_locale', '_LWPCookieJar', '_md5',
22+
'_MozillaCookieJar', '_random', '_sha', '_sha256', '_sha512',
23+
'_socket', '_sre', '_ssl', '_struct', '_subprocess',
24+
'_threading_local', '_warnings', '_weakref', '_weakrefset',
25+
'_winreg'
26+
}) - {'__main__'}

requirements.txt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
PyYAML==3.11
2-
cov-core==1.15.0
3-
coverage==3.7.1
4-
enum34==1.0.4
5-
py==1.4.26
6-
pytest==2.6.4
7-
pytest-cov==1.8.1
8-
Sphinx==1.2.3
1+
PyYAML==3.12
2+
cov-core==1.15.0
3+
coverage==4.2
4+
enum34==1.0.4
5+
py==1.4.31
6+
pytest==3.0.3
7+
pytest-cov==2.4.0
8+
Sphinx==1.4.8
9+
stdlib-list>=0.3.3

0 commit comments

Comments
 (0)