Repository: asottile/pyupgrade

Issue:
two steps to converge: io.open(..., 'r')
the first step goes to `open(..., 'r')` and the second step goes to `open(...)`

Discussion:

Patch:
diff --git a/pyupgrade/_plugins/open_mode.py b/pyupgrade/_plugins/open_mode.py
index fb3f3b6..bc3169a 100644
--- a/pyupgrade/_plugins/open_mode.py
+++ b/pyupgrade/_plugins/open_mode.py
@@ -55,8 +55,17 @@ def visit_Call(
 ) -> Iterable[tuple[Offset, TokenFunc]]:
     if (
             state.settings.min_version >= (3,) and
-            isinstance(node.func, ast.Name) and
-            node.func.id == 'open' and
+            (
+                (
+                    isinstance(node.func, ast.Name) and
+                    node.func.id == 'open'
+                ) or (
+                    isinstance(node.func, ast.Attribute) and
+                    isinstance(node.func.value, ast.Name) and
+                    node.func.value.id == 'io' and
+                    node.func.attr == 'open'
+                )
+            ) and
             not has_starargs(node)
     ):
         if len(node.args) >= 2 and isinstance(node.args[1], ast.Str):


Test patch:
diff --git a/tests/features/open_mode_test.py b/tests/features/open_mode_test.py
index 9fafc7c..32a2fd1 100644
--- a/tests/features/open_mode_test.py
+++ b/tests/features/open_mode_test.py
@@ -65,6 +65,11 @@ def test_fix_open_mode_noop(s):
             'open(encoding="UTF-8", file="t.py")',
         ),
         pytest.param('open(f, u"r")', 'open(f)', id='string with u flag'),
+        pytest.param(
+            'io.open("foo", "r")',
+            'open("foo")',
+            id='io.open also rewrites modes in a single pass',
+        ),
     ),
 )
 def test_fix_open_mode(s, expected):