Whole file

PyCQA/pyflakes

The author described this change as Fix TypeError when processing relative imports (#61). It counts as a record because the checks below fail on the code as it stood at 885a8e539 and pass on d8591997d, with nothing else changed between the two runs.

Fix saved2016-05-12
Sharing licenceMIT · LICENSE
Change size+964 953

What the code was meant to do, written into the code itself as a save note

Fix TypeError when processing relative imports (#61)

The change

209209 def __init__(self, name, source, module, real_name=None):
210210 self.module = module
211211 self.real_name = real_name or name
212- full_name = module + '.' + self.real_name
213- super(ImportationFrom, self).__init__(name, source, full_name)
214-
215- def __str__(self):
216- """Return import full name with alias."""
217- if self.real_name != self.name:
218- return self.fullName + ' as ' + self.name
219- else:
220- return self.fullName
221-
222- @property
223- def source_statement(self):
224- if self.real_name != self.name:
225- return 'from %s import %s as %s' % (self.module,
226- self.real_name,
227- self.name)
228- else:
229- return 'from %s import %s' % (self.module, self.name)
230-
231-
232-class StarImportation(Importation):
233- """A binding created by an 'from x import *' statement."""
234-
235- def __init__(self, name, source):
236- super(StarImportation, self).__init__('*', source)
237- # Each star importation needs a unique name, and
238- # may not be the module name otherwise it will be deemed imported
239- self.name = name + '.*'
240- self.fullName = name
241-
242- @property
243- def source_statement(self):
244- return 'from ' + self.fullName + ' import *'
245-
246- def __str__(self):
247- return self.name
248-
249-
250-class FutureImportation(ImportationFrom):
251- """
252- A binding created by a from `__future__` import statement.
253-
254- `__future__` imports are implicitly used.
255- """
256-
257- def __init__(self, name, source, scope):
258- super(FutureImportation, self).__init__(name, source, '__future__')
259- self.used = (scope, source)
260-
261-
262-class Argument(Binding):
263- """
264- Represents binding a name as an argument.
265- """
266-
267-
268-class Assignment(Binding):
269- """
270- Represents binding a name with an explicit assignment.
271-
272- The checker will raise warnings for any Assignment that isn't used. Also,
273- the checker does not consider assignments in tuple/list unpacking to be
274- Assignments, rather it treats them as simple Bindings.
275- """
276-
277-
278-class FunctionDefinition(Definition):
279- pass
280-
281-
282-class ClassDefinition(Definition):
283- pass
284-
285-
286-class ExportBinding(Binding):
287- """
288- A binding created by an C{__all__} assignment. If the names in the list
289- can be determined statically, they will be treated as names for export and
290- additional checking applied to them.
291-
292- The only C{__all__} assignment that can be recognized is one which takes
293- the value of a literal list containing literal strings. For example::
294-
295- __all__ = ["foo", "bar"]
296-
297- Names which are imported and not otherwise used but appear in the value of
298- C{__all__} will not have an unused import warning reported for them.
299- """
300-
301- def __init__(self, name, source, scope):
302- if '__all__' in scope and isinstance(source, ast.AugAssign):
303- self.names = list(scope['__all__'].names)
304- else:
305- self.names = []
306- if isinstance(source.value, (ast.List, ast.Tuple)):
307- for node in source.value.elts:
308- if isinstance(node, ast.Str):
309- self.names.append(node.s)
310- super(ExportBinding, self).__init__(name, source)
311-
312-
313-class Scope(dict):
314- importStarred = False # set to True when import * is found
315-
316- def __repr__(self):
317- scope_cls = self.__class__.__name__
318- return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self))
319-
320-
321-class ClassScope(Scope):
322- pass
323-
324-
325-class FunctionScope(Scope):
326- """
327- I represent a name scope for a function.
328-
329- @ivar globals: Names declared 'global' in this function.
330- """
331- usesLocals = False
332- alwaysUsed = set(['__tracebackhide__',
333- '__traceback_info__', '__traceback_supplement__'])
334-
335- def __init__(self):
336- super(FunctionScope, self).__init__()
337- # Simplify: manage the special locals as globals
338- self.globals = self.alwaysUsed.copy()
339- self.returnValue = None # First non-empty return
340- self.isGenerator = False # Detect a generator
341-
342- def unusedAssignments(self):
343- """
344- Return a generator for the assignments which have not been used.
345- """
346- for name, binding in self.items():
347- if (not binding.used and name not in self.globals
348- and not self.usesLocals
349- and isinstance(binding, Assignment)):
350- yield name, binding
351-
352-
353-class GeneratorScope(Scope):
354- pass
355-
356-
357-class ModuleScope(Scope):
358- """Scope for a module."""
359- _futures_allowed = True
360-
361-
362-class DoctestScope(ModuleScope):
363- """Scope for a doctest."""
364-
365-
366-# Globally defined names which are not attributes of the builtins module, or
367-# are only present on some platforms.
368-_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
369-
370-
371-def getNodeName(node):
372- # Returns node.id, or node.name, or None
373- if hasattr(node, 'id'): # One of the many nodes with an id
374- return node.id
375- if hasattr(node, 'name'): # an ExceptHandler node
376- return node.name
377-
378-
379-class Checker(object):
380- """
381- I check the cleanliness and sanity of Python code.
382-
383- @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
384- of the list are two-tuples. The first element is the callable passed
385- to L{deferFunction}. The second element is a copy of the scope stack
386- at the time L{deferFunction} was called.
387-
388- @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
389- callables which are deferred assignment checks.
390- """
391-
392- nodeDepth = 0
393- offset = None
394- traceTree = False
395-
396- builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
397- _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
398- if _customBuiltIns:
399- builtIns.update(_customBuiltIns.split(','))
400- del _customBuiltIns
401-
402- def __init__(self, tree, filename='(none)', builtins=None,
403- withDoctest='PYFLAKES_DOCTEST' in os.environ):
404- self._nodeHandlers = {}
405- self._deferredFunctions = []
406- self._deferredAssignments = []
407- self.deadScopes = []
408- self.messages = []
409- self.filename = filename
410- if builtins:
411- self.builtIns = self.builtIns.union(builtins)
412- self.withDoctest = withDoctest
413- self.scopeStack = [ModuleScope()]
414- self.exceptHandlers = [()]
415- self.root = tree
416- self.handleChildren(tree)
417- self.runDeferred(self._deferredFunctions)
418- # Set _deferredFunctions to None so that deferFunction will fail
419- # noisily if called after we've run through the deferred functions.
420- self._deferredFunctions = None
421- self.runDeferred(self._deferredAssignments)
422- # Set _deferredAssignments to None so that deferAssignment will fail
423- # noisily if called after we've run through the deferred assignments.
424- self._deferredAssignments = None
425- del self.scopeStack[1:]
426- self.popScope()
427- self.checkDeadScopes()
428-
429- def deferFunction(self, callable):
430- """
431- Schedule a function handler to be called just before completion.
432-
433- This is used for handling function bodies, which must be deferred
434- because code later in the file might modify the global scope. When
435- `callable` is called, the scope at the time this is called will be
436- restored, however it will contain any new bindings added to it.
437- """
438- self._deferredFunctions.append((callable, self.scopeStack[:], self.offset))
439-
440- def deferAssignment(self, callable):
441- """
442- Schedule an assignment handler to be called just after deferred
443- function handlers.
444- """
445- self._deferredAssignments.append((callable, self.scopeStack[:], self.offset))
446-
447- def runDeferred(self, deferred):
448- """
449- Run the callables in C{deferred} using their associated scope stack.
450- """
451- for handler, scope, offset in deferred:
452- self.scopeStack = scope
453- self.offset = offset
454- handler()
455-
456- def _in_doctest(self):
457- return (len(self.scopeStack) >= 2 and
458- isinstance(self.scopeStack[1], DoctestScope))
459-
460- @property
461- def futuresAllowed(self):
462- if not all(isinstance(scope, ModuleScope)
463- for scope in self.scopeStack):
464- return False
465-
466- return self.scope._futures_allowed
467-
468- @futuresAllowed.setter
469- def futuresAllowed(self, value):
470- assert value is False
471- if isinstance(self.scope, ModuleScope):
472- self.scope._futures_allowed = False
473-
474- @property
475- def scope(self):
476- return self.scopeStack[-1]
477-
478- def popScope(self):
479- self.deadScopes.append(self.scopeStack.pop())
480-
481- def checkDeadScopes(self):
482- """
483- Look at scopes which have been fully examined and report names in them
484- which were imported but unused.
485- """
486- for scope in self.deadScopes:
487- # imports in classes are public members
488- if isinstance(scope, ClassScope):
489- continue
490-
491- all_binding = scope.get('__all__')
492- if all_binding and not isinstance(all_binding, ExportBinding):
493- all_binding = None
494-
495- if all_binding:
496- all_names = set(all_binding.names)
497- undefined = all_names.difference(scope)
498- else:
499- all_names = undefined = []
500-
501- if undefined:
502- if not scope.importStarred and \
503- os.path.basename(self.filename) != '__init__.py':
504- # Look for possible mistakes in the export list
505- for name in undefined:
506- self.report(messages.UndefinedExport,
507- scope['__all__'].source, name)
508-
509- # mark all import '*' as used by the undefined in __all__
510- if scope.importStarred:
511- for binding in scope.values():
512- if isinstance(binding, StarImportation):
513- binding.used = all_binding
514-
515- # Look for imported names that aren't used.
516- for value in scope.values():
517- if isinstance(value, Importation):
518- used = value.used or value.name in all_names
519- if not used:
520- messg = messages.UnusedImport
521- self.report(messg, value.source, str(value))
522- for node in value.redefined:
523- if isinstance(self.getParent(node), ast.For):
524- messg = messages.ImportShadowedByLoopVar
525- elif used:
526- continue
527- else:
528- messg = messages.RedefinedWhileUnused
529- self.report(messg, node, value.name, value.source)
530-
531- def pushScope(self, scopeClass=FunctionScope):
532- self.scopeStack.append(scopeClass())
533-
534- def report(self, messageClass, *args, **kwargs):
535- self.messages.append(messageClass(self.filename, *args, **kwargs))
536-
537- def getParent(self, node):
538- # Lookup the first parent which is not Tuple, List or Starred
539- while True:
540- node = node.parent
541- if not hasattr(node, 'elts') and not hasattr(node, 'ctx'):
542- return node
543-
544- def getCommonAncestor(self, lnode, rnode, stop):
545- if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and
546- hasattr(rnode, 'parent')):
547- return None
548- if lnode is rnode:
549- return lnode
550-
551- if (lnode.depth > rnode.depth):
552- return self.getCommonAncestor(lnode.parent, rnode, stop)
553- if (lnode.depth < rnode.depth):
554- return self.getCommonAncestor(lnode, rnode.parent, stop)
555- return self.getCommonAncestor(lnode.parent, rnode.parent, stop)
556-
557- def descendantOf(self, node, ancestors, stop):
558- for a in ancestors:
559- if self.getCommonAncestor(node, a, stop):
560- return True
561- return False
562-
563- def differentForks(self, lnode, rnode):
564- """True, if lnode and rnode are located on different forks of IF/TRY"""
565- ancestor = self.getCommonAncestor(lnode, rnode, self.root)
566- parts = getAlternatives(ancestor)
567- if parts:
568- for items in parts:
569- if self.descendantOf(lnode, items, ancestor) ^ \
570- self.descendantOf(rnode, items, ancestor):
571- return True
572- return False
573-
574- def addBinding(self, node, value):
575- """
576- Called when a binding is altered.
577-
578- - `node` is the statement responsible for the change
579- - `value` is the new value, a Binding instance
580- """
581- # assert value.source in (node, node.parent):
582- for scope in self.scopeStack[::-1]:
583- if value.name in scope:
584- break
585- existing = scope.get(value.name)
586-
587- if existing and not self.differentForks(node, existing.source):
588-
589- parent_stmt = self.getParent(value.source)
590- if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For):
591- self.report(messages.ImportShadowedByLoopVar,
592- node, value.name, existing.source)
593-
594- elif scope is self.scope:
595- if (isinstance(parent_stmt, ast.comprehension) and
596- not isinstance(self.getParent(existing.source),
597- (ast.For, ast.comprehension))):
598- self.report(messages.RedefinedInListComp,
599- node, value.name, existing.source)
600- elif not existing.used and value.redefines(existing):
601- self.report(messages.RedefinedWhileUnused,
602- node, value.name, existing.source)
603-
604- elif isinstance(existing, Importation) and value.redefines(existing):
605- existing.redefined.append(node)
606-
607- if value.name in self.scope:
608- # then assume the rebound name is used as a global or within a loop
1523 further changed lines not shown

The check that tells the two apart

failpass·pyflakes/test/test_imports.py::Test::test_importStar_relative
failpass·pyflakes/test/test_imports.py::Test::test_unusedImport_relative
failpass·pyflakes/test/test_imports.py::Test::test_usedImport_relative
failpass·pyflakes/test/test_imports.py::TestImportationObject::test_importfrom_relative
failpass·pyflakes/test/test_imports.py::TestImportationObject::test_importfrom_relative_parent

Check file pyflakes/test/test_imports.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.

Origin and history

The code before it885a8e5395f3eac58a995a49dbe347b8f6648f64
Broken version dated2016-05-06
Modulepyflakes.checker
Units changedChecker, ImportationFrom, StarImportation
Fingerprint294770267807fb7b
Checked2026-08-18 by goldset/0.1

Every field above is generated by our program. None of it is written by hand.

Other bugs found in PyCQA/pyflakes