= ConcurrentHashMap()
companion object {
fun getInstance(project: Project): PoetryPyProjectTomlPythonVersionsService = project.service()
}
fun setVersion(moduleFile: VirtualFile, stringVersion: String) {
- modulePythonVersions[moduleFile] = PoetryPythonVersion(stringVersion)
+ modulePythonVersions[moduleFile] = PyVersionSpecifiers(stringVersion)
}
- fun getVersionString(moduleFile: VirtualFile): String = getVersion(moduleFile).stringVersion
+ fun getVersionString(moduleFile: VirtualFile): String = getVersion(moduleFile).constraintSpec
fun validateInterpretersVersions(moduleFile: VirtualFile, interpreters: Flow>?>): Flow>?> {
val version = getVersion(moduleFile)
return interpreters.map { list -> list?.filter { version.isValid(it.pythonInfo.languageLevel) } }
}
- private fun getVersion(moduleFile: VirtualFile): PoetryPythonVersion =
- modulePythonVersions[moduleFile] ?: PoetryPythonVersion("")
+ private fun getVersion(moduleFile: VirtualFile): PyVersionSpecifiers =
+ modulePythonVersions[moduleFile] ?: PyVersionSpecifiers.ANY_SUPPORTED
override fun dispose() {}
-}
-
-@Internal
-enum class VersionType {
- LESS,
- LESS_OR_EQUAL,
- EQUAL,
- MORE_OR_EQUAL,
- MORE;
-
- companion object {
- fun String.getVersionType(): VersionType? =
- when (this) {
- "<" -> LESS
- "<=" -> LESS_OR_EQUAL
- "=", "" -> EQUAL
- "^", ">=" -> MORE_OR_EQUAL
- ">" -> MORE
- else -> null
- }
- }
-}
-
-
-private fun getDefaultValueByType(type: VersionType): Int? =
- when (type) {
- VersionType.LESS, VersionType.MORE_OR_EQUAL -> 0
- VersionType.LESS_OR_EQUAL, VersionType.MORE -> 20
- VersionType.EQUAL -> null
- }
-
-private fun Triple.compare(versionTriple: Pair>): Int {
- val type = versionTriple.first
- val version = versionTriple.second
-
- return this.first.compareTo(version.first).takeIf { it != 0 }
- ?: this.second?.compareTo(version.second ?: (getDefaultValueByType(type) ?: this.second ?: 0)).takeIf { it != 0 }
- ?: this.third?.compareTo(version.third ?: (getDefaultValueByType(type) ?: this.third ?: 0)).takeIf { it != 0 }
- ?: 0
-}
-
-@Internal
-data class PoetryPythonVersion(val stringVersion: String) {
- val descriptions: List>>
-
- init {
- descriptions = parseVersion(stringVersion)
- }
-
- private fun parseVersion(versionString: String): List>> {
- if (versionString.isEmpty()) return emptyList()
- val versionParts = versionString.split(",")
- val result = mutableListOf>>()
-
- for (part in versionParts) {
- val firstDigit = part.indexOfFirst { it.isDigit() }
- if (firstDigit == -1) continue
- val type = part.substring(0, firstDigit).trim().getVersionType() ?: continue
- val version = part.substring(firstDigit).trim()
- val versionTriple = PoetryVersionValue.create(version).getOrNull()?.version
- versionTriple?.let { result.add(Pair(type, versionTriple)) }
- }
- return result
- }
-
- fun isValid(versionString: String?): Boolean {
- if (versionString.isNullOrBlank()) return false
- val baseInterpreterVersion = PoetryVersionValue.create(versionString).getOrNull()?.version ?: return false
- if (baseInterpreterVersion.first < 3 || baseInterpreterVersion.first == 3 && baseInterpreterVersion.second?.let { it < 6 } == true) return false
- for (description in descriptions) {
- val type = description.first
- val compareResult = baseInterpreterVersion.compare(description)
- when (type) {
- VersionType.LESS -> if (compareResult >= 0) return false
- VersionType.LESS_OR_EQUAL -> if (compareResult > 0) return false
- VersionType.EQUAL -> if (compareResult != 0) return false
- VersionType.MORE_OR_EQUAL -> if (compareResult < 0) return false
- VersionType.MORE -> if (compareResult <= 0) return false
- }
- }
- return true
- }
-
- fun isValid(languageLevel: LanguageLevel): Boolean {
- val languageLevelString = languageLevel.toString()
- return isValid(languageLevelString)
- }
-}
-
-@JvmInline
-value class PoetryVersionValue private constructor(val version: Triple) {
- companion object {
- fun create(versionString: String): Result {
- try {
- val integers = versionString.split(".").map { it.toInt() }
- return when (integers.size) {
- 1 -> Result.success(PoetryVersionValue(Triple(integers[0], null, null)))
- 2 -> Result.success(PoetryVersionValue(Triple(integers[0], integers[1], null)))
- 3 -> Result.success(PoetryVersionValue(Triple(integers[0], integers[1], integers[2])))
- else -> Result.failure(NumberFormatException())
- }
- }
- catch (e: NumberFormatException) {
- return Result.failure(e)
- }
- }
- }
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt b/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt
index d380724b3269..25d40d9a902f 100644
--- a/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt
+++ b/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt
@@ -14,12 +14,18 @@ import com.intellij.platform.util.progress.reportRawProgress
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
import com.intellij.python.community.impl.poetry.common.poetryPath
+import com.intellij.python.community.services.systemPython.SystemPythonService
+import com.intellij.openapi.application.readAction
+import com.intellij.openapi.vfs.findPsiFile
import com.intellij.python.pyproject.PyProjectToml
+import com.intellij.python.pyproject.psi.resolvePythonVersionSpecifiers
import com.jetbrains.python.PyBundle
+import com.jetbrains.python.packaging.PyVersionSpecifiers
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.poetry.findPoetryLock
import com.jetbrains.python.poetry.getPyProjectTomlForPoetry
+import com.jetbrains.python.projectCreation.getSystemPython
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.configuration.CheckToml
@@ -120,7 +126,23 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
)
}
val tomlFile = PyProjectToml.findFile(module)
- val poetry = setupPoetry(basePath, null, true, tomlFile == null).getOr { return@withBackgroundProgress it }
+ val versionSpecifiers = tomlFile?.let { vf ->
+ readAction { vf.findPsiFile(module.project) }?.resolvePythonVersionSpecifiers()
+ } ?: PyVersionSpecifiers.ANY_SUPPORTED
+
+ val baseSystemPython = getSystemPython(
+ confirmInstallation = { true },
+ pythonService = SystemPythonService(),
+ versionSpecifiers = versionSpecifiers,
+ ).getOr { return@withBackgroundProgress it }
+
+ val poetry = setupPoetry(
+ projectPath = basePath,
+ basePythonBinaryPath = baseSystemPython.pythonBinary,
+ installPackages = true,
+ init = tomlFile == null
+ ).getOr { return@withBackgroundProgress it }
+
val path = poetry.resolvePythonBinary()
?: return@withBackgroundProgress PyResult.localizedError(PySdkBundle.message("cannot.find.executable", "python", poetry))
diff --git a/python/src/com/jetbrains/python/projectCreation/venvWithSdkCreator.kt b/python/src/com/jetbrains/python/projectCreation/venvWithSdkCreator.kt
index 5870045fd1ab..b83c1936a7b6 100644
--- a/python/src/com/jetbrains/python/projectCreation/venvWithSdkCreator.kt
+++ b/python/src/com/jetbrains/python/projectCreation/venvWithSdkCreator.kt
@@ -27,6 +27,8 @@ import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.getOr
+import com.intellij.python.community.services.systemPython.findMatchingPython
+import com.jetbrains.python.packaging.PyVersionSpecifiers
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.configurePythonSdk
@@ -133,14 +135,13 @@ private suspend fun findExistingVenv(
}
}
-private suspend fun getSystemPython(
+internal suspend fun getSystemPython(
confirmInstallation: suspend () -> Boolean,
pythonService: SystemPythonService,
+ versionSpecifiers: PyVersionSpecifiers = PyVersionSpecifiers.ANY_SUPPORTED,
): Result {
-
-
// First, find the latest python according to strategy
- var systemPythonBinary = pythonService.findSystemPythons(forceRefresh = true).firstOrNull()
+ var systemPythonBinary = pythonService.findSystemPythons(forceRefresh = true).findMatchingPython(versionSpecifiers)
// No python found?
if (systemPythonBinary == null) {
@@ -149,7 +150,7 @@ private suspend fun getSystemPython(
?: return PyResult.localizedError(PyBundle.message("project.error.install.not.supported"))
if (confirmInstallation()) {
// Install
- when (val r = installer.installLatestPython()) {
+ when (val r = installer.installLatestPython(versionSpecifiers)) {
is Result.Failure -> {
val error = r.error
logger.warn("Python installation failed $error")
@@ -157,14 +158,14 @@ private suspend fun getSystemPython(
}
is Result.Success -> {
// Find the latest python again, after installation
- systemPythonBinary = pythonService.findSystemPythons(forceRefresh = true).firstOrNull()
+ systemPythonBinary = pythonService.findSystemPythons(forceRefresh = true).findMatchingPython(versionSpecifiers)
}
}
}
}
return if (systemPythonBinary == null) {
- return PyResult.localizedError(PyBundle.message("project.error.all.pythons.bad"))
+ PyResult.localizedError(PyBundle.message("project.error.all.pythons.bad"))
}
else {
Result.Success(systemPythonBinary)
diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt
index 7dc8dbf8ca70..d22b5af20664 100644
--- a/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt
+++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryCommandExecutor.kt
@@ -84,7 +84,7 @@ suspend fun runPoetryWithSdk(sdk: Sdk, vararg args: String): PyResult {
@Internal
suspend fun setupPoetry(
projectPath: Path,
- basePythonBinaryPath: PythonBinary?,
+ basePythonBinaryPath: PythonBinary,
installPackages: Boolean,
init: Boolean,
): PyResult {
@@ -92,25 +92,18 @@ suspend fun setupPoetry(
// Build poetry init command with Python version constraint if available
val initArgs = mutableListOf("init", "-n")
- if (basePythonBinaryPath != null) {
- // Validate Python and get version info
- val pythonInfo = basePythonBinaryPath.validatePythonAndGetInfo().getOr { return it }
- val major = pythonInfo.languageLevel.majorVersion
- val minor = pythonInfo.languageLevel.minorVersion
- // Add --python flag with caret constraint (e.g., "^3.10")
- initArgs.add("--python")
- initArgs.add("^$major.$minor")
- }
+ // Validate Python and get version info
+ val pythonInfo = basePythonBinaryPath.validatePythonAndGetInfo().getOr { return it }
+ val major = pythonInfo.languageLevel.majorVersion
+ val minor = pythonInfo.languageLevel.minorVersion
+ // Add --python flag with caret constraint (e.g., "^3.10")
+ initArgs.add("--python")
+ initArgs.add("^$major.$minor")
runPoetry(projectPath, *initArgs.toTypedArray()).getOr { return it }
}
- if (basePythonBinaryPath != null) {
- runPoetry(projectPath, "env", "use", basePythonBinaryPath.pathString).getOr { return it }
- }
- else {
- runPoetry(projectPath, "run", "python", "-V").getOr { return it }
- }
+ runPoetry(projectPath, "env", "use", basePythonBinaryPath.pathString).getOr { return it }
if (installPackages) {
runPoetry(projectPath, "install", "--no-root").getOr { return it }
diff --git a/python/src/com/jetbrains/python/sdk/poetry/poetry.kt b/python/src/com/jetbrains/python/sdk/poetry/poetry.kt
index 22f4c135346a..0e5b18907130 100644
--- a/python/src/com/jetbrains/python/sdk/poetry/poetry.kt
+++ b/python/src/com/jetbrains/python/sdk/poetry/poetry.kt
@@ -27,7 +27,7 @@ fun suggestedSdkName(basePath: Path): @NlsSafe String = "Poetry (${PathUtil.getF
@Internal
suspend fun createNewPoetrySdk(
moduleBasePath: Path,
- basePythonBinaryPath: PythonBinary?,
+ basePythonBinaryPath: PythonBinary,
installPackages: Boolean,
): PyResult {
val pythonBinaryPath = setUpPoetry(moduleBasePath, basePythonBinaryPath, installPackages).getOr { return it }
@@ -58,7 +58,7 @@ internal val Sdk.isPoetry: Boolean
return getOrCreateAdditionalData() is PyPoetrySdkAdditionalData
}
-private suspend fun setUpPoetry(moduleBasePath: Path, basePythonBinaryPath: PythonBinary?, installPackages: Boolean): PyResult {
+private suspend fun setUpPoetry(moduleBasePath: Path, basePythonBinaryPath: PythonBinary, installPackages: Boolean): PyResult {
val init = PyProjectToml.findInRoot(moduleBasePath) == null
val pythonHomePath = setupPoetry(moduleBasePath, basePythonBinaryPath, installPackages, init).getOr { return it }
val pythonBinaryPath = pythonHomePath.resolvePythonBinary()