Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-flake8-bugbear for openSUSE:Factory checked in at 2022-10-27 13:54:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-flake8-bugbear (Old) and /work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-flake8-bugbear" Thu Oct 27 13:54:33 2022 rev:9 rq:1031467 version:22.10.27 Changes: -------- --- /work/SRC/openSUSE:Factory/python-flake8-bugbear/python-flake8-bugbear.changes 2022-10-26 12:32:05.768351483 +0200 +++ /work/SRC/openSUSE:Factory/.python-flake8-bugbear.new.2275/python-flake8-bugbear.changes 2022-10-27 13:55:09.512869342 +0200 @@ -1,0 +2,8 @@ +Thu Oct 27 07:26:17 UTC 2022 - Martin Li??ka <mli...@suse.cz> + +- Update to 22.10.27: + * B027: Ignore @overload decorator (#306) + * B023: Also fix map (#305) + * B023: Avoid false alarms with filter, reduce, key= and return. Added tests for functools (#303) + +------------------------------------------------------------------- Old: ---- flake8-bugbear-22.10.25.tar.gz New: ---- flake8-bugbear-22.10.27.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-flake8-bugbear.spec ++++++ --- /var/tmp/diff_new_pack.NaroCG/_old 2022-10-27 13:55:09.960871626 +0200 +++ /var/tmp/diff_new_pack.NaroCG/_new 2022-10-27 13:55:09.964871647 +0200 @@ -19,7 +19,7 @@ %define skip_python2 1 %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-flake8-bugbear -Version: 22.10.25 +Version: 22.10.27 Release: 0 Summary: A plugin for flake8 finding likely bugs and design problems in your program License: MIT ++++++ flake8-bugbear-22.10.25.tar.gz -> flake8-bugbear-22.10.27.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/PKG-INFO new/flake8-bugbear-22.10.27/PKG-INFO --- old/flake8-bugbear-22.10.25/PKG-INFO 2022-10-25 01:25:14.457198000 +0200 +++ new/flake8-bugbear-22.10.27/PKG-INFO 2022-10-27 00:37:28.730224100 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: flake8-bugbear -Version: 22.10.25 +Version: 22.10.27 Summary: A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle. Author-email: ??ukasz Langa <luk...@langa.pl> License: MIT @@ -326,6 +326,13 @@ Change Log ---------- +22.10.27 +~~~~~~~~~ + +* B027: Ignore @overload decorator (#306) +* B023: Also fix map (#305) +* B023: Avoid false alarms with filter, reduce, key= and return. Added tests for functools (#303) + 22.10.25 ~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/README.rst new/flake8-bugbear-22.10.27/README.rst --- old/flake8-bugbear-22.10.25/README.rst 2022-10-25 01:25:06.000000000 +0200 +++ new/flake8-bugbear-22.10.27/README.rst 2022-10-27 00:37:19.000000000 +0200 @@ -297,6 +297,13 @@ Change Log ---------- +22.10.27 +~~~~~~~~~ + +* B027: Ignore @overload decorator (#306) +* B023: Also fix map (#305) +* B023: Avoid false alarms with filter, reduce, key= and return. Added tests for functools (#303) + 22.10.25 ~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/bugbear.py new/flake8-bugbear-22.10.27/bugbear.py --- old/flake8-bugbear-22.10.25/bugbear.py 2022-10-25 01:25:06.000000000 +0200 +++ new/flake8-bugbear-22.10.27/bugbear.py 2022-10-27 00:37:19.000000000 +0200 @@ -12,7 +12,7 @@ import attr import pycodestyle -__version__ = "22.10.25" +__version__ = "22.10.27" LOG = logging.getLogger("flake8.bugbear") CONTEXTFUL_NODES = ( @@ -280,7 +280,7 @@ 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_),) + vs = (f"`except (){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)) @@ -568,7 +568,7 @@ n = targets.names[name][0] self.errors.append(B020(n.lineno, n.col_offset, vars=(name,))) - def check_for_b023(self, loop_node): + def check_for_b023(self, loop_node): # noqa: C901 """Check that functions (including lambdas) do not use loop variables. https://docs.python-guide.org/writing/gotchas/#late-binding-closures from @@ -584,9 +584,41 @@ # implement this "backwards": first we find all the candidate variable # uses, and then if there are any we check for assignment of those names # inside the loop body. + safe_functions = [] suspicious_variables = [] for node in ast.walk(loop_node): - if isinstance(node, FUNCTION_NODES): + # check if function is immediately consumed to avoid false alarm + if isinstance(node, ast.Call): + # check for filter&reduce + if ( + isinstance(node.func, ast.Name) + and node.func.id in ("filter", "reduce", "map") + ) or ( + isinstance(node.func, ast.Attribute) + and node.func.attr == "reduce" + and isinstance(node.func.value, ast.Name) + and node.func.value.id == "functools" + ): + for arg in node.args: + if isinstance(arg, FUNCTION_NODES): + safe_functions.append(arg) + + # check for key= + for keyword in node.keywords: + if keyword.arg == "key" and isinstance( + keyword.value, FUNCTION_NODES + ): + safe_functions.append(keyword.value) + + # mark `return lambda: x` as safe + # does not (currently) check inner lambdas in a returned expression + # e.g. `return (lambda: x, ) + if isinstance(node, ast.Return): + if isinstance(node.value, FUNCTION_NODES): + safe_functions.append(node.value) + + # find unsafe functions + if isinstance(node, FUNCTION_NODES) and node not in safe_functions: argnames = { arg.arg for arg in ast.walk(node.args) if isinstance(arg, ast.arg) } @@ -594,16 +626,19 @@ body_nodes = ast.walk(node.body) else: body_nodes = itertools.chain.from_iterable(map(ast.walk, node.body)) + errors = [] for name in body_nodes: - if ( - isinstance(name, ast.Name) - and name.id not in argnames - and isinstance(name.ctx, ast.Load) - ): - err = B023(name.lineno, name.col_offset, vars=(name.id,)) - if err not in self._b023_seen: - self._b023_seen.add(err) # dedupe across nested loops - suspicious_variables.append(err) + if isinstance(name, ast.Name) and name.id not in argnames: + if isinstance(name.ctx, ast.Load): + errors.append( + B023(name.lineno, name.col_offset, vars=(name.id,)) + ) + elif isinstance(name.ctx, ast.Store): + argnames.add(name.id) + for err in errors: + if err.vars[0] not in argnames and err not in self._b023_seen: + self._b023_seen.add(err) # dedupe across nested loops + suspicious_variables.append(err) if suspicious_variables: reassigned_in_loop = set(self._get_assigned_names(loop_node)) @@ -634,6 +669,14 @@ isinstance(expr, ast.Attribute) and expr.attr[:8] == "abstract" ) + def is_overload(expr): + return (isinstance(expr, ast.Name) and expr.id == "overload") or ( + isinstance(expr, ast.Attribute) + and isinstance(expr.value, ast.Name) + and expr.value.id == "typing" + and expr.attr == "overload" + ) + def empty_body(body) -> bool: def is_str_or_ellipsis(node): # ast.Ellipsis and ast.Str used in python<3.8 @@ -677,7 +720,11 @@ has_abstract_method |= has_abstract_decorator - if not has_abstract_decorator and empty_body(stmt.body): + if ( + not has_abstract_decorator + and empty_body(stmt.body) + and not any(map(is_overload, stmt.decorator_list)) + ): self.errors.append( B027(stmt.lineno, stmt.col_offset, vars=(stmt.name,)) ) @@ -912,7 +959,7 @@ uniques.add(name) seen.extend(uniques) # sort to have a deterministic output - duplicates = sorted(set(x for x in seen if seen.count(x) > 1)) + duplicates = sorted({x for x in seen if seen.count(x) > 1}) for duplicate in duplicates: self.errors.append(B025(node.lineno, node.col_offset, vars=(duplicate,))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/flake8_bugbear.egg-info/PKG-INFO new/flake8-bugbear-22.10.27/flake8_bugbear.egg-info/PKG-INFO --- old/flake8-bugbear-22.10.25/flake8_bugbear.egg-info/PKG-INFO 2022-10-25 01:25:14.000000000 +0200 +++ new/flake8-bugbear-22.10.27/flake8_bugbear.egg-info/PKG-INFO 2022-10-27 00:37:28.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: flake8-bugbear -Version: 22.10.25 +Version: 22.10.27 Summary: A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle. Author-email: ??ukasz Langa <luk...@langa.pl> License: MIT @@ -326,6 +326,13 @@ Change Log ---------- +22.10.27 +~~~~~~~~~ + +* B027: Ignore @overload decorator (#306) +* B023: Also fix map (#305) +* B023: Avoid false alarms with filter, reduce, key= and return. Added tests for functools (#303) + 22.10.25 ~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/tests/b023.py new/flake8-bugbear-22.10.27/tests/b023.py --- old/flake8-bugbear-22.10.25/tests/b023.py 2022-10-25 01:25:06.000000000 +0200 +++ new/flake8-bugbear-22.10.27/tests/b023.py 2022-10-27 00:37:19.000000000 +0200 @@ -2,10 +2,10 @@ Should emit: B023 - on lines 12, 13, 16, 28, 29, 30, 31, 40, 42, 50, 51, 52, 53, 61, 68. """ +from functools import reduce functions = [] z = 0 - for x in range(3): y = x + 1 # Subject to late-binding problems @@ -25,10 +25,10 @@ def check_inside_functions_too(): - ls = [lambda: x for x in range(2)] - st = {lambda: x for x in range(2)} - gn = (lambda: x for x in range(2)) - dt = {x: lambda: x for x in range(2)} + ls = [lambda: x for x in range(2)] # error + st = {lambda: x for x in range(2)} # error + gn = (lambda: x for x in range(2)) # error + dt = {x: lambda: x for x in range(2)} # error async def pointless_async_iterable(): @@ -37,9 +37,9 @@ async def container_for_problems(): async for x in pointless_async_iterable(): - functions.append(lambda: x) + functions.append(lambda: x) # error - [lambda: x async for x in pointless_async_iterable()] + [lambda: x async for x in pointless_async_iterable()] # error a = 10 @@ -47,10 +47,10 @@ while True: a = a_ = a - 1 b += 1 - functions.append(lambda: a) - functions.append(lambda: a_) - functions.append(lambda: b) - functions.append(lambda: c) # not a name error because of late binding! + functions.append(lambda: a) # error + functions.append(lambda: a_) # error + functions.append(lambda: b) # error + functions.append(lambda: c) # error, but not a name error due to late binding c: bool = a > 3 if not c: break @@ -58,7 +58,7 @@ # Nested loops should not duplicate reports for j in range(2): for k in range(3): - lambda: j * k + lambda: j * k # error for j, k, l in [(1, 2, 3)]: @@ -76,3 +76,95 @@ def explicit_capture(captured=var): return captured + + +# `query` is defined in the function, so also defining it in the loop should be OK. +for name in ["a", "b"]: + query = name + + def myfunc(x): + query = x + query_post = x + _ = query + _ = query_post + + query_post = name # in case iteration order matters + + +# Bug here because two dict comprehensions reference `name`, one of which is inside +# the lambda. This should be totally fine, of course. +_ = { + k: v + for k, v in reduce( + lambda data, event: merge_mappings( + [data, {name: f(caches, data, event) for name, f in xx}] + ), + events, + {name: getattr(group, name) for name in yy}, + ).items() + if k in backfill_fields +} + + +# OK to define lambdas if they're immediately consumed, typically as the `key=` +# argument or in a consumed `filter()` (even if a comprehension is better style) +for x in range(2): + # It's not a complete get-out-of-linting-free construct - these should fail: + min([None, lambda: x], key=repr) + sorted([None, lambda: x], key=repr) + any(filter(bool, [None, lambda: x])) + list(filter(bool, [None, lambda: x])) + all(reduce(bool, [None, lambda: x])) + + # But all these ones should be OK: + min(range(3), key=lambda y: x * y) + max(range(3), key=lambda y: x * y) + sorted(range(3), key=lambda y: x * y) + + any(map(lambda y: x < y, range(3))) + all(map(lambda y: x < y, range(3))) + set(map(lambda y: x < y, range(3))) + list(map(lambda y: x < y, range(3))) + tuple(map(lambda y: x < y, range(3))) + sorted(map(lambda y: x < y, range(3))) + frozenset(map(lambda y: x < y, range(3))) + + any(filter(lambda y: x < y, range(3))) + all(filter(lambda y: x < y, range(3))) + set(filter(lambda y: x < y, range(3))) + list(filter(lambda y: x < y, range(3))) + tuple(filter(lambda y: x < y, range(3))) + sorted(filter(lambda y: x < y, range(3))) + frozenset(filter(lambda y: x < y, range(3))) + + any(reduce(lambda y: x | y, range(3))) + all(reduce(lambda y: x | y, range(3))) + set(reduce(lambda y: x | y, range(3))) + list(reduce(lambda y: x | y, range(3))) + tuple(reduce(lambda y: x | y, range(3))) + sorted(reduce(lambda y: x | y, range(3))) + frozenset(reduce(lambda y: x | y, range(3))) + + import functools + + any(functools.reduce(lambda y: x | y, range(3))) + all(functools.reduce(lambda y: x | y, range(3))) + set(functools.reduce(lambda y: x | y, range(3))) + list(functools.reduce(lambda y: x | y, range(3))) + tuple(functools.reduce(lambda y: x | y, range(3))) + sorted(functools.reduce(lambda y: x | y, range(3))) + frozenset(functools.reduce(lambda y: x | y, range(3))) + +# OK because the lambda which references a loop variable is defined in a `return` +# statement, and after we return the loop variable can't be redefined. +# In principle we could do something fancy with `break`, but it's not worth it. +def iter_f(names): + for name in names: + if exists(name): + return lambda: name if exists(name) else None + + if foo(name): + return [lambda: name] # known false alarm + + if False: + return [lambda: i for i in range(3)] # error diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/tests/b027.py new/flake8-bugbear-22.10.27/tests/b027.py --- old/flake8-bugbear-22.10.25/tests/b027.py 2022-10-25 01:25:06.000000000 +0200 +++ new/flake8-bugbear-22.10.27/tests/b027.py 2022-10-27 00:37:19.000000000 +0200 @@ -5,7 +5,7 @@ """ Should emit: -B025 - on lines 13, 16, 19, 23, 31 +B027 - on lines 13, 16, 19, 23, 31 """ @@ -57,3 +57,22 @@ def empty_2(self): # safe pass + + +# ignore @overload, fixes issue #304 +import typing +from typing import Union, overload + + +class AstractClass(ABC): + @overload + def empty_1(self, foo: str): + ... + + @typing.overload + def empty_1(self, foo: int): + ... + + @abstractmethod + def empty_1(self, foo: Union[str, int]): + ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8-bugbear-22.10.25/tests/test_bugbear.py new/flake8-bugbear-22.10.27/tests/test_bugbear.py --- old/flake8-bugbear-22.10.25/tests/test_bugbear.py 2022-10-25 01:25:06.000000000 +0200 +++ new/flake8-bugbear-22.10.27/tests/test_bugbear.py 2022-10-27 00:37:19.000000000 +0200 @@ -351,6 +351,13 @@ B023(61, 16, vars=("j",)), B023(61, 20, vars=("k",)), B023(68, 9, vars=("l",)), + B023(113, 23, vars=("x",)), + B023(114, 26, vars=("x",)), + B023(115, 36, vars=("x",)), + B023(116, 37, vars=("x",)), + B023(117, 36, vars=("x",)), + B023(167, 28, vars=("name",)), # known false alarm + B023(170, 28, vars=("i",)), ) self.assertEqual(errors, expected)