sourcery icon indicating copy to clipboard operation
sourcery copied to clipboard

Merge repeated with statements into single with

Open cleder opened this issue 4 years ago • 3 comments

Issue description or question

In unit tests of a legacy system I often come across code like:

        with patch('...', return_value='whatever'):
            with patch.object(LocalObj, 'get_...', return_value=expected_data) as mocked_x:
                with patch('..', return_value=gen_link) as mocked_y:

which imho could be refactored to:

        with patch('...', return_value='whatever'), \
                patch.object(LocalObj, 'get_...', return_value=expected_data) as mocked_x, \
                patch('..', return_value=gen_link) as mocked_y:

cleder avatar Sep 15 '21 13:09 cleder

This does nicely reduce nesting, and doing a little digging does look like they're exactly equivalent. Just a little concerned about nice formatting of the conjoined with statements.

Hellebore avatar Sep 15 '21 15:09 Hellebore

Formatting context managers statements is a bit of a bother with python < 3.10 where you can at last use parenthesis for multiple with statements https://docs.python.org/3.10/whatsnew/3.10.html#parenthesized-context-managers until 3.10 is mainstream, the , \ continuation is probably the best solution

cleder avatar Sep 16 '21 16:09 cleder

Note that we can now handle this internally so have added it to our roadmap. In the meantime this custom rule will handle the case of merging two withs, though not multiple ones:

- id: merge-withs
  description: Merge nested with statements
  pattern: |
    with ${thing} as ${a?}:
      with ${next_thing} as ${b?}:
        ${body}
  replacement: |
    with (${thing} as ${a}, ${next_thing} as ${b}):
      ${body}

Hellebore avatar Dec 02 '22 11:12 Hellebore