Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix magic method yields (__next__, __anext__, __aiter__) (#2400)
* do not allow yield in __anext__ nor __next__ __anext__: This is a runtime constraint. __anext__ cannot be a generator. This will cause the runtime to throw a TypeError when used in an async for loop. __next__: The usefulness of having __next__ be a generator is practically nil. Doing so will cause an infinite loop with a new generator object passed to each iteration. * fix handling of __aiter__ and yield __aiter__ can only contain yield if it is an async function (async generator). Otherwise it must be a sync function that does not contain yield and returns an object that implements __anext__. * update violation class documentation * remove unnecessary parenthesis * add __anext__ and __next__ to invalid yields test * add new tests for magic methods that depend on async + yield * add missing argument when creating new tests * Make tests pass * Make tests pass Co-authored-by: sobolevn <mail@sobolevn.me>
- Loading branch information
Showing
7 changed files
with
226 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 148 additions & 0 deletions
148
tests/test_visitors/test_ast/test_classes/test_methods/test_async_yield_magic_methods.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import pytest | ||
|
||
from wemake_python_styleguide.violations.oop import AsyncMagicMethodViolation | ||
from wemake_python_styleguide.visitors.ast.classes import WrongMethodVisitor | ||
|
||
sync_method = """ | ||
class Example(object): | ||
def {0}(self, *args, **kwargs): | ||
{1} | ||
""" | ||
|
||
async_method = """ | ||
class Example(object): | ||
async def {0}(self, *args, **kwargs): | ||
{1} | ||
""" | ||
|
||
|
||
@pytest.mark.parametrize('template', [ | ||
sync_method, | ||
async_method, | ||
]) | ||
@pytest.mark.parametrize('method', [ | ||
'__aiter__', | ||
]) | ||
@pytest.mark.parametrize('statement', [ | ||
'yield', | ||
'yield 1', | ||
]) | ||
def test_yield_is_always_allowed_in_aiter( | ||
assert_errors, | ||
parse_ast_tree, | ||
default_options, | ||
template, | ||
method, | ||
statement, | ||
): | ||
"""Testing that the `__aiter__` can always have `yield`.""" | ||
tree = parse_ast_tree(template.format(method, statement)) | ||
|
||
visitor = WrongMethodVisitor(default_options, tree=tree) | ||
visitor.run() | ||
|
||
assert_errors(visitor, []) | ||
|
||
|
||
@pytest.mark.parametrize('method', [ | ||
'__aiter__', | ||
]) | ||
@pytest.mark.parametrize('statement', [ | ||
'return some_async_iterator()', | ||
]) | ||
def test_wrong_async_magic_used( | ||
assert_errors, | ||
assert_error_text, | ||
parse_ast_tree, | ||
default_options, | ||
method, | ||
statement, | ||
): | ||
"""Testing that the method cannot be a coroutine.""" | ||
tree = parse_ast_tree(async_method.format(method, statement)) | ||
|
||
visitor = WrongMethodVisitor(default_options, tree=tree) | ||
visitor.run() | ||
|
||
assert_errors(visitor, [AsyncMagicMethodViolation]) | ||
assert_error_text(visitor, method) | ||
|
||
|
||
@pytest.mark.parametrize('method', [ | ||
'__aiter__', | ||
]) | ||
@pytest.mark.parametrize('statement', [ | ||
'yield', | ||
'yield 1', | ||
]) | ||
def test_correct_async_yield_magic_used( | ||
assert_errors, | ||
parse_ast_tree, | ||
default_options, | ||
method, | ||
statement, | ||
): | ||
"""Testing that the method can be an async generator.""" | ||
tree = parse_ast_tree(async_method.format(method, statement)) | ||
|
||
visitor = WrongMethodVisitor(default_options, tree=tree) | ||
visitor.run() | ||
|
||
assert_errors(visitor, []) | ||
|
||
|
||
@pytest.mark.parametrize('method', [ | ||
'__aiter__', | ||
]) | ||
@pytest.mark.parametrize('statement', [ | ||
'return some_async_iterator()', | ||
]) | ||
def test_correct_sync_magic_used( | ||
assert_errors, | ||
parse_ast_tree, | ||
default_options, | ||
method, | ||
statement, | ||
): | ||
"""Testing that the method can be a normal method.""" | ||
tree = parse_ast_tree(sync_method.format(method, statement)) | ||
|
||
visitor = WrongMethodVisitor(default_options, tree=tree) | ||
visitor.run() | ||
|
||
assert_errors(visitor, []) | ||
|
||
|
||
# Examples: | ||
|
||
correct_nested_example = """ | ||
class Some: | ||
{0}def __aiter__(self): | ||
async def inner(): | ||
yield 1 | ||
return inner() | ||
""" | ||
|
||
|
||
@pytest.mark.parametrize('example', [ | ||
correct_nested_example, | ||
]) | ||
@pytest.mark.parametrize('mode', [ | ||
# We don't use `mode()` fixture here, because we have a nested func. | ||
'', # sync | ||
'async ', | ||
]) | ||
def test_correct_examples( | ||
assert_errors, | ||
parse_ast_tree, | ||
default_options, | ||
example, | ||
mode, | ||
): | ||
"""Testing specific real-life examples that should be working.""" | ||
tree = parse_ast_tree(example.format(mode)) | ||
|
||
visitor = WrongMethodVisitor(default_options, tree=tree) | ||
visitor.run() | ||
|
||
assert_errors(visitor, []) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.