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,308 @@
1
+ """
2
+ Recipes for migrating langchain imports to langchain_classic (v1.0).
3
+
4
+ In LangChain v1.0, legacy chains, retrievers, and indexing functionality
5
+ were moved from `langchain` to `langchain_classic`. This recipe handles:
6
+
7
+ from langchain.chains import LLMChain -> from langchain_classic.chains import LLMChain
8
+ from langchain.retrievers import ... -> from langchain_classic.retrievers import ...
9
+ from langchain.indexes import ... -> from langchain_classic.indexes import ...
10
+ from langchain import hub -> from langchain_classic import hub
11
+
12
+ See: https://docs.langchain.com/oss/python/migrate/langchain-v1
13
+ """
14
+
15
+ from typing import Any, List, Optional, Set
16
+
17
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
18
+ from rewrite.category import CategoryDescriptor
19
+ from rewrite.decorators import categorize
20
+ from rewrite.marketplace import Python
21
+ from rewrite.markers import Markers, SearchResult
22
+ from rewrite.python.visitor import PythonVisitor
23
+ from rewrite.python.tree import MultiImport
24
+ from rewrite.java.tree import FieldAccess, Identifier
25
+ from rewrite.utils import random_id
26
+
27
+ _LangChain1 = [
28
+ *Python,
29
+ CategoryDescriptor(display_name="Migrate"),
30
+ CategoryDescriptor(display_name="LangChain 1.0"),
31
+ ]
32
+
33
+ # Submodules that moved from langchain to langchain_classic in v1.0
34
+ CLASSIC_SUBMODULES: Set[str] = {
35
+ "chains",
36
+ "indexes",
37
+ "retrievers",
38
+ }
39
+
40
+ # Names that can be imported directly from langchain but moved to langchain_classic
41
+ CLASSIC_DIRECT_IMPORTS: Set[str] = {
42
+ "hub",
43
+ }
44
+
45
+
46
+ def _get_root_and_first_submodule(from_part: Any) -> tuple:
47
+ """Extract root module name and first submodule from a FieldAccess chain."""
48
+ if not isinstance(from_part, FieldAccess):
49
+ return None, None
50
+
51
+ current = from_part
52
+ while isinstance(current.target, FieldAccess):
53
+ current = current.target
54
+
55
+ if isinstance(current.target, Identifier):
56
+ return current.target.simple_name, current.name.simple_name
57
+ return None, None
58
+
59
+
60
+ def _replace_root_module(from_part: Any, new_root_name: str) -> Any:
61
+ """Replace the root module name in a FieldAccess chain."""
62
+ if isinstance(from_part, Identifier):
63
+ return from_part.replace(_simple_name=new_root_name)
64
+ if isinstance(from_part, FieldAccess):
65
+ new_target = _replace_root_module(from_part.target, new_root_name)
66
+ return from_part.replace(_target=new_target)
67
+ return from_part
68
+
69
+
70
+ def _get_import_names(multi: MultiImport) -> List[str]:
71
+ """Get the list of names being imported from a MultiImport."""
72
+ names = []
73
+ for import_node in multi.names:
74
+ qualid = import_node.qualid
75
+ if isinstance(qualid, FieldAccess):
76
+ if isinstance(qualid.name, Identifier):
77
+ names.append(qualid.name.simple_name)
78
+ elif isinstance(qualid, Identifier):
79
+ names.append(qualid.simple_name)
80
+ return names
81
+
82
+
83
+ def _mark_deprecated(tree: Any, message: str) -> Any:
84
+ """Add a SearchResult marker for a deprecation warning."""
85
+ search_marker = SearchResult(random_id(), message)
86
+ current_markers = tree.markers
87
+ new_markers_list = list(current_markers.markers) + [search_marker]
88
+ new_markers = Markers(current_markers.id, new_markers_list)
89
+ return tree.replace(_markers=new_markers)
90
+
91
+
92
+ @categorize(_LangChain1)
93
+ class ReplaceLangchainClassicImports(Recipe):
94
+ """
95
+ Replace `langchain` legacy imports with `langchain_classic`.
96
+
97
+ In LangChain v1.0, legacy chains, retrievers, and indexing functionality
98
+ were moved to `langchain_classic`.
99
+
100
+ Example:
101
+ Before:
102
+ from langchain.chains import LLMChain
103
+
104
+ After:
105
+ from langchain_classic.chains import LLMChain
106
+ """
107
+
108
+ @property
109
+ def name(self) -> str:
110
+ return "org.openrewrite.python.migrate.langchain.ReplaceLangchainClassicImports"
111
+
112
+ @property
113
+ def display_name(self) -> str:
114
+ return "Replace `langchain` legacy imports with `langchain_classic`"
115
+
116
+ @property
117
+ def description(self) -> str:
118
+ return (
119
+ "Migrate legacy chain, retriever, and indexing imports from "
120
+ "`langchain` to `langchain_classic`. These were moved in "
121
+ "LangChain v1.0."
122
+ )
123
+
124
+ @property
125
+ def tags(self) -> List[str]:
126
+ return ["python", "migration", "langchain", "1.0"]
127
+
128
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
129
+ class Visitor(PythonVisitor[ExecutionContext]):
130
+ def visit_multi_import(
131
+ self, multi: MultiImport, p: ExecutionContext
132
+ ) -> Optional[MultiImport]:
133
+ multi = super().visit_multi_import(multi, p)
134
+
135
+ from_part = multi.from_
136
+ if from_part is None:
137
+ return multi
138
+
139
+ # Handle 'from langchain.chains import ...' pattern
140
+ if isinstance(from_part, FieldAccess):
141
+ root_name, first_sub = _get_root_and_first_submodule(from_part)
142
+ if root_name != "langchain":
143
+ return multi
144
+ if first_sub not in CLASSIC_SUBMODULES:
145
+ return multi
146
+
147
+ new_from = _replace_root_module(from_part, "langchain_classic")
148
+ old_padded_from = multi.padding.from_
149
+ if old_padded_from is None:
150
+ return multi
151
+ new_padded_from = old_padded_from.replace(_element=new_from)
152
+ return multi.padding.replace(_from=new_padded_from)
153
+
154
+ # Handle 'from langchain import hub' pattern
155
+ if isinstance(from_part, Identifier) and from_part.simple_name == "langchain":
156
+ imported_names = _get_import_names(multi)
157
+ if all(name in CLASSIC_DIRECT_IMPORTS for name in imported_names):
158
+ new_from = from_part.replace(_simple_name="langchain_classic")
159
+ old_padded_from = multi.padding.from_
160
+ if old_padded_from is None:
161
+ return multi
162
+ new_padded_from = old_padded_from.replace(_element=new_from)
163
+ return multi.padding.replace(_from=new_padded_from)
164
+
165
+ return multi
166
+
167
+ return Visitor()
168
+
169
+
170
+ @categorize(_LangChain1)
171
+ class FindDeprecatedLangchainAgents(Recipe):
172
+ """
173
+ Find deprecated LangChain agent patterns.
174
+
175
+ In LangChain v1.0, the legacy agent helpers `initialize_agent` and
176
+ `AgentExecutor` are deprecated. Use `create_agent` instead.
177
+
178
+ Also detects usage of deprecated `LLMChain` which should be replaced
179
+ with direct LLM invocation or LangGraph agents.
180
+ """
181
+
182
+ @property
183
+ def name(self) -> str:
184
+ return "org.openrewrite.python.migrate.langchain.FindDeprecatedLangchainAgents"
185
+
186
+ @property
187
+ def display_name(self) -> str:
188
+ return "Find deprecated LangChain agent patterns"
189
+
190
+ @property
191
+ def description(self) -> str:
192
+ return (
193
+ "Find usage of deprecated LangChain agent patterns including "
194
+ "`initialize_agent`, `AgentExecutor`, and `LLMChain`. These "
195
+ "were deprecated in LangChain v0.2 and removed in v1.0."
196
+ )
197
+
198
+ @property
199
+ def tags(self) -> List[str]:
200
+ return ["python", "migration", "langchain", "1.0"]
201
+
202
+ DEPRECATED_IMPORTS: Set[str] = {
203
+ "initialize_agent",
204
+ "AgentExecutor",
205
+ "LLMChain",
206
+ "ConversationChain",
207
+ "LLMRequestsChain",
208
+ "TransformChain",
209
+ }
210
+
211
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
212
+ deprecated_imports = self.DEPRECATED_IMPORTS
213
+
214
+ class Visitor(PythonVisitor[ExecutionContext]):
215
+ def visit_multi_import(
216
+ self, multi: MultiImport, p: ExecutionContext
217
+ ) -> Optional[MultiImport]:
218
+ multi = super().visit_multi_import(multi, p)
219
+
220
+ from_part = multi.from_
221
+ if from_part is None:
222
+ return multi
223
+
224
+ imported_names = _get_import_names(multi)
225
+ found_deprecated = [
226
+ name for name in imported_names if name in deprecated_imports
227
+ ]
228
+
229
+ if found_deprecated:
230
+ names_str = ", ".join(found_deprecated)
231
+ return _mark_deprecated(
232
+ multi,
233
+ f"Deprecated LangChain API: {names_str} "
234
+ f"(use create_agent / LangGraph in v1.0)",
235
+ )
236
+
237
+ return multi
238
+
239
+ return Visitor()
240
+
241
+
242
+ @categorize(_LangChain1)
243
+ class FindLangchainCreateReactAgent(Recipe):
244
+ """
245
+ Find `create_react_agent` imports that should be migrated.
246
+
247
+ In LangChain v1.0, the agent creation function was renamed:
248
+
249
+ from langgraph.prebuilt import create_react_agent
250
+
251
+ should become:
252
+
253
+ from langchain.agents import create_agent
254
+ """
255
+
256
+ @property
257
+ def name(self) -> str:
258
+ return "org.openrewrite.python.migrate.langchain.FindLangchainCreateReactAgent"
259
+
260
+ @property
261
+ def display_name(self) -> str:
262
+ return "Find `create_react_agent` usage (replace with `create_agent`)"
263
+
264
+ @property
265
+ def description(self) -> str:
266
+ return (
267
+ "Find `from langgraph.prebuilt import create_react_agent` which "
268
+ "should be replaced with `from langchain.agents import create_agent` "
269
+ "in LangChain v1.0."
270
+ )
271
+
272
+ @property
273
+ def tags(self) -> List[str]:
274
+ return ["python", "migration", "langchain", "1.0"]
275
+
276
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
277
+ class Visitor(PythonVisitor[ExecutionContext]):
278
+ def visit_multi_import(
279
+ self, multi: MultiImport, p: ExecutionContext
280
+ ) -> Optional[MultiImport]:
281
+ multi = super().visit_multi_import(multi, p)
282
+
283
+ from_part = multi.from_
284
+ if from_part is None:
285
+ return multi
286
+
287
+ # Check for 'from langgraph.prebuilt import create_react_agent'
288
+ if not isinstance(from_part, FieldAccess):
289
+ return multi
290
+ if not isinstance(from_part.target, Identifier):
291
+ return multi
292
+ if from_part.target.simple_name != "langgraph":
293
+ return multi
294
+ if not isinstance(from_part.name, Identifier):
295
+ return multi
296
+ if from_part.name.simple_name != "prebuilt":
297
+ return multi
298
+
299
+ imported_names = _get_import_names(multi)
300
+ if "create_react_agent" not in imported_names:
301
+ return multi
302
+
303
+ return _mark_deprecated(
304
+ multi,
305
+ "Migrate to `from langchain.agents import create_agent` (LangChain v1.0)",
306
+ )
307
+
308
+ return Visitor()
@@ -0,0 +1,154 @@
1
+ """
2
+ Recipes for migrating langchain imports to langchain_community.
3
+
4
+ In LangChain v0.2, third-party integrations were moved from the `langchain`
5
+ package to `langchain_community`. This recipe rewrites imports like:
6
+
7
+ from langchain.chat_models import ChatOpenAI
8
+ from langchain.llms import OpenAI
9
+ from langchain.embeddings import OpenAIEmbeddings
10
+
11
+ to:
12
+
13
+ from langchain_community.chat_models import ChatOpenAI
14
+ from langchain_community.llms import OpenAI
15
+ from langchain_community.embeddings import OpenAIEmbeddings
16
+
17
+ See: https://python.langchain.com/v0.2/docs/versions/v0_2/
18
+ """
19
+
20
+ from typing import Any, List, Optional, Set
21
+
22
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
23
+ from rewrite.category import CategoryDescriptor
24
+ from rewrite.decorators import categorize
25
+ from rewrite.marketplace import Python
26
+ from rewrite.python.visitor import PythonVisitor
27
+ from rewrite.python.tree import MultiImport
28
+ from rewrite.java.tree import FieldAccess, Identifier
29
+
30
+ _LangChain02 = [
31
+ *Python,
32
+ CategoryDescriptor(display_name="Migrate"),
33
+ CategoryDescriptor(display_name="LangChain 0.2"),
34
+ ]
35
+
36
+ # Submodules that moved from langchain to langchain_community in v0.2
37
+ COMMUNITY_SUBMODULES: Set[str] = {
38
+ "adapters",
39
+ "agent_toolkits",
40
+ "cache",
41
+ "callbacks",
42
+ "chat_loaders",
43
+ "chat_message_histories",
44
+ "chat_models",
45
+ "cross_encoders",
46
+ "docstore",
47
+ "document_compressors",
48
+ "document_loaders",
49
+ "document_transformers",
50
+ "embeddings",
51
+ "example_selectors",
52
+ "graph_vectorstores",
53
+ "graphs",
54
+ "llms",
55
+ "retrievers",
56
+ "storage",
57
+ "tools",
58
+ "utilities",
59
+ "vectorstores",
60
+ }
61
+
62
+
63
+ def _get_root_and_first_submodule(from_part: Any) -> tuple:
64
+ """Extract root module name and first submodule from a FieldAccess chain.
65
+
66
+ For 'langchain.chat_models': returns ('langchain', 'chat_models')
67
+ For 'langchain.chat_models.openai': returns ('langchain', 'chat_models')
68
+ """
69
+ if not isinstance(from_part, FieldAccess):
70
+ return None, None
71
+
72
+ current = from_part
73
+ while isinstance(current.target, FieldAccess):
74
+ current = current.target
75
+
76
+ if isinstance(current.target, Identifier):
77
+ return current.target.simple_name, current.name.simple_name
78
+ return None, None
79
+
80
+
81
+ def _replace_root_module(from_part: Any, new_root_name: str) -> Any:
82
+ """Replace the root module name in a FieldAccess chain."""
83
+ if isinstance(from_part, Identifier):
84
+ return from_part.replace(_simple_name=new_root_name)
85
+ if isinstance(from_part, FieldAccess):
86
+ new_target = _replace_root_module(from_part.target, new_root_name)
87
+ return from_part.replace(_target=new_target)
88
+ return from_part
89
+
90
+
91
+ @categorize(_LangChain02)
92
+ class ReplaceLangchainCommunityImports(Recipe):
93
+ """
94
+ Replace `langchain` integration imports with `langchain_community`.
95
+
96
+ In LangChain v0.2, third-party integrations were split out of the main
97
+ `langchain` package into `langchain_community`. This recipe rewrites
98
+ imports like `from langchain.chat_models import X` to
99
+ `from langchain_community.chat_models import X`.
100
+
101
+ Example:
102
+ Before:
103
+ from langchain.chat_models import ChatOpenAI
104
+
105
+ After:
106
+ from langchain_community.chat_models import ChatOpenAI
107
+ """
108
+
109
+ @property
110
+ def name(self) -> str:
111
+ return "org.openrewrite.python.migrate.langchain.ReplaceLangchainCommunityImports"
112
+
113
+ @property
114
+ def display_name(self) -> str:
115
+ return "Replace `langchain` imports with `langchain_community`"
116
+
117
+ @property
118
+ def description(self) -> str:
119
+ return (
120
+ "Migrate third-party integration imports from `langchain` to "
121
+ "`langchain_community`. These integrations were moved in "
122
+ "LangChain v0.2."
123
+ )
124
+
125
+ @property
126
+ def tags(self) -> List[str]:
127
+ return ["python", "migration", "langchain", "0.2"]
128
+
129
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
130
+ class Visitor(PythonVisitor[ExecutionContext]):
131
+ def visit_multi_import(
132
+ self, multi: MultiImport, p: ExecutionContext
133
+ ) -> Optional[MultiImport]:
134
+ multi = super().visit_multi_import(multi, p)
135
+
136
+ from_part = multi.from_
137
+ if from_part is None:
138
+ return multi
139
+
140
+ root_name, first_sub = _get_root_and_first_submodule(from_part)
141
+ if root_name != "langchain":
142
+ return multi
143
+
144
+ if first_sub not in COMMUNITY_SUBMODULES:
145
+ return multi
146
+
147
+ new_from = _replace_root_module(from_part, "langchain_community")
148
+ old_padded_from = multi.padding.from_
149
+ if old_padded_from is None:
150
+ return multi
151
+ new_padded_from = old_padded_from.replace(_element=new_from)
152
+ return multi.padding.replace(_from=new_padded_from)
153
+
154
+ return Visitor()
@@ -0,0 +1,240 @@
1
+ """
2
+ Recipes for migrating langchain_community imports to provider-specific packages.
3
+
4
+ In LangChain v0.2+, provider-specific classes should be imported from their
5
+ dedicated packages instead of langchain_community. For example:
6
+
7
+ from langchain_community.chat_models import ChatOpenAI
8
+ from langchain_community.embeddings import OpenAIEmbeddings
9
+ from langchain_community.vectorstores import Pinecone
10
+
11
+ should become:
12
+
13
+ from langchain_openai import ChatOpenAI
14
+ from langchain_openai import OpenAIEmbeddings
15
+ from langchain_pinecone import Pinecone
16
+
17
+ See: https://python.langchain.com/v0.2/docs/versions/v0_2/
18
+ """
19
+
20
+ from typing import Any, Dict, List, Optional, Set, Tuple
21
+
22
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
23
+ from rewrite.category import CategoryDescriptor
24
+ from rewrite.decorators import categorize
25
+ from rewrite.marketplace import Python
26
+ from rewrite.markers import Markers, SearchResult
27
+ from rewrite.python.visitor import PythonVisitor
28
+ from rewrite.python.tree import MultiImport
29
+ from rewrite.java.tree import FieldAccess, Identifier
30
+ from rewrite.utils import random_id
31
+
32
+ _LangChain02 = [
33
+ *Python,
34
+ CategoryDescriptor(display_name="Migrate"),
35
+ CategoryDescriptor(display_name="LangChain 0.2"),
36
+ ]
37
+
38
+ # Maps (submodule, class_name) -> provider_package
39
+ PROVIDER_MAPPING: Dict[Tuple[str, str], str] = {
40
+ # OpenAI
41
+ ("chat_models", "ChatOpenAI"): "langchain_openai",
42
+ ("chat_models", "AzureChatOpenAI"): "langchain_openai",
43
+ ("llms", "OpenAI"): "langchain_openai",
44
+ ("llms", "AzureOpenAI"): "langchain_openai",
45
+ ("embeddings", "OpenAIEmbeddings"): "langchain_openai",
46
+ ("embeddings", "AzureOpenAIEmbeddings"): "langchain_openai",
47
+ # Anthropic
48
+ ("chat_models", "ChatAnthropic"): "langchain_anthropic",
49
+ ("llms", "Anthropic"): "langchain_anthropic",
50
+ # Google GenAI
51
+ ("chat_models", "ChatGoogleGenerativeAI"): "langchain_google_genai",
52
+ ("llms", "GoogleGenerativeAI"): "langchain_google_genai",
53
+ ("embeddings", "GoogleGenerativeAIEmbeddings"): "langchain_google_genai",
54
+ # Google Vertex AI
55
+ ("chat_models", "ChatVertexAI"): "langchain_google_vertexai",
56
+ ("llms", "VertexAI"): "langchain_google_vertexai",
57
+ ("embeddings", "VertexAIEmbeddings"): "langchain_google_vertexai",
58
+ # AWS
59
+ ("chat_models", "ChatBedrock"): "langchain_aws",
60
+ ("chat_models", "ChatBedrockConverse"): "langchain_aws",
61
+ ("llms", "Bedrock"): "langchain_aws",
62
+ ("llms", "BedrockLLM"): "langchain_aws",
63
+ ("embeddings", "BedrockEmbeddings"): "langchain_aws",
64
+ # Cohere
65
+ ("chat_models", "ChatCohere"): "langchain_cohere",
66
+ ("llms", "Cohere"): "langchain_cohere",
67
+ ("embeddings", "CohereEmbeddings"): "langchain_cohere",
68
+ # Fireworks
69
+ ("chat_models", "ChatFireworks"): "langchain_fireworks",
70
+ ("llms", "Fireworks"): "langchain_fireworks",
71
+ ("embeddings", "FireworksEmbeddings"): "langchain_fireworks",
72
+ # Groq
73
+ ("chat_models", "ChatGroq"): "langchain_groq",
74
+ # MistralAI
75
+ ("chat_models", "ChatMistralAI"): "langchain_mistralai",
76
+ ("embeddings", "MistralAIEmbeddings"): "langchain_mistralai",
77
+ # Ollama
78
+ ("chat_models", "ChatOllama"): "langchain_ollama",
79
+ ("llms", "Ollama"): "langchain_ollama",
80
+ ("embeddings", "OllamaEmbeddings"): "langchain_ollama",
81
+ # Pinecone
82
+ ("vectorstores", "Pinecone"): "langchain_pinecone",
83
+ # Chroma
84
+ ("vectorstores", "Chroma"): "langchain_chroma",
85
+ # HuggingFace
86
+ ("embeddings", "HuggingFaceEmbeddings"): "langchain_huggingface",
87
+ ("embeddings", "HuggingFaceBgeEmbeddings"): "langchain_huggingface",
88
+ ("embeddings", "HuggingFaceInstructEmbeddings"): "langchain_huggingface",
89
+ ("llms", "HuggingFacePipeline"): "langchain_huggingface",
90
+ ("llms", "HuggingFaceEndpoint"): "langchain_huggingface",
91
+ ("llms", "HuggingFaceHub"): "langchain_huggingface",
92
+ # Nvidia
93
+ ("chat_models", "ChatNVIDIA"): "langchain_nvidia_ai_endpoints",
94
+ ("embeddings", "NVIDIAEmbeddings"): "langchain_nvidia_ai_endpoints",
95
+ # IBM
96
+ ("chat_models", "ChatWatsonx"): "langchain_ibm",
97
+ ("llms", "WatsonxLLM"): "langchain_ibm",
98
+ ("embeddings", "WatsonxEmbeddings"): "langchain_ibm",
99
+ # Together
100
+ ("chat_models", "ChatTogether"): "langchain_together",
101
+ ("llms", "Together"): "langchain_together",
102
+ ("embeddings", "TogetherEmbeddings"): "langchain_together",
103
+ }
104
+
105
+
106
+ def _get_import_names(multi: MultiImport) -> List[str]:
107
+ """Get the list of names being imported from a MultiImport."""
108
+ names = []
109
+ for import_node in multi.names:
110
+ qualid = import_node.qualid
111
+ if isinstance(qualid, FieldAccess):
112
+ if isinstance(qualid.name, Identifier):
113
+ names.append(qualid.name.simple_name)
114
+ elif isinstance(qualid, Identifier):
115
+ names.append(qualid.simple_name)
116
+ return names
117
+
118
+
119
+ def _get_submodule_from_community(from_part: Any) -> Optional[str]:
120
+ """Get the submodule name from a langchain_community.X import.
121
+
122
+ Returns the submodule name if from_part is 'langchain_community.X',
123
+ None otherwise.
124
+ """
125
+ if not isinstance(from_part, FieldAccess):
126
+ return None
127
+ if not isinstance(from_part.target, Identifier):
128
+ return None
129
+ if from_part.target.simple_name != "langchain_community":
130
+ return None
131
+ if isinstance(from_part.name, Identifier):
132
+ return from_part.name.simple_name
133
+ return None
134
+
135
+
136
+ def _mark_provider_migration(tree: Any, message: str) -> Any:
137
+ """Add a SearchResult marker for a provider migration suggestion."""
138
+ search_marker = SearchResult(random_id(), message)
139
+ current_markers = tree.markers
140
+ new_markers_list = list(current_markers.markers) + [search_marker]
141
+ new_markers = Markers(current_markers.id, new_markers_list)
142
+ return tree.replace(_markers=new_markers)
143
+
144
+
145
+ @categorize(_LangChain02)
146
+ class ReplaceLangchainProviderImports(Recipe):
147
+ """
148
+ Replace `langchain_community` imports with provider-specific packages.
149
+
150
+ Provider-specific classes should be imported from their dedicated packages
151
+ for better dependency management and versioning. This recipe auto-fixes
152
+ imports when all imported classes map to the same provider package.
153
+
154
+ Example:
155
+ Before:
156
+ from langchain_community.chat_models import ChatOpenAI
157
+
158
+ After:
159
+ from langchain_openai import ChatOpenAI
160
+ """
161
+
162
+ @property
163
+ def name(self) -> str:
164
+ return "org.openrewrite.python.migrate.langchain.ReplaceLangchainProviderImports"
165
+
166
+ @property
167
+ def display_name(self) -> str:
168
+ return "Replace `langchain_community` imports with provider packages"
169
+
170
+ @property
171
+ def description(self) -> str:
172
+ return (
173
+ "Migrate provider-specific imports from `langchain_community` to "
174
+ "dedicated provider packages like `langchain_openai`, "
175
+ "`langchain_anthropic`, etc."
176
+ )
177
+
178
+ @property
179
+ def tags(self) -> List[str]:
180
+ return ["python", "migration", "langchain", "0.2"]
181
+
182
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
183
+ class Visitor(PythonVisitor[ExecutionContext]):
184
+ def visit_multi_import(
185
+ self, multi: MultiImport, p: ExecutionContext
186
+ ) -> Optional[MultiImport]:
187
+ multi = super().visit_multi_import(multi, p)
188
+
189
+ from_part = multi.from_
190
+ if from_part is None:
191
+ return multi
192
+
193
+ submodule = _get_submodule_from_community(from_part)
194
+ if submodule is None:
195
+ return multi
196
+
197
+ imported_names = _get_import_names(multi)
198
+ if not imported_names:
199
+ return multi
200
+
201
+ # Look up provider packages for all imported names
202
+ providers: Set[str] = set()
203
+ has_unmapped = False
204
+ for class_name in imported_names:
205
+ key = (submodule, class_name)
206
+ if key in PROVIDER_MAPPING:
207
+ providers.add(PROVIDER_MAPPING[key])
208
+ else:
209
+ has_unmapped = True
210
+
211
+ if not providers:
212
+ return multi
213
+
214
+ # Auto-fix only when all classes map to the same provider
215
+ # and none are unmapped
216
+ if len(providers) == 1 and not has_unmapped:
217
+ provider_package = providers.pop()
218
+ new_from = Identifier(
219
+ _id=random_id(),
220
+ _prefix=from_part.prefix,
221
+ _markers=Markers.EMPTY,
222
+ _annotations=[],
223
+ _simple_name=provider_package,
224
+ _type=None,
225
+ _field_type=None,
226
+ )
227
+ old_padded_from = multi.padding.from_
228
+ if old_padded_from is None:
229
+ return multi
230
+ new_padded_from = old_padded_from.replace(_element=new_from)
231
+ return multi.padding.replace(_from=new_padded_from)
232
+
233
+ # Flag mixed-provider imports for manual migration
234
+ provider_list = ", ".join(sorted(providers))
235
+ return _mark_provider_migration(
236
+ multi,
237
+ f"Split this import across provider packages: {provider_list}",
238
+ )
239
+
240
+ return Visitor()