1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345.
|
#!/usr/bin/env python
"""importnanny
----------
Ensures that all imported modules are referenced from the Python code
in the module. Use to detect unneeded imports.
Provide a list of file names to the ARGV of the script or pipe in a
list of file names to STDIN.
CHANGELOG
=========
Version 3
---------
Incorperated into PyTis tools.
Version 2
---------
Update by Josh Lee to handle meta classes.
Version 1
---------
Originaly created by Jeremy Lowery
"""
import compiler
import optparse
import os
import sys
import traceback
import pytis as PyTis
__curdir__ = os.path.abspath(os.path.dirname(__file__))
__author__ = 'Jeremy Lowery'
__created__ = '06:24pm 06 Jun, 2010'
__copyright__ = 'PyTis.com'
__version__ = '3.0'
class modref(object):
def __init__(self, modname, alias, lineno):
self.modname = modname
self.alias = alias
self.lineno = lineno
def __repr__(self):
return '<%s:%s at line %s>' % (self.alias, self.modname, self.lineno)
class ns_stack(list):
def __init__(self):
self._master_list = {}
def add_mod(self, mod):
self[0][mod.alias] = [mod]
self._master_list[mod] = False
def mark(self, name):
""" Mark a name as matched. propigating up the stack until we're done
or we hit a None. """
for stack in self:
for ref in stack.get(name, []):
if ref is None:
# We hit a name that was covered up
return
self._master_list[ref] = True
return
def missing(self):
return [m for m, f in self._master_list.items() if not f]
def push_stack(self, stack=None):
if stack is None:
stack = {}
self.insert(0, stack)
def pop_stack(self):
return self.pop(0)
def has_name(self, name):
for lookup in self:
if name in lookup:
return True
return False
def cover_name(self, name):
if self.has_name(name):
if name not in self[0]:
self[0][name] = []
self[0][name].insert(0, None)
class ImportVisitor(object):
def __init__(self):
self._ns_stack = ns_stack()
def visit(self, node):
self._ns_stack.push_stack()
self._visit(node)
def missing(self):
return self._ns_stack.missing()
def _visit(self, node):
global log
if not node:
return
type = node.__class__.__name__
if type == 'Function':
log.debug('starting stack on function %s' % node.name)
# The function name covers up
self._ns_stack.cover_name(node.name)
self._ns_stack.push_stack()
# In the inner frame, the arguments cover up
for arg in node.argnames:
self._ns_stack.cover_name(arg)
for sub in node.getChildren():
self._visit(sub)
self._ns_stack.pop_stack()
log.debug('ending stack on function %s' % node.name)
elif type == 'Class':
# classes are tricky because what happens in the class scope never
# leaves the class. The methods in the class don't have access to
#this scope
self._ns_stack.push_stack()
for e in node.bases:
self._dispatch_type(e)
self.walk_class(node.code, class_name=node.name)
self._ns_stack.pop_stack()
self._ns_stack.cover_name(node.name)
elif self._dispatch_type(node):
pass
elif hasattr(node, 'getChildren'):
for sub in node.getChildren():
self._visit(sub)
def _dispatch_type(self, node):
global log
type = node.__class__.__name__
if type == 'Import':
for modname, alias in node.getChildren()[0]:
if alias is None:
alias = modname
self._ns_stack.add_mod(modref(modname, alias, node.lineno))
elif type == 'From':
implist = node.getChildren()
modname, implist = implist[:2]
for var, alias in implist:
if alias is None:
alias = var
self._ns_stack.add_mod(modref(modname, alias, node.lineno))
elif type == 'AssName':
var_name = node.getChildren()[0]
self._ns_stack.cover_name(var_name)
elif type == 'Name':
log.debug('checking Name: %s stack level: %s' % (node.getChildren()[0], len(self._ns_stack)))
self._ns_stack.mark(node.getChildren()[0])
elif type == 'Getattr':
# We have to handle the "DOT" notation with GetAttr.
#print 'GETATTR', node.attrname, node
#print '\t', node.getChildren()
resolved = self.resolve_attribute(node)
#print 'Our result for', node.attrname, resolved
for r in resolved:
self._ns_stack.mark(r)
elif type == 'Lambda':
self._ns_stack.push_stack()
for arg_name in node.argnames:
self._ns_stack.cover_name(arg_name)
self._visit(node.code)
self._ns_stack.pop_stack()
else:
return False
return True
def resolve_attribute(self, node):
""" Resolving an attribute builds up a dotted list of names. """
head, tail = node.getChildren()
head_type = head.__class__.__name__
if head_type == 'Getattr':
hv = self.resolve_attribute(head)
if hv:
return hv + ["%s.%s" % (hv[-1], tail)]
else:
return []
elif head_type == 'Name':
return [head.name, "%s.%s" % (head.name, tail)]
else:
#print 'Deferring type %s: %s' % (head_type, head)
# It's something strange like b().c. We defer off to visit
# However, none of the names in the tail will
# count towards a resolution
self._visit(head)
return []
def walk_class(self, node, class_name=None):
global log
# Whenever we dive into a lexical close, we have to track
# ourselves and add ourselves back because of the funny scope class
# semantics.
if not hasattr(node, 'getChildren'):
return
for child in node.getChildren():
type = child.__class__.__name__
if type in ('Function', 'Lambda', 'Class'):
log.debug('going to function from class on %s' % child)
cur = self._ns_stack.pop_stack()
# The class name is available in functions under the class, but
# not the class scope itself
if class_name:
self._ns_stack.push_stack()
self._ns_stack.cover_name(class_name)
self._visit(child)
if class_name:
self._ns_stack.pop_stack()
self._ns_stack.push_stack(cur)
elif self._dispatch_type(child):
pass
else:
self.walk_class(child, class_name)
def run(opts,files):
global log
for path in files:
if path.strip():
log.info("CHECKING: %s" % os.path.abspath(path))
try:
mod = compiler.parseFile(path)
except SyntaxError:
print 'Syntax Error in %r' % path
traceback.print_exc(0, file=sys.stdout)
continue
v = ImportVisitor()
v.visit(mod.node)
file_buf = [x for x in open(path)]
mods = v.missing()
mods.sort(lambda x, y: cmp(x.lineno, y.lineno))
if opts.verbose:
show_filename = True
else:
show_filename = False
for mod in mods:
if not show_filename:
print 'Results for %r' % path
show_filename = True
if mod.alias == '*':
print 'Skipping * import on module %r (line %s)'\
% (mod.modname, mod.lineno)
print ' %s' % file_buf[mod.lineno-1].strip()
else:
if mod.alias == mod.modname:
print 'Missing reference %r (line %s)'\
% (mod.modname, mod.lineno)
else:
print 'Missing reference %r for variable %r (line %s)'\
% (mod.alias, mod.modname, mod.lineno)
print ' %s' % file_buf[mod.lineno-1].strip()
if show_filename:
print
def main():
"""usage: importnanny """
global log
hlp = __doc__ % dict(version=__version__,
author=__author__,
copyright=__copyright__)
parser = PyTis.MyParser()
if '?' in sys.argv[1:] or '-h' in sys.argv[1:] or '--help' in sys.argv[1:]:
hlp = "%s\n%s" % (hlp,
"""
example:
jlee on bin $ importnanny -rV pg_*
CHECKING: /home/jlee/bin/pg_diff
CHECKING: /home/jlee/bin/pg_diff.rb
Syntax Error in '/home/jlee/bin/pg_diff.rb'
Traceback (most recent call last):
File "<string>", line 18
require 'postgres'
^
SyntaxError: invalid syntax
CHECKING: /home/jlee/bin/pg_func_diff
CHECKING: /home/jlee/bin/pg_import
Results for '/home/jlee/bin/pg_import'
Missing reference 'cStringIO' (line 74)
import cStringIO
Missing reference 'math' (line 75)
import math
Missing reference 'pydoc' (line 78)
import pydoc
CHECKING: /home/jlee/bin/pg_strip
""" )
parser.set_description(hlp)
parser.set_usage(main.__doc__)
parser.formatter.format_description = lambda s:s
parser.add_option("-D", "--debug", action="store_true",
default=False,
help="Enable debugging")
parser.add_option("-v", "--version", action="store_true",
default=False,
help="Display Version")
parser.add_option("-V", "--verbose", action="store_true",
default=False,
help="Be more Verbose")
parser.add_option("-r", "--recursive", action="store_true",
default=False,
help="Recursively apply copyright to all files.")
(opts, args) = parser.parse_args()
log = PyTis.set_logging(opts, 'importnanny')
log.debug("OPTS debug: %s" % opts.debug)
log.debug("OPTS version: %s" % opts.version)
if opts.version:
return PyTis.version(__version__)
if sys.stdin.isatty():
files = PyTis.filesFromArgs(opts,args)
else:
files = [x.strip() for x in sys.stdin]
if not files:
return parser.print_help()
else:
return run(opts,files)
if __name__ == '__main__':
main()
|