[python][poetry] don't create pyproject.toml if it already exists (PY-76274)

+ project.basePath -> module.basePath migrations

GitOrigin-RevId: 8461d6d273f7482aca180abe63d958e551c3c7dd
This commit is contained in:
Vitaly Legchilkin
2025-08-19 15:49:34 +00:00
committed by intellij-monorepo-bot
parent a3983498f8
commit 56fbdf6fee
20 changed files with 167 additions and 173 deletions
@@ -43,7 +43,7 @@ internal class PyHatchSdkConfiguration : PyProjectSdkConfigurationExtension {
val createdEnvironment = hatchService.createVirtualEnvironment().getOr { return@runWithModalBlockingOrInBackground it }
val hatchVenv = HatchVirtualEnvironment(HatchEnvironment.DEFAULT, createdEnvironment)
val sdk = hatchVenv.createSdk(hatchService.getWorkingDirectoryPath(), module)
val sdk = hatchVenv.createSdk(hatchService.getWorkingDirectoryPath())
sdk
}
override suspend fun createAndAddSdkForConfigurator(module: Module): PyResult<Sdk> = createSdk(module)
@@ -16,13 +16,16 @@ import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.poetry.findPoetryLock
import com.jetbrains.python.poetry.getPyProjectTomlForPoetry
import com.jetbrains.python.resolvePythonBinary
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.PythonSdkUtil
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
import com.jetbrains.python.sdk.poetry.*
import com.jetbrains.python.sdk.poetry.PyPoetrySdkAdditionalData
import com.jetbrains.python.sdk.poetry.getPoetryExecutable
import com.jetbrains.python.sdk.poetry.setupPoetry
import com.jetbrains.python.sdk.poetry.suggestedSdkName
import com.jetbrains.python.sdk.setAssociationToModule
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
@@ -64,11 +67,8 @@ internal class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
}
val tomlFile = PyProjectToml.findFile(module)
val poetry = setupPoetry(basePath, null, true, tomlFile == null).getOr { return@withBackgroundProgress it }
val path = withContext(Dispatchers.IO) { VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(poetry)) }
if (path == null) {
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", poetry))
}
val path = poetry.resolvePythonBinary()
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", poetry))
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.pathString)
if (file == null) {
@@ -437,7 +437,7 @@ python.sdk.rendering.project.default.0=Project Default ({0})
python.sdk.installation.notification.group=Python interpreter installation
python.sdk.installation.balloon.error.message=<b>Python installation interrupted</b><br/>Download Python manually from python.org, install it and configure the project interpreter.
python.sdk.installation.balloon.error.action=See the documentation
python.sdk.directory.not.found=Directory {0} not found
python.sdk.python.executable.not.found=Python executable {0} is missing
python.sdk.failed.to.create.interpreter.title=Failed to create interpreter
python.sdk.can.t.obtain.python.version=Can't obtain python version
python.sdk.empty.version.string=Python interpreter returned the empty output as a version string
@@ -13,6 +13,8 @@ import org.apache.tuweni.toml.TomlParseError
import org.apache.tuweni.toml.TomlTable
import org.jetbrains.annotations.ApiStatus.Internal
import java.io.InputStream
import java.nio.file.Path
import kotlin.io.path.isRegularFile
/**
* Stores the file name of `pyproject.toml`.
@@ -240,6 +242,9 @@ data class PyProjectToml(
findAmongRoots(module, PY_PROJECT_TOML)
}
suspend fun findInRoot(moduleBasePath: Path): Path? = withContext(Dispatchers.IO) {
moduleBasePath.resolve(PY_PROJECT_TOML).takeIf { it.isRegularFile() }
}
private fun TomlTable.parseContacts(
key: String,
@@ -2,10 +2,12 @@
package com.jetbrains.python.hatch.sdk
import com.intellij.openapi.application.EDT
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.NlsSafe
import com.intellij.python.hatch.*
import com.intellij.python.hatch.BasePythonExecutableNotFoundHatchError
import com.intellij.python.hatch.HatchVirtualEnvironment
import com.intellij.python.hatch.PythonVirtualEnvironment
import com.intellij.python.hatch.getHatchEnvVirtualProjectPath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.resolvePythonBinary
@@ -19,7 +21,7 @@ import java.nio.file.Path
import kotlin.io.path.name
@ApiStatus.Internal
suspend fun HatchVirtualEnvironment.createSdk(workingDirectoryPath: Path, module: Module?): PyResult<Sdk> {
suspend fun HatchVirtualEnvironment.createSdk(workingDirectoryPath: Path): PyResult<Sdk> {
val existingPythonEnvironment = pythonVirtualEnvironment as? PythonVirtualEnvironment.Existing
?: return Result.failure(BasePythonExecutableNotFoundHatchError(null as String?))
val pythonHomePath = pythonVirtualEnvironment?.pythonHomePath
@@ -29,9 +31,9 @@ suspend fun HatchVirtualEnvironment.createSdk(workingDirectoryPath: Path, module
val hatchSdkAdditionalData = HatchSdkAdditionalData(workingDirectoryPath, this.hatchEnvironment.name)
val sdk = createSdk(
sdkHomePath = pythonBinary,
pythonBinaryPath = pythonBinary,
existingSdks = PythonSdkUtil.getAllSdks(),
associatedProjectPath = module?.project?.basePath,
associatedProjectPath = workingDirectoryPath.toString(),
suggestedSdkName = existingPythonEnvironment.suggestHatchSdkName(),
sdkAdditionalData = hatchSdkAdditionalData
).getOr { return it }
@@ -5,7 +5,10 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.execution.ExecutionException
import com.intellij.execution.target.*
import com.intellij.ide.projectView.actions.MarkRootsManager
import com.intellij.openapi.application.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
@@ -29,6 +32,7 @@ import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.util.PathUtil
import com.intellij.webcore.packaging.PackagesNotificationPanel
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.isCondaVirtualEnv
import com.jetbrains.python.isVirtualEnv
@@ -188,7 +192,7 @@ fun createSdkByGenerateTask(
val homeFile = try {
val homePath = ProgressManager.getInstance().run(generateSdkHomePath)
StandardFileSystems.local().refreshAndFindFileByPath(homePath) ?: throw ExecutionException(
PyBundle.message("python.sdk.directory.not.found", homePath)
PyBundle.message("python.sdk.python.executable.not.found", homePath)
)
}
catch (e: ExecutionException) {
@@ -218,19 +222,20 @@ fun createSdkByGenerateTask(
@Internal
suspend fun createSdk(
sdkHomePath: Path,
pythonBinaryPath: PythonBinary,
existingSdks: List<Sdk>,
associatedProjectPath: String?,
suggestedSdkName: String?,
sdkAdditionalData: PythonSdkAdditionalData? = null,
): PyResult<Sdk> {
val homeFile = withContext(Dispatchers.IO) { StandardFileSystems.local().refreshAndFindFileByPath(sdkHomePath.pathString) }
?: return PyResult.localizedError(PyBundle.message("python.sdk.directory.not.found", sdkHomePath.pathString))
val pythonBinaryVirtualFile = withContext(Dispatchers.IO) {
StandardFileSystems.local().refreshAndFindFileByPath(pythonBinaryPath.pathString)
} ?: return PyResult.localizedError(PyBundle.message("python.sdk.python.executable.not.found", pythonBinaryPath.pathString))
val sdkName = suggestedSdkName ?: suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
val sdkName = suggestedSdkName ?: suggestAssociatedSdkName(pythonBinaryPath.pathString, associatedProjectPath)
val sdk = SdkConfigurationUtil.setupSdk(
existingSdks.toTypedArray(),
homeFile,
pythonBinaryVirtualFile,
PythonSdkType.getInstance(),
false,
sdkAdditionalData,
@@ -1,9 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.add.v2
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.openapi.util.io.toNioPathOrNull
@@ -13,6 +11,7 @@ import com.intellij.ui.components.ActionLink
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
@@ -69,19 +68,20 @@ internal abstract class CustomNewEnvironmentCreator(
// todo think about better error handling
val selectedBasePython = model.state.baseInterpreter.get()!!
val homePath = model.installPythonIfNeeded(selectedBasePython)
val basePythonBinaryPath = model.installPythonIfNeeded(selectedBasePython)
val module = when (moduleOrProject) {
is ModuleOrProject.ModuleAndProject -> moduleOrProject.module
is ModuleOrProject.ProjectOnly -> null
}
val moduleBasePath = module?.basePath?.let { Path.of(it) }
?: model.projectPathFlows.projectPath.first()
?: error("module base path can't be recognized, both module and project are nulls")
val newSdk = setupEnvSdk(
project = moduleOrProject.project,
module = module,
moduleBasePath = moduleBasePath,
baseSdks = PythonSdkUtil.getAllSdks(),
projectPath = model.projectPathFlows.projectPathWithDefault.first().toString(),
homePath = homePath,
basePythonBinaryPath = basePythonBinaryPath,
installPackages = false
).getOr { return it }
@@ -184,7 +184,7 @@ internal abstract class CustomNewEnvironmentCreator(
*/
internal abstract fun savePathToExecutableToProperties(path: Path?)
protected abstract suspend fun setupEnvSdk(project: Project, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): PyResult<Sdk>
protected abstract suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<Sdk>
internal abstract suspend fun detectExecutable()
@@ -2,11 +2,10 @@
package com.jetbrains.python.sdk.add.v2
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.util.text.nullize
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.pipenv.pipEnvPath
@@ -24,8 +23,9 @@ internal class EnvironmentCreatorPip(model: PythonMutableTargetAddInterpreterMod
PropertiesComponent.getInstance().pipEnvPath = savingPath
}
override suspend fun setupEnvSdk(project: Project, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): PyResult<Sdk> =
setupPipEnvSdkWithProgressReport(project, module, baseSdks, projectPath, homePath, installPackages)
override suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<Sdk> {
return setupPipEnvSdkWithProgressReport(moduleBasePath, baseSdks, basePythonBinaryPath, installPackages)
}
override suspend fun detectExecutable() {
model.detectPipEnvExecutable()
@@ -72,7 +72,7 @@ internal class HatchExistingEnvironmentSelector(
else -> {
val (project, module) = moduleOrProject.destructured
val workingDirectory = resolveHatchWorkingDirectory(project, module).getOr { return it }
environment.createSdk(workingDirectory, module).onSuccess { sdk ->
environment.createSdk(workingDirectory).onSuccess { sdk ->
module?.let { module -> sdk.setAssociationToModule(module) }
}
}
@@ -3,7 +3,6 @@ package com.jetbrains.python.sdk.add.v2.hatch
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.ui.validation.DialogValidationRequestor
@@ -11,9 +10,9 @@ import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.python.hatch.HatchConfiguration
import com.intellij.python.hatch.HatchVirtualEnvironment
import com.intellij.python.hatch.getHatchService
import com.intellij.python.hatch.resolveHatchWorkingDirectory
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.text.nullize
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
@@ -81,21 +80,20 @@ internal class HatchNewEnvironmentCreator(
return Result.success(Unit)
}
override suspend fun setupEnvSdk(project: Project, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): PyResult<Sdk> {
override suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<Sdk> {
val hatchEnv = hatchEnvironmentProperty.get()?.hatchEnvironment
?: return Result.failure(HatchUIError.HatchEnvironmentIsNotSelected())
val hatchExecutablePath = executable.get().toPath().getOr { return it }
val hatchWorkingDirectory = resolveHatchWorkingDirectory(project, module).getOr { return it }
val hatchService = hatchWorkingDirectory.getHatchService(hatchExecutablePath = hatchExecutablePath).getOr { return it }
val hatchService = moduleBasePath.getHatchService(hatchExecutablePath = hatchExecutablePath).getOr { return it }
val virtualEnvironment = hatchService.createVirtualEnvironment(
basePythonBinaryPath = homePath?.let { Path.of(it) },
basePythonBinaryPath = basePythonBinaryPath,
envName = hatchEnv.name
).getOr { return it }
val hatchVirtualEnv = HatchVirtualEnvironment(hatchEnv, virtualEnvironment)
val createdSdk = hatchVirtualEnv.createSdk(hatchService.getWorkingDirectoryPath(), module).onSuccess {
val createdSdk = hatchVirtualEnv.createSdk(hatchService.getWorkingDirectoryPath()).onSuccess {
HatchConfiguration.persistPathForTarget(hatchExecutablePath = hatchExecutablePath)
}
return createdSdk
@@ -6,7 +6,6 @@ import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.*
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.openapi.vfs.VirtualFileManager
@@ -16,9 +15,12 @@ import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.util.text.nullize
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
import com.jetbrains.python.poetry.PoetryPyProjectTomlPythonVersionsService
import com.jetbrains.python.poetry.findPoetryToml
import com.jetbrains.python.sdk.add.v2.CustomNewEnvironmentCreator
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
@@ -29,10 +31,8 @@ import com.jetbrains.python.sdk.add.v2.VenvExistenceValidationState.Error
import com.jetbrains.python.sdk.add.v2.VenvExistenceValidationState.Invisible
import com.jetbrains.python.sdk.add.v2.getBasePath
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.poetry.PoetryPyProjectTomlPythonVersionsService
import com.jetbrains.python.sdk.poetry.configurePoetryEnvironment
import com.jetbrains.python.poetry.findPoetryToml
import com.jetbrains.python.sdk.poetry.setupPoetrySdk
import com.jetbrains.python.sdk.poetry.createNewPoetrySdk
import com.jetbrains.python.statistics.InterpreterType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -113,9 +113,9 @@ internal class EnvironmentCreatorPoetry(
PropertiesComponent.getInstance().poetryPath = savingPath
}
override suspend fun setupEnvSdk(project: Project, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): PyResult<Sdk> {
override suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<Sdk> {
module?.let { service<PoetryConfigService>().setInProjectEnv(it) }
return setupPoetrySdk(project, module, baseSdks, projectPath, homePath, installPackages)
return createNewPoetrySdk(moduleBasePath, baseSdks, basePythonBinaryPath, installPackages)
}
override suspend fun detectExecutable() {
@@ -5,6 +5,7 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.PyBundle
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.ModuleOrProject
@@ -12,9 +13,10 @@ import com.jetbrains.python.sdk.PythonSdkUtil
import com.jetbrains.python.sdk.add.v2.CustomExistingEnvironmentSelector
import com.jetbrains.python.sdk.add.v2.DetectedSelectableInterpreter
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.poetry.createPoetrySdk
import com.jetbrains.python.sdk.poetry.detectPoetryEnvs
import com.jetbrains.python.sdk.poetry.isPoetry
import com.jetbrains.python.sdk.poetry.setupPoetrySdk
import com.jetbrains.python.statistics.InterpreterType
import com.jetbrains.python.statistics.version
import java.nio.file.Path
@@ -25,16 +27,24 @@ internal class PoetryExistingEnvironmentSelector(model: PythonMutableTargetAddIn
override val interpreterType: InterpreterType = InterpreterType.POETRY
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
val selectedInterpreter = selectedEnv.get()
PythonSdkUtil.getAllSdks().find { sdk -> sdk.isPoetry && sdk.homePath == selectedInterpreter?.homePath }?.let { return Result.success(it) }
val pythonBinaryPath = selectedEnv.get()?.let { Path.of(it.homePath) }
?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", selectedEnv.get()?.homePath))
PythonSdkUtil.getAllSdks().find { sdk -> sdk.isPoetry && sdk.homePath == pythonBinaryPath.pathString }?.let { return Result.success(it) }
val module = when (moduleOrProject) {
is ModuleOrProject.ModuleAndProject -> {
moduleOrProject.module
}
else -> null
}
val moduleBasePath = module?.basePath?.let { Path.of(it) } ?: error("module base path is not valid: ${module?.basePath}")
return setupPoetrySdk(moduleOrProject.project, module, ProjectJdkTable.getInstance().allJdks.toList(), null, selectedInterpreter?.homePath, true)
return createPoetrySdk(
moduleBasePath,
existingSdks = ProjectJdkTable.getInstance().allJdks.toList(),
pythonBinaryPath = pythonBinaryPath
)
}
override suspend fun detectEnvironments(modulePath: Path): List<DetectedSelectableInterpreter> {
@@ -95,10 +95,11 @@ suspend fun PythonAddInterpreterModel.selectCondaEnvironment(base: Boolean): PyR
}
internal fun PythonAddInterpreterModel.installPythonIfNeeded(interpreter: PythonSelectableInterpreter): String? {
internal fun PythonAddInterpreterModel.installPythonIfNeeded(interpreter: PythonSelectableInterpreter): Path? {
// todo use target config
return if (interpreter is InstallableSelectableInterpreter) {
installBaseSdk(interpreter.sdk, existingSdks)?.homePath ?: return null
val pathString = if (interpreter is InstallableSelectableInterpreter) {
installBaseSdk(interpreter.sdk, existingSdks)?.homePath
}
else interpreter.homePath
return pathString?.let { Path.of(it) }
}
@@ -5,7 +5,6 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.util.not
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.validation.DialogValidationRequestor
@@ -19,6 +18,7 @@ import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.intellij.util.text.nullize
import com.intellij.util.ui.AsyncProcessIcon
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrNull
@@ -30,13 +30,11 @@ import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.PYTHON
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.UV
import com.jetbrains.python.sdk.add.v2.VenvExistenceValidationState
import com.jetbrains.python.sdk.add.v2.executableSelector
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.uv.impl.createUvCli
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
import com.jetbrains.python.sdk.uv.impl.setUvExecutable
import com.jetbrains.python.sdk.uv.setupNewUvSdkAndEnv
import com.jetbrains.python.statistics.InterpreterType
import com.jetbrains.python.venvReader.tryResolvePath
import io.github.z4kn4fein.semver.Version
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -173,19 +171,12 @@ internal class EnvironmentCreatorUv(
}
override suspend fun setupEnvSdk(
project: Project,
module: Module?,
moduleBasePath: Path,
baseSdks: List<Sdk>,
projectPath: String,
homePath: String?,
installPackages: Boolean,
basePythonBinaryPath: PythonBinary?,
installPackages: Boolean
): PyResult<Sdk> {
val workingDir = module?.basePath?.let { tryResolvePath(it) } ?: project.basePath?.let { tryResolvePath(it) }
if (workingDir == null) {
return PyResult.localizedError(message("python.sdk.uv.working.dir.is.not.specified"))
}
return setupNewUvSdkAndEnv(workingDir, baseSdks, pythonVersion.get())
return setupNewUvSdkAndEnv(moduleBasePath, baseSdks, pythonVersion.get())
}
override suspend fun detectExecutable() {
@@ -2,18 +2,16 @@
package com.jetbrains.python.sdk.pipenv
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.localEel
import com.intellij.platform.eel.where
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrNull
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.createSdk
import com.jetbrains.python.sdk.runExecutableWithProgress
import com.jetbrains.python.venvReader.VirtualEnvReader
@@ -22,6 +20,7 @@ import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.SystemDependent
import java.nio.file.Path
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.minutes
@Internal
@@ -81,24 +80,19 @@ suspend fun getPipEnvExecutable(): PyResult<Path> =
*/
@Internal
suspend fun setupPipEnvSdkWithProgressReport(
project: Project?,
module: Module?,
moduleBasePath: Path,
existingSdks: List<Sdk>,
newProjectPath: String?,
python: String?,
basePythonBinaryPath: PythonBinary?,
installPackages: Boolean,
): PyResult<Sdk> {
val projectPath = newProjectPath ?: module?.basePath ?: project?.basePath
?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
val actualProject = project ?: module?.project
val pythonExecutablePath = if (actualProject != null) {
setUpPipEnv(projectPath, python, installPackages)
}
else {
setUpPipEnv(projectPath, python, installPackages)
}.getOr { return it }
val pythonExecutablePath = setUpPipEnv(moduleBasePath, basePythonBinaryPath, installPackages).getOr { return it }
return createSdk(pythonExecutablePath, existingSdks, projectPath, suggestedSdkName(projectPath), PyPipEnvSdkAdditionalData())
return createSdk(
pythonExecutablePath,
existingSdks, moduleBasePath.pathString,
suggestedSdkName(moduleBasePath.pathString),
PyPipEnvSdkAdditionalData()
)
}
/**
@@ -107,23 +101,23 @@ suspend fun setupPipEnvSdkWithProgressReport(
* @return the path to the pipenv environment.
*/
@Internal
suspend fun setupPipEnv(projectPath: Path, python: String?, installPackages: Boolean): PyResult<@SystemDependent String> {
suspend fun setupPipEnv(projectPath: Path, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<@SystemDependent String> {
when {
installPackages -> {
val pythonArgs = if (python != null) listOf("--python", python) else emptyList()
val pythonArgs = if (basePythonBinaryPath != null) listOf("--python", basePythonBinaryPath.pathString) else emptyList()
val command = pythonArgs + listOf("install", "--dev")
runPipEnv(projectPath, *command.toTypedArray()).getOr { return it }
}
python != null ->
runPipEnv(projectPath, "--python", python).getOr { return it }
basePythonBinaryPath != null ->
runPipEnv(projectPath, "--python", basePythonBinaryPath.pathString).getOr { return it }
else ->
runPipEnv(projectPath, "run", "python", "-V").getOr { return it }
}
return runPipEnv(projectPath, "--venv")
}
private suspend fun setUpPipEnv(projectPathString: String, python: String?, installPackages: Boolean): PyResult<Path> {
val pipEnv = setupPipEnv(Path.of(projectPathString), python, installPackages).getOr { return it }
private suspend fun setUpPipEnv(moduleBasePath: Path, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<Path> {
val pipEnv = setupPipEnv(moduleBasePath, basePythonBinaryPath, installPackages).getOr { return it }
val pipEnvExecutablePathString = withContext(Dispatchers.IO) {
VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(pipEnv))?.toString()
} ?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", pipEnv))
@@ -3,7 +3,6 @@ package com.jetbrains.python.sdk.pipenv.ui
import com.intellij.application.options.ModuleListCellRenderer
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
@@ -24,10 +23,13 @@ import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.*
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.PySdkSettings
import com.jetbrains.python.sdk.add.PyAddNewEnvPanel
import com.jetbrains.python.sdk.add.PySdkPathChoosingComboBox
import com.jetbrains.python.sdk.add.addBaseInterpretersAsync
import com.jetbrains.python.sdk.associatedModulePath
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.installSdkIfNeeded
import com.jetbrains.python.sdk.pipenv.*
import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
@@ -36,6 +38,7 @@ import kotlinx.coroutines.withContext
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.ItemEvent
import java.nio.file.Path
import javax.swing.Icon
import javax.swing.JComboBox
import javax.swing.event.DocumentEvent
@@ -128,12 +131,17 @@ class PyAddPipEnvPanel(
@RequiresEdt
override fun getOrCreateSdk(): Sdk? {
PropertiesComponent.getInstance().pipEnvPath = pipEnvPathField.text.nullize()
val baseSdk = installSdkIfNeeded(baseSdkField.selectedSdk, selectedModule, existingSdks, context).getOrLogException(LOGGER)?.homePath
val baseSdk = installSdkIfNeeded(baseSdkField.selectedSdk, selectedModule, existingSdks, context).getOrThrow()
val moduleBasePath = module?.basePath?.let { Path.of(it) }
?: error("module base path is invalid: ${module?.basePath}")
val basePythonBinaryPath = baseSdk.homePath?.let { Path.of(it) }
?: error("base python binary path is invalid: ${baseSdk.homePath}")
return runWithModalProgressBlocking(ModalTaskOwner.guess(), PyBundle.message("python.sdk.setting.up.pipenv.title")) {
setupPipEnvSdkWithProgressReport(project, selectedModule, existingSdks, newProjectPath,
baseSdk, installPackagesCheckBox.isSelected).onSuccess {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseSdk
setupPipEnvSdkWithProgressReport(moduleBasePath, existingSdks, basePythonBinaryPath, installPackagesCheckBox.isSelected).onSuccess {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseSdk.homePath
}
}.getOrNull()
}
@@ -16,11 +16,8 @@ import com.intellij.python.community.execService.execGetStdout
import com.intellij.python.community.impl.poetry.poetryPath
import com.intellij.python.pyproject.PY_PROJECT_TOML
import com.intellij.util.SystemProperties
import com.jetbrains.python.PyBundle
import com.jetbrains.python.*
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrNull
import com.jetbrains.python.isSuccess
import com.jetbrains.python.onFailure
import com.jetbrains.python.packaging.PyPackage
import com.jetbrains.python.packaging.PyRequirement
import com.jetbrains.python.packaging.PyRequirementParser
@@ -41,7 +38,6 @@ import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.SystemDependent
import org.jetbrains.annotations.SystemIndependent
import java.nio.file.Path
import kotlin.io.path.exists
@@ -114,32 +110,30 @@ suspend fun runPoetryWithSdk(sdk: Sdk, vararg args: String): PyResult<String> {
* @return the path to the poetry environment.
*/
@Internal
suspend fun setupPoetry(projectPath: Path, python: String?, installPackages: Boolean, init: Boolean): PyResult<@SystemDependent String> {
suspend fun setupPoetry(projectPath: Path, basePythonBinaryPath: PythonBinary?, installPackages: Boolean, init: Boolean): PyResult<PythonHomePath> {
if (init) {
runPoetry(projectPath, *listOf("init", "-n").toTypedArray())
.getOr { return it }
runPoetry(projectPath, *listOf("init", "-n").toTypedArray()).getOr { return it }
if (python != null) { // Replace a python version in toml
ExecService().execGetStdout(BinOnEel(Path.of(python), workDir = projectPath), Args("-c", REPLACE_PYTHON_VERSION))
.getOr { return it }
if (basePythonBinaryPath != null) { // Replace a python version in toml
ExecService().execGetStdout(
binary = BinOnEel(path = basePythonBinaryPath, workDir = projectPath),
args = Args("-c", REPLACE_PYTHON_VERSION)
).getOr { return it }
}
}
val env = if (python != null) {
runPoetry(projectPath, "env", "use", python)
if (basePythonBinaryPath != null) {
runPoetry(projectPath, "env", "use", basePythonBinaryPath.pathString).getOr { return it }
}
else {
runPoetry(projectPath, "run", "python", "-V")
runPoetry(projectPath, "run", "python", "-V").getOr { return it }
}
env.onFailure { return PyResult.failure(it) }
if (installPackages) {
runPoetry(projectPath, "install")
.onFailure { return PyResult.failure(it) }
runPoetry(projectPath, "install").getOr { return it }
}
return runPoetry(projectPath, "env", "info", "-p")
return runPoetry(projectPath, "env", "info", "-p").mapSuccess { Path.of(it) }
}
internal suspend fun detectPoetryEnvs(module: Module?, existingSdkPaths: Set<String>?, projectPath: @SystemIndependent @NonNls String?): List<PyDetectedSdk> {
@@ -6,22 +6,18 @@ import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.python.pyproject.PY_PROJECT_TOML
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.util.PathUtil
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.icons.PythonIcons
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
import com.jetbrains.python.poetry.getPyProjectTomlForPoetry
import com.jetbrains.python.resolvePythonBinary
import com.jetbrains.python.sdk.PythonSdkUtil
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.createSdk
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import com.jetbrains.python.venvReader.tryResolvePath
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
import java.nio.file.Path
import java.util.regex.Pattern
@@ -34,44 +30,32 @@ val POETRY_ICON: Icon = PythonIcons.Python.Origami
@Internal
fun suggestedSdkName(basePath: Path): @NlsSafe String = "Poetry (${PathUtil.getFileName(basePath.pathString)})"
/**
* Sets up the poetry environment under the modal progress window.
*
* The poetry is associated with the first valid object from this list:
*
* 1. New project specified by [newProjectPath]
* 2. Existing module specified by [module]
* 3. Existing project specified by [project]
*
* @return the SDK for poetry, not stored in the SDK table yet.
*/
@Internal
suspend fun setupPoetrySdk(
project: Project?,
module: Module?,
suspend fun createNewPoetrySdk(
moduleBasePath: Path,
existingSdks: List<Sdk>,
newProjectPath: String?,
python: String?,
basePythonBinaryPath: PythonBinary?,
installPackages: Boolean,
poetryPath: String? = null,
): PyResult<Sdk> {
val pathString = newProjectPath ?: module?.basePath ?: project?.basePath
val projectPath = pathString?.let { tryResolvePath(it) }
?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", pathString))
val pythonBinaryPath = setUpPoetry(moduleBasePath, basePythonBinaryPath, installPackages).getOr { return it }
val pythonExecutablePath = setUpPoetry(projectPath, python, installPackages, poetryPath)
.getOr { return it }
val result = createSdk(
sdkHomePath = pythonExecutablePath,
existingSdks = existingSdks,
associatedProjectPath = projectPath.toString(),
suggestedSdkName = suggestedSdkName(projectPath),
sdkAdditionalData = PyPoetrySdkAdditionalData(projectPath)
)
return result
return createPoetrySdk(moduleBasePath, existingSdks, pythonBinaryPath)
}
@Internal
suspend fun createPoetrySdk(
moduleBasePath: Path,
existingSdks: List<Sdk>,
pythonBinaryPath: PythonBinary,
): PyResult<Sdk> = createSdk(
pythonBinaryPath = pythonBinaryPath,
existingSdks = existingSdks,
associatedProjectPath = moduleBasePath.toString(),
suggestedSdkName = suggestedSdkName(moduleBasePath),
sdkAdditionalData = PyPoetrySdkAdditionalData(moduleBasePath)
)
internal val Sdk.isPoetry: Boolean
get() {
if (!PythonSdkUtil.isPythonSdk(this)) {
@@ -91,21 +75,12 @@ internal fun allModules(project: Project?): List<Module> {
}?.sortedBy { it.name } ?: emptyList()
}
private suspend fun setUpPoetry(projectPath: Path, python: String?, installPackages: Boolean, poetryPath: String? = null): PyResult<Path> {
val poetryExecutablePathString = when (poetryPath) {
is String -> poetryPath
else -> {
val pyProjectToml = withContext(Dispatchers.IO) {
StandardFileSystems.local().findFileByPath(projectPath.toString())?.findChild(PY_PROJECT_TOML)
}
val init = pyProjectToml?.let {
getPyProjectTomlForPoetry(it)
} == null
setupPoetry(projectPath, python, installPackages, init).getOr { return it }
}
}
return PyResult.success(Path.of(getPythonExecutable(poetryExecutablePathString)))
private suspend fun setUpPoetry(moduleBasePath: Path, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult<PythonBinary> {
val init = PyProjectToml.findInRoot(moduleBasePath) == null
val pythonHomePath = setupPoetry(moduleBasePath, basePythonBinaryPath, installPackages, init).getOr { return it }
val pythonBinaryPath = pythonHomePath.resolvePythonBinary()
?: return PyResult.localizedError(PyBundle.message("python.sdk.cannot.setup.sdk", pythonHomePath))
return PyResult.success(pythonBinaryPath)
}
fun parsePoetryShowOutdated(input: String): Map<String, PythonOutdatedPackage> {
@@ -21,6 +21,7 @@ import com.jetbrains.python.sdk.poetry.*
import com.jetbrains.python.ui.pyMayBeModalBlocking
import com.jetbrains.python.util.runWithModalBlockingOrInBackground
import java.awt.BorderLayout
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import javax.swing.Icon
@@ -75,9 +76,15 @@ class PyAddExistingPoetryEnvPanel(
return when (val sdk = sdkComboBox.selectedSdk) {
is PyDetectedSdk -> {
val mappedModule = sdkToModule[sdk.name] ?: module
runWithModalBlockingOrInBackground(project, msg = PyBundle.message("python.sdk.dialog.title.setting.up.poetry.environment")) {
setupPoetrySdk(project, mappedModule, existingSdks, newProjectPath,
getPythonExecutable(sdk.name), false, sdk.name).onSuccess {
val moduleBasePath = mappedModule?.basePath?.let { Path.of(it) }
?: error("module base path is invalid: ${mappedModule?.basePath}")
val basePythonBinaryPath = getPythonExecutable(sdk.name).let { Path.of(it) }
?: error("base python binary path is invalid, home path is ${sdk.name}")
createPoetrySdk(moduleBasePath, existingSdks, basePythonBinaryPath).onSuccess {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = getPythonExecutable(sdk.name)
}
}.getOrNull()
@@ -42,6 +42,7 @@ import kotlinx.coroutines.withContext
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.ItemEvent
import java.nio.file.Path
import javax.swing.Icon
import javax.swing.JComboBox
import javax.swing.event.DocumentEvent
@@ -139,8 +140,11 @@ class PyAddNewPoetryPanel(
override fun getOrCreateSdk(): Sdk? {
PropertiesComponent.getInstance().poetryPath = poetryPathField.text.nullize()
return pyModalBlocking {
setupPoetrySdk(project, selectedModule, existingSdks, newProjectPath,
baseSdkField.selectedSdk.homePath, installPackagesCheckBox.isSelected).onSuccess {
val moduleBasePath = selectedModule?.basePath?.let { Path.of(it) }
?: error("select module base path is invalid: ${selectedModule?.basePath}")
val basePythonBinaryPath = baseSdkField.selectedSdk.homePath?.let { Path.of(it) }
createNewPoetrySdk(moduleBasePath, existingSdks, basePythonBinaryPath, installPackagesCheckBox.isSelected).onSuccess {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseSdkField.selectedSdk.homePath
}
}.getOrNull()