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

pylint pass #30

Merged
merged 2 commits into from
Aug 10, 2022
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
53 changes: 37 additions & 16 deletions flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,48 @@

Error_codes = {
"TRIO100": "{} context contains no checkpoints, add `await trio.sleep(0)`",
"TRIO101": "yield inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling",
"TRIO102": "await inside {0.name} on line {0.lineno} must have shielded cancel scope with a timeout",
"TRIO101": (
"yield inside a nursery or cancel scope is only safe when implementing "
"a context manager - otherwise, it breaks exception handling"
),
"TRIO102": (
"await inside {0.name} on line {0.lineno} must have shielded cancel "
"scope with a timeout"
),
"TRIO103": "{} block with a code path that doesn't re-raise the error",
"TRIO104": "Cancelled (and therefore BaseException) must be re-raised",
"TRIO105": "trio async function {} must be immediately awaited",
"TRIO106": "trio must be imported with `import trio` for the linter to work",
"TRIO107": "{0} from async function with no guaranteed checkpoint or exception since function definition on line {1.lineno}",
"TRIO108": "{0} from async iterable with no guaranteed checkpoint since {1.name} on line {1.lineno}",
"TRIO109": "Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead",
"TRIO107": (
"{0} from async function with no guaranteed checkpoint or exception "
"since function definition on line {1.lineno}"
),
"TRIO108": (
"{0} from async iterable with no guaranteed checkpoint since {1.name} "
"on line {1.lineno}"
),
"TRIO109": (
"Async function definition with a `timeout` parameter - use "
"`trio.[fail/move_on]_[after/at]` instead"
),
"TRIO110": "`while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.",
}


class Statement(NamedTuple):
name: str
lineno: int
col_offset: int = 0
col_offset: int = -1

# ignore col offset since many tests don't supply that
def __eq__(self, other: Any) -> bool:
return isinstance(other, Statement) and self[:2] == other[:2]
return (
isinstance(other, Statement)
and self[:2] == other[:2]
and (
self.col_offset == other.col_offset
or -1 in (self.col_offset, other.col_offset)
)
)


HasLineInfo = Union[ast.expr, ast.stmt, ast.arg, ast.excepthandler, Statement]
Expand All @@ -51,18 +72,19 @@ def __init__(self, node: ast.Call, funcname: str):
self.funcname = funcname
self.variable_name: Optional[str] = None
self.shielded: bool = False
self.has_timeout: bool = False
self.has_timeout: bool = True

# scope.shield is assigned to in visit_Assign

if self.funcname == "CancelScope":
self.has_timeout = False
for kw in node.keywords:
# Only accepts constant values
if kw.arg == "shield" and isinstance(kw.value, ast.Constant):
self.shielded = kw.value.value
# sets to True even if timeout is explicitly set to inf
if kw.arg == "deadline":
self.has_timeout = True
else:
self.has_timeout = True

def __str__(self):
# Not supporting other ways of importing trio, per TRIO106
Expand Down Expand Up @@ -298,16 +320,15 @@ def has_exception(node: Optional[ast.expr]) -> str:
if node.type is None:
return Statement("bare except", node.lineno, node.col_offset)
# several exceptions
elif isinstance(node.type, ast.Tuple):
if isinstance(node.type, ast.Tuple):
for element in node.type.elts:
name = has_exception(element)
if name:
return Statement(name, element.lineno, element.col_offset)
# single exception, either a Name or an Attribute
else:
name = has_exception(node.type)
if name:
return Statement(name, node.type.lineno, node.type.col_offset)
name = has_exception(node.type)
if name:
return Statement(name, node.type.lineno, node.type.col_offset)
return None


Expand Down
4 changes: 2 additions & 2 deletions tests/test_changelog_and_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def from_string(cls, string):

def get_releases() -> Iterable[Version]:
valid_pattern = re.compile(r"^## (\d\d\.\d?\d\.\d?\d)$")
with open(Path(__file__).parent.parent / "CHANGELOG.md") as f:
with open(Path(__file__).parent.parent / "CHANGELOG.md", encoding="utf-8") as f:
lines = f.readlines()
for aline in lines:
version_match = valid_pattern.match(aline)
Expand Down Expand Up @@ -54,7 +54,7 @@ def runTest(self):
"CHANGELOG.md",
"README.md",
):
with open(Path(__file__).parent.parent / filename) as f:
with open(Path(__file__).parent.parent / filename, encoding="utf-8") as f:
lines = f.readlines()
documented_errors[filename] = set()
for line in lines:
Expand Down
18 changes: 12 additions & 6 deletions tests/test_flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,29 @@ class ParseError(Exception):
...


@pytest.mark.parametrize("test, path", test_files)
def test_eval(test: str, path: str):
# version check
# check for presence of _pyXX, skip if version is later, and prune parameter
def check_version(test: str) -> str:
python_version = re.search(r"(?<=_PY)\d*", test)
if python_version:
version_str = python_version.group()
major, minor = version_str[0], version_str[1:]
v_i = sys.version_info
if (v_i.major, v_i.minor) < (int(major), int(minor)):
raise unittest.SkipTest("v_i, major, minor")
test = test.split("_")[0]
return test.split("_")[0]
return test


@pytest.mark.parametrize("test, path", test_files)
def test_eval(test: str, path: str):
# version check
test = check_version(test)

assert test in Error_codes.keys(), "error code not defined in flake8_trio.py"

include = [test]
expected: List[Error] = []
with open(os.path.join("tests", path)) as file:
with open(os.path.join("tests", path), encoding="utf-8") as file:
lines = file.readlines()

for lineno, line in enumerate(lines, start=1):
Expand Down Expand Up @@ -191,7 +197,7 @@ def assert_correct_lines_and_codes(errors: Iterable[Error], expected: Iterable[E
for line in all_lines:
if error_dict[line] == expected_dict[line]:
continue
for code in {*error_dict[line], *expected_dict[line]}:
for code in sorted({*error_dict[line], *expected_dict[line]}):
if not any_error:
print(
"Lines with different # of errors:",
Expand Down
9 changes: 0 additions & 9 deletions tests/trio103.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@
except trio.Cancelled: # error: 7, "trio.Cancelled"
pass

# raise different exception
except BaseException:
raise ValueError() # TRIO104
except trio.Cancelled as e:
raise ValueError() from e # TRIO104
except trio.Cancelled as e:
# see https://github.com/Zac-HD/flake8-trio/pull/8#discussion_r932737341
raise BaseException() from e # TRIO104

# if
except BaseException as e: # error: 7, "BaseException"
if True:
Expand Down