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,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating from the deprecated pipes module.
|
|
3
|
+
|
|
4
|
+
The pipes module was deprecated in Python 3.11 (PEP 594) and removed
|
|
5
|
+
in Python 3.13.
|
|
6
|
+
|
|
7
|
+
Replacements:
|
|
8
|
+
- Use the `subprocess` module with `shlex.quote()` for shell escaping
|
|
9
|
+
- Use `shlex.join()` (Python 3.8+) to build command lines
|
|
10
|
+
|
|
11
|
+
See: https://peps.python.org/pep-0594/
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any, List, Optional
|
|
15
|
+
|
|
16
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
17
|
+
from rewrite.category import CategoryDescriptor
|
|
18
|
+
from rewrite.decorators import categorize
|
|
19
|
+
from rewrite.marketplace import Python
|
|
20
|
+
from rewrite.markers import Markers, SearchResult
|
|
21
|
+
from rewrite.utils import random_id
|
|
22
|
+
from rewrite.python.visitor import PythonVisitor
|
|
23
|
+
from rewrite.python.tree import MultiImport, Import
|
|
24
|
+
from rewrite.java.tree import Identifier, FieldAccess
|
|
25
|
+
|
|
26
|
+
# Define category path: Python > Migrate > Python 3.13
|
|
27
|
+
_Python313 = [
|
|
28
|
+
*Python,
|
|
29
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
30
|
+
CategoryDescriptor(display_name="Python 3.13"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _mark_deprecated(tree: Any, message: str) -> Any:
|
|
35
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
36
|
+
search_marker = SearchResult(random_id(), message)
|
|
37
|
+
current_markers = tree.markers
|
|
38
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
39
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
40
|
+
return tree.replace(_markers=new_markers)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_import_name(import_node: Any) -> Optional[str]:
|
|
44
|
+
"""Extract the module name from an import node's qualid."""
|
|
45
|
+
qualid = import_node.qualid
|
|
46
|
+
if isinstance(qualid, FieldAccess):
|
|
47
|
+
return qualid.name.simple_name
|
|
48
|
+
if isinstance(qualid, Identifier):
|
|
49
|
+
return qualid.simple_name
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@categorize(_Python313)
|
|
54
|
+
class FindPipesModule(Recipe):
|
|
55
|
+
"""
|
|
56
|
+
Find imports of the deprecated `pipes` module.
|
|
57
|
+
|
|
58
|
+
The `pipes` module was deprecated in Python 3.11 and removed in
|
|
59
|
+
Python 3.13 as part of PEP 594 ("Removing dead batteries").
|
|
60
|
+
|
|
61
|
+
Alternatives:
|
|
62
|
+
- Use `subprocess` module for process pipelines
|
|
63
|
+
- Use `shlex.quote()` for shell escaping
|
|
64
|
+
- Use `shlex.join()` (Python 3.8+) to build command lines
|
|
65
|
+
|
|
66
|
+
See: https://peps.python.org/pep-0594/
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def name(self) -> str:
|
|
71
|
+
return "org.openrewrite.python.migrate.FindPipesModule"
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def display_name(self) -> str:
|
|
75
|
+
return "Find deprecated `pipes` module usage"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def description(self) -> str:
|
|
79
|
+
return (
|
|
80
|
+
"The `pipes` module was deprecated in Python 3.11 and removed in "
|
|
81
|
+
"Python 3.13. Use subprocess with shlex.quote() for shell escaping."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def tags(self) -> List[str]:
|
|
86
|
+
return ["python", "migration", "3.13", "PEP 594"]
|
|
87
|
+
|
|
88
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
89
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
90
|
+
def visit_multi_import(
|
|
91
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
92
|
+
) -> Optional[MultiImport]:
|
|
93
|
+
multi = super().visit_multi_import(multi, p)
|
|
94
|
+
|
|
95
|
+
from_part = multi.from_
|
|
96
|
+
if from_part is not None:
|
|
97
|
+
if isinstance(from_part, Identifier) and from_part.simple_name == "pipes":
|
|
98
|
+
return _mark_deprecated(
|
|
99
|
+
multi,
|
|
100
|
+
"The `pipes` module was removed in Python 3.13. "
|
|
101
|
+
"Use subprocess with shlex.quote() instead."
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
for import_node in multi.names:
|
|
105
|
+
name = _get_import_name(import_node)
|
|
106
|
+
if name == "pipes":
|
|
107
|
+
return _mark_deprecated(
|
|
108
|
+
multi,
|
|
109
|
+
"The `pipes` module was removed in Python 3.13. "
|
|
110
|
+
"Use subprocess with shlex.quote() instead."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return multi
|
|
114
|
+
|
|
115
|
+
def visit_import(
|
|
116
|
+
self, import_stmt: Import, p: ExecutionContext
|
|
117
|
+
) -> Optional[Import]:
|
|
118
|
+
import_stmt = super().visit_import(import_stmt, p)
|
|
119
|
+
if self.cursor.first_enclosing(MultiImport) is not None:
|
|
120
|
+
return import_stmt
|
|
121
|
+
name = _get_import_name(import_stmt)
|
|
122
|
+
if name == "pipes":
|
|
123
|
+
return _mark_deprecated(
|
|
124
|
+
import_stmt,
|
|
125
|
+
"The `pipes` module was removed in Python 3.13. "
|
|
126
|
+
"Use subprocess with shlex.quote() instead."
|
|
127
|
+
)
|
|
128
|
+
return import_stmt
|
|
129
|
+
|
|
130
|
+
return Visitor()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating deprecated pkgutil module functions.
|
|
3
|
+
|
|
4
|
+
Several pkgutil functions were deprecated in Python 3.12:
|
|
5
|
+
|
|
6
|
+
- pkgutil.find_loader() -> importlib.util.find_spec()
|
|
7
|
+
- pkgutil.get_loader() -> importlib.util.find_spec()
|
|
8
|
+
|
|
9
|
+
See: https://docs.python.org/3/library/pkgutil.html
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Optional
|
|
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.python.visitor import PythonVisitor
|
|
20
|
+
from rewrite.java.tree import Identifier, MethodInvocation
|
|
21
|
+
from rewrite.utils import random_id
|
|
22
|
+
|
|
23
|
+
# Define category path: Python > Migrate > Python 3.12
|
|
24
|
+
_Python312 = [
|
|
25
|
+
*Python,
|
|
26
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
27
|
+
CategoryDescriptor(display_name="Python 3.12"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _mark_deprecated(tree: Any, message: str, detail: Optional[str] = None) -> Any:
|
|
32
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
33
|
+
full_message = f"{message}: {detail}" if detail else message
|
|
34
|
+
search_marker = SearchResult(random_id(), full_message)
|
|
35
|
+
current_markers = tree.markers
|
|
36
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
37
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
38
|
+
return tree.replace(_markers=new_markers)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _is_pkgutil_method(method: MethodInvocation, method_name: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if this is a pkgutil.method_name() call.
|
|
44
|
+
|
|
45
|
+
Uses multiple strategies to identify the method:
|
|
46
|
+
1. Check method name matches
|
|
47
|
+
2. Check type attribution if available (FQN contains 'pkgutil')
|
|
48
|
+
3. Check simple name on select is 'pkgutil'
|
|
49
|
+
"""
|
|
50
|
+
# Check method name first
|
|
51
|
+
if not isinstance(method.name, Identifier):
|
|
52
|
+
return False
|
|
53
|
+
if method.name.simple_name != method_name:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
# Check if type info confirms this is a pkgutil method
|
|
57
|
+
if method.method_type and method.method_type.declaring_type:
|
|
58
|
+
dt = method.method_type.declaring_type
|
|
59
|
+
if hasattr(dt, '_fully_qualified_name'):
|
|
60
|
+
fqn = str(dt._fully_qualified_name)
|
|
61
|
+
if 'pkgutil' in fqn:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
# Fallback: Check simple name on select
|
|
65
|
+
select = method.select
|
|
66
|
+
if select is None:
|
|
67
|
+
return False
|
|
68
|
+
if isinstance(select, Identifier) and select.simple_name == "pkgutil":
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@categorize(_Python312)
|
|
75
|
+
class ReplacePkgutilFindLoader(Recipe):
|
|
76
|
+
"""
|
|
77
|
+
Find and migrate `pkgutil.find_loader()` calls to `importlib.util.find_spec()`.
|
|
78
|
+
|
|
79
|
+
The `pkgutil.find_loader()` function was deprecated in Python 3.12.
|
|
80
|
+
The recommended replacement is `importlib.util.find_spec()`.
|
|
81
|
+
|
|
82
|
+
Note: The return types differ - `find_loader()` returns a loader while
|
|
83
|
+
`find_spec()` returns a ModuleSpec. Code may need additional changes.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
Before:
|
|
87
|
+
import pkgutil
|
|
88
|
+
loader = pkgutil.find_loader('mymodule')
|
|
89
|
+
|
|
90
|
+
After:
|
|
91
|
+
import importlib.util
|
|
92
|
+
spec = importlib.util.find_spec('mymodule')
|
|
93
|
+
loader = spec.loader if spec else None
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def name(self) -> str:
|
|
98
|
+
return "org.openrewrite.python.migrate.ReplacePkgutilFindLoader"
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def display_name(self) -> str:
|
|
102
|
+
return "Replace `pkgutil.find_loader()` with `importlib.util.find_spec()`"
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def description(self) -> str:
|
|
106
|
+
return (
|
|
107
|
+
"The `pkgutil.find_loader()` function was deprecated in Python 3.12. "
|
|
108
|
+
"Replace with `importlib.util.find_spec()`. Note: returns ModuleSpec, use .loader for loader."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
112
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
113
|
+
def visit_method_invocation(
|
|
114
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
115
|
+
) -> Optional[MethodInvocation]:
|
|
116
|
+
method = super().visit_method_invocation(method, p)
|
|
117
|
+
|
|
118
|
+
if _is_pkgutil_method(method, "find_loader"):
|
|
119
|
+
return _mark_deprecated(
|
|
120
|
+
method,
|
|
121
|
+
"pkgutil.find_loader() is deprecated",
|
|
122
|
+
"Replace with importlib.util.find_spec() (Python 3.12+)"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return method
|
|
126
|
+
|
|
127
|
+
return Visitor()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@categorize(_Python312)
|
|
131
|
+
class ReplacePkgutilGetLoader(Recipe):
|
|
132
|
+
"""
|
|
133
|
+
Find and migrate `pkgutil.get_loader()` calls to `importlib.util.find_spec()`.
|
|
134
|
+
|
|
135
|
+
The `pkgutil.get_loader()` function was deprecated in Python 3.12.
|
|
136
|
+
The recommended replacement is `importlib.util.find_spec()`.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
Before:
|
|
140
|
+
import pkgutil
|
|
141
|
+
loader = pkgutil.get_loader('mymodule')
|
|
142
|
+
|
|
143
|
+
After:
|
|
144
|
+
import importlib.util
|
|
145
|
+
spec = importlib.util.find_spec('mymodule')
|
|
146
|
+
loader = spec.loader if spec else None
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def name(self) -> str:
|
|
151
|
+
return "org.openrewrite.python.migrate.ReplacePkgutilGetLoader"
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def display_name(self) -> str:
|
|
155
|
+
return "Replace `pkgutil.get_loader()` with `importlib.util.find_spec()`"
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def description(self) -> str:
|
|
159
|
+
return (
|
|
160
|
+
"The `pkgutil.get_loader()` function was deprecated in Python 3.12. "
|
|
161
|
+
"Replace with `importlib.util.find_spec()`. Note: returns ModuleSpec, use .loader for loader."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
165
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
166
|
+
def visit_method_invocation(
|
|
167
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
168
|
+
) -> Optional[MethodInvocation]:
|
|
169
|
+
method = super().visit_method_invocation(method, p)
|
|
170
|
+
|
|
171
|
+
if _is_pkgutil_method(method, "get_loader"):
|
|
172
|
+
return _mark_deprecated(
|
|
173
|
+
method,
|
|
174
|
+
"pkgutil.get_loader() is deprecated",
|
|
175
|
+
"Replace with importlib.util.find_spec() (Python 3.12+)"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return method
|
|
179
|
+
|
|
180
|
+
return Visitor()
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating deprecated platform module functions.
|
|
3
|
+
|
|
4
|
+
The following function was deprecated in Python 3.3 and removed in Python 3.8:
|
|
5
|
+
- platform.popen() -> os.popen() or subprocess
|
|
6
|
+
|
|
7
|
+
See: https://docs.python.org/3/whatsnew/3.8.html#api-and-feature-removals
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, List, Optional
|
|
11
|
+
|
|
12
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
13
|
+
from rewrite.category import CategoryDescriptor
|
|
14
|
+
from rewrite.decorators import categorize
|
|
15
|
+
from rewrite.marketplace import Python
|
|
16
|
+
from rewrite.markers import Markers, SearchResult
|
|
17
|
+
from rewrite.utils import random_id
|
|
18
|
+
from rewrite.python.visitor import PythonVisitor
|
|
19
|
+
from rewrite.java.tree import Identifier, MethodInvocation
|
|
20
|
+
|
|
21
|
+
# Define category path: Python > Migrate > Python 3.8
|
|
22
|
+
_Python38 = [
|
|
23
|
+
*Python,
|
|
24
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
25
|
+
CategoryDescriptor(display_name="Python 3.8"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _mark_deprecated(tree: Any, message: str) -> Any:
|
|
30
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
31
|
+
search_marker = SearchResult(random_id(), message)
|
|
32
|
+
current_markers = tree.markers
|
|
33
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
34
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
35
|
+
return tree.replace(_markers=new_markers)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@categorize(_Python38)
|
|
39
|
+
class FindPlatformPopen(Recipe):
|
|
40
|
+
"""
|
|
41
|
+
Find usages of removed `platform.popen()`.
|
|
42
|
+
|
|
43
|
+
`platform.popen()` was deprecated in Python 3.3 and removed in Python 3.8.
|
|
44
|
+
Use `os.popen()` or `subprocess.run()` instead.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
Before:
|
|
48
|
+
import platform
|
|
49
|
+
output = platform.popen('ls').read()
|
|
50
|
+
|
|
51
|
+
After:
|
|
52
|
+
import subprocess
|
|
53
|
+
output = subprocess.run(['ls'], capture_output=True, text=True).stdout
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def name(self) -> str:
|
|
58
|
+
return "org.openrewrite.python.migrate.FindPlatformPopen"
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def display_name(self) -> str:
|
|
62
|
+
return "Find removed `platform.popen()` usage"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def description(self) -> str:
|
|
66
|
+
return (
|
|
67
|
+
"`platform.popen()` was removed in Python 3.8. "
|
|
68
|
+
"Use `os.popen()` or `subprocess.run()` instead."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def tags(self) -> List[str]:
|
|
73
|
+
return ["python", "migration", "3.8", "platform"]
|
|
74
|
+
|
|
75
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
76
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
77
|
+
def visit_method_invocation(
|
|
78
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
79
|
+
) -> Optional[MethodInvocation]:
|
|
80
|
+
method = super().visit_method_invocation(method, p)
|
|
81
|
+
|
|
82
|
+
if not isinstance(method.name, Identifier):
|
|
83
|
+
return method
|
|
84
|
+
if method.name.simple_name != "popen":
|
|
85
|
+
return method
|
|
86
|
+
|
|
87
|
+
select = method.select
|
|
88
|
+
if not isinstance(select, Identifier):
|
|
89
|
+
return method
|
|
90
|
+
if select.simple_name != "platform":
|
|
91
|
+
return method
|
|
92
|
+
|
|
93
|
+
return _mark_deprecated(
|
|
94
|
+
method,
|
|
95
|
+
"platform.popen() was removed in Python 3.8. "
|
|
96
|
+
"Use os.popen() or subprocess.run() instead."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return Visitor()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe for finding deprecated re.template() and re.TEMPLATE usage.
|
|
3
|
+
|
|
4
|
+
re.template() and re.TEMPLATE were deprecated in Python 3.11 and
|
|
5
|
+
removed in Python 3.13.
|
|
6
|
+
|
|
7
|
+
See: https://docs.python.org/3/whatsnew/3.11.html#deprecated
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, List, Optional
|
|
11
|
+
|
|
12
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
13
|
+
from rewrite.category import CategoryDescriptor
|
|
14
|
+
from rewrite.decorators import categorize
|
|
15
|
+
from rewrite.marketplace import Python
|
|
16
|
+
from rewrite.markers import Markers, SearchResult
|
|
17
|
+
from rewrite.utils import random_id
|
|
18
|
+
from rewrite.python.visitor import PythonVisitor
|
|
19
|
+
from rewrite.java.tree import FieldAccess, Identifier, MethodInvocation
|
|
20
|
+
|
|
21
|
+
# Define category path: Python > Migrate > Python 3.11
|
|
22
|
+
_Python311 = [
|
|
23
|
+
*Python,
|
|
24
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
25
|
+
CategoryDescriptor(display_name="Python 3.11"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _mark_deprecated(tree: Any, message: str) -> Any:
|
|
30
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
31
|
+
search_marker = SearchResult(random_id(), message)
|
|
32
|
+
current_markers = tree.markers
|
|
33
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
34
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
35
|
+
return tree.replace(_markers=new_markers)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@categorize(_Python311)
|
|
39
|
+
class FindReTemplate(Recipe):
|
|
40
|
+
"""
|
|
41
|
+
Find usages of deprecated `re.template()` and `re.TEMPLATE`.
|
|
42
|
+
|
|
43
|
+
`re.template()` and `re.TEMPLATE` (aka `re.T`) were deprecated in
|
|
44
|
+
Python 3.11 and removed in Python 3.13. The template flag made
|
|
45
|
+
patterns match differently but was rarely useful.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
Before:
|
|
49
|
+
import re
|
|
50
|
+
pattern = re.template(r'\\d+')
|
|
51
|
+
flags = re.TEMPLATE
|
|
52
|
+
|
|
53
|
+
After:
|
|
54
|
+
import re
|
|
55
|
+
pattern = re.compile(r'\\d+')
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def name(self) -> str:
|
|
60
|
+
return "org.openrewrite.python.migrate.FindReTemplate"
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def display_name(self) -> str:
|
|
64
|
+
return "Find deprecated `re.template()` / `re.TEMPLATE` usage"
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def description(self) -> str:
|
|
68
|
+
return (
|
|
69
|
+
"`re.template()` and `re.TEMPLATE` were deprecated in Python 3.11 "
|
|
70
|
+
"and removed in 3.13."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def tags(self) -> List[str]:
|
|
75
|
+
return ["python", "migration", "3.11", "re"]
|
|
76
|
+
|
|
77
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
78
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
79
|
+
def visit_method_invocation(
|
|
80
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
81
|
+
) -> Optional[MethodInvocation]:
|
|
82
|
+
method = super().visit_method_invocation(method, p)
|
|
83
|
+
|
|
84
|
+
if not isinstance(method.name, Identifier):
|
|
85
|
+
return method
|
|
86
|
+
if method.name.simple_name != "template":
|
|
87
|
+
return method
|
|
88
|
+
|
|
89
|
+
select = method.select
|
|
90
|
+
if not isinstance(select, Identifier):
|
|
91
|
+
return method
|
|
92
|
+
if select.simple_name != "re":
|
|
93
|
+
return method
|
|
94
|
+
|
|
95
|
+
return _mark_deprecated(
|
|
96
|
+
method,
|
|
97
|
+
"re.template() was deprecated in Python 3.11 and removed in 3.13."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def visit_field_access(
|
|
101
|
+
self, field_access: FieldAccess, p: ExecutionContext
|
|
102
|
+
) -> Optional[FieldAccess]:
|
|
103
|
+
field_access = super().visit_field_access(field_access, p)
|
|
104
|
+
|
|
105
|
+
if not isinstance(field_access.name, Identifier):
|
|
106
|
+
return field_access
|
|
107
|
+
if field_access.name.simple_name not in ("TEMPLATE", "T"):
|
|
108
|
+
return field_access
|
|
109
|
+
|
|
110
|
+
target = field_access.target
|
|
111
|
+
if not isinstance(target, Identifier):
|
|
112
|
+
return field_access
|
|
113
|
+
if target.simple_name != "re":
|
|
114
|
+
return field_access
|
|
115
|
+
|
|
116
|
+
return _mark_deprecated(
|
|
117
|
+
field_access,
|
|
118
|
+
f"re.{field_access.name.simple_name} was deprecated in Python 3.11 "
|
|
119
|
+
"and removed in 3.13."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return Visitor()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe for finding imports of modules removed in Python 3.12.
|
|
3
|
+
|
|
4
|
+
The following modules were removed in Python 3.12:
|
|
5
|
+
- asynchat (use asyncio)
|
|
6
|
+
- asyncore (use asyncio)
|
|
7
|
+
- smtpd (use aiosmtpd)
|
|
8
|
+
|
|
9
|
+
Note: The `imp` and `distutils` modules were also removed in 3.12
|
|
10
|
+
but are handled by dedicated recipes (imp_migrations.py and
|
|
11
|
+
distutils_deprecations.py).
|
|
12
|
+
|
|
13
|
+
See: https://docs.python.org/3/whatsnew/3.12.html#removed
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Optional
|
|
17
|
+
|
|
18
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
19
|
+
from rewrite.category import CategoryDescriptor
|
|
20
|
+
from rewrite.decorators import categorize
|
|
21
|
+
from rewrite.marketplace import Python
|
|
22
|
+
from rewrite.markers import Markers, SearchResult
|
|
23
|
+
from rewrite.python.visitor import PythonVisitor
|
|
24
|
+
from rewrite.python.tree import MultiImport, Import
|
|
25
|
+
from rewrite.java.tree import FieldAccess, Identifier
|
|
26
|
+
from rewrite.utils import random_id
|
|
27
|
+
|
|
28
|
+
# Define category path: Python > Migrate > Python 3.12
|
|
29
|
+
_Python312 = [
|
|
30
|
+
*Python,
|
|
31
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
32
|
+
CategoryDescriptor(display_name="Python 3.12"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Modules removed in Python 3.12 (excluding imp and distutils which have dedicated recipes)
|
|
36
|
+
REMOVED_MODULES_312 = {
|
|
37
|
+
"asynchat": "Use `asyncio` instead.",
|
|
38
|
+
"asyncore": "Use `asyncio` instead.",
|
|
39
|
+
"smtpd": "Use `aiosmtpd` instead.",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _mark_removed(tree: Any, module_name: str) -> Any:
|
|
44
|
+
"""Add a SearchResult marker for a removed module."""
|
|
45
|
+
replacement = REMOVED_MODULES_312.get(module_name, "")
|
|
46
|
+
message = f"Module '{module_name}' was removed in Python 3.12. {replacement}"
|
|
47
|
+
search_marker = SearchResult(random_id(), 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
|
+
def _get_import_name(import_node: Any) -> Optional[str]:
|
|
55
|
+
"""Extract the module name from an import node's qualid.
|
|
56
|
+
|
|
57
|
+
For a simple import like 'import asynchat', the qualid is a FieldAccess
|
|
58
|
+
where the name property is the identifier 'asynchat'.
|
|
59
|
+
"""
|
|
60
|
+
qualid = import_node.qualid
|
|
61
|
+
if isinstance(qualid, FieldAccess):
|
|
62
|
+
return qualid.name.simple_name
|
|
63
|
+
if isinstance(qualid, Identifier):
|
|
64
|
+
return qualid.simple_name
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@categorize(_Python312)
|
|
69
|
+
class FindRemovedModules312(Recipe):
|
|
70
|
+
"""
|
|
71
|
+
Find imports of modules that were removed in Python 3.12.
|
|
72
|
+
|
|
73
|
+
The following modules were removed in Python 3.12:
|
|
74
|
+
asynchat, asyncore, smtpd.
|
|
75
|
+
|
|
76
|
+
These modules were deprecated in earlier Python versions and have
|
|
77
|
+
been fully removed in Python 3.12. Code using these modules will
|
|
78
|
+
need to be updated to use alternative libraries.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
Before:
|
|
82
|
+
import asynchat
|
|
83
|
+
from asyncore import dispatcher
|
|
84
|
+
|
|
85
|
+
After migration (manual):
|
|
86
|
+
# asynchat/asyncore - use asyncio instead
|
|
87
|
+
# smtpd - use aiosmtpd instead
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def name(self) -> str:
|
|
92
|
+
return "org.openrewrite.python.migrate.FindRemovedModules312"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def display_name(self) -> str:
|
|
96
|
+
return "Find modules removed in Python 3.12"
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def description(self) -> str:
|
|
100
|
+
return (
|
|
101
|
+
"Find imports of modules that were removed in Python 3.12, "
|
|
102
|
+
"including asynchat, asyncore, and smtpd."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
106
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
107
|
+
def visit_multi_import(
|
|
108
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
109
|
+
) -> Optional[MultiImport]:
|
|
110
|
+
multi = super().visit_multi_import(multi, p)
|
|
111
|
+
|
|
112
|
+
from_part = multi.from_
|
|
113
|
+
if from_part is not None:
|
|
114
|
+
# Handle 'from X import ...' pattern
|
|
115
|
+
if isinstance(from_part, Identifier):
|
|
116
|
+
module_name = from_part.simple_name
|
|
117
|
+
if module_name in REMOVED_MODULES_312:
|
|
118
|
+
return _mark_removed(multi, module_name)
|
|
119
|
+
else:
|
|
120
|
+
# Handle 'import X' pattern (no from clause)
|
|
121
|
+
for import_node in multi.names:
|
|
122
|
+
name = _get_import_name(import_node)
|
|
123
|
+
if name in REMOVED_MODULES_312:
|
|
124
|
+
return _mark_removed(multi, name)
|
|
125
|
+
|
|
126
|
+
return multi
|
|
127
|
+
|
|
128
|
+
def visit_import(
|
|
129
|
+
self, import_stmt: Import, p: ExecutionContext
|
|
130
|
+
) -> Optional[Import]:
|
|
131
|
+
import_stmt = super().visit_import(import_stmt, p)
|
|
132
|
+
if self.cursor.first_enclosing(MultiImport) is not None:
|
|
133
|
+
return import_stmt
|
|
134
|
+
name = _get_import_name(import_stmt)
|
|
135
|
+
if name in REMOVED_MODULES_312:
|
|
136
|
+
return _mark_removed(import_stmt, name)
|
|
137
|
+
return import_stmt
|
|
138
|
+
|
|
139
|
+
return Visitor()
|