refactor [python]: Remove redundant Result.

`Result` was introduced to attach addition error information. If there is one, and only one possible error, `null` is much cleaner solution.

Just like `Map.get` doesn't return `PyResult` but `null` of no element found, one doesn't use `PyResult` for things like `detectTool` if the only two possible results are either tool or "no tool found".

GitOrigin-RevId: 969c817f84dfc9d61cc3737214deac1bc5c84327
This commit is contained in:
Ilya.Kazakevich
2025-11-13 00:25:32 +01:00
committed by intellij-monorepo-bot
parent 1de2d076f3
commit f65bb65fd7
16 changed files with 34 additions and 33 deletions

View File

@@ -42,7 +42,7 @@ class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
): EnvCheckerResult = reportRawProgress {
it.text(PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis"))
val hatchService = module.getHatchService().getOr { return EnvCheckerResult.CannotConfigure }
val canManage = if (checkToml) hatchService.isHatchManagedProject().orLogException(LOGGER) == true else true
val canManage = if (checkToml) hatchService.isHatchManagedProject() else true
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)

View File

@@ -66,7 +66,7 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
module: Module, checkExistence: CheckExistence,
): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
val pipfile = findAmongRoots(module, PipEnvFileHelper.PIP_FILE)?.name ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
val pipEnvExecutable = getPipEnvExecutable().orLogException(LOGGER) ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
val pipEnvExecutable = getPipEnvExecutable() ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
val canManage = pipEnvExecutable.isExecutable()
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", pipfile)
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
@@ -100,7 +100,7 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
}
private suspend fun askForEnvData(module: Module, source: Source, envExists: Boolean): PyAddNewPipEnvFromFilePanel.Data? {
val pipEnvExecutable = getPipEnvExecutable().orLogException(LOGGER)
val pipEnvExecutable = getPipEnvExecutable()
if ((envExists || source == Source.INSPECTION) && pipEnvExecutable?.isExecutable() == true) {
return PyAddNewPipEnvFromFilePanel.Data(pipEnvExecutable)

View File

@@ -64,7 +64,7 @@ class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
}
else true
val canManage = isPoetryProject && getPoetryExecutable().successOrNull != null
val canManage = isPoetryProject && getPoetryExecutable() != null
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)

View File

@@ -38,6 +38,6 @@ object HatchConfiguration {
return result
}
suspend fun detectHatchExecutable(eelApi: EelApi): Path? = detectTool("hatch", eelApi).successOrNull
suspend fun detectHatchExecutable(eelApi: EelApi): Path? = detectTool("hatch", eelApi)
}

View File

