Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ jobs:
max-parallel: 4
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
## Tools

### Production
- python 3.9+
- python 3.10+
- [flake8](http://flake8.pycqa.org/en/latest/)

### Development
Expand Down
5 changes: 4 additions & 1 deletion flake8_debug/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class Error:
from abc import ABC


class Error(ABC):
code: str
func_name: str

Expand Down
28 changes: 23 additions & 5 deletions flake8_debug/plugin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import ast
import sys
from typing import List, Tuple, Generator, Type, Any, Optional

from flake8_debug.errors import ERRORS, Error
from flake8_debug.meta import Meta

TDebug = Generator[Tuple[int, int, str, Type[Any]], None, None]

# Func names meaningful only as attribute calls (e.g. pdb.set_trace())
_ATTR_DETECTABLE: frozenset = frozenset({'set_trace'})
# Only flag attribute calls when the object looks like a known debugger module
_DEBUGGER_MODULES: frozenset = frozenset({'pdb', 'ipdb'})


class DebugVisitor(ast.NodeVisitor):
def __init__(self, errors: Tuple[Type[Error], ...]) -> None:
Expand All @@ -14,14 +20,20 @@ def __init__(self, errors: Tuple[Type[Error], ...]) -> None:

def visit_Call(self, node: ast.Call) -> None:
for error in self._errors:
if (
is_bare_call = (
isinstance(node.func, ast.Name)
and node.func.id == error.func_name
) or (
)
is_attr_call = (
isinstance(node.func, ast.Attribute)
and node.func.attr == error.func_name
):
and error.func_name in _ATTR_DETECTABLE
and isinstance(node.func.value, ast.Name)
and node.func.value.id in _DEBUGGER_MODULES
Comment on lines +31 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track aliased debugger imports before filtering set_trace

Restricting set_trace checks to literal names pdb and ipdb misses common alias imports such as import pdb as dbg; dbg.set_trace() or import ipdb as debugger; debugger.set_trace(). Those are still debugger calls, and previous versions reported them, but this branch now lets them pass silently because node.func.value.id is dbg/debugger instead of one of the hard-coded identifiers.

Useful? React with πŸ‘Β / πŸ‘Ž.

)
if is_bare_call or is_attr_call:
self.issues.append((node.lineno, node.col_offset, error().msg))
self.generic_visit(node)


class NoDebug:
Expand All @@ -32,10 +44,16 @@ def __init__(
self, tree: ast.Module, filename: Optional[str] = None
) -> None:
self._tree = tree
self._filename = filename

def run(self) -> TDebug:
debug = DebugVisitor(ERRORS)
debug.visit(self._tree)
old_limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(max(old_limit, 2000))
debug.visit(self._tree)
except RecursionError:
pass
finally:
sys.setrecursionlimit(old_limit)
for lineno, column, msg in debug.issues: # type: int, int, str
yield lineno, column, msg, type(self)
8 changes: 4 additions & 4 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
black==25.9.0
flake8>=4.0.1
pytest==8.4.2
pytest-cov==3.0.0
coverage==5.3
coveralls==2.1.2
pytest-cov==7.1.0
coverage==7.13.5
coveralls==4.1.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep coveralls on a Python-3.9-compatible release

Pinning coveralls to 4.1.0 breaks one of the versions this repo still supports. PyPI metadata for 4.1.0 requires Python 3.10+, but .github/workflows/analysis.yml still installs requirements-dev.txt in the 3.9 matrix entry and setup.py still advertises Python 3.9 support. In practice, pip install -r requirements-dev.txt will now fail on 3.9 in CI and for contributors using the documented minimum version.

Useful? React with πŸ‘Β / πŸ‘Ž.

setuptools==80.9.0
wheel==0.45.1
wheel==0.46.3
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
flake8>=4.0.1
astpretty==2.1.0
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

from setuptools import find_packages, setup

from flake8_debug.meta import Meta


def __load_readme() -> str:
"""Returns project description."""
Expand All @@ -19,6 +17,8 @@ def __load_requirements() -> Sequence[str]:


if __name__ == '__main__':
from flake8_debug.meta import Meta

setup(
name=Meta.name,
version=Meta.version,
Expand Down
24 changes: 24 additions & 0 deletions tests/flake8_debug_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,27 @@ def test_present_bare_set_trace():
) == (
_out(line=3, column=5, err=PdbError()),
)


def test_nested_print_is_detected():
assert _plugin_results('foo(print(0))') == (
_out(line=1, column=5, err=PrintError()),
)


def test_nested_breakpoint_is_detected():
assert _plugin_results('foo(breakpoint())') == (
_out(line=1, column=5, err=BreakpointError()),
)


def test_no_false_positive_on_arbitrary_object_print():
assert not _plugin_results('logger.print("msg")')


def test_no_false_positive_on_arbitrary_object_breakpoint():
assert not _plugin_results('self.breakpoint()')


def test_no_false_positive_on_arbitrary_object_set_trace():
assert not _plugin_results('cursor.set_trace()')
Loading