Repository: wemake-services/wemake-python-styleguide

Issue:
Allow to use `assert` outside `pytest.raises` block
# Rule request

There is impossible to validate correctness of custom exception.

## Thesis

I propose to allow `assert` statement to use variable outside context manager.

```
with pytest.raises(CustomError) as exc_info:
    client.send_sms(sms)

    assert exc_info.value.http_status == expected_status
    assert exc_info.value.description == expected_description
```
Code above raise an error:
```
148:16   WPS441 Found control variable used after block: exc_info
  assert exc_info.value.http_status == expected_status
```

## Reasoning

It is typical use case for [pytest](https://docs.pytest.org/en/7.1.x/how-to/assert.html#assertions-about-expected-exceptions). 


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b39f4c1..e7cea4e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -80,6 +80,7 @@ Semantic versioning in our case means:
 - Allows any compares in `assert` statements for `WPS520`, #3112
 - Allows walrus operator (`:=`) in comprehesions #3121
 - Allows `pass` in `case` bodies, #2642
+- Allows using variables after blocks for `WPS441` in `assert` statements, #2543
 - Does not count `self`, `cls`, and `mcs` as arguments
   for `WPS211` complexity check anymore, #2394
 - `WPS303`: Underscore (`_`) now only allowed with 3 digits after it, #3120
diff --git a/wemake_python_styleguide/visitors/ast/blocks.py b/wemake_python_styleguide/visitors/ast/blocks.py
index ceeebb9e..241a9e56 100644
--- a/wemake_python_styleguide/visitors/ast/blocks.py
+++ b/wemake_python_styleguide/visitors/ast/blocks.py
@@ -7,10 +7,10 @@ from typing_extensions import final
 
 from wemake_python_styleguide.compat.aliases import ForNodes, WithNodes
 from wemake_python_styleguide.compat.types import NamedMatch
+from wemake_python_styleguide.logic import walk
 from wemake_python_styleguide.logic.naming.name_nodes import flat_variable_names
 from wemake_python_styleguide.logic.nodes import get_context, get_parent
 from wemake_python_styleguide.logic.scopes import defs, predicates
-from wemake_python_styleguide.logic.walk import is_contained_by
 from wemake_python_styleguide.types import (
     AnyAssignWithWalrus,
     AnyFor,
@@ -234,10 +234,13 @@ class AfterBlockVariablesVisitor(base.BaseNodeVisitor):
             self._block_variables[context][var_name].append(node)
 
     def _check_variable_usage(self, node: ast.Name) -> None:
+        if walk.get_closest_parent(node, ast.Assert):
+            return  # Allow any names to be used in `assert` statements
+
         context = cast(ast.AST, get_context(node))
         blocks = self._block_variables[context][node.id]
         is_contained_block_var = any(
-            is_contained_by(node, block) for block in blocks
+            walk.is_contained_by(node, block) for block in blocks
         )
         # Restrict the use of block variables with the same name to
         # the same type of block - either `for` or `with`.


Test patch:
diff --git a/tests/test_visitors/test_ast/test_blocks/test_control_variables.py b/tests/test_visitors/test_ast/test_blocks/test_control_variables.py
index 5c3f2277..84902c41 100644
--- a/tests/test_visitors/test_ast/test_blocks/test_control_variables.py
+++ b/tests/test_visitors/test_ast/test_blocks/test_control_variables.py
@@ -43,6 +43,13 @@ def wrapper():
     print(wrapper)
 """
 
+correct_for_loop6 = """
+def wrapper():
+    for i in ():
+        ...
+    assert i == 0
+"""
+
 correct_for_multi_loops = """
 def wrapper():
     for my_var in range(3):
@@ -155,6 +162,14 @@ def other():
     print(first)
 """
 
+correct_with4 = """
+def wrapper():
+    with open() as (first, second):
+        ...
+    assert first
+    assert second
+"""
+
 # Wrong:
 
 wrong_for_loop1 = """
@@ -246,6 +261,7 @@ def test_control_variable_used_after_block(
         correct_for_loop3,
         correct_for_loop4,
         correct_for_loop5,
+        correct_for_loop6,
         correct_for_multi_loops,
         correct_for_comprehension1,
         correct_for_comprehension2,
@@ -259,6 +275,7 @@ def test_control_variable_used_after_block(
         correct_with1,
         correct_with2,
         correct_with3,
+        correct_with4,
     ],
 )
 def test_control_variable_used_correctly(