@@ -85,7 +85,7 @@ interface HatchService {
suspend fun syncDependencies(envName: String? = null): PyResult<String>
suspend fun isHatchManagedProject(): PyResult<Boolean>
suspend fun isHatchManagedProject(): Boolean
suspend fun createNewProject(projectName: String): PyResult<ProjectStructure>

View File

@@ -61,7 +61,7 @@ internal class CliBasedHatchService private constructor(
}
}
override suspend fun isHatchManagedProject(): PyResult<Boolean> {
override suspend fun isHatchManagedProject(): Boolean {
val isHatchManaged = withContext(Dispatchers.IO) {
when {
workingDirectoryPath.resolve("hatch.toml").exists() -> true
@@ -72,7 +72,7 @@ internal class CliBasedHatchService private constructor(
}
}
}
return Result.success(isHatchManaged)
return isHatchManaged
}

View File

@@ -20,7 +20,7 @@ import kotlin.io.path.isExecutable
import kotlin.io.path.pathString
/**
* Detects the path to a CLI tool executable in the given Eel environment.
* Detects the path to a CLI tool executable in the given Eel environment, returns `null` it no tool found
*
* Search order (first match wins):
* - [EelApi.exec.where] for the given [toolName].
@@ -45,10 +45,10 @@ suspend fun detectTool(
toolName: String,
eel: EelApi = localEel,
additionalSearchPaths: List<Path> = listOf(),
): PyResult<Path> = withContext(Dispatchers.IO) {
): Path? = withContext(Dispatchers.IO) {
val binary = eel.exec.where(toolName)?.asNioPath()
if (binary != null) {
return@withContext PyResult.success(binary)
return@withContext binary
}
val binaryName = if (eel.platform.isWindows) "$toolName.exe" else toolName
@@ -62,8 +62,8 @@ suspend fun detectTool(
}
}
paths.firstOrNull { it.isExecutable() }?.let { PyResult.success(it) }
?: PyResult.localizedError(PySdkBundle.message("cannot.find.executable", binaryName, localEel.descriptor.machine.name))
paths.firstOrNull { it.isExecutable() }
}
private fun MutableList<Path>.addUnixPaths(eel: EelApi, binaryName: String) {

View File

@@ -33,9 +33,7 @@ import kotlin.io.path.name
private class PoetrySelectSdkProvider() : EvoSelectSdkProvider {
override fun getTreeElement(evoModuleSdk: EvoModuleSdk): EvoTreeElement = EvoTreeLazyNodeElement("Poetry", PythonIcons.Python.Origami) {
getPoetryExecutable().getOr {
return@EvoTreeLazyNodeElement it
}
getPoetryExecutable() ?:PyResult.localizedError(PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message"))
val pyProjectTomlFile = withContext(Dispatchers.IO) {
PyProjectToml.findFile(evoModuleSdk.module)

View File

@@ -9,8 +9,9 @@ import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.execService.ProcessOutputTransformer
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.getOrNull
import com.jetbrains.python.sdk.impl.PySdkBundle
import org.jetbrains.annotations.SystemIndependent
import java.nio.file.Path
import kotlin.time.Duration.Companion.minutes
@@ -30,27 +31,29 @@ internal data class ToolCommandExecutor(
/**
* Detects the executable in `$PATH`.
*/
suspend fun detectToolExecutable(eel: EelApi): PyResult<Path> = detectTool(toolName, eel, additionalSearchPaths = getAdditionalSearchPaths(eel))
suspend fun detectToolExecutable(eel: EelApi): Path? = detectTool(toolName, eel, additionalSearchPaths = getAdditionalSearchPaths(eel))
/**
* Returns the configured executable or detects it automatically.
*/
suspend fun getToolExecutable(eel: EelApi): PyResult<Path> {
suspend fun getToolExecutable(eel: EelApi): Path? {
val toolPathFromSettings = getToolPathFromSettings(PropertiesComponent.getInstance())?.let { Path.of(it) }
if (toolPathFromSettings != null && toolPathFromSettings.getEelDescriptor() == eel.descriptor) {
return PyResult.success(toolPathFromSettings)
return toolPathFromSettings
}
return detectToolExecutable(eel)
}
suspend fun <T> runTool(dirPath: Path?, vararg args: String, transformer: ProcessOutputTransformer<T>): PyResult<T> {
val executable = getToolExecutable(dirPath?.getEelDescriptor()?.toEelApi() ?: localEel)
?: return PyResult.localizedError(PySdkBundle.message("cannot.find.executable", toolName, localEel.descriptor.machine.name))
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args, transformer = transformer)
}
}
@RequiresBackgroundThread
internal fun ToolCommandExecutor.detectToolExecutableOrNull(eel: EelApi): Path? {
return runBlockingCancellable { detectToolExecutable(eel) }.getOrNull()
}
internal suspend fun <T> ToolCommandExecutor.runTool(dirPath: Path?, vararg args: String, transformer: ProcessOutputTransformer<T>): PyResult<T> {
val executable = getToolExecutable(dirPath?.getEelDescriptor()?.toEelApi() ?: localEel).getOr { return it }
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args, transformer = transformer)
return runBlockingCancellable { detectToolExecutable(eel) }
}
internal suspend fun ToolCommandExecutor.runTool(dirPath: Path?, vararg args: String): PyResult<String> =

