Hello community,

here is the log from the commit of package python-flake8-bugbear for 
openSUSE:Factory checked in at 2020-04-22 20:41:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-flake8-bugbear (Old)
 and      /work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-flake8-bugbear"

Wed Apr 22 20:41:12 2020 rev:6 rq:794563 version:20.1.4

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-flake8-bugbear/python-flake8-bugbear.changes  
    2019-09-17 13:36:07.109855206 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2738/python-flake8-bugbear.changes
    2020-04-22 20:41:14.398011745 +0200
@@ -1,0 +2,14 @@
+Thu Apr 16 11:09:56 UTC 2020 - Tomáš Chvátal <[email protected]>
+
+- Update to 20.1.4:
+  * Ignore keywords for B009/B010
+  * Silence B009/B010 for non-identifiers
+  * State an ignore might be needed for optional B9x checks
+  * Fix error on attributes-of-attributes in except (...): clauses
+  * Allow continue/break within loops in finally clauses for B012
+  * For B001, also check for except ():
+  * Introduce B013 and B014 to check tuples in except (..., ): statements
+  * Warn about continue/return/break in finally block (#100)
+  * Removed a colon from the descriptive message in B008. (#96)
+
+-------------------------------------------------------------------

Old:
----
  flake8-bugbear-19.8.0.tar.gz

New:
----
  flake8-bugbear-20.1.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-flake8-bugbear.spec ++++++
--- /var/tmp/diff_new_pack.TqdSQh/_old  2020-04-22 20:41:15.066013053 +0200
+++ /var/tmp/diff_new_pack.TqdSQh/_new  2020-04-22 20:41:15.066013053 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-flake8-bugbear
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,19 +19,24 @@
 %define skip_python2 1
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-flake8-bugbear
-Version:        19.8.0
+Version:        20.1.4
 Release:        0
 Summary:        A plugin for flake8 finding likely bugs and design problems in 
your program
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/PyCQA/flake8-bugbear
 Source:         
https://files.pythonhosted.org/packages/source/f/flake8-bugbear/flake8-bugbear-%{version}.tar.gz
-BuildRequires:  %{python_module attrs}
+BuildRequires:  %{python_module attrs >= 19.2.0}
+BuildRequires:  %{python_module base >= 3.6}
 BuildRequires:  %{python_module flake8 >= 3.0.0}
+BuildRequires:  %{python_module hypothesis}
+BuildRequires:  %{python_module hypothesmith}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+Requires:       python-attrs >= 19.2.0
+Requires:       python-flake8 >= 3.0.0
 BuildArch:      noarch
 %python_subpackages
 
@@ -51,8 +56,8 @@
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check
-# https://github.com/PyCQA/flake8-bugbear/issues/89
-%pytest -k 'not test_selfclean_bugbear'
+# Fuzz testing needs fast machine for quick enough data responses
+%pytest -k 'not TestFuzz'
 
 %files %{python_files}
 %license LICENSE

++++++ flake8-bugbear-19.8.0.tar.gz -> flake8-bugbear-20.1.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/PKG-INFO 
new/flake8-bugbear-20.1.4/PKG-INFO
--- old/flake8-bugbear-19.8.0/PKG-INFO  2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/PKG-INFO  2020-02-01 05:10:59.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: flake8-bugbear
-Version: 19.8.0
+Version: 20.1.4
 Summary: A plugin for flake8 finding likely bugs and design problems in your 
program. Contains warnings that don't belong in pyflakes and pycodestyle.
 Home-page: https://github.com/PyCQA/flake8-bugbear
 Author: Łukasz Langa
@@ -31,6 +31,12 @@
             3. A fearsome imaginary creature, especially one evoked to frighten
                children.
         
+        It is felt that these lints don't belong in the main Python tools as 
they
+        are very opinionated and do not have a PEP or standard behind them. 
Due to
+        ``flake8`` being designed to be extensible, the original creators of 
these lints
+        believed that a plugin was the best route. This has resulted in better 
development
+        velocity for contributors and adaptive deployment for ``flake8`` users.
+        
         Installation
         ------------
         
@@ -102,6 +108,18 @@
         **B011**: Do not call `assert False` since `python -O` removes these 
calls.
         Instead callers should `raise AssertionError()`.
         
+        **B012**: Use of `break`, `continue` or `return` inside `finally` 
blocks will
+        silence exceptions or override return values from the `try` or 
`except` blocks.
+        To silence an exception, do it explicitly in the `except` block. To 
properly use
+        a `break`, `continue` or `return` refactor your code so these 
statements are not
+        in the `finally` block.
+        
+        **B013**: A length-one tuple literal is redundant.  Write `except 
SomeError:`
+        instead of `except (SomeError,):`.
+        
+        **B014**: Redundant exception types in `except (Exception, 
TypeError):`.
+        Write `except Exception:`, which catches exactly the same exceptions.
+        
         
         Python 3 compatibility warnings
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -192,11 +210,14 @@
                max-line-length = 80
                max-complexity = 12
                ...
+               ignore = E501
                select = C,E,F,W,B,B901
         
         Note that we're enabling the complexity checks, the PEP8 
``pycodestyle``
         errors and warnings, the pyflakes fatals and all default Bugbear 
checks.
         Finally, we're also specifying B901 as a check that we want enabled.
+        Some checks might need other flake8 checks disabled - e.g. E501 must be
+        disabled for B950 to be hit.
         
         If you'd like all optional warnings to be enabled for you (future proof
         your config!), say ``B9`` instead of ``B901``. You will need Flake8 
3.2+
@@ -213,27 +234,7 @@
         
         Just run::
         
-            python setup.py test
-        
-        
-        OMG, this is Python 3 only!
-        ---------------------------
-        
-        Relax, you can run ``flake8`` with all popular plugins as a *tool*
-        perfectly fine under Python 3.5+ even if you want to analyze Python 2
-        code.  This way you'll be able to parse all of the new syntax supported
-        on Python 3 but also *effectively all* the Python 2 syntax at the same
-        time.
-        
-        If you're still invested in Python 2, there might be a small subset of
-        deprecated syntax that you'd have to abandon... but you're already 
doing
-        that, right?  `six <https://pypi.org/project/six/>`_ or
-        `python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-        
-        By making the code exclusively Python 3.5+, I'm able to focus on the
-        quality of the checks and re-use all the nice features of the new
-        releases (check out `pathlib 
<https://docs.python.org/3/library/pathlib.html>`_)
-        instead of wasting cycles on Unicode compatibility, etc.
+            python tests/test_bugbear.py
         
         
         License
@@ -245,6 +246,35 @@
         Change Log
         ----------
         
+        20.1.4
+        ~~~~~~
+        
+        * Ignore keywords for B009/B010
+        
+        20.1.3
+        ~~~~~~
+        
+        * Silence B009/B010 for non-identifiers
+        * State an ignore might be needed for optional B9x checks
+        
+        20.1.2
+        ~~~~~~
+        
+        * Fix error on attributes-of-attributes in `except (...):` clauses
+        
+        20.1.1
+        ~~~~~~
+        
+        * Allow continue/break within loops in finally clauses for B012
+        * For B001, also check for ``except ():``
+        * Introduce B013 and B014 to check tuples in ``except (..., ):`` 
statements
+        
+        20.1.0
+        ~~~~~~
+        
+        * Warn about continue/return/break in finally block (#100)
+        * Removed a colon from the descriptive message in B008. (#96)
+        
         19.8.0
         ~~~~~~
         
@@ -422,10 +452,10 @@
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Classifier: Topic :: Software Development :: Quality Assurance
-Requires-Python: >=3.5
+Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/README.rst 
new/flake8-bugbear-20.1.4/README.rst
--- old/flake8-bugbear-19.8.0/README.rst        2019-08-12 06:18:59.000000000 
+0200
+++ new/flake8-bugbear-20.1.4/README.rst        2020-02-01 05:10:39.000000000 
+0100
@@ -23,6 +23,12 @@
     3. A fearsome imaginary creature, especially one evoked to frighten
        children.
 
+It is felt that these lints don't belong in the main Python tools as they
+are very opinionated and do not have a PEP or standard behind them. Due to
+``flake8`` being designed to be extensible, the original creators of these 
lints
+believed that a plugin was the best route. This has resulted in better 
development
+velocity for contributors and adaptive deployment for ``flake8`` users.
+
 Installation
 ------------
 
@@ -94,6 +100,18 @@
 **B011**: Do not call `assert False` since `python -O` removes these calls.
 Instead callers should `raise AssertionError()`.
 
+**B012**: Use of `break`, `continue` or `return` inside `finally` blocks will
+silence exceptions or override return values from the `try` or `except` blocks.
+To silence an exception, do it explicitly in the `except` block. To properly 
use
+a `break`, `continue` or `return` refactor your code so these statements are 
not
+in the `finally` block.
+
+**B013**: A length-one tuple literal is redundant.  Write `except SomeError:`
+instead of `except (SomeError,):`.
+
+**B014**: Redundant exception types in `except (Exception, TypeError):`.
+Write `except Exception:`, which catches exactly the same exceptions.
+
 
 Python 3 compatibility warnings
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -184,11 +202,14 @@
        max-line-length = 80
        max-complexity = 12
        ...
+       ignore = E501
        select = C,E,F,W,B,B901
 
 Note that we're enabling the complexity checks, the PEP8 ``pycodestyle``
 errors and warnings, the pyflakes fatals and all default Bugbear checks.
 Finally, we're also specifying B901 as a check that we want enabled.
+Some checks might need other flake8 checks disabled - e.g. E501 must be
+disabled for B950 to be hit.
 
 If you'd like all optional warnings to be enabled for you (future proof
 your config!), say ``B9`` instead of ``B901``. You will need Flake8 3.2+
@@ -205,27 +226,7 @@
 
 Just run::
 
-    python setup.py test
-
-
-OMG, this is Python 3 only!
----------------------------
-
-Relax, you can run ``flake8`` with all popular plugins as a *tool*
-perfectly fine under Python 3.5+ even if you want to analyze Python 2
-code.  This way you'll be able to parse all of the new syntax supported
-on Python 3 but also *effectively all* the Python 2 syntax at the same
-time.
-
-If you're still invested in Python 2, there might be a small subset of
-deprecated syntax that you'd have to abandon... but you're already doing
-that, right?  `six <https://pypi.org/project/six/>`_ or
-`python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-
-By making the code exclusively Python 3.5+, I'm able to focus on the
-quality of the checks and re-use all the nice features of the new
-releases (check out `pathlib 
<https://docs.python.org/3/library/pathlib.html>`_)
-instead of wasting cycles on Unicode compatibility, etc.
+    python tests/test_bugbear.py
 
 
 License
@@ -237,6 +238,35 @@
 Change Log
 ----------
 
+20.1.4
+~~~~~~
+
+* Ignore keywords for B009/B010
+
+20.1.3
+~~~~~~
+
+* Silence B009/B010 for non-identifiers
+* State an ignore might be needed for optional B9x checks
+
+20.1.2
+~~~~~~
+
+* Fix error on attributes-of-attributes in `except (...):` clauses
+
+20.1.1
+~~~~~~
+
+* Allow continue/break within loops in finally clauses for B012
+* For B001, also check for ``except ():``
+* Introduce B013 and B014 to check tuples in ``except (..., ):`` statements
+
+20.1.0
+~~~~~~
+
+* Warn about continue/return/break in finally block (#100)
+* Removed a colon from the descriptive message in B008. (#96)
+
 19.8.0
 ~~~~~~
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/bugbear.py 
new/flake8-bugbear-20.1.4/bugbear.py
--- old/flake8-bugbear-19.8.0/bugbear.py        2019-08-12 06:18:59.000000000 
+0200
+++ new/flake8-bugbear-20.1.4/bugbear.py        2020-02-01 05:10:39.000000000 
+0100
@@ -1,15 +1,18 @@
 import ast
+import builtins
 from collections import namedtuple
 from contextlib import suppress
 from functools import lru_cache, partial
 import itertools
+from keyword import iskeyword
 import logging
+import re
 
 import attr
 import pycodestyle
 
 
-__version__ = "19.8.0"
+__version__ = "20.1.4"
 
 LOG = logging.getLogger("flake8.bugbear")
 
@@ -109,6 +112,25 @@
         return False
 
 
+def _is_identifier(arg):
+    # Return True if arg is a valid identifier, per
+    # https://docs.python.org/2/reference/lexical_analysis.html#identifiers
+
+    if not isinstance(arg, ast.Str):
+        return False
+
+    return re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", arg.s) is not None
+
+
+def _to_name_str(node):
+    # Turn Name and Attribute nodes to strings, e.g "ValueError" or
+    # "pkg.mod.error", handling any depth of attribute accesses.
+    if isinstance(node, ast.Name):
+        return node.id
+    assert isinstance(node, ast.Attribute)
+    return _to_name_str(node.value) + "." + node.attr
+
+
 @attr.s
 class BugBearVisitor(ast.NodeVisitor):
     filename = attr.ib()
@@ -136,7 +158,44 @@
 
     def visit_ExceptHandler(self, node):
         if node.type is None:
-            self.errors.append(B001(node.lineno, node.col_offset))
+            self.errors.append(
+                B001(node.lineno, node.col_offset, vars=("bare `except:`",))
+            )
+        elif isinstance(node.type, ast.Tuple):
+            names = [_to_name_str(e) for e in node.type.elts]
+            as_ = " as " + node.name if node.name is not None else ""
+            if len(names) == 0:
+                vs = ("`except (){}:`".format(as_),)
+                self.errors.append(B001(node.lineno, node.col_offset, vars=vs))
+            elif len(names) == 1:
+                self.errors.append(B013(node.lineno, node.col_offset, 
vars=names))
+            else:
+                # See if any of the given exception names could be removed, 
e.g. from:
+                #    (MyError, MyError)  # duplicate names
+                #    (MyError, BaseException)  # everything derives from the 
Base
+                #    (Exception, TypeError)  # builtins where one subclasses 
another
+                # but note that other cases are impractical to hande from the 
AST.
+                # We expect this is mostly useful for users who do not have the
+                # builtin exception hierarchy memorised, and include a 
'shadowed'
+                # subtype without realising that it's redundant.
+                good = sorted(set(names), key=names.index)
+                if "BaseException" in good:
+                    good = ["BaseException"]
+                for name, other in itertools.permutations(tuple(good), 2):
+                    if issubclass(
+                        getattr(builtins, name, type), getattr(builtins, 
other, ())
+                    ):
+                        if name in good:
+                            good.remove(name)
+                if good != names:
+                    desc = good[0] if len(good) == 1 else "({})".format(", 
".join(good))
+                    self.errors.append(
+                        B014(
+                            node.lineno,
+                            node.col_offset,
+                            vars=(", ".join(names), as_, desc),
+                        )
+                    )
         self.generic_visit(node)
 
     def visit_UAdd(self, node):
@@ -166,13 +225,15 @@
                 if (
                     node.func.id == "getattr"
                     and len(node.args) == 2  # noqa: W503
-                    and isinstance(node.args[1], ast.Str)  # noqa: W503
+                    and _is_identifier(node.args[1])  # noqa: W503
+                    and not iskeyword(node.args[1].s)  # noqa: W503
                 ):
                     self.errors.append(B009(node.lineno, node.col_offset))
                 elif (
                     node.func.id == "setattr"
                     and len(node.args) == 3  # noqa: W503
-                    and isinstance(node.args[1], ast.Str)  # noqa: W503
+                    and _is_identifier(node.args[1])  # noqa: W503
+                    and not iskeyword(node.args[1].s)  # noqa: W503
                 ):
                     self.errors.append(B010(node.lineno, node.col_offset))
 
@@ -226,6 +287,10 @@
         self.check_for_b903(node)
         self.generic_visit(node)
 
+    def visit_Try(self, node):
+        self.check_for_b012(node)
+        self.generic_visit(node)
+
     def compose_call_path(self, node):
         if isinstance(node, ast.Attribute):
             yield from self.compose_call_path(node.value)
@@ -280,22 +345,42 @@
         if isinstance(node.test, ast.NameConstant) and node.test.value is 
False:
             self.errors.append(B011(node.lineno, node.col_offset))
 
+    def check_for_b012(self, node):
+        def _loop(node, bad_node_types):
+            if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
+                return
+
+            if isinstance(node, (ast.While, ast.For)):
+                bad_node_types = (ast.Return,)
+
+            elif isinstance(node, bad_node_types):
+                self.errors.append(B012(node.lineno, node.col_offset))
+
+            for child in ast.iter_child_nodes(node):
+                _loop(child, bad_node_types)
+
+        for child in node.finalbody:
+            _loop(child, (ast.Return, ast.Continue, ast.Break))
+
+    def walk_function_body(self, node):
+        def _loop(parent, node):
+            if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)):
+                return
+            yield parent, node
+            for child in ast.iter_child_nodes(node):
+                yield from _loop(node, child)
+
+        for child in node.body:
+            yield from _loop(node, child)
+
     def check_for_b901(self, node):
         if node.name == "__await__":
             return
 
-        xs = [(None, x) for x in node.body]
-
         has_yield = False
         return_node = None
 
-        while xs:
-            parent, x = xs.pop()
-            if isinstance(x, (ast.AsyncFunctionDef, ast.FunctionDef)):
-                # Do not recurse into inner functions (`def` in
-                # `def`).
-                continue
-
+        for parent, x in self.walk_function_body(node):
             # Only consider yield when it is part of an Expr statement.
             if isinstance(parent, ast.Expr) and isinstance(
                 x, (ast.Yield, ast.YieldFrom)
@@ -309,9 +394,6 @@
                 self.errors.append(B901(return_node.lineno, 
return_node.col_offset))
                 break
 
-            for x2 in ast.iter_child_nodes(x):
-                xs.append((x, x2))
-
     def check_for_b902(self, node):
         if not isinstance(self.node_stack[-2], ast.ClassDef):
             return
@@ -438,7 +520,7 @@
 
 
 B001 = Error(
-    message="B001 Do not use bare `except:`, it also catches unexpected "
+    message="B001 Do not use {}, it also catches unexpected "
     "events like memory errors, interrupts, system exit, and so on.  "
     "Prefer `except Exception:`.  If you're sure what you're doing, "
     "be explicit and write `except BaseException:`."
@@ -497,7 +579,7 @@
     "If this is intended, start the name with an underscore."
 )
 B008 = Error(
-    message="B008: Do not perform function calls in argument defaults.  The 
call is "
+    message="B008 Do not perform function calls in argument defaults.  The 
call is "
     "performed only once at function definition time. All calls to your "
     "function will reuse the result of that definition-time function call.  If 
"
     "this is intended, assign the function call to a module-level variable and 
"
@@ -516,7 +598,19 @@
     message="B011 Do not call assert False since python -O removes these 
calls. "
     "Instead callers should raise AssertionError()."
 )
-
+B012 = Error(
+    message="B012 return/continue/break inside finally blocks cause exceptions 
"
+    "to be silenced. Exceptions should be silenced in except blocks. Control "
+    "statements can be moved outside the finally block."
+)
+B013 = Error(
+    message="B013 A length-one tuple literal is redundant.  "
+    "Write `except {0}:` instead of `except ({0},):`."
+)
+B014 = Error(
+    message="B014 Redundant exception types in `except ({0}){1}:`.  "
+    "Write `except {2}{1}:`, which catches exactly the same exceptions."
+)
 
 # Those could be false positives but it's more dangerous to let them slip
 # through if they're not.
@@ -562,6 +656,7 @@
     "to the exception."
 )
 
+# Warnings disabled by default.
 B901 = Error(
     message="B901 Using `yield` together with `return x`. Use native "
     "`async def` coroutines or put a `# noqa` comment on this "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/PKG-INFO 
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/PKG-INFO
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/PKG-INFO  2019-08-12 
06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/PKG-INFO  2020-02-01 
05:10:59.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: flake8-bugbear
-Version: 19.8.0
+Version: 20.1.4
 Summary: A plugin for flake8 finding likely bugs and design problems in your 
program. Contains warnings that don't belong in pyflakes and pycodestyle.
 Home-page: https://github.com/PyCQA/flake8-bugbear
 Author: Łukasz Langa
@@ -31,6 +31,12 @@
             3. A fearsome imaginary creature, especially one evoked to frighten
                children.
         
+        It is felt that these lints don't belong in the main Python tools as 
they
+        are very opinionated and do not have a PEP or standard behind them. 
Due to
+        ``flake8`` being designed to be extensible, the original creators of 
these lints
+        believed that a plugin was the best route. This has resulted in better 
development
+        velocity for contributors and adaptive deployment for ``flake8`` users.
+        
         Installation
         ------------
         
@@ -102,6 +108,18 @@
         **B011**: Do not call `assert False` since `python -O` removes these 
calls.
         Instead callers should `raise AssertionError()`.
         
+        **B012**: Use of `break`, `continue` or `return` inside `finally` 
blocks will
+        silence exceptions or override return values from the `try` or 
`except` blocks.
+        To silence an exception, do it explicitly in the `except` block. To 
properly use
+        a `break`, `continue` or `return` refactor your code so these 
statements are not
+        in the `finally` block.
+        
+        **B013**: A length-one tuple literal is redundant.  Write `except 
SomeError:`
+        instead of `except (SomeError,):`.
+        
+        **B014**: Redundant exception types in `except (Exception, 
TypeError):`.
+        Write `except Exception:`, which catches exactly the same exceptions.
+        
         
         Python 3 compatibility warnings
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -192,11 +210,14 @@
                max-line-length = 80
                max-complexity = 12
                ...
+               ignore = E501
                select = C,E,F,W,B,B901
         
         Note that we're enabling the complexity checks, the PEP8 
``pycodestyle``
         errors and warnings, the pyflakes fatals and all default Bugbear 
checks.
         Finally, we're also specifying B901 as a check that we want enabled.
+        Some checks might need other flake8 checks disabled - e.g. E501 must be
+        disabled for B950 to be hit.
         
         If you'd like all optional warnings to be enabled for you (future proof
         your config!), say ``B9`` instead of ``B901``. You will need Flake8 
3.2+
@@ -213,27 +234,7 @@
         
         Just run::
         
-            python setup.py test
-        
-        
-        OMG, this is Python 3 only!
-        ---------------------------
-        
-        Relax, you can run ``flake8`` with all popular plugins as a *tool*
-        perfectly fine under Python 3.5+ even if you want to analyze Python 2
-        code.  This way you'll be able to parse all of the new syntax supported
-        on Python 3 but also *effectively all* the Python 2 syntax at the same
-        time.
-        
-        If you're still invested in Python 2, there might be a small subset of
-        deprecated syntax that you'd have to abandon... but you're already 
doing
-        that, right?  `six <https://pypi.org/project/six/>`_ or
-        `python-future <https://pypi.org/project/future/>`_ bridge the gaps.
-        
-        By making the code exclusively Python 3.5+, I'm able to focus on the
-        quality of the checks and re-use all the nice features of the new
-        releases (check out `pathlib 
<https://docs.python.org/3/library/pathlib.html>`_)
-        instead of wasting cycles on Unicode compatibility, etc.
+            python tests/test_bugbear.py
         
         
         License
@@ -245,6 +246,35 @@
         Change Log
         ----------
         
+        20.1.4
+        ~~~~~~
+        
+        * Ignore keywords for B009/B010
+        
+        20.1.3
+        ~~~~~~
+        
+        * Silence B009/B010 for non-identifiers
+        * State an ignore might be needed for optional B9x checks
+        
+        20.1.2
+        ~~~~~~
+        
+        * Fix error on attributes-of-attributes in `except (...):` clauses
+        
+        20.1.1
+        ~~~~~~
+        
+        * Allow continue/break within loops in finally clauses for B012
+        * For B001, also check for ``except ():``
+        * Introduce B013 and B014 to check tuples in ``except (..., ):`` 
statements
+        
+        20.1.0
+        ~~~~~~
+        
+        * Warn about continue/return/break in finally block (#100)
+        * Removed a colon from the descriptive message in B008. (#96)
+        
         19.8.0
         ~~~~~~
         
@@ -422,10 +452,10 @@
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Classifier: Topic :: Software Development :: Quality Assurance
-Requires-Python: >=3.5
+Requires-Python: >=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/SOURCES.txt 
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/SOURCES.txt
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/SOURCES.txt       
2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/SOURCES.txt       
2020-02-01 05:10:59.000000000 +0100
@@ -21,6 +21,9 @@
 tests/b007.py
 tests/b009_b010.py
 tests/b011.py
+tests/b012.py
+tests/b013.py
+tests/b014.py
 tests/b301_b302_b305.py
 tests/b303_b304.py
 tests/b306.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/requires.txt 
new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/requires.txt
--- old/flake8-bugbear-19.8.0/flake8_bugbear.egg-info/requires.txt      
2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/flake8_bugbear.egg-info/requires.txt      
2020-02-01 05:10:59.000000000 +0100
@@ -1,2 +1,2 @@
 flake8>=3.0.0
-attrs
+attrs>=19.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/setup.cfg 
new/flake8-bugbear-20.1.4/setup.cfg
--- old/flake8-bugbear-19.8.0/setup.cfg 2019-08-12 06:20:22.000000000 +0200
+++ new/flake8-bugbear-20.1.4/setup.cfg 2020-02-01 05:10:59.000000000 +0100
@@ -1,9 +1,9 @@
 [bdist_wheel]
-python-tag = py35.py36.py37
+python-tag = py36.py37.py38
 
 [flake8]
-ignore = E302, E501
-max-line-length = 80
+ignore = E203, E302, E501, E999
+max-line-length = 88
 max-complexity = 12
 select = B,C,E,F,W,B9
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/setup.py 
new/flake8-bugbear-20.1.4/setup.py
--- old/flake8-bugbear-19.8.0/setup.py  2019-08-05 17:36:44.000000000 +0200
+++ new/flake8-bugbear-20.1.4/setup.py  2020-02-01 05:10:39.000000000 +0100
@@ -7,7 +7,7 @@
 import sys
 
 
-assert sys.version_info >= (3, 5, 0), "bugbear requires Python 3.5+"
+assert sys.version_info >= (3, 6, 0), "bugbear requires Python 3.6+"
 
 
 current_dir = os.path.abspath(os.path.dirname(__file__))
@@ -37,8 +37,8 @@
     license="MIT",
     py_modules=["bugbear"],
     zip_safe=False,
-    python_requires=">=3.5",
-    install_requires=["flake8 >= 3.0.0", "attrs"],
+    python_requires=">=3.6",
+    install_requires=["flake8 >= 3.0.0", "attrs>=19.2.0"],
     test_suite="tests.test_bugbear",
     classifiers=[
         "Development Status :: 5 - Production/Stable",
@@ -49,9 +49,9 @@
         "Operating System :: OS Independent",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3 :: Only",
         "Topic :: Software Development :: Libraries :: Python Modules",
         "Topic :: Software Development :: Quality Assurance",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b001.py 
new/flake8-bugbear-20.1.4/tests/b001.py
--- old/flake8-bugbear-19.8.0/tests/b001.py     2019-08-05 17:36:44.000000000 
+0200
+++ new/flake8-bugbear-20.1.4/tests/b001.py     2020-02-01 05:10:39.000000000 
+0100
@@ -1,6 +1,6 @@
 """
 Should emit:
-B001 - on lines 8 and 40
+B001 - on lines 8, 40, and 54
 """
 
 try:
@@ -47,3 +47,10 @@
     except:  # noqa
         # warning silenced
         return
+
+
+try:
+    pass
+except ():
+    # Literal empty tuple is just like bare except:
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b009_b010.py 
new/flake8-bugbear-20.1.4/tests/b009_b010.py
--- old/flake8-bugbear-19.8.0/tests/b009_b010.py        2019-08-05 
17:36:44.000000000 +0200
+++ new/flake8-bugbear-20.1.4/tests/b009_b010.py        2020-02-01 
05:10:39.000000000 +0100
@@ -1,7 +1,7 @@
 """
 Should emit:
-B009 - Line 15
-B010 - Line 22
+B009 - Line 17, 18, 19
+B010 - Line 28, 29, 30
 """
 
 # Valid getattr usage
@@ -10,13 +10,21 @@
 getattr(foo, "bar{foo}".format(foo="a"), None)
 getattr(foo, "bar{foo}".format(foo="a"))
 getattr(foo, bar, None)
+getattr(foo, "123abc")
+getattr(foo, "except")
 
 # Invalid usage
 getattr(foo, "bar")
+getattr(foo, "_123abc")
+getattr(foo, "abc123")
 
 # Valid setattr usage
 setattr(foo, bar, None)
 setattr(foo, "bar{foo}".format(foo="a"), None)
+setattr(foo, "123abc", None)
+getattr(foo, "except", None)
 
 # Invalid usage
 setattr(foo, "bar", None)
+setattr(foo, "_123abc", None)
+setattr(foo, "abc123", None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b012.py 
new/flake8-bugbear-20.1.4/tests/b012.py
--- old/flake8-bugbear-19.8.0/tests/b012.py     1970-01-01 01:00:00.000000000 
+0100
+++ new/flake8-bugbear-20.1.4/tests/b012.py     2020-02-01 05:10:39.000000000 
+0100
@@ -0,0 +1,107 @@
+def a():
+    try:
+        pass
+    finally:
+        return  # warning
+
+
+def b():
+    try:
+        pass
+    finally:
+        if 1 + 0 == 2 - 1:
+            return  # warning
+
+
+def c():
+    try:
+        pass
+    finally:
+        try:
+            return  # warning
+        except Exception:
+            pass
+
+
+def d():
+    try:
+        try:
+            pass
+        finally:
+            return  # warning
+    finally:
+        pass
+
+
+def e():
+    if 1 == 2 - 1:
+        try:
+
+            def f():
+                try:
+                    pass
+                finally:
+                    return  # warning
+
+        finally:
+            pass
+
+
+def g():
+    try:
+        pass
+    finally:
+
+        def h():
+            return  # no warning
+
+        e()
+
+
+def i():
+    while True:
+        try:
+            pass
+        finally:
+            break  # warning
+
+            def j():
+                while True:
+                    break  # no warning
+
+
+def h():
+    while True:
+        try:
+            pass
+        finally:
+            continue  # warning
+
+            def j():
+                while True:
+                    continue  # no warning
+
+
+def k():
+    try:
+        pass
+    finally:
+        while True:
+            break  # no warning
+        while True:
+            continue  # no warning
+        while True:
+            return  # warning
+
+
+while True:
+    try:
+        pass
+    finally:
+        continue  # warning
+
+while True:
+    try:
+        pass
+    finally:
+        break  # warning
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b013.py 
new/flake8-bugbear-20.1.4/tests/b013.py
--- old/flake8-bugbear-19.8.0/tests/b013.py     1970-01-01 01:00:00.000000000 
+0100
+++ new/flake8-bugbear-20.1.4/tests/b013.py     2020-02-01 05:10:39.000000000 
+0100
@@ -0,0 +1,36 @@
+"""
+Should emit:
+B013 - on lines 10 and 28
+"""
+
+import re
+
+try:
+    pass
+except (ValueError,):
+    # pointless use of tuple
+    pass
+
+try:
+    pass
+except (ValueError):
+    # not using a tuple means it's OK (if odd)
+    pass
+
+try:
+    pass
+except ValueError:
+    # no warning here, all good
+    pass
+
+try:
+    pass
+except (re.error,):
+    # pointless use of tuple with dotted attribute
+    pass
+
+try:
+    pass
+except (a.b.c.d, b.c.d):
+    # attribute of attribute, etc.
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b014.py 
new/flake8-bugbear-20.1.4/tests/b014.py
--- old/flake8-bugbear-19.8.0/tests/b014.py     1970-01-01 01:00:00.000000000 
+0100
+++ new/flake8-bugbear-20.1.4/tests/b014.py     2020-02-01 05:10:39.000000000 
+0100
@@ -0,0 +1,50 @@
+"""
+Should emit:
+B014 - on lines 10, 16, 27, 41, and 48
+"""
+
+import re
+
+try:
+    pass
+except (Exception, TypeError):
+    # TypeError is a subclass of Exception, so it doesn't add anything
+    pass
+
+try:
+    pass
+except (OSError, OSError) as err:
+    # Duplicate exception types are useless
+    pass
+
+
+class MyError(Exception):
+    pass
+
+
+try:
+    pass
+except (MyError, MyError):
+    # Detect duplicate non-builtin errors
+    pass
+
+
+try:
+    pass
+except (MyError, Exception) as e:
+    # Don't assume that we're all subclasses of Exception
+    pass
+
+
+try:
+    pass
+except (MyError, BaseException) as e:
+    # But we *can* assume that everything is a subclass of BaseException
+    pass
+
+
+try:
+    pass
+except (re.error, re.error):
+    # Duplicate exception types as attributes
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/b901.py 
new/flake8-bugbear-20.1.4/tests/b901.py
--- old/flake8-bugbear-19.8.0/tests/b901.py     2019-08-05 17:36:44.000000000 
+0200
+++ new/flake8-bugbear-20.1.4/tests/b901.py     2020-02-01 05:10:39.000000000 
+0100
@@ -62,7 +62,7 @@
 
 
 def not_broken7():
-    x = (yield from [])
+    x = yield from []
     return x
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flake8-bugbear-19.8.0/tests/test_bugbear.py 
new/flake8-bugbear-20.1.4/tests/test_bugbear.py
--- old/flake8-bugbear-19.8.0/tests/test_bugbear.py     2019-08-12 
06:18:59.000000000 +0200
+++ new/flake8-bugbear-20.1.4/tests/test_bugbear.py     2020-02-01 
05:10:39.000000000 +0100
@@ -1,8 +1,14 @@
+import ast
+import os
 from pathlib import Path
+import site
 import subprocess
 import unittest
 
-from bugbear import BugBearChecker
+from hypothesis import given
+from hypothesmith import from_grammar
+
+from bugbear import BugBearChecker, BugBearVisitor
 from bugbear import (
     B001,
     B002,
@@ -15,6 +21,9 @@
     B009,
     B010,
     B011,
+    B012,
+    B013,
+    B014,
     B301,
     B302,
     B303,
@@ -38,7 +47,12 @@
         filename = Path(__file__).absolute().parent / "b001.py"
         bbc = BugBearChecker(filename=str(filename))
         errors = list(bbc.run())
-        self.assertEqual(errors, self.errors(B001(8, 0), B001(40, 4)))
+        expected = self.errors(
+            B001(8, 0, vars=("bare `except:`",)),
+            B001(40, 4, vars=("bare `except:`",)),
+            B001(54, 0, vars=("`except ():`",)),
+        )
+        self.assertEqual(errors, expected)
 
     def test_b002(self):
         filename = Path(__file__).absolute().parent / "b002.py"
@@ -109,7 +123,15 @@
         filename = Path(__file__).absolute().parent / "b009_b010.py"
         bbc = BugBearChecker(filename=str(filename))
         errors = list(bbc.run())
-        self.assertEqual(errors, self.errors(B009(15, 0), B010(22, 0)))
+        all_errors = [
+            B009(17, 0),
+            B009(18, 0),
+            B009(19, 0),
+            B010(28, 0),
+            B010(29, 0),
+            B010(30, 0),
+        ]
+        self.assertEqual(errors, self.errors(*all_errors))
 
     def test_b011(self):
         filename = Path(__file__).absolute().parent / "b011.py"
@@ -119,6 +141,46 @@
             errors, self.errors(B011(8, 0, vars=("i",)), B011(10, 0, 
vars=("k",)))
         )
 
+    def test_b012(self):
+        filename = Path(__file__).absolute().parent / "b012.py"
+        bbc = BugBearChecker(filename=str(filename))
+        errors = list(bbc.run())
+        all_errors = [
+            B012(5, 8),
+            B012(13, 12),
+            B012(21, 12),
+            B012(31, 12),
+            B012(44, 20),
+            B012(66, 12),
+            B012(78, 12),
+            B012(94, 12),
+            B012(101, 8),
+            B012(107, 8),
+        ]
+        self.assertEqual(errors, self.errors(*all_errors))
+
+    def test_b013(self):
+        filename = Path(__file__).absolute().parent / "b013.py"
+        bbc = BugBearChecker(filename=str(filename))
+        errors = list(bbc.run())
+        expected = self.errors(
+            B013(10, 0, vars=("ValueError",)), B013(28, 0, vars=("re.error",))
+        )
+        self.assertEqual(errors, expected)
+
+    def test_b014(self):
+        filename = Path(__file__).absolute().parent / "b014.py"
+        bbc = BugBearChecker(filename=str(filename))
+        errors = list(bbc.run())
+        expected = self.errors(
+            B014(10, 0, vars=("Exception, TypeError", "", "Exception")),
+            B014(16, 0, vars=("OSError, OSError", " as err", "OSError")),
+            B014(27, 0, vars=("MyError, MyError", "", "MyError")),
+            B014(41, 0, vars=("MyError, BaseException", " as e", 
"BaseException")),
+            B014(48, 0, vars=("re.error, re.error", "", "re.error")),
+        )
+        self.assertEqual(errors, expected)
+
     def test_b301_b302_b305(self):
         filename = Path(__file__).absolute().parent / "b301_b302_b305.py"
         bbc = BugBearChecker(filename=str(filename))
@@ -199,7 +261,7 @@
         )
         self.assertEqual(proc.returncode, 0, proc.stdout.decode("utf8"))
         self.assertEqual(proc.stdout, b"")
-        # self.assertEqual(proc.stderr, b"")
+        self.assertEqual(proc.stderr, b"")
 
     def test_selfclean_test_bugbear(self):
         filename = Path(__file__).absolute()
@@ -211,7 +273,26 @@
         )
         self.assertEqual(proc.returncode, 0, proc.stdout.decode("utf8"))
         self.assertEqual(proc.stdout, b"")
-        # self.assertEqual(proc.stderr, b'')
+        self.assertEqual(proc.stderr, b"")
+
+
+class TestFuzz(unittest.TestCase):
+    @given(from_grammar().map(ast.parse))
+    def test_does_not_crash_on_any_valid_code(self, syntax_tree):
+        # Given any syntatically-valid source code, flake8-bugbear should
+        # not crash.  This tests doesn't check that we do the *right* thing,
+        # just that we don't crash on valid-if-poorly-styled code!
+        BugBearVisitor(filename="<string>", lines=[]).visit(syntax_tree)
+
+    def test_does_not_crash_on_site_code(self):
+        # Because the generator isn't perfect, we'll also test on all the code
+        # we can easily find in our current Python environment - this includes
+        # the standard library, and all installed packages.
+        for base in sorted(set(site.PREFIXES)):
+            for dirname, _, files in os.walk(base):
+                for f in files:
+                    if f.endswith(".py"):
+                        BugBearChecker(filename=str(Path(dirname) / f))
 
 
 if __name__ == "__main__":


Reply via email to