Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Python Compatibility Warnings #182

Merged
merged 1 commit into from Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 0 additions & 39 deletions README.rst
Expand Up @@ -131,45 +131,6 @@ Either assert for a more specific exception (builtin or custom), use
data available in ``ex``.


Python 3 compatibility warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These have higher risk of false positives but discover regressions that
are dangerous to slip through when test coverage is not great. Let me
know if a popular library is triggering any of the following warnings
for valid code.

**B301**: Python 3 does not include ``.iter*`` methods on dictionaries.
The default behavior is to return iterables. Simply remove the ``iter``
prefix from the method. For Python 2 compatibility, also prefer the
Python 3 equivalent if you expect that the size of the dict to be small
and bounded. The performance regression on Python 2 will be negligible
and the code is going to be the clearest. Alternatively, use
``six.iter*`` or ``future.utils.iter*``.

**B302**: Python 3 does not include ``.view*`` methods on dictionaries.
The default behavior is to return viewables. Simply remove the ``view``
prefix from the method. For Python 2 compatibility, also prefer the
Python 3 equivalent if you expect that the size of the dict to be small
and bounded. The performance regression on Python 2 will be negligible
and the code is going to be the clearest. Alternatively, use
``six.view*`` or ``future.utils.view*``.

**B303**: The ``__metaclass__`` attribute on a class definition does
nothing on Python 3. Use ``class MyClass(BaseClass, metaclass=...)``.
For Python 2 compatibility, use ``six.add_metaclass``.

**B304**: ``sys.maxint`` is not a thing on Python 3. Use
``sys.maxsize``.

**B305**: ``.next()`` is not a thing on Python 3. Use the ``next()``
builtin. For Python 2 compatibility, use ``six.next()``.

**B306**: ``BaseException.message`` has been deprecated as of Python 2.6
and is removed in Python 3. Use ``str(e)`` to access the user-readable
message. Use ``e.args`` to access arguments passed to the exception.


Opinionated warnings
~~~~~~~~~~~~~~~~~~~~

Expand Down
82 changes: 2 additions & 80 deletions bugbear.py
Expand Up @@ -226,14 +226,7 @@ def visit_UAdd(self, node):

def visit_Call(self, node):
if isinstance(node.func, ast.Attribute):
for bug in (B301, B302, B305):
if node.func.attr in bug.methods:
call_path = ".".join(self.compose_call_path(node.func.value))
if call_path not in bug.valid_paths:
self.errors.append(bug(node.lineno, node.col_offset))
break
else:
self.check_for_b005(node)
self.check_for_b005(node)
else:
with suppress(AttributeError, IndexError):
if (
Expand All @@ -258,25 +251,8 @@ def visit_Call(self, node):

self.generic_visit(node)

def visit_Attribute(self, node):
call_path = list(self.compose_call_path(node))
if ".".join(call_path) == "sys.maxint":
self.errors.append(B304(node.lineno, node.col_offset))
elif len(call_path) == 2 and call_path[1] == "message":
name = call_path[0]
for elem in reversed(self.node_stack[:-1]):
if isinstance(elem, ast.ExceptHandler) and elem.name == name:
self.errors.append(B306(node.lineno, node.col_offset))
break

def visit_Assign(self, node):
if isinstance(self.node_stack[-2], ast.ClassDef):
# note: by hasattr below we're ignoring starred arguments, slices
# and tuples for simplicity.
assign_targets = {t.id for t in node.targets if hasattr(t, "id")}
if "__metaclass__" in assign_targets:
self.errors.append(B303(node.lineno, node.col_offset))
elif len(node.targets) == 1:
if len(node.targets) == 1:
t = node.targets[0]
if isinstance(t, ast.Attribute) and isinstance(t.value, ast.Name):
if (t.value.id, t.attr) == ("os", "environ"):
Expand Down Expand Up @@ -771,60 +747,6 @@ def visit(self, node):
)
)

# Those could be false positives but it's more dangerous to let them slip
# through if they're not.
B301 = Error(
message=(
"B301 Python 3 does not include `.iter*` methods on dictionaries. "
"Remove the `iter` prefix from the method name. For Python 2 "
"compatibility, prefer the Python 3 equivalent unless you expect "
"the size of the container to be large or unbounded. Then use "
"`six.iter*` or `future.utils.iter*`."
)
)
B301.methods = {"iterkeys", "itervalues", "iteritems", "iterlists"}
B301.valid_paths = {"six", "future.utils", "builtins"}

B302 = Error(
message=(
"B302 Python 3 does not include `.view*` methods on dictionaries. "
"Remove the `view` prefix from the method name. For Python 2 "
"compatibility, prefer the Python 3 equivalent unless you expect "
"the size of the container to be large or unbounded. Then use "
"`six.view*` or `future.utils.view*`."
)
)
B302.methods = {"viewkeys", "viewvalues", "viewitems", "viewlists"}
B302.valid_paths = {"six", "future.utils", "builtins"}

B303 = Error(
message=(
"B303 `__metaclass__` does nothing on Python 3. Use "
"`class MyClass(BaseClass, metaclass=...)`. For Python 2 "
"compatibility, use `six.add_metaclass`."
)
)

B304 = Error(message="B304 `sys.maxint` is not a thing on Python 3. Use `sys.maxsize`.")

B305 = Error(
message=(
"B305 `.next()` is not a thing on Python 3. Use the `next()` "
"builtin. For Python 2 compatibility, use `six.next()`."
)
)
B305.methods = {"next"}
B305.valid_paths = {"six", "future.utils", "builtins"}

B306 = Error(
message=(
"B306 `BaseException.message` has been deprecated as of Python "
"2.6 and is removed in Python 3. Use `str(e)` to access the "
"user-readable message. Use `e.args` to access arguments passed "
"to the exception."
)
)

# Warnings disabled by default.
B901 = Error(
message=(
Expand Down
48 changes: 0 additions & 48 deletions tests/b301_b302_b305.py

This file was deleted.

46 changes: 0 additions & 46 deletions tests/b303_b304.py

This file was deleted.

11 changes: 0 additions & 11 deletions tests/b306.py

This file was deleted.

38 changes: 0 additions & 38 deletions tests/test_bugbear.py
Expand Up @@ -28,12 +28,6 @@
B015,
B016,
B017,
B301,
B302,
B303,
B304,
B305,
B306,
B901,
B902,
B903,
Expand Down Expand Up @@ -213,38 +207,6 @@ def test_b017(self):
expected = self.errors(B017(22, 8))
self.assertEqual(errors, expected)

def test_b301_b302_b305(self):
filename = Path(__file__).absolute().parent / "b301_b302_b305.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
self.assertEqual(
errors,
self.errors(
B301(39, 4),
B301(40, 4),
B301(41, 4),
B301(42, 4),
B302(43, 4),
B302(44, 4),
B302(45, 4),
B302(46, 4),
B305(47, 4),
B305(48, 4),
),
)

def test_b303_b304(self):
filename = Path(__file__).absolute().parent / "b303_b304.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
self.assertEqual(errors, self.errors(B303(25, 4), B304(46, 4)))

def test_b306(self):
filename = Path(__file__).absolute().parent / "b306.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
self.assertEqual(errors, self.errors(B306(9, 10)))

def test_b901(self):
filename = Path(__file__).absolute().parent / "b901.py"
bbc = BugBearChecker(filename=str(filename))
Expand Down