openrewrite-migrate-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openrewrite_migrate_python/__init__.py +35 -0
- openrewrite_migrate_python/migrate/__init__.py +219 -0
- openrewrite_migrate_python/migrate/aifc_migrations.py +517 -0
- openrewrite_migrate_python/migrate/array_deprecations.py +133 -0
- openrewrite_migrate_python/migrate/ast_deprecations.py +322 -0
- openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +225 -0
- openrewrite_migrate_python/migrate/asyncio_deprecations.py +117 -0
- openrewrite_migrate_python/migrate/calendar_deprecations.py +134 -0
- openrewrite_migrate_python/migrate/cgi_migrations.py +213 -0
- openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +170 -0
- openrewrite_migrate_python/migrate/collections_abc_migrations.py +207 -0
- openrewrite_migrate_python/migrate/configparser_deprecations.py +252 -0
- openrewrite_migrate_python/migrate/datetime_utc.py +136 -0
- openrewrite_migrate_python/migrate/distutils_deprecations.py +107 -0
- openrewrite_migrate_python/migrate/distutils_migrations.py +125 -0
- openrewrite_migrate_python/migrate/functools_deprecations.py +98 -0
- openrewrite_migrate_python/migrate/gettext_deprecations.py +139 -0
- openrewrite_migrate_python/migrate/html_parser_deprecations.py +110 -0
- openrewrite_migrate_python/migrate/imp_migrations.py +135 -0
- openrewrite_migrate_python/migrate/langchain_classic_imports.py +308 -0
- openrewrite_migrate_python/migrate/langchain_community_imports.py +154 -0
- openrewrite_migrate_python/migrate/langchain_provider_imports.py +240 -0
- openrewrite_migrate_python/migrate/locale_deprecations.py +177 -0
- openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +103 -0
- openrewrite_migrate_python/migrate/macpath_deprecations.py +125 -0
- openrewrite_migrate_python/migrate/mailcap_migrations.py +129 -0
- openrewrite_migrate_python/migrate/nntplib_migrations.py +127 -0
- openrewrite_migrate_python/migrate/os_deprecations.py +158 -0
- openrewrite_migrate_python/migrate/pathlib_deprecations.py +98 -0
- openrewrite_migrate_python/migrate/pep594_system_migrations.py +429 -0
- openrewrite_migrate_python/migrate/pipes_migrations.py +130 -0
- openrewrite_migrate_python/migrate/pkgutil_deprecations.py +180 -0
- openrewrite_migrate_python/migrate/platform_deprecations.py +99 -0
- openrewrite_migrate_python/migrate/re_deprecations.py +122 -0
- openrewrite_migrate_python/migrate/removed_modules_312.py +139 -0
- openrewrite_migrate_python/migrate/shutil_deprecations.py +112 -0
- openrewrite_migrate_python/migrate/socket_deprecations.py +96 -0
- openrewrite_migrate_python/migrate/ssl_deprecations.py +121 -0
- openrewrite_migrate_python/migrate/string_formatting.py +526 -0
- openrewrite_migrate_python/migrate/sys_deprecations.py +97 -0
- openrewrite_migrate_python/migrate/sys_last_deprecations.py +104 -0
- openrewrite_migrate_python/migrate/tarfile_deprecations.py +120 -0
- openrewrite_migrate_python/migrate/telnetlib_migrations.py +132 -0
- openrewrite_migrate_python/migrate/tempfile_deprecations.py +113 -0
- openrewrite_migrate_python/migrate/threading_deprecations.py +488 -0
- openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +82 -0
- openrewrite_migrate_python/migrate/typing_callable.py +153 -0
- openrewrite_migrate_python/migrate/typing_deprecations.py +337 -0
- openrewrite_migrate_python/migrate/typing_union_to_pipe.py +272 -0
- openrewrite_migrate_python/migrate/unittest_deprecations.py +140 -0
- openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +56 -0
- openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +66 -0
- openrewrite_migrate_python/migrate/upgrade_to_python310.py +91 -0
- openrewrite_migrate_python/migrate/upgrade_to_python311.py +92 -0
- openrewrite_migrate_python/migrate/upgrade_to_python312.py +101 -0
- openrewrite_migrate_python/migrate/upgrade_to_python313.py +109 -0
- openrewrite_migrate_python/migrate/upgrade_to_python314.py +87 -0
- openrewrite_migrate_python/migrate/upgrade_to_python38.py +85 -0
- openrewrite_migrate_python/migrate/upgrade_to_python39.py +125 -0
- openrewrite_migrate_python/migrate/urllib_deprecations.py +204 -0
- openrewrite_migrate_python/migrate/uu_migrations.py +129 -0
- openrewrite_migrate_python/migrate/xdrlib_migrations.py +129 -0
- openrewrite_migrate_python/migrate/xml_deprecations.py +154 -0
- openrewrite_migrate_python-0.1.0.dist-info/METADATA +31 -0
- openrewrite_migrate_python-0.1.0.dist-info/RECORD +68 -0
- openrewrite_migrate_python-0.1.0.dist-info/WHEEL +5 -0
- openrewrite_migrate_python-0.1.0.dist-info/entry_points.txt +2 -0
- openrewrite_migrate_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe for migrating typing.Callable to collections.abc.Callable.
|
|
3
|
+
|
|
4
|
+
PEP 585 (Python 3.9+) deprecated typing.Callable in favor of
|
|
5
|
+
collections.abc.Callable:
|
|
6
|
+
|
|
7
|
+
- typing.Callable[[int], str] -> collections.abc.Callable[[int], str]
|
|
8
|
+
|
|
9
|
+
See: https://peps.python.org/pep-0585/
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Optional, Set
|
|
13
|
+
|
|
14
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
15
|
+
from rewrite.category import CategoryDescriptor
|
|
16
|
+
from rewrite.decorators import categorize
|
|
17
|
+
from rewrite.marketplace import Python
|
|
18
|
+
from rewrite.markers import Markers, SearchResult
|
|
19
|
+
from rewrite.utils import random_id
|
|
20
|
+
from rewrite.python.visitor import PythonVisitor
|
|
21
|
+
from rewrite.python.tree import MultiImport
|
|
22
|
+
from rewrite.java.tree import FieldAccess, Identifier, Import
|
|
23
|
+
|
|
24
|
+
# Define category path
|
|
25
|
+
_Python39 = [
|
|
26
|
+
*Python,
|
|
27
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
28
|
+
CategoryDescriptor(display_name="Python 3.9"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _mark_deprecated(tree: Any, message: str, detail: Optional[str] = None) -> Any:
|
|
33
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
34
|
+
full_message = f"{message}: {detail}" if detail else message
|
|
35
|
+
search_marker = SearchResult(random_id(), full_message)
|
|
36
|
+
current_markers = tree.markers
|
|
37
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
38
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
39
|
+
return tree.replace(_markers=new_markers)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@categorize(_Python39)
|
|
43
|
+
class ReplaceTypingCallableWithCollectionsAbcCallable(Recipe):
|
|
44
|
+
"""
|
|
45
|
+
Find and migrate `typing.Callable` to `collections.abc.Callable`.
|
|
46
|
+
|
|
47
|
+
PEP 585 deprecated `typing.Callable` in Python 3.9. Use
|
|
48
|
+
`collections.abc.Callable` instead for type annotations.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
Before:
|
|
52
|
+
from typing import Callable
|
|
53
|
+
handler: Callable[[int], str] = lambda x: str(x)
|
|
54
|
+
|
|
55
|
+
After:
|
|
56
|
+
from collections.abc import Callable
|
|
57
|
+
handler: Callable[[int], str] = lambda x: str(x)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def name(self) -> str:
|
|
62
|
+
return "org.openrewrite.python.migrate.ReplaceTypingCallableWithCollectionsAbcCallable"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def display_name(self) -> str:
|
|
66
|
+
return "Replace `typing.Callable` with `collections.abc.Callable`"
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def description(self) -> str:
|
|
70
|
+
return (
|
|
71
|
+
"PEP 585 deprecated `typing.Callable` in Python 3.9. "
|
|
72
|
+
"Replace with `collections.abc.Callable` for type annotations."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
76
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
77
|
+
def __init__(self):
|
|
78
|
+
super().__init__()
|
|
79
|
+
self._has_typing_callable_import = False
|
|
80
|
+
self._has_typing_star_import = False
|
|
81
|
+
|
|
82
|
+
def visit_multi_import(self, multi_import: MultiImport, p: ExecutionContext):
|
|
83
|
+
"""Track 'from typing import Callable' imports."""
|
|
84
|
+
from_ = multi_import.from_
|
|
85
|
+
if isinstance(from_, Identifier) and from_.simple_name == "typing":
|
|
86
|
+
for imp in multi_import.names:
|
|
87
|
+
qualid = imp.qualid
|
|
88
|
+
if isinstance(qualid, FieldAccess):
|
|
89
|
+
name = qualid.name
|
|
90
|
+
if isinstance(name, Identifier) and name.simple_name == "Callable":
|
|
91
|
+
self._has_typing_callable_import = True
|
|
92
|
+
elif isinstance(qualid, Identifier) and qualid.simple_name == "Callable":
|
|
93
|
+
self._has_typing_callable_import = True
|
|
94
|
+
return super().visit_multi_import(multi_import, p)
|
|
95
|
+
|
|
96
|
+
def visit_import(self, import_node: Import, p: ExecutionContext):
|
|
97
|
+
"""Track 'import typing' imports."""
|
|
98
|
+
qualid = import_node.qualid
|
|
99
|
+
if isinstance(qualid, Identifier) and qualid.simple_name == "typing":
|
|
100
|
+
self._has_typing_star_import = True
|
|
101
|
+
elif isinstance(qualid, FieldAccess):
|
|
102
|
+
name = qualid.name
|
|
103
|
+
if isinstance(name, Identifier) and name.simple_name == "typing":
|
|
104
|
+
self._has_typing_star_import = True
|
|
105
|
+
return super().visit_import(import_node, p)
|
|
106
|
+
|
|
107
|
+
def visit_identifier(
|
|
108
|
+
self, identifier: Identifier, p: ExecutionContext
|
|
109
|
+
) -> Optional[Identifier]:
|
|
110
|
+
identifier = super().visit_identifier(identifier, p)
|
|
111
|
+
|
|
112
|
+
if identifier.simple_name == "Callable" and self._has_typing_callable_import:
|
|
113
|
+
# Skip identifiers that are part of import statements
|
|
114
|
+
if not self._is_in_import(identifier):
|
|
115
|
+
return _mark_deprecated(
|
|
116
|
+
identifier,
|
|
117
|
+
"typing.Callable is deprecated",
|
|
118
|
+
"Replace with collections.abc.Callable (PEP 585)"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return identifier
|
|
122
|
+
|
|
123
|
+
def visit_field_access(self, field_access: FieldAccess, p: ExecutionContext):
|
|
124
|
+
"""Handle typing.Callable qualified access."""
|
|
125
|
+
field_access = super().visit_field_access(field_access, p)
|
|
126
|
+
|
|
127
|
+
if self._has_typing_star_import:
|
|
128
|
+
name = field_access.name
|
|
129
|
+
target = field_access.target
|
|
130
|
+
if (isinstance(name, Identifier) and name.simple_name == "Callable" and
|
|
131
|
+
isinstance(target, Identifier) and target.simple_name == "typing"):
|
|
132
|
+
marked_name = _mark_deprecated(
|
|
133
|
+
name,
|
|
134
|
+
"typing.Callable is deprecated",
|
|
135
|
+
"Replace with collections.abc.Callable (PEP 585)"
|
|
136
|
+
)
|
|
137
|
+
return field_access.replace(
|
|
138
|
+
_name=field_access.padding.name.replace(_element=marked_name)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return field_access
|
|
142
|
+
|
|
143
|
+
def _is_in_import(self, identifier: Identifier) -> bool:
|
|
144
|
+
"""Check if the identifier is inside an import statement."""
|
|
145
|
+
cursor = self.cursor
|
|
146
|
+
while cursor is not None:
|
|
147
|
+
value = cursor.value
|
|
148
|
+
if isinstance(value, (Import, MultiImport)):
|
|
149
|
+
return True
|
|
150
|
+
cursor = cursor.parent
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
return Visitor()
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating deprecated typing module aliases to built-in generics.
|
|
3
|
+
|
|
4
|
+
PEP 585 (Python 3.9+) deprecated many typing module aliases in favor of
|
|
5
|
+
using built-in types directly as generics:
|
|
6
|
+
|
|
7
|
+
- typing.List[X] -> list[X]
|
|
8
|
+
- typing.Dict[K, V] -> dict[K, V]
|
|
9
|
+
- typing.Set[X] -> set[X]
|
|
10
|
+
- typing.FrozenSet[X] -> frozenset[X]
|
|
11
|
+
- typing.Tuple[X, ...] -> tuple[X, ...]
|
|
12
|
+
- typing.Type[X] -> type[X]
|
|
13
|
+
|
|
14
|
+
Additionally, typing.Text is deprecated (alias for str since Python 3.11).
|
|
15
|
+
|
|
16
|
+
See: https://peps.python.org/pep-0585/
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Any, Optional, Dict
|
|
20
|
+
|
|
21
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
22
|
+
from rewrite.category import CategoryDescriptor
|
|
23
|
+
from rewrite.decorators import categorize
|
|
24
|
+
from rewrite.marketplace import Python
|
|
25
|
+
from rewrite.markers import Markers, SearchResult
|
|
26
|
+
from rewrite.utils import random_id
|
|
27
|
+
from rewrite.python.visitor import PythonVisitor
|
|
28
|
+
from rewrite.java.tree import Identifier
|
|
29
|
+
|
|
30
|
+
# Define category paths
|
|
31
|
+
_Python39 = [
|
|
32
|
+
*Python,
|
|
33
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
34
|
+
CategoryDescriptor(display_name="Python 3.9"),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
_Python311 = [
|
|
38
|
+
*Python,
|
|
39
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
40
|
+
CategoryDescriptor(display_name="Python 3.11"),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _mark_deprecated(tree: Any, message: str, detail: Optional[str] = None) -> Any:
|
|
45
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
46
|
+
full_message = f"{message}: {detail}" if detail else message
|
|
47
|
+
search_marker = SearchResult(random_id(), full_message)
|
|
48
|
+
current_markers = tree.markers
|
|
49
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
50
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
51
|
+
return tree.replace(_markers=new_markers)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# PEP 585 deprecated typing aliases -> built-in replacements
|
|
55
|
+
PEP585_DEPRECATIONS: Dict[str, str] = {
|
|
56
|
+
"List": "list",
|
|
57
|
+
"Dict": "dict",
|
|
58
|
+
"Set": "set",
|
|
59
|
+
"FrozenSet": "frozenset",
|
|
60
|
+
"Tuple": "tuple",
|
|
61
|
+
"Type": "type",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@categorize(_Python39)
|
|
66
|
+
class ReplaceTypingListWithList(Recipe):
|
|
67
|
+
"""
|
|
68
|
+
Find and migrate `typing.List` to built-in `list`.
|
|
69
|
+
|
|
70
|
+
PEP 585 deprecated `typing.List` in Python 3.9. The built-in `list` type
|
|
71
|
+
can now be used directly as a generic type.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
Before:
|
|
75
|
+
from typing import List
|
|
76
|
+
items: List[str] = []
|
|
77
|
+
|
|
78
|
+
After:
|
|
79
|
+
items: list[str] = []
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def name(self) -> str:
|
|
84
|
+
return "org.openrewrite.python.migrate.ReplaceTypingListWithList"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def display_name(self) -> str:
|
|
88
|
+
return "Replace `typing.List` with `list`"
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def description(self) -> str:
|
|
92
|
+
return (
|
|
93
|
+
"PEP 585 deprecated `typing.List` in Python 3.9. "
|
|
94
|
+
"Replace with the built-in `list` type for generic annotations."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
98
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
99
|
+
def visit_identifier(
|
|
100
|
+
self, identifier: Identifier, p: ExecutionContext
|
|
101
|
+
) -> Optional[Identifier]:
|
|
102
|
+
identifier = super().visit_identifier(identifier, p)
|
|
103
|
+
|
|
104
|
+
if identifier.simple_name == "List":
|
|
105
|
+
# Check if this is from typing module via type attribution
|
|
106
|
+
if identifier.field_type:
|
|
107
|
+
fqn = str(getattr(identifier.field_type, '_fully_qualified_name', ''))
|
|
108
|
+
if 'typing' in fqn:
|
|
109
|
+
return _mark_deprecated(
|
|
110
|
+
identifier,
|
|
111
|
+
"typing.List is deprecated",
|
|
112
|
+
"Replace with built-in list (PEP 585)"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return identifier
|
|
116
|
+
|
|
117
|
+
return Visitor()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@categorize(_Python39)
|
|
121
|
+
class ReplaceTypingDictWithDict(Recipe):
|
|
122
|
+
"""
|
|
123
|
+
Find and migrate `typing.Dict` to built-in `dict`.
|
|
124
|
+
|
|
125
|
+
PEP 585 deprecated `typing.Dict` in Python 3.9. The built-in `dict` type
|
|
126
|
+
can now be used directly as a generic type.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
Before:
|
|
130
|
+
from typing import Dict
|
|
131
|
+
mapping: Dict[str, int] = {}
|
|
132
|
+
|
|
133
|
+
After:
|
|
134
|
+
mapping: dict[str, int] = {}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def name(self) -> str:
|
|
139
|
+
return "org.openrewrite.python.migrate.ReplaceTypingDictWithDict"
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def display_name(self) -> str:
|
|
143
|
+
return "Replace `typing.Dict` with `dict`"
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def description(self) -> str:
|
|
147
|
+
return (
|
|
148
|
+
"PEP 585 deprecated `typing.Dict` in Python 3.9. "
|
|
149
|
+
"Replace with the built-in `dict` type for generic annotations."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
153
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
154
|
+
def visit_identifier(
|
|
155
|
+
self, identifier: Identifier, p: ExecutionContext
|
|
156
|
+
) -> Optional[Identifier]:
|
|
157
|
+
identifier = super().visit_identifier(identifier, p)
|
|
158
|
+
|
|
159
|
+
if identifier.simple_name == "Dict":
|
|
160
|
+
if identifier.field_type:
|
|
161
|
+
fqn = str(getattr(identifier.field_type, '_fully_qualified_name', ''))
|
|
162
|
+
if 'typing' in fqn:
|
|
163
|
+
return _mark_deprecated(
|
|
164
|
+
identifier,
|
|
165
|
+
"typing.Dict is deprecated",
|
|
166
|
+
"Replace with built-in dict (PEP 585)"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return identifier
|
|
170
|
+
|
|
171
|
+
return Visitor()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@categorize(_Python39)
|
|
175
|
+
class ReplaceTypingSetWithSet(Recipe):
|
|
176
|
+
"""
|
|
177
|
+
Find and migrate `typing.Set` to built-in `set`.
|
|
178
|
+
|
|
179
|
+
PEP 585 deprecated `typing.Set` in Python 3.9. The built-in `set` type
|
|
180
|
+
can now be used directly as a generic type.
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
Before:
|
|
184
|
+
from typing import Set
|
|
185
|
+
items: Set[int] = set()
|
|
186
|
+
|
|
187
|
+
After:
|
|
188
|
+
items: set[int] = set()
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def name(self) -> str:
|
|
193
|
+
return "org.openrewrite.python.migrate.ReplaceTypingSetWithSet"
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def display_name(self) -> str:
|
|
197
|
+
return "Replace `typing.Set` with `set`"
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def description(self) -> str:
|
|
201
|
+
return (
|
|
202
|
+
"PEP 585 deprecated `typing.Set` in Python 3.9. "
|
|
203
|
+
"Replace with the built-in `set` type for generic annotations."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
207
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
208
|
+
def visit_identifier(
|
|
209
|
+
self, identifier: Identifier, p: ExecutionContext
|
|
210
|
+
) -> Optional[Identifier]:
|
|
211
|
+
identifier = super().visit_identifier(identifier, p)
|
|
212
|
+
|
|
213
|
+
if identifier.simple_name == "Set":
|
|
214
|
+
if identifier.field_type:
|
|
215
|
+
fqn = str(getattr(identifier.field_type, '_fully_qualified_name', ''))
|
|
216
|
+
if 'typing' in fqn:
|
|
217
|
+
return _mark_deprecated(
|
|
218
|
+
identifier,
|
|
219
|
+
"typing.Set is deprecated",
|
|
220
|
+
"Replace with built-in set (PEP 585)"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return identifier
|
|
224
|
+
|
|
225
|
+
return Visitor()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@categorize(_Python39)
|
|
229
|
+
class ReplaceTypingTupleWithTuple(Recipe):
|
|
230
|
+
"""
|
|
231
|
+
Find and migrate `typing.Tuple` to built-in `tuple`.
|
|
232
|
+
|
|
233
|
+
PEP 585 deprecated `typing.Tuple` in Python 3.9. The built-in `tuple` type
|
|
234
|
+
can now be used directly as a generic type.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
Before:
|
|
238
|
+
from typing import Tuple
|
|
239
|
+
point: Tuple[int, int] = (0, 0)
|
|
240
|
+
|
|
241
|
+
After:
|
|
242
|
+
point: tuple[int, int] = (0, 0)
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def name(self) -> str:
|
|
247
|
+
return "org.openrewrite.python.migrate.ReplaceTypingTupleWithTuple"
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def display_name(self) -> str:
|
|
251
|
+
return "Replace `typing.Tuple` with `tuple`"
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def description(self) -> str:
|
|
255
|
+
return (
|
|
256
|
+
"PEP 585 deprecated `typing.Tuple` in Python 3.9. "
|
|
257
|
+
"Replace with the built-in `tuple` type for generic annotations."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
261
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
262
|
+
def visit_identifier(
|
|
263
|
+
self, identifier: Identifier, p: ExecutionContext
|
|
264
|
+
) -> Optional[Identifier]:
|
|
265
|
+
identifier = super().visit_identifier(identifier, p)
|
|
266
|
+
|
|
267
|
+
if identifier.simple_name == "Tuple":
|
|
268
|
+
if identifier.field_type:
|
|
269
|
+
fqn = str(getattr(identifier.field_type, '_fully_qualified_name', ''))
|
|
270
|
+
if 'typing' in fqn:
|
|
271
|
+
return _mark_deprecated(
|
|
272
|
+
identifier,
|
|
273
|
+
"typing.Tuple is deprecated",
|
|
274
|
+
"Replace with built-in tuple (PEP 585)"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return identifier
|
|
278
|
+
|
|
279
|
+
return Visitor()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@categorize(_Python311)
|
|
283
|
+
class ReplaceTypingText(Recipe):
|
|
284
|
+
"""
|
|
285
|
+
Find and migrate `typing.Text` to `str`.
|
|
286
|
+
|
|
287
|
+
`typing.Text` was an alias for `str` provided for Python 2/3 compatibility.
|
|
288
|
+
It is deprecated as of Python 3.11 since Python 2 is no longer supported.
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
Before:
|
|
292
|
+
from typing import Text
|
|
293
|
+
name: Text = "hello"
|
|
294
|
+
|
|
295
|
+
After:
|
|
296
|
+
name: str = "hello"
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def name(self) -> str:
|
|
301
|
+
return "org.openrewrite.python.migrate.ReplaceTypingText"
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def display_name(self) -> str:
|
|
305
|
+
return "Replace `typing.Text` with `str`"
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def description(self) -> str:
|
|
309
|
+
return (
|
|
310
|
+
"`typing.Text` is deprecated as of Python 3.11. "
|
|
311
|
+
"It was an alias for `str` for Python 2/3 compatibility. Replace with `str`."
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
315
|
+
from rewrite.python.tree import Import, MultiImport
|
|
316
|
+
|
|
317
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
318
|
+
def visit_identifier(
|
|
319
|
+
self, ident: Identifier, p: ExecutionContext
|
|
320
|
+
) -> Optional[Identifier]:
|
|
321
|
+
ident = super().visit_identifier(ident, p)
|
|
322
|
+
|
|
323
|
+
if ident.simple_name != "Text":
|
|
324
|
+
return ident
|
|
325
|
+
|
|
326
|
+
# Skip identifiers inside import statements
|
|
327
|
+
if (self.cursor.first_enclosing(MultiImport) is not None or
|
|
328
|
+
self.cursor.first_enclosing(Import) is not None):
|
|
329
|
+
return ident
|
|
330
|
+
|
|
331
|
+
# Type attribution correctly resolves typing.Text to str
|
|
332
|
+
# (since Text is just an alias), so we can't use field_type to
|
|
333
|
+
# confirm the import origin. Instead we rely on the name check
|
|
334
|
+
# and the import-context guard above.
|
|
335
|
+
return ident.replace(_simple_name="str")
|
|
336
|
+
|
|
337
|
+
return Visitor()
|