View File

@@ -188,7 +188,7 @@ sealed interface FileSystem<P : PathHolder> {
return pythonHome.path.resolvePythonBinary()?.let { PathHolder.Eel(it) }
}
override suspend fun which(cmd: String): PathHolder.Eel? = detectTool(cmd, eelApi).mapSuccess { PathHolder.Eel(it) }.successOrNull
override suspend fun which(cmd: String): PathHolder.Eel? = detectTool(cmd, eelApi)?.let { PathHolder.Eel(it) }
}
data class Target(

View File

@@ -27,7 +27,7 @@ class PipenvViewModel<P : PathHolder>(
defaultPathSupplier = {
when (fileSystem) {
is FileSystem.Eel -> {
if (fileSystem.eelApi == localEel) getPipEnvExecutable().getOrNull()?.let { PathHolder.Eel(it) } as P?
if (fileSystem.eelApi == localEel) getPipEnvExecutable()?.let { PathHolder.Eel(it) } as P?
else null // getPipEnvExecutable() works only with localEel currently
}
else -> null

View File

@@ -23,7 +23,7 @@ class PoetryViewModel<P : PathHolder>(
defaultPathSupplier = {
when (fileSystem) {
is FileSystem.Eel -> {
if (fileSystem.eelApi == localEel) getPoetryExecutable().getOrNull()?.let { PathHolder.Eel(it) } as P?
if (fileSystem.eelApi == localEel) getPoetryExecutable()?.let { PathHolder.Eel(it) } as P?
else null // getPoetryExecutable() works only with localEel currently
}
else -> null

View File

@@ -38,7 +38,7 @@ internal fun detectPipEnvExecutableOrNull(eel: EelApi = localEel): Path? = PIP_
* Returns the configured pipenv executable or detects it automatically.
*/
@Internal
suspend fun getPipEnvExecutable(eel: EelApi = localEel): PyResult<Path> = PIP_TOOL.getToolExecutable(eel)
suspend fun getPipEnvExecutable(eel: EelApi = localEel): Path? = PIP_TOOL.getToolExecutable(eel)
/**
* Sets up the pipenv environment under the modal progress window.

View File

@@ -29,7 +29,7 @@ class PyAddNewPipEnvFromFilePanel(private val module: Module) : JPanel() {
init {
PyPackageCoroutine.launch(project = module.project) {
pipEnvPathField.apply {
getPipEnvExecutable().getOrNull()?.pathString?.also { text = it }
getPipEnvExecutable()?.pathString?.also { text = it }
addBrowseFolderListener(module.project, withContext(Dispatchers.IO) { FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor() }
.withTitle(PyBundle.message("python.sdk.pipenv.select.executable.title")))

View File

@@ -60,7 +60,7 @@ suspend fun runPoetry(projectPath: Path?, vararg args: String): PyResult<String>
* Returns the configured poetry executable or detects it automatically.
*/
@Internal
suspend fun getPoetryExecutable(eel: EelApi = localEel): PyResult<Path> = POETRY_TOOL.getToolExecutable(eel)
suspend fun getPoetryExecutable(eel: EelApi = localEel): Path? = POETRY_TOOL.getToolExecutable(eel)
/**
* Runs poetry command for the specified Poetry SDK.

View File

@@ -54,7 +54,7 @@ private class UvCliImpl(val dispatcher: CoroutineDispatcher, val uv: Path) : UvC
}
}
suspend fun detectUvExecutable(eel: EelApi): Path? = detectTool("uv", eel).successOrNull
suspend fun detectUvExecutable(eel: EelApi): Path? = detectTool("uv", eel)
suspend fun getUvExecutable(eel: EelApi = localEel): Path? {
return PropertiesComponent.getInstance().uvPath?.takeIf { it.exists() } ?: detectUvExecutable(eel)