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.
Files changed (68) hide show
  1. openrewrite_migrate_python/__init__.py +35 -0
  2. openrewrite_migrate_python/migrate/__init__.py +219 -0
  3. openrewrite_migrate_python/migrate/aifc_migrations.py +517 -0
  4. openrewrite_migrate_python/migrate/array_deprecations.py +133 -0
  5. openrewrite_migrate_python/migrate/ast_deprecations.py +322 -0
  6. openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +225 -0
  7. openrewrite_migrate_python/migrate/asyncio_deprecations.py +117 -0
  8. openrewrite_migrate_python/migrate/calendar_deprecations.py +134 -0
  9. openrewrite_migrate_python/migrate/cgi_migrations.py +213 -0
  10. openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +170 -0
  11. openrewrite_migrate_python/migrate/collections_abc_migrations.py +207 -0
  12. openrewrite_migrate_python/migrate/configparser_deprecations.py +252 -0
  13. openrewrite_migrate_python/migrate/datetime_utc.py +136 -0
  14. openrewrite_migrate_python/migrate/distutils_deprecations.py +107 -0
  15. openrewrite_migrate_python/migrate/distutils_migrations.py +125 -0
  16. openrewrite_migrate_python/migrate/functools_deprecations.py +98 -0
  17. openrewrite_migrate_python/migrate/gettext_deprecations.py +139 -0
  18. openrewrite_migrate_python/migrate/html_parser_deprecations.py +110 -0
  19. openrewrite_migrate_python/migrate/imp_migrations.py +135 -0
  20. openrewrite_migrate_python/migrate/langchain_classic_imports.py +308 -0
  21. openrewrite_migrate_python/migrate/langchain_community_imports.py +154 -0
  22. openrewrite_migrate_python/migrate/langchain_provider_imports.py +240 -0
  23. openrewrite_migrate_python/migrate/locale_deprecations.py +177 -0
  24. openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +103 -0
  25. openrewrite_migrate_python/migrate/macpath_deprecations.py +125 -0
  26. openrewrite_migrate_python/migrate/mailcap_migrations.py +129 -0
  27. openrewrite_migrate_python/migrate/nntplib_migrations.py +127 -0
  28. openrewrite_migrate_python/migrate/os_deprecations.py +158 -0
  29. openrewrite_migrate_python/migrate/pathlib_deprecations.py +98 -0
  30. openrewrite_migrate_python/migrate/pep594_system_migrations.py +429 -0
  31. openrewrite_migrate_python/migrate/pipes_migrations.py +130 -0
  32. openrewrite_migrate_python/migrate/pkgutil_deprecations.py +180 -0
  33. openrewrite_migrate_python/migrate/platform_deprecations.py +99 -0
  34. openrewrite_migrate_python/migrate/re_deprecations.py +122 -0
  35. openrewrite_migrate_python/migrate/removed_modules_312.py +139 -0
  36. openrewrite_migrate_python/migrate/shutil_deprecations.py +112 -0
  37. openrewrite_migrate_python/migrate/socket_deprecations.py +96 -0
  38. openrewrite_migrate_python/migrate/ssl_deprecations.py +121 -0
  39. openrewrite_migrate_python/migrate/string_formatting.py +526 -0
  40. openrewrite_migrate_python/migrate/sys_deprecations.py +97 -0
  41. openrewrite_migrate_python/migrate/sys_last_deprecations.py +104 -0
  42. openrewrite_migrate_python/migrate/tarfile_deprecations.py +120 -0
  43. openrewrite_migrate_python/migrate/telnetlib_migrations.py +132 -0
  44. openrewrite_migrate_python/migrate/tempfile_deprecations.py +113 -0
  45. openrewrite_migrate_python/migrate/threading_deprecations.py +488 -0
  46. openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +82 -0
  47. openrewrite_migrate_python/migrate/typing_callable.py +153 -0
  48. openrewrite_migrate_python/migrate/typing_deprecations.py +337 -0
  49. openrewrite_migrate_python/migrate/typing_union_to_pipe.py +272 -0
  50. openrewrite_migrate_python/migrate/unittest_deprecations.py +140 -0
  51. openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +56 -0
  52. openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +66 -0
  53. openrewrite_migrate_python/migrate/upgrade_to_python310.py +91 -0
  54. openrewrite_migrate_python/migrate/upgrade_to_python311.py +92 -0
  55. openrewrite_migrate_python/migrate/upgrade_to_python312.py +101 -0
  56. openrewrite_migrate_python/migrate/upgrade_to_python313.py +109 -0
  57. openrewrite_migrate_python/migrate/upgrade_to_python314.py +87 -0
  58. openrewrite_migrate_python/migrate/upgrade_to_python38.py +85 -0
  59. openrewrite_migrate_python/migrate/upgrade_to_python39.py +125 -0
  60. openrewrite_migrate_python/migrate/urllib_deprecations.py +204 -0
  61. openrewrite_migrate_python/migrate/uu_migrations.py +129 -0
  62. openrewrite_migrate_python/migrate/xdrlib_migrations.py +129 -0
  63. openrewrite_migrate_python/migrate/xml_deprecations.py +154 -0
  64. openrewrite_migrate_python-0.1.0.dist-info/METADATA +31 -0
  65. openrewrite_migrate_python-0.1.0.dist-info/RECORD +68 -0
  66. openrewrite_migrate_python-0.1.0.dist-info/WHEEL +5 -0
  67. openrewrite_migrate_python-0.1.0.dist-info/entry_points.txt +2 -0
  68. 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()