Hello community, here is the log from the commit of package python3-pyflakes for openSUSE:Factory checked in at 2016-05-16 12:04:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-pyflakes (Old) and /work/SRC/openSUSE:Factory/.python3-pyflakes.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-pyflakes" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-pyflakes/python3-pyflakes.changes 2016-03-07 13:29:46.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python3-pyflakes.new/python3-pyflakes.changes 2016-05-16 12:04:14.000000000 +0200 @@ -1,0 +2,23 @@ +Sun May 15 04:30:52 UTC 2016 - [email protected] + +- update to version 1.2.3: + * Fix TypeError when processing relative imports + +- changes from version 1.2.2: + * Avoid traceback when exception is del-ed in except + +- changes from version 1.2.1: + * Fix false RedefinedWhileUnesed for submodule imports + +- changes from version 1.2.0: + * Warn against reusing exception names after the except: block on + Python 3 + * Improve the error messages for imports + +------------------------------------------------------------------- +Sun May 8 07:04:51 UTC 2016 - [email protected] + +- specfile: + * updated source url to files.pythonhosted.org + +------------------------------------------------------------------- Old: ---- pyflakes-1.1.0.tar.gz New: ---- pyflakes-1.2.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-pyflakes.spec ++++++ --- /var/tmp/diff_new_pack.mXXAYB/_old 2016-05-16 12:04:15.000000000 +0200 +++ /var/tmp/diff_new_pack.mXXAYB/_new 2016-05-16 12:04:15.000000000 +0200 @@ -17,13 +17,13 @@ Name: python3-pyflakes -Version: 1.1.0 +Version: 1.2.3 Release: 0 Url: https://github.com/pyflakes/pyflakes Summary: Passive checker of Python 3 programs License: MIT Group: Development/Languages/Python -Source: https://pypi.python.org/packages/source/p/pyflakes/pyflakes-%{version}.tar.gz +Source: https://files.pythonhosted.org/packages/source/p/pyflakes/pyflakes-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-build # please do not remove, needed for openSUSE <= 12.2 BuildRequires: python3 ++++++ pyflakes-1.1.0.tar.gz -> pyflakes-1.2.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/NEWS.txt new/pyflakes-1.2.3/NEWS.txt --- old/pyflakes-1.1.0/NEWS.txt 2015-11-03 14:18:49.000000000 +0100 +++ new/pyflakes-1.2.3/NEWS.txt 2016-05-12 20:30:42.000000000 +0200 @@ -1,3 +1,33 @@ +1.2.3 (2016-05-12): + - Fix TypeError when processing relative imports + +1.2.2 (2016-05-06): + - Avoid traceback when exception is del-ed in except + +1.2.1 (2015-05-05): + - Fix false RedefinedWhileUnesed for submodule imports + +1.2.0 (2016-05-03): + - Warn against reusing exception names after the except: block on Python 3 + - Improve the error messages for imports + +1.1.0 (2016-03-01): + - Allow main() to accept arguments. + - Support @ matrix-multiplication operator + - Validate __future__ imports + - Fix doctest scope testing + - Warn for tuple assertions which are always true + - Warn for "import *" not at module level on Python 3 + - Catch many more kinds of SyntaxErrors + - Check PEP 498 f-strings + - (and a few more sundry bugfixes) + +1.0.0 (2015-09-20): + - Python 3.5 support. async/await statements in particular. + - Fix test_api.py on Windows. + - Eliminate a false UnusedImport warning when the name has been + declared "global" + 0.9.2 (2015-06-17): - Fix a traceback when a global is defined in one scope, and used in another. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/PKG-INFO new/pyflakes-1.2.3/PKG-INFO --- old/pyflakes-1.1.0/PKG-INFO 2016-03-01 16:32:21.000000000 +0100 +++ new/pyflakes-1.2.3/PKG-INFO 2016-05-12 20:38:30.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyflakes -Version: 1.1.0 +Version: 1.2.3 Summary: passive checker of Python programs Home-page: https://github.com/pyflakes/pyflakes Author: A lot of people @@ -69,6 +69,8 @@ Contributing ------------ + Issues are tracked on `Launchpad <https://bugs.launchpad.net/pyflakes>`_. + Patches may be submitted via a `GitHub pull request`_ or via the mailing list if you prefer. If you are comfortable doing so, please `rebase your changes`_ so they may be applied to master with a fast-forward merge, and each commit is diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/README.rst new/pyflakes-1.2.3/README.rst --- old/pyflakes-1.1.0/README.rst 2015-11-13 18:40:37.000000000 +0100 +++ new/pyflakes-1.2.3/README.rst 2016-05-06 00:37:30.000000000 +0200 @@ -61,6 +61,8 @@ Contributing ------------ +Issues are tracked on `Launchpad <https://bugs.launchpad.net/pyflakes>`_. + Patches may be submitted via a `GitHub pull request`_ or via the mailing list if you prefer. If you are comfortable doing so, please `rebase your changes`_ so they may be applied to master with a fast-forward merge, and each commit is diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/pyflakes/__init__.py new/pyflakes-1.2.3/pyflakes/__init__.py --- old/pyflakes-1.1.0/pyflakes/__init__.py 2016-03-01 15:41:11.000000000 +0100 +++ new/pyflakes-1.2.3/pyflakes/__init__.py 2016-05-12 20:29:17.000000000 +0200 @@ -1 +1 @@ -__version__ = '1.1.0' +__version__ = '1.2.3' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/pyflakes/checker.py new/pyflakes-1.2.3/pyflakes/checker.py --- old/pyflakes-1.1.0/pyflakes/checker.py 2016-03-01 15:04:37.000000000 +0100 +++ new/pyflakes-1.2.3/pyflakes/checker.py 2016-05-12 20:29:04.000000000 +0200 @@ -136,17 +136,103 @@ @type fullName: C{str} """ - def __init__(self, name, source): - self.fullName = name + def __init__(self, name, source, full_name=None): + self.fullName = full_name or name self.redefined = [] - name = name.split('.')[0] super(Importation, self).__init__(name, source) def redefines(self, other): - if isinstance(other, Importation): + if isinstance(other, SubmoduleImportation): + # See note in SubmoduleImportation about RedefinedWhileUnused return self.fullName == other.fullName return isinstance(other, Definition) and self.name == other.name + def _has_alias(self): + """Return whether importation needs an as clause.""" + return not self.fullName.split('.')[-1] == self.name + + @property + def source_statement(self): + """Generate a source statement equivalent to the import.""" + if self._has_alias(): + return 'import %s as %s' % (self.fullName, self.name) + else: + return 'import %s' % self.fullName + + def __str__(self): + """Return import full name with alias.""" + if self._has_alias(): + return self.fullName + ' as ' + self.name + else: + return self.fullName + + +class SubmoduleImportation(Importation): + """ + A binding created by a submodule import statement. + + A submodule import is a special case where the root module is implicitly + imported, without an 'as' clause, and the submodule is also imported. + Python does not restrict which attributes of the root module may be used. + + This class is only used when the submodule import is without an 'as' clause. + + pyflakes handles this case by registering the root module name in the scope, + allowing any attribute of the root module to be accessed. + + RedefinedWhileUnused is suppressed in `redefines` unless the submodule + name is also the same, to avoid false positives. + """ + + def __init__(self, name, source): + # A dot should only appear in the name when it is a submodule import + assert '.' in name and (not source or isinstance(source, ast.Import)) + package_name = name.split('.')[0] + super(SubmoduleImportation, self).__init__(package_name, source) + self.fullName = name + + def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return super(SubmoduleImportation, self).redefines(other) + + def __str__(self): + return self.fullName + + @property + def source_statement(self): + return 'import ' + self.fullName + + +class ImportationFrom(Importation): + + def __init__(self, name, source, module, real_name=None): + self.module = module + self.real_name = real_name or name + + if module.endswith('.'): + full_name = module + self.real_name + else: + full_name = module + '.' + self.real_name + + super(ImportationFrom, self).__init__(name, source, full_name) + + def __str__(self): + """Return import full name with alias.""" + if self.real_name != self.name: + return self.fullName + ' as ' + self.name + else: + return self.fullName + + @property + def source_statement(self): + if self.real_name != self.name: + return 'from %s import %s as %s' % (self.module, + self.real_name, + self.name) + else: + return 'from %s import %s' % (self.module, self.name) + class StarImportation(Importation): """A binding created by an 'from x import *' statement.""" @@ -158,8 +244,19 @@ self.name = name + '.*' self.fullName = name + @property + def source_statement(self): + return 'from ' + self.fullName + ' import *' + + def __str__(self): + # When the module ends with a ., avoid the ambiguous '..*' + if self.fullName.endswith('.'): + return self.source_statement + else: + return self.name -class FutureImportation(Importation): + +class FutureImportation(ImportationFrom): """ A binding created by a from `__future__` import statement. @@ -167,7 +264,7 @@ """ def __init__(self, name, source, scope): - super(FutureImportation, self).__init__(name, source) + super(FutureImportation, self).__init__(name, source, '__future__') self.used = (scope, source) @@ -284,7 +381,7 @@ # Returns node.id, or node.name, or None if hasattr(node, 'id'): # One of the many nodes with an id return node.id - if hasattr(node, 'name'): # a ExceptHandler node + if hasattr(node, 'name'): # an ExceptHandler node return node.name @@ -430,7 +527,7 @@ used = value.used or value.name in all_names if not used: messg = messages.UnusedImport - self.report(messg, value.source, value.name) + self.report(messg, value.source, str(value)) for node in value.redefined: if isinstance(self.getParent(node), ast.For): messg = messages.ImportShadowedByLoopVar @@ -1039,8 +1136,11 @@ def IMPORT(self, node): for alias in node.names: - name = alias.asname or alias.name - importation = Importation(name, node) + if '.' in alias.name and not alias.asname: + importation = SubmoduleImportation(alias.name, node) + else: + name = alias.asname or alias.name + importation = Importation(name, node, alias.name) self.addBinding(node, importation) def IMPORTFROM(self, node): @@ -1051,6 +1151,8 @@ else: self.futuresAllowed = False + module = ('.' * node.level) + (node.module or '') + for alias in node.names: name = alias.asname or alias.name if node.module == '__future__': @@ -1062,14 +1164,15 @@ # Only Python 2, local import * is a SyntaxWarning if not PY2 and not isinstance(self.scope, ModuleScope): self.report(messages.ImportStarNotPermitted, - node, node.module) + node, module) continue self.scope.importStarred = True - self.report(messages.ImportStarUsed, node, node.module) - importation = StarImportation(node.module, node) + self.report(messages.ImportStarUsed, node, module) + importation = StarImportation(module, node) else: - importation = Importation(name, node) + importation = ImportationFrom(name, node, + module, alias.name) self.addBinding(node, importation) def TRY(self, node): @@ -1095,8 +1198,35 @@ TRYEXCEPT = TRY def EXCEPTHANDLER(self, node): - # 3.x: in addition to handling children, we must handle the name of - # the exception, which is not a Name node, but a simple string. - if isinstance(node.name, str): - self.handleNodeStore(node) + if PY2 or node.name is None: + self.handleChildren(node) + return + + # 3.x: the name of the exception, which is not a Name node, but + # a simple string, creates a local that is only bound within the scope + # of the except: block. + + for scope in self.scopeStack[::-1]: + if node.name in scope: + is_name_previously_defined = True + break + else: + is_name_previously_defined = False + + self.handleNodeStore(node) self.handleChildren(node) + if not is_name_previously_defined: + # See discussion on https://github.com/pyflakes/pyflakes/pull/59. + + # We're removing the local name since it's being unbound + # after leaving the except: block and it's always unbound + # if the except: block is never entered. This will cause an + # "undefined name" error raised if the checked code tries to + # use the name afterwards. + # + # Unless it's been removed already. Then do nothing. + + try: + del self.scope[node.name] + except KeyError: + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/pyflakes/test/test_imports.py new/pyflakes-1.2.3/pyflakes/test/test_imports.py --- old/pyflakes-1.1.0/pyflakes/test/test_imports.py 2016-03-01 15:04:37.000000000 +0100 +++ new/pyflakes-1.2.3/pyflakes/test/test_imports.py 2016-05-12 20:29:04.000000000 +0200 @@ -2,26 +2,152 @@ from sys import version_info from pyflakes import messages as m +from pyflakes.checker import ( + FutureImportation, + Importation, + ImportationFrom, + StarImportation, + SubmoduleImportation, +) from pyflakes.test.harness import TestCase, skip, skipIf +class TestImportationObject(TestCase): + + def test_import_basic(self): + binding = Importation('a', None, 'a') + assert binding.source_statement == 'import a' + assert str(binding) == 'a' + + def test_import_as(self): + binding = Importation('c', None, 'a') + assert binding.source_statement == 'import a as c' + assert str(binding) == 'a as c' + + def test_import_submodule(self): + binding = SubmoduleImportation('a.b', None) + assert binding.source_statement == 'import a.b' + assert str(binding) == 'a.b' + + def test_import_submodule_as(self): + # A submodule import with an as clause is not a SubmoduleImportation + binding = Importation('c', None, 'a.b') + assert binding.source_statement == 'import a.b as c' + assert str(binding) == 'a.b as c' + + def test_import_submodule_as_source_name(self): + binding = Importation('a', None, 'a.b') + assert binding.source_statement == 'import a.b as a' + assert str(binding) == 'a.b as a' + + def test_importfrom_relative(self): + binding = ImportationFrom('a', None, '.', 'a') + assert binding.source_statement == 'from . import a' + assert str(binding) == '.a' + + def test_importfrom_relative_parent(self): + binding = ImportationFrom('a', None, '..', 'a') + assert binding.source_statement == 'from .. import a' + assert str(binding) == '..a' + + def test_importfrom_relative_with_module(self): + binding = ImportationFrom('b', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b' + assert str(binding) == '..a.b' + + def test_importfrom_relative_with_module_as(self): + binding = ImportationFrom('c', None, '..a', 'b') + assert binding.source_statement == 'from ..a import b as c' + assert str(binding) == '..a.b as c' + + def test_importfrom_member(self): + binding = ImportationFrom('b', None, 'a', 'b') + assert binding.source_statement == 'from a import b' + assert str(binding) == 'a.b' + + def test_importfrom_submodule_member(self): + binding = ImportationFrom('c', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c' + assert str(binding) == 'a.b.c' + + def test_importfrom_member_as(self): + binding = ImportationFrom('c', None, 'a', 'b') + assert binding.source_statement == 'from a import b as c' + assert str(binding) == 'a.b as c' + + def test_importfrom_submodule_member_as(self): + binding = ImportationFrom('d', None, 'a.b', 'c') + assert binding.source_statement == 'from a.b import c as d' + assert str(binding) == 'a.b.c as d' + + def test_importfrom_star(self): + binding = StarImportation('a.b', None) + assert binding.source_statement == 'from a.b import *' + assert str(binding) == 'a.b.*' + + def test_importfrom_star_relative(self): + binding = StarImportation('.b', None) + assert binding.source_statement == 'from .b import *' + assert str(binding) == '.b.*' + + def test_importfrom_future(self): + binding = FutureImportation('print_function', None, None) + assert binding.source_statement == 'from __future__ import print_function' + assert str(binding) == '__future__.print_function' + + class Test(TestCase): def test_unusedImport(self): self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport) self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport) + def test_unusedImport_relative(self): + self.flakes('from . import fu', m.UnusedImport) + self.flakes('from . import fu as baz', m.UnusedImport) + self.flakes('from .. import fu', m.UnusedImport) + self.flakes('from ... import fu', m.UnusedImport) + self.flakes('from .. import fu as baz', m.UnusedImport) + self.flakes('from .bar import fu', m.UnusedImport) + self.flakes('from ..bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu', m.UnusedImport) + self.flakes('from ...bar import fu as baz', m.UnusedImport) + + checker = self.flakes('from . import fu', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu', ) + + checker = self.flakes('from . import fu as baz', m.UnusedImport) + + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu as baz', ) + def test_aliasedImport(self): self.flakes('import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport) self.flakes('from moo import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport) + def test_aliasedImportShadowModule(self): + """Imported aliases can shadow the source of the import.""" + self.flakes('from moo import fu as moo; moo') + self.flakes('import fu as fu; fu') + self.flakes('import fu.bar as fu; fu') + def test_usedImport(self): self.flakes('import fu; print(fu)') self.flakes('from baz import fu; print(fu)') self.flakes('import fu; del fu') + def test_usedImport_relative(self): + self.flakes('from . import fu; assert fu') + self.flakes('from .bar import fu; assert fu') + self.flakes('from .. import fu; assert fu') + self.flakes('from ..bar import fu as baz; assert baz') + def test_redefinedWhileUnused(self): self.flakes('import fu; fu = 3', m.RedefinedWhileUnused) self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused) @@ -615,6 +741,49 @@ pass ''', m.ImportStarUsed, m.UnusedImport) + checker = self.flakes('from fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.*', ) + + def test_importStar_relative(self): + """Use of import * from a relative import is reported.""" + self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport) + self.flakes(''' + try: + from .fu import * + except: + pass + ''', m.ImportStarUsed, m.UnusedImport) + + checker = self.flakes('from .fu import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('.fu', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('.fu.*', ) + + checker = self.flakes('from .. import *', + m.ImportStarUsed, m.UnusedImport) + + error = checker.messages[0] + assert error.message.startswith("'from %s import *' used; unable ") + assert error.message_args == ('..', ) + + error = checker.messages[1] + assert error.message == '%r imported but unused' + assert error.message_args == ('from .. import *', ) + @skipIf(version_info < (3,), 'import * below module level is a warning on Python 2') def test_localImportStar(self): @@ -628,6 +797,14 @@ from fu import * ''', m.ImportStarNotPermitted) + checker = self.flakes(''' + class a: + from .. import * + ''', m.ImportStarNotPermitted) + error = checker.messages[0] + assert error.message == "'from %s import *' only allowed at module level" + assert error.message_args == ('..', ) + @skipIf(version_info > (3,), 'import * below module level is an error on Python 3') def test_importStarNested(self): @@ -685,6 +862,35 @@ fu.bar, fu.baz ''') + def test_used_package_with_submodule_import(self): + """ + Usage of package marks submodule imports as used. + """ + self.flakes(''' + import fu + import fu.bar + fu.x + ''') + + self.flakes(''' + import fu.bar + import fu + fu.x + ''') + + def test_unused_package_with_submodule_import(self): + """ + When a package and its submodule are imported, only report once. + """ + checker = self.flakes(''' + import fu + import fu.bar + ''', m.UnusedImport) + error = checker.messages[0] + assert error.message == '%r imported but unused' + assert error.message_args == ('fu.bar', ) + assert error.lineno == 5 if self.withDoctest else 3 + def test_assignRHSFirst(self): self.flakes('import fu; fu = fu') self.flakes('import fu; fu, bar = fu') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/pyflakes/test/test_undefined_names.py new/pyflakes-1.2.3/pyflakes/test/test_undefined_names.py --- old/pyflakes-1.1.0/pyflakes/test/test_undefined_names.py 2016-03-01 15:04:37.000000000 +0100 +++ new/pyflakes-1.2.3/pyflakes/test/test_undefined_names.py 2016-05-06 18:40:47.000000000 +0200 @@ -22,6 +22,184 @@ ''', m.UndefinedName) + @skipIf(version_info < (3,), + 'in Python 2 exception names stay bound after the except: block') + def test_undefinedExceptionName(self): + """Exception names can't be used after the except: block.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + ''', + m.UndefinedName) + + def test_namesDeclaredInExceptBlocks(self): + """Locals declared in except: blocks can be used after the block. + + This shows the example in test_undefinedExceptionName is + different.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + e = exc + e + ''') + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringLocalVariable(self): + """Exception names obscure locals, can't be used after. + + Last line will raise UnboundLocalError on Python 3 after exiting + the except: block. Note next two examples for false positives to + watch out for.""" + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + pass + exc + ''', + m.UndefinedName) + + @skipIf(version_info < (3,), + 'in Python 2 exception names stay bound after the except: block') + def test_undefinedExceptionNameObscuringLocalVariable2(self): + """Exception names are unbound after the `except:` block. + + Last line will raise UnboundLocalError on Python 3 but would print out + 've' on Python 2.""" + self.flakes(''' + try: + raise ValueError('ve') + except ValueError as exc: + pass + print(exc) + exc = 'Original value' + ''', + m.UndefinedName) + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because it's only + entered if no exception was raised.""" + self.flakes(''' + exc = 'Original value' + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + ''') + + def test_delExceptionInExcept(self): + """The exception name can be deleted in the except: block.""" + self.flakes(''' + try: + pass + except Exception as exc: + del exc + ''') + + def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self): + """Exception names obscure locals, can't be used after. Unless. + + Last line will never raise UnboundLocalError because `error` is + only falsy if the `except:` block has not been entered.""" + self.flakes(''' + exc = 'Original value' + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + ''') + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable(self): + """Exception names obscure globals, can't be used after. + + Last line will raise UnboundLocalError on both Python 2 and + Python 3 because the existence of that exception name creates + a local scope placeholder for it, obscuring any globals, etc.""" + self.flakes(''' + exc = 'Original value' + def func(): + try: + pass # nothing is raised + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + ''', + m.UndefinedLocal) + + @skip('error reporting disabled due to false positives below') + def test_undefinedExceptionNameObscuringGlobalVariable2(self): + """Exception names obscure globals, can't be used after. + + Last line will raise NameError on Python 3 because the name is + locally unbound after the `except:` block, even if it's + nonlocal. We should issue an error in this case because code + only working correctly if an exception isn't raised, is invalid. + Unless it's explicitly silenced, see false positives below.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + pass # block never entered, exc stays unbound + exc + ''', + m.UndefinedLocal) + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because it's only entered + if no exception was raised.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + try: + raise ValueError('ve') + except ValueError as exc: + print('exception logged') + raise + exc + ''') + + def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self): + """Exception names obscure globals, can't be used after. Unless. + + Last line will never raise NameError because `error` is only + falsy if the `except:` block has not been entered.""" + self.flakes(''' + exc = 'Original value' + def func(): + global exc + error = None + try: + raise ValueError('ve') + except ValueError as exc: + error = 'exception logged' + if error: + print(error) + else: + exc + ''') + def test_functionsNeedGlobalScope(self): self.flakes(''' class a: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-1.1.0/pyflakes.egg-info/PKG-INFO new/pyflakes-1.2.3/pyflakes.egg-info/PKG-INFO --- old/pyflakes-1.1.0/pyflakes.egg-info/PKG-INFO 2016-03-01 16:32:19.000000000 +0100 +++ new/pyflakes-1.2.3/pyflakes.egg-info/PKG-INFO 2016-05-12 20:38:29.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyflakes -Version: 1.1.0 +Version: 1.2.3 Summary: passive checker of Python programs Home-page: https://github.com/pyflakes/pyflakes Author: A lot of people @@ -69,6 +69,8 @@ Contributing ------------ + Issues are tracked on `Launchpad <https://bugs.launchpad.net/pyflakes>`_. + Patches may be submitted via a `GitHub pull request`_ or via the mailing list if you prefer. If you are comfortable doing so, please `rebase your changes`_ so they may be applied to master with a fast-forward merge, and each commit is
