Skip to content

Commit 9fef380

Browse files
alexkatsintellij-monorepo-bot
authored andcommitted
PY-52688 support pipenv for remote targets
GitOrigin-RevId: 497680bc7ae4fd820e7a8e77d677e24a4ec557d8
1 parent 007bdc6 commit 9fef380

8 files changed

Lines changed: 181 additions & 82 deletions

File tree

python/mcp/src/com/intellij/python/mcp/environment/PythonEnvironmentMcpToolset.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@ import com.intellij.python.hatch.impl.sdk.HatchSdkFlavor
2020
import com.intellij.python.pyproject.model.api.ModuleCreateInfo
2121
import com.intellij.python.pyproject.model.api.autoConfigureSdkIfNeeded
2222
import com.intellij.python.pyproject.model.api.getModuleInfo
23-
import com.jetbrains.python.hatch.sdk.HatchSdkAdditionalData
2423
import com.jetbrains.python.sdk.PythonEnvironment
2524
import com.jetbrains.python.sdk.configurePythonSdk
2625
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
2726
import com.jetbrains.python.sdk.configuration.createSdk
2827
import com.jetbrains.python.sdk.findPythonSdk
28+
import com.jetbrains.python.sdk.pipenv.PyPipEnvSdkFlavor
2929
import com.jetbrains.python.sdk.poetry.PyPoetrySdkFlavor
3030
import com.jetbrains.python.sdk.pySdkAdditionalData
3131
import com.jetbrains.python.sdk.pythonInterpreterAsync
32-
import com.jetbrains.python.sdk.uv.UvSdkAdditionalData
3332
import com.jetbrains.python.sdk.uv.UvSdkFlavor
3433
import com.jetbrains.python.sdk.withSdkConfigurationLock
3534
import kotlinx.coroutines.currentCoroutineContext
@@ -108,7 +107,7 @@ class PythonEnvironmentMcpToolset : McpToolset {
108107
@McpDescription(
109108
"""
110109
Configures a Python interpreter for the module containing the given file using PyCharm's own detection
111-
(existing .venv folder, or a fresh uv/poetry/hatch venv when the corresponding tool is already installed).
110+
(existing .venv folder, or a fresh uv/poetry/hatch/pipenv venv when the corresponding tool is already installed).
112111
Call this only after get_python_environment reported that no interpreter is configured.
113112
This tool does NOT install env-management tools like uv or poetry; if PyCharm needs one, the call fails
114113
with the missing tool name so the agent can install it manually first and retry.
@@ -224,6 +223,7 @@ class PythonEnvironmentMcpToolset : McpToolset {
224223
when (sdk.pySdkAdditionalData.flavor) {
225224
PyPoetrySdkFlavor -> "poetry"
226225
HatchSdkFlavor -> "hatch"
226+
PyPipEnvSdkFlavor -> "pipenv"
227227
UvSdkFlavor -> "uv"
228228
else -> when (env) {
229229
is PythonEnvironment.Conda -> "conda"
@@ -242,7 +242,7 @@ class PythonEnvironmentMcpToolset : McpToolset {
242242
val executablePath: String? = null,
243243
@property:McpDescription("Root of the virtual environment (venv prefix or conda prefix); null for system Python or unconfigured")
244244
val environmentPath: String? = null,
245-
@property:McpDescription("'pip', 'uv', 'poetry', 'hatch', 'conda', or 'unknown'; null when no interpreter is configured")
245+
@property:McpDescription("'pip', 'uv', 'poetry', 'hatch', 'pipenv', 'conda', or 'unknown'; null when no interpreter is configured")
246246
val packageManager: String? = null,
247247
@property:McpDescription("When non-null, no interpreter is configured for this module. Describes the current state and what action to take next.")
248248
val noInterpreterConfigured: String? = null,

python/src/com/jetbrains/python/packaging/pipenv/PipEnvPackageManager.kt

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,20 @@ import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
1515
import com.jetbrains.python.packaging.management.PythonPackageManager
1616
import com.jetbrains.python.packaging.management.PythonRepositoryManager
1717
import com.jetbrains.python.packaging.pip.PipRepositoryManager
18-
import com.jetbrains.python.sdk.associatedModulePath
1918
import com.jetbrains.python.sdk.pipenv.PIP_FILE
20-
import com.jetbrains.python.sdk.pipenv.runPipEnv
19+
import com.jetbrains.python.sdk.pipenv.runPipEnvWithSdk
2120
import java.nio.file.Path
2221
import com.jetbrains.python.sdk.pipenv.PipEnvParser as SdkPipEnvParser
2322

2423
internal class PipEnvPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) {
25-
private val modulePath: Path?
26-
get() = sdk.associatedModulePath?.let { Path.of(it) }
27-
2824
override val repositoryManager: PythonRepositoryManager = PipRepositoryManager.getInstance(project)
2925

3026
override suspend fun syncLockedCommand(): PyResult<Unit> {
31-
return runPipEnv(modulePath, "install", "--dev").mapSuccess { }
27+
return runPipEnvWithSdk(sdk, "install", "--dev").mapSuccess { }
3228
}
3329

3430
suspend fun lock(): PyResult<Unit> {
35-
return runPipEnv(modulePath, "lock").mapSuccess { }
31+
return runPipEnvWithSdk(sdk, "lock").mapSuccess { }
3632
}
3733

3834
override suspend fun installPackageCommand(installRequest: PythonPackageInstallRequest, options: List<String>, module: Module?, dependencyGroup: PyDependencyGroup?): PyResult<Unit> {
@@ -41,24 +37,24 @@ internal class PipEnvPackageManager(project: Project, sdk: Sdk) : PythonPackageM
4137
is PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications -> {
4238

4339
val args = listOf("install") + installRequest.specifications.map { it.nameWithVersionSpec } + options
44-
runPipEnv(modulePath, *args.toTypedArray()).mapSuccess { }
40+
runPipEnvWithSdk(sdk, *args.toTypedArray()).mapSuccess { }
4541
}
4642
}
4743
}
4844

4945
override suspend fun updatePackageCommand(vararg specifications: PythonRepositoryPackageSpecification): PyResult<Unit> {
5046
val args = listOf("install") + specifications.map { it.nameWithVersionSpec }
51-
return runPipEnv(modulePath, *args.toTypedArray()).mapSuccess { }
47+
return runPipEnvWithSdk(sdk, *args.toTypedArray()).mapSuccess { }
5248
}
5349

5450
override suspend fun uninstallPackageCommand(vararg pythonPackages: String, workspaceMember: PyWorkspaceMember?, dependencyGroup: PyDependencyGroup?): PyResult<Unit> {
5551
val args = listOf("uninstall") + pythonPackages.toList()
56-
return runPipEnv(modulePath, *args.toTypedArray()).mapSuccess { }
52+
return runPipEnvWithSdk(sdk, *args.toTypedArray()).mapSuccess { }
5753

5854
}
5955

6056
override suspend fun loadPackagesCommand(): PyResult<List<PythonPackage>> {
61-
val output = runPipEnv(modulePath, "graph", "--json")
57+
val output = runPipEnvWithSdk(sdk, "graph", "--json")
6258
output.getOr {
6359
return it
6460
}
@@ -72,7 +68,7 @@ internal class PipEnvPackageManager(project: Project, sdk: Sdk) : PythonPackageM
7268
}
7369

7470
override suspend fun loadOutdatedPackagesCommand(): PyResult<List<PythonOutdatedPackage>> {
75-
val output = runPipEnv(modulePath, "update", "--dry-run").getOr { return it }
71+
val output = runPipEnvWithSdk(sdk, "update", "--dry-run").getOr { return it }
7672
val outdated = PipEnvParser.parseOutdatedPackagesOutput(output)
7773

7874
return PyResult.success(outdated)
@@ -87,4 +83,4 @@ internal class PipEnvPackageManager(project: Project, sdk: Sdk) : PythonPackageM
8783
override val dependenciesFilesRelativePaths: List<Path>
8884
get() =
8985
listOf(Path.of(PIP_FILE))
90-
}
86+
}

python/src/com/jetbrains/python/sdk/add/v2/common.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ enum class PythonSupportedEnvironmentManagers(
146146
VIRTUALENV(VENV_TOOL_ID, "sdk.create.custom.virtualenv", PythonVenvIcons.VirtualEnv, sshAutoUploadRequired = false, { true }),
147147
CONDA(CONDA_TOOL_ID, "sdk.create.custom.conda", PythonCommunityImplCondaIcons.Anaconda, sshAutoUploadRequired = false, { true }),
148148
POETRY(POETRY_TOOL_ID, "sdk.create.custom.poetry", PythonCommunityImplPoetryCommonIcons.Poetry, sshAutoUploadRequired = true, { true }),
149-
PIPENV(PIPENV_TOOL_ID, "sdk.create.custom.pipenv", PythonCommunityImplPipenvIcons.PythonClosed, sshAutoUploadRequired = false),
149+
PIPENV(PIPENV_TOOL_ID, "sdk.create.custom.pipenv", PythonCommunityImplPipenvIcons.PythonClosed, sshAutoUploadRequired = true, { true }),
150150
UV(UV_TOOL_ID, "sdk.create.custom.uv", PythonUvCommonIcons.UV, sshAutoUploadRequired = true, { true }),
151151
HATCH(HATCH_TOOL_ID, "sdk.create.custom.hatch", PythonHatchIcons.Logo, sshAutoUploadRequired = true, { true }),
152152
PYTHON(VENV_TOOL_ID, "sdk.create.custom.python", PythonParserIcons.PythonFile, sshAutoUploadRequired = false, { true })

python/src/com/jetbrains/python/sdk/add/v2/pipenv/EnvironmentCreatorPip.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import com.intellij.python.community.impl.pipenv.PipEnvPyTool
88
import com.intellij.python.community.impl.pipenv.pipenvPath
99
import com.intellij.python.pytools.PyTool
1010
import com.intellij.platform.util.progress.withProgressText
11-
import com.jetbrains.python.PyBundle
1211
import com.jetbrains.python.PyBundle.message
1312
import com.jetbrains.python.errorProcessing.ErrorSink
1413
import com.jetbrains.python.errorProcessing.PyResult
@@ -34,16 +33,19 @@ internal class EnvironmentCreatorPip<P : PathHolder>(model: PythonMutableTargetA
3433

3534
override suspend fun setupEnvSdk(moduleBasePath: Path): PyResult<Sdk> {
3635
val basePythonBinaryPath = model.getOrInstallBasePython()
36+
?: return PyResult.localizedError(message("python.sdk.provided.path.is.invalid", null))
37+
val pipenvExecutable = model.pipenvViewModel.pipenvExecutable.get()?.pathHolder
38+
?: return PyResult.localizedError(message("python.sdk.provided.path.is.invalid", null))
3739

38-
return when (basePythonBinaryPath) {
39-
is PathHolder.Eel -> withProgressText(message("python.sdk.progress.pipenv.creating")) {
40-
setupPipEnvSdkWithProgressReport(
41-
moduleBasePath = moduleBasePath,
42-
basePythonBinaryPath = basePythonBinaryPath.path,
43-
installPackages = false
44-
)
45-
}
46-
else -> PyResult.localizedError(message("target.is.not.supported", basePythonBinaryPath))
40+
return withProgressText(message("python.sdk.progress.pipenv.creating")) {
41+
setupPipEnvSdkWithProgressReport(
42+
moduleBasePath = moduleBasePath,
43+
basePythonBinaryPath = basePythonBinaryPath,
44+
fileSystem = model.fileSystem,
45+
pipenvExecutable = pipenvExecutable,
46+
installPackages = false,
47+
targetPanelExtension = model.state.targetPanelExtension.get(),
48+
)
4749
}
4850
}
4951
}

python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@ import com.jetbrains.python.errorProcessing.emit
2525
import com.jetbrains.python.onFailure
2626
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
2727
import com.jetbrains.python.sdk.associatedModuleDir
28-
import com.jetbrains.python.sdk.associatedModulePath
2928
import com.jetbrains.python.sdk.findAmongRoots
3029
import com.jetbrains.python.sdk.pythonSdk
3130
import com.jetbrains.python.sdk.skeleton.PySkeletonUtil
3231
import com.jetbrains.python.statistics.PipfileWatcherIdsHolder.Companion.RUN_PIPENV_LOCK_SUGGESTION
3332
import com.jetbrains.python.errorProcessing.ErrorSink
3433
import kotlinx.coroutines.Dispatchers
3534
import kotlinx.coroutines.withContext
36-
import java.nio.file.Path
3735

3836
/**
3937
* Watches for edits in Pipfiles inside modules with a pipenv SDK set.
@@ -114,7 +112,7 @@ internal class PipEnvPipFileWatcher : EditorFactoryListener {
114112
PyPackageCoroutine.launch(module.project) {
115113
withBackgroundProgress(module.project, description) {
116114
val sdk = module.pythonSdk ?: return@withBackgroundProgress
117-
runPipEnv(sdk.associatedModulePath?.let { Path.of(it) }, *args.toTypedArray()).onFailure {
115+
runPipEnvWithSdk(sdk, *args.toTypedArray()).onFailure {
118116
ErrorSink().emit(it, module.project)
119117
}
120118

@@ -139,4 +137,4 @@ internal class PipEnvPipFileWatcher : EditorFactoryListener {
139137
get() = FileDocumentManager.getInstance().getFile(this)
140138

141139
private fun VirtualFile.getModule(project: Project): Module? = ModuleUtil.findModuleForFile(this, project)
142-
}
140+
}

0 commit comments

Comments
 (0)