PY-82119 Packaging: Fix env tests for conda

Signed-off-by: Nikita.Ashihmin <nikita.ashihmin@jetbrains.com>

GitOrigin-RevId: 47286211e8ca894aa2e04c3f65528a2ab22fc37c
This commit is contained in:
Nikita.Ashihmin
2025-06-25 17:06:43 +00:00
committed by intellij-monorepo-bot
parent 3156c22dee
commit 024d2ae147
24 changed files with 115 additions and 129 deletions
@@ -51,8 +51,12 @@ public final class PyPackage extends InstalledPackage {
return myLocation;
}
/**
* @deprecated Return always true, becuase all packages found are installed.
*/
@Deprecated(forRemoval = true)
public boolean isInstalled() {
return myLocation != null;
return true;
}
@Override
@@ -6,7 +6,6 @@ import com.intellij.python.community.execService.impl.transformerToHandler
import com.intellij.python.community.execService.python.HelperName
import com.intellij.python.community.execService.python.impl.validatePythonAndGetVersionImpl
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
@@ -21,7 +20,7 @@ suspend fun <T> ExecService.executePythonAdvanced(
argsBuilder: suspend ArgsBuilder.() -> Unit = {},
options: ExecOptions = ExecOptions(),
processInteractiveHandler: ProcessInteractiveHandler<T>,
): PyExecResult<T> =
): PyResult<T> =
executeAdvanced(python.binary, {
addArgs(*python.args.toTypedArray())
argsBuilder()
@@ -39,7 +38,7 @@ suspend fun <T> ExecService.executeHelperAdvanced(
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
processOutputTransformer: ProcessOutputTransformer<T>,
): PyExecResult<T> = executePythonAdvanced(python, {
): PyResult<T> = executePythonAdvanced(python, {
addLocalFile(PythonHelpersLocator.findPathInHelpers(helper))
addArgs(*args.toTypedArray())
@@ -9,7 +9,6 @@ import com.intellij.python.community.execService.python.advancedApi.ExecutablePy
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
@@ -24,7 +23,7 @@ suspend fun ExecService.executeHelper(
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> =
): PyResult<String> =
executeHelperAdvanced(ExecutablePython.vanillaExecutablePython(python), helper, args, options, procListener, ZeroCodeStdoutTransformer)
/**
@@ -6,7 +6,7 @@ import com.intellij.platform.eel.channels.EelSendChannel
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.python.community.execService.impl.ProcessSemiInteractiveHandlerImpl
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.coroutines.Deferred
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CheckReturnValue
@@ -41,7 +41,7 @@ interface ExecService {
argsBuilder: suspend ArgsBuilder.() -> Unit = {},
options: ExecOptions = ExecOptions(),
processInteractiveHandler: ProcessInteractiveHandler<T>,
): PyExecResult<T>
): PyResult<T>
}
@@ -15,7 +15,6 @@ import com.intellij.python.community.execService.impl.PyExecBundle
import com.intellij.python.community.execService.impl.transformerToHandler
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import org.jetbrains.annotations.CheckReturnValue
import org.jetbrains.annotations.Nls
@@ -38,7 +37,7 @@ suspend fun ExecService.execGetStdout(
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> = execute(
): PyResult<String> = execute(
binary = binary,
args = args,
options = options,
@@ -74,7 +73,7 @@ suspend fun ExecService.execGetStdoutInShell(
args: List<String> = emptyList(),
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
): PyExecResult<String> {
): PyResult<String> {
val (shell, arg) = eelApi.exec.getShell()
return execGetStdout(shell.asNioPath(), listOf(arg, commandForShell) + args, options, procListener)
}
@@ -94,7 +93,7 @@ suspend fun <T> ExecService.execute(
options: ExecOptions = ExecOptions(),
procListener: PyProcessListener? = null,
processOutputTransformer: ProcessOutputTransformer<T>,
): PyExecResult<T> {
): PyResult<T> {
return reportRawProgress { reporter ->
val ansiDecoder = AnsiEscapeDecoder()
val listener = procListener ?: PyProcessListener {
@@ -6,6 +6,7 @@ import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelProcess
import com.intellij.platform.eel.ExecuteProcessException
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.path.EelPathException
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.utils.EelPathUtils
@@ -18,6 +19,7 @@ import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.ExecErrorReason
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.errorProcessing.failure
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
@@ -34,12 +36,18 @@ import kotlin.time.Duration
internal object ExecServiceImpl : ExecService {
override suspend fun <T> executeAdvanced(binary: Path, argsBuilder: suspend ArgsBuilder.() -> Unit, options: ExecOptions, processInteractiveHandler: ProcessInteractiveHandler<T>): PyExecResult<T> {
override suspend fun <T> executeAdvanced(binary: Path, argsBuilder: suspend ArgsBuilder.() -> Unit, options: ExecOptions, processInteractiveHandler: ProcessInteractiveHandler<T>): PyResult<T> {
val args = ArgsBuilderImpl(binary.getEelDescriptor().toEelApi()).apply { argsBuilder() }.args
val description = options.processDescription
?: PyExecBundle.message("py.exec.defaultName.process", (listOf(binary.pathString) + args).joinToString(" "))
val executableProcess = EelExecutableProcess(binary.asEelPath(), args, options.env, options.workingDirectory, description)
val eelPath = try {
binary.asEelPath()
}
catch (e: EelPathException) {
return PyExecResult.localizedError(e.localizedMessage)
}
val executableProcess = EelExecutableProcess(eelPath, args, options.env, options.workingDirectory, description)
val eelProcess = executableProcess.run().getOr { return it }
val result = try {
@@ -13,6 +13,7 @@ import com.intellij.platform.testFramework.junit5.eel.params.api.TestApplication
import com.intellij.python.community.execService.*
import com.intellij.testFramework.common.timeoutRunBlocking
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.getOrThrow
import kotlinx.coroutines.*
import org.hamcrest.CoreMatchers
@@ -194,7 +195,7 @@ class ExecServiceShowCaseTest {
is Result.Failure -> {
assertFalse(sunny, "Unexpected failure ${result.error}")
assertThat("Wrong message to user", result.error.message, CoreMatchers.containsString(messageToUser))
assertEquals(shell, result.error.exe.asNioPath(), "Wrong exe")
assertEquals(shell, (result.error as ExecError).exe.asNioPath(), "Wrong exe")
}
is Result.Success -> {
assertTrue(sunny, "Unexpected success")
@@ -211,7 +212,7 @@ class ExecServiceShowCaseTest {
when (val output = ExecService().execGetStdout(binary, listOf(arg))) {
is Result.Success -> fail("Execution of bad command should lead to an error")
is Result.Failure -> {
val err = output.error
val err = (output.error as ExecError)
assertEquals(binary, err.exe.asNioPath(), "Wrong command reported")
assertEquals("foo", err.args[0], "Wrong args reported")
}
@@ -2,7 +2,6 @@
package com.intellij.python.hatch.cli
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.getOr
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResultInfo
import com.intellij.platform.eel.provider.utils.sendWholeText
import com.intellij.platform.eel.provider.utils.stderrString
@@ -12,7 +11,6 @@ import com.intellij.python.hatch.PyHatchBundle
import com.intellij.python.hatch.runtime.HatchConstants
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import io.github.z4kn4fein.semver.Version
import io.github.z4kn4fein.semver.VersionFormatException
@@ -22,7 +20,7 @@ import java.nio.file.Path
/**
* Handles hatch-specific errors, runs [transformer] only on outputs with codes 0 or 1 without tracebacks.
*/
private suspend fun <T> HatchRuntime.executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): PyExecResult<T> {
private suspend fun <T> HatchRuntime.executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): PyResult<T> {
val errorHandlerTransformer: ProcessOutputTransformer<T> = { output ->
when {
output.exitCode !in 0..1 -> Result.failure(null)
@@ -42,7 +40,7 @@ private suspend fun <T> HatchRuntime.executeAndMatch(
expectedOutput: Regex,
outputContentSupplier: (EelProcessExecutionResultInfo) -> String = { it.stdoutString },
transformer: (MatchResult) -> Result<T, @NlsSafe String?>,
): PyExecResult<T> {
): PyResult<T> {
return this.executeAndHandleErrors(*arguments) { processOutput ->
if (processOutput.exitCode != 0) return@executeAndHandleErrors Result.failure(null)
@@ -61,11 +59,11 @@ sealed class HatchCommand(private val command: Array<String>, protected val runt
@Suppress("unused")
constructor(command: String, runtime: HatchRuntime) : this(arrayOf(command), runtime)
protected suspend fun <T> executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): PyExecResult<T> {
protected suspend fun <T> executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): PyResult<T> {
return runtime.executeAndHandleErrors(*command, *arguments, transformer = transformer)
}
protected suspend fun <T> executeAndMatch(vararg arguments: String, expectedOutput: Regex, transformer: (MatchResult) -> Result<T, @NlsSafe String?>): PyExecResult<T> {
protected suspend fun <T> executeAndMatch(vararg arguments: String, expectedOutput: Regex, transformer: (MatchResult) -> Result<T, @NlsSafe String?>): PyResult<T> {
return runtime.executeAndMatch(*command, *arguments, expectedOutput = expectedOutput, transformer = transformer)
}
}
@@ -74,12 +72,12 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Build a project
*/
fun build(): PyExecResult<Unit> = TODO()
fun build(): PyResult<Unit> = TODO()
/**
* Remove build artifacts
*/
fun clean(): PyExecResult<Unit> = TODO()
fun clean(): PyResult<Unit> = TODO()
/**
* Manage the config file
@@ -99,7 +97,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Format and lint source code
*/
fun fmt(): PyExecResult<Unit> = TODO()
fun fmt(): PyResult<Unit> = TODO()
/**
* Create or initialize a project.
@@ -128,7 +126,8 @@ class HatchCli(private val runtime: HatchRuntime) {
if (initExistingProject) {
try {
eelProcess.sendWholeText("$projectName\n")
} catch (error: IOException) {
}
catch (error: IOException) {
return@executeInteractive Result.failure("Failed to write to process: ${error.localizedMessage}")
}
}
@@ -144,7 +143,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Publish build artifacts
*/
fun publish(): PyExecResult<Unit> = TODO()
fun publish(): PyResult<Unit> = TODO()
/**
* Manage Python installations
@@ -154,7 +153,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Run commands within project environments
*/
suspend fun run(envName: String? = null, vararg command: String): PyExecResult<String> {
suspend fun run(envName: String? = null, vararg command: String): PyResult<String> {
val envRuntime = envName?.let { runtime.withEnv(HatchConstants.AppEnvVars.ENV to it) } ?: runtime
return envRuntime.executeAndHandleErrors("run", *command) { output ->
if (output.exitCode != 0) return@executeAndHandleErrors Result.failure(null)
@@ -175,14 +174,14 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Enter a shell within a project's environment
*/
fun shell(): PyExecResult<Unit> = TODO()
fun shell(): PyResult<Unit> = TODO()
data class HatchStatus(val project: String, val location: Path, val config: Path)
/**
* Show information about the current environment
*/
suspend fun status(): PyExecResult<HatchStatus> {
suspend fun status(): PyResult<HatchStatus> {
val expectedOutput = """^\[Project] - (.*)\n\[Location] - (.*)\n\[Config] - (.*)\n$""".toRegex()
return runtime.executeAndMatch("status", expectedOutput = expectedOutput, outputContentSupplier = { it.stderrString }) { matchResult ->
@@ -199,14 +198,14 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Run tests
*/
fun test(): PyExecResult<Unit> = TODO()
fun test(): PyResult<Unit> = TODO()
/**
* View a project's version.
*
* @return Project Version
*/
suspend fun getVersion(): PyExecResult<Version> {
suspend fun getVersion(): PyResult<Version> {
return runtime.executeAndHandleErrors("version") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdoutString?.trim()
?: return@executeAndHandleErrors Result.failure(null)
@@ -3,7 +3,7 @@ package com.intellij.python.hatch.cli
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
/**
* Manage environment dependencies
@@ -12,28 +12,28 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
/**
* Open the config location in your file manager
*/
suspend fun explore(): PyExecResult<String> {
suspend fun explore(): PyResult<String> {
return executeAndHandleErrors("explore", transformer = ZeroCodeStdoutTransformer)
}
/**
* Show the location of the config file
*/
suspend fun find(): PyExecResult<String> {
suspend fun find(): PyResult<String> {
return executeAndHandleErrors("find", transformer = ZeroCodeStdoutTransformer)
}
/**
* Restore the config file to default settings
*/
suspend fun restore(): PyExecResult<String> {
suspend fun restore(): PyResult<String> {
return executeAndHandleErrors("restore", transformer = ZeroCodeStdoutTransformer)
}
/**
* Assign values to config file entries
*/
suspend fun set(key: String, value: String): PyExecResult<String> {
suspend fun set(key: String, value: String): PyResult<String> {
return executeAndHandleErrors("set", key, value, transformer = ZeroCodeStdoutTransformer)
}
@@ -42,7 +42,7 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
*
* @param all Do not scrub secret fields
*/
suspend fun show(all: Boolean? = null): PyExecResult<String> {
suspend fun show(all: Boolean? = null): PyResult<String> {
val options = listOf(all to "--all").makeOptions()
return executeAndHandleErrors("show", *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -50,7 +50,7 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
/**
* Update the config file with any new fields
*/
suspend fun update(): PyExecResult<String> {
suspend fun update(): PyResult<String> {
return executeAndHandleErrors("update", transformer = ZeroCodeStdoutTransformer)
}
}
@@ -3,7 +3,7 @@ package com.intellij.python.hatch.cli
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
enum class Scope(val options: Array<String>) {
All(emptyArray()),
@@ -18,7 +18,7 @@ class HatchDep(runtime: HatchRuntime) : HatchCommand("dep", runtime) {
/**
* Output a hash of the currently defined dependencies
**/
suspend fun hash(scope: Scope = Scope.All): PyExecResult<String> {
suspend fun hash(scope: Scope = Scope.All): PyResult<String> {
return executeAndHandleErrors("hash", *scope.options, transformer = ZeroCodeStdoutTransformer)
}
@@ -37,7 +37,7 @@ class HatchDepShow(runtime: HatchRuntime) : HatchCommand(arrayOf("dep", "show"),
*
* @param features only show the dependencies of the specified features
*/
suspend fun requirements(scope: Scope = Scope.All, features: List<String>? = null): PyExecResult<String> {
suspend fun requirements(scope: Scope = Scope.All, features: List<String>? = null): PyResult<String> {
val options = features?.flatMap { listOf("--feature", it) }?.toTypedArray() ?: arrayOf("--all")
return executeAndHandleErrors("requirements", *scope.options, *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -45,7 +45,7 @@ class HatchDepShow(runtime: HatchRuntime) : HatchCommand(arrayOf("dep", "show"),
/**
* Enumerate dependencies in a tabular format.
*/
suspend fun table(scope: Scope = Scope.All): PyExecResult<String> {
suspend fun table(scope: Scope = Scope.All): PyResult<String> {
val options = listOf(null to "--lines", true to "--ascii").makeOptions()
return executeAndHandleErrors("table", *scope.options, *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -8,7 +8,7 @@ import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -130,7 +130,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
*
* @return true if created, false if already exists
*/
suspend fun create(envName: String? = null): PyExecResult<CreateResult> {
suspend fun create(envName: String? = null): PyResult<CreateResult> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("create", *arguments) {
val actualEnvName = envName ?: DEFAULT_ENV_NAME
@@ -148,7 +148,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
*
* @return path to environment
*/
suspend fun find(envName: String? = null): PyExecResult<PythonHomePath?> {
suspend fun find(envName: String? = null): PyResult<PythonHomePath?> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("find", *arguments) {
when (it.exitCode) {
@@ -183,7 +183,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* - [RemoveResult.CantRemoveActiveEnvironment] if the environment cannot be removed because it is currently active.
* - An error wrapped in [ExecError] in case of execution failure.
*/
suspend fun remove(envName: String? = null): PyExecResult<RemoveResult> {
suspend fun remove(envName: String? = null): PyResult<RemoveResult> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("remove", *arguments) {
val actualEnvName = envName ?: DEFAULT_ENV_NAME
@@ -205,7 +205,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* - [HatchDetailedEnvironments] if operation is successful.
* - An error wrapped in [ExecError] if an execution failure occurs.
*/
suspend fun showWithDetails(vararg envs: String): PyExecResult<HatchDetailedEnvironments> {
suspend fun showWithDetails(vararg envs: String): PyResult<HatchDetailedEnvironments> {
return executeAndHandleErrors("show", "--json", *envs) { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdoutString
?: return@executeAndHandleErrors Result.failure(null)
@@ -232,7 +232,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* - [HatchDetailedEnvironments] if operation is successful.
* - An error wrapped in [ExecError] if an execution failure occurs.
*/
suspend fun show(vararg envs: String, internal: Boolean = false): PyExecResult<HatchEnvironments> {
suspend fun show(vararg envs: String, internal: Boolean = false): PyResult<HatchEnvironments> {
val options = listOf(internal to "--internal").makeOptions()
return executeAndMatch("show", "--ascii", *options, *envs, expectedOutput = SHOW_RESPONSE_REGEX) { matchResult ->
@@ -4,7 +4,7 @@ package com.intellij.python.hatch.cli
import com.intellij.platform.eel.provider.utils.stdoutString
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -62,7 +62,7 @@ class HatchProject(runtime: HatchRuntime) : HatchCommand("project", runtime) {
/**
* Display project metadata
*/
suspend fun metadata(): PyExecResult<Metadata> {
suspend fun metadata(): PyResult<Metadata> {
return executeAndHandleErrors("metadata") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdoutString
?: return@executeAndHandleErrors Result.failure(null)
@@ -8,7 +8,7 @@ import com.intellij.platform.eel.provider.utils.stdoutString
import com.intellij.python.hatch.cli.HatchPython.PythonInstallResponse.AbortReason
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import java.nio.file.Path
/**
@@ -26,7 +26,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
* @param parent Show the parent directory of the Python binary
* @param dir The directory in which distributions reside
*/
suspend fun find(name: String, parent: Boolean? = null, dir: String? = null): PyExecResult<Path?> {
suspend fun find(name: String, parent: Boolean? = null, dir: String? = null): PyResult<Path?> {
val options = listOf(parent to "--parent").makeOptions() + buildDirOption(dir)
return executeAndHandleErrors("find", *options, name) { output ->
@@ -129,7 +129,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
private: Boolean? = null,
update: Boolean? = null,
dir: String? = null,
): PyExecResult<PythonInstallResponse> {
): PyResult<PythonInstallResponse> {
val options = listOf(update to "--update", private to "--private").makeOptions() + buildDirOption(dir)
return executeAndHandleErrors("install", *options, *names) { output ->
Result.success(parsePythonInstallCommandOutput(output))
@@ -144,7 +144,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
* @param names Distributions to remove, you may select `all` to install all compatible distributions
* @param dir The directory in which distributions reside
*/
suspend fun remove(vararg names: String = ALL_NAMES, dir: String? = null): PyExecResult<PythonRemoveResponse> {
suspend fun remove(vararg names: String = ALL_NAMES, dir: String? = null): PyResult<PythonRemoveResponse> {
return executeAndHandleErrors("remove", *buildDirOption(dir), *names) { processOutput ->
val output = processOutput.stderrString
val notInstalledRegex = Regex("""^Distribution is not installed: (.*)$""", RegexOption.MULTILINE)
@@ -165,7 +165,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
* @param dir The directory in which distributions reside
* @return Name to Version as a map
*/
suspend fun show(dir: String? = null): PyExecResult<ShowResponse> {
suspend fun show(dir: String? = null): PyResult<ShowResponse> {
val nameToVersionRegex = """\|\s+([^|\s]+)\s+\|\s+([^|\s]+)\s+\|""".toRegex()
fun parseNameToVersions(payload: String) = nameToVersionRegex.findAll(payload).associate {
val (name, version) = it.destructured
@@ -200,7 +200,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
* @param names Distributions to update, you may select `all` to install all compatible distributions
* @param dir The directory in which distributions reside
*/
suspend fun update(vararg names: String = ALL_NAMES, dir: String? = null): PyExecResult<PythonInstallResponse> {
suspend fun update(vararg names: String = ALL_NAMES, dir: String? = null): PyResult<PythonInstallResponse> {
return executeAndHandleErrors("update", *buildDirOption(dir), *names) { output ->
Result.success(parsePythonInstallCommandOutput(output))
}
@@ -7,7 +7,7 @@ import com.intellij.python.hatch.runtime.HatchRuntime
import com.intellij.util.Url
import com.intellij.util.Urls
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
/**
* Manage environment dependencies
@@ -17,7 +17,7 @@ class HatchSelf(runtime: HatchRuntime) : HatchCommand("self", runtime) {
/**
* Generate a pre-populated GitHub issue.
*/
suspend fun report(): PyExecResult<Url> {
suspend fun report(): PyResult<Url> {
return executeAndHandleErrors("report", "--no-open") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdoutString?.trim()
?: return@executeAndHandleErrors Result.failure(null)
@@ -34,10 +34,10 @@ class HatchSelf(runtime: HatchRuntime) : HatchCommand("self", runtime) {
/**
* Restore the installation
*/
fun restore(): PyExecResult<String> = TODO()
fun restore(): PyResult<String> = TODO()
/**
* Install the latest version
*/
fun update(): PyExecResult<String> = TODO()
fun update(): PyResult<String> = TODO()
}
@@ -8,7 +8,6 @@ import com.intellij.python.hatch.cli.HatchCli
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.resolvePythonBinary
import java.nio.file.Path
@@ -55,11 +54,11 @@ class HatchRuntime(
* Pure execution of [hatchBinary] with command line [arguments] and [execOptions] by [execService]
* Doesn't make any validation of stdout/stderr content.
*/
internal suspend fun <T> execute(vararg arguments: String, processOutputTransformer: ProcessOutputTransformer<T>): PyExecResult<T> {
internal suspend fun <T> execute(vararg arguments: String, processOutputTransformer: ProcessOutputTransformer<T>): PyResult<T> {
return execService.execute(hatchBinary, arguments.toList(), execOptions, processOutputTransformer = processOutputTransformer)
}
internal suspend fun <T> executeInteractive(vararg arguments: String, processSemiInteractiveFun: ProcessSemiInteractiveFun<T>): PyExecResult<T> {
internal suspend fun <T> executeInteractive(vararg arguments: String, processSemiInteractiveFun: ProcessSemiInteractiveFun<T>): PyResult<T> {
return execService.executeAdvanced(hatchBinary, { addArgs(*arguments) }, execOptions, processSemiInteractiveHandler(code = processSemiInteractiveFun))
}
@@ -5,7 +5,7 @@ import com.intellij.python.community.execService.ExecOptions
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.execGetStdout
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import org.jetbrains.annotations.ApiStatus.Internal
import java.nio.file.Path
import kotlin.time.Duration
@@ -27,7 +27,7 @@ suspend fun runExecutableWithProgress(
timeout: Duration = 10.minutes,
env: Map<String, String> = emptyMap(),
vararg args: String,
): PyExecResult<String> {
): PyResult<String> {
val execOptions = ExecOptions(workingDirectory = workDir, timeout = timeout, env = env)
return ExecService().execGetStdout(executable, args.toList(), execOptions)
}
+10 -11
View File
@@ -1,7 +1,6 @@
// 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.uv
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
@@ -12,7 +11,7 @@ import java.nio.file.Path
@ApiStatus.Internal
interface UvCli {
suspend fun runUv(workingDir: Path, vararg args: String): PyExecResult<String>
suspend fun runUv(workingDir: Path, vararg args: String): PyResult<String>
}
@ApiStatus.Internal
@@ -24,26 +23,26 @@ interface UvLowLevel {
/**
* Manage project dependencies by adding/removing them to the project along side installation
*/
suspend fun addDependency(pyPackages: PythonPackageInstallRequest, options: List<String>): PyExecResult<Unit>
suspend fun removeDependencies(pyPackages: Array<out String>): PyExecResult<Unit>
suspend fun addDependency(pyPackages: PythonPackageInstallRequest, options: List<String>): PyResult<Unit>
suspend fun removeDependencies(pyPackages: Array<out String>): PyResult<Unit>
/**
* Managing environment packages directly w/o depending or changing the project
*/
suspend fun installPackage(name: PythonPackageInstallRequest, options: List<String>): PyExecResult<Unit>
suspend fun uninstallPackages(pyPackages: Array<out String>): PyExecResult<Unit>
suspend fun installPackage(name: PythonPackageInstallRequest, options: List<String>): PyResult<Unit>
suspend fun uninstallPackages(pyPackages: Array<out String>): PyResult<Unit>
suspend fun listPackages(): PyExecResult<List<PythonPackage>>
suspend fun listPackages(): PyResult<List<PythonPackage>>
suspend fun listOutdatedPackages(): PyResult<List<PythonOutdatedPackage>>
suspend fun listTopLevelPackages(): PyResult<List<PythonPackage>>
suspend fun listPackageRequirements(name: PythonPackage): PyResult<List<NormalizedPythonPackageName>>
suspend fun listPackageRequirementsTree(name: PythonPackage): PyResult<String>
suspend fun isProjectSynced(inexact: Boolean): PyExecResult<Boolean>
suspend fun isScriptSynced(inexact: Boolean, scriptPath: Path): PyExecResult<ScriptSyncCheckResult>
suspend fun isProjectSynced(inexact: Boolean): PyResult<Boolean>
suspend fun isScriptSynced(inexact: Boolean, scriptPath: Path): PyResult<ScriptSyncCheckResult>
suspend fun sync(): PyExecResult<String>
suspend fun lock(): PyExecResult<String>
suspend fun sync(): PyResult<String>
suspend fun lock(): PyResult<String>
}
@ApiStatus.Internal
@@ -7,7 +7,7 @@ import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.SystemProperties
import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.PyExecResult
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.pathValidation.PlatformAndRoot
import com.jetbrains.python.pathValidation.ValidationRequest
import com.jetbrains.python.pathValidation.validateExecutableFile
@@ -40,7 +40,7 @@ private fun validateUvExecutable(uvPath: Path?): ValidationInfo? {
))
}
private suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): PyExecResult<String> {
private suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): PyResult<String> {
return runExecutableWithProgress(uv, workingDir, 10.minutes, args = args)
}
@@ -57,7 +57,7 @@ private class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?) : Uv
uv = path!!
}
override suspend fun runUv(workingDir: Path, vararg args: String): PyExecResult<String> {
override suspend fun runUv(workingDir: Path, vararg args: String): PyResult<String> {
return withContext(dispatcher) {
runUv(uv, workingDir, *args)
}
@@ -93,7 +93,7 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
return PyResult.success(pythons)
}
override suspend fun listPackages(): PyExecResult<List<PythonPackage>> {
override suspend fun listPackages(): PyResult<List<PythonPackage>> {
val out = uvCli.runUv(cwd, "pip", "list", "--format", "json")
.getOr { return it }
@@ -149,14 +149,14 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
return PyExecResult.success(out)
}
override suspend fun installPackage(name: PythonPackageInstallRequest, options: List<String>): PyExecResult<Unit> {
override suspend fun installPackage(name: PythonPackageInstallRequest, options: List<String>): PyResult<Unit> {
uvCli.runUv(cwd, "pip", "install", *name.formatPackageName(), *options.toTypedArray())
.getOr { return it }
return PyExecResult.success(Unit)
}
override suspend fun uninstallPackages(pyPackages: Array<out String>): PyExecResult<Unit> {
override suspend fun uninstallPackages(pyPackages: Array<out String>): PyResult<Unit> {
// TODO: check if package is in dependencies and reject it
uvCli.runUv(cwd, "pip", "uninstall", *pyPackages)
.getOr { return it }
@@ -164,21 +164,21 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
return PyExecResult.success(Unit)
}
override suspend fun addDependency(pyPackages: PythonPackageInstallRequest, options: List<String>): PyExecResult<Unit> {
override suspend fun addDependency(pyPackages: PythonPackageInstallRequest, options: List<String>): PyResult<Unit> {
uvCli.runUv(cwd, "add", *pyPackages.formatPackageName(), *options.toTypedArray())
.getOr { return it }
return PyExecResult.success(Unit)
}
override suspend fun removeDependencies(pyPackages: Array<out String>): PyExecResult<Unit> {
override suspend fun removeDependencies(pyPackages: Array<out String>): PyResult<Unit> {
uvCli.runUv(cwd, "remove", *pyPackages)
.getOr { return it }
return PyExecResult.success(Unit)
}
override suspend fun isProjectSynced(inexact: Boolean): PyExecResult<Boolean> {
override suspend fun isProjectSynced(inexact: Boolean): PyResult<Boolean> {
val args = constructSyncArgs(inexact)
uvCli.runUv(cwd, *args.toTypedArray())
@@ -195,7 +195,7 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
return PyExecResult.success(true)
}
override suspend fun isScriptSynced(inexact: Boolean, scriptPath: Path): PyExecResult<ScriptSyncCheckResult> {
override suspend fun isScriptSynced(inexact: Boolean, scriptPath: Path): PyResult<ScriptSyncCheckResult> {
val args = constructSyncArgs(inexact) + listOf("--script", scriptPath.pathString)
uvCli.runUv(cwd, *args.toTypedArray())
@@ -254,11 +254,11 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
return pythons
}
override suspend fun sync(): PyExecResult<String> {
override suspend fun sync(): PyResult<String> {
return uvCli.runUv(cwd, "sync")
}
override suspend fun lock(): PyExecResult<String> {
override suspend fun lock(): PyResult<String> {
return uvCli.runUv(cwd, "lock")
}
@@ -299,8 +299,7 @@ fun createUvLowLevel(cwd: Path, uvCli: UvCli = createUvCli()): UvLowLevel {
private fun tryExtractStderr(err: PyError): String? =
when (err) {
is ExecError -> {
val errorReason = err.errorReason
when (errorReason) {
when (val errorReason = err.errorReason) {
is ExecErrorReason.UnexpectedProcessTermination -> String(errorReason.stderr)
else -> null
}
@@ -115,16 +115,15 @@ public class PyPackagingTest extends PyEnvTestCase {
manager.install(Arrays.asList(pyRequirement("Markdown", PyRequirementRelation.LTE, "3.3.3"), pyRequirement("httplib2",null)),
Collections.emptyList());
final List<PyPackage> packages2 = manager.refreshAndGetPackages(false);
final PyPackage markdown2 = findPackage("Markdown", packages2);
final PyPackage markdown2 = findPackage("markdown", packages2);
assertNotNull(markdown2);
assertTrue(markdown2.isInstalled());
final PyPackage pip1 = findPackage("pip", packages1);
assertNotNull(pip1);
assertEquals("pip", pip1.getName());
manager.uninstall(Collections.singletonList(pip1));
manager.uninstall(Collections.singletonList(markdown2));
final List<PyPackage> packages3 = manager.refreshAndGetPackages(false);
final PyPackage pip2 = findPackage("pip", packages3);
assertNull(pip2);
final PyPackage markdown3 = findPackage("markdown", packages3);
assertNull(markdown3);
}
catch (ExecutionException e) {
throw new RuntimeException(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()), e);
@@ -1,16 +1,14 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.env.python.conda
import com.intellij.execution.processTools.getResultStdoutStr
import com.intellij.execution.target.TargetProgressIndicator
import com.intellij.execution.target.TargetedCommandLineBuilder
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
import com.intellij.util.io.delete
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import kotlinx.coroutines.runBlocking
import org.junit.rules.ExternalResource
import java.io.File
@@ -24,27 +22,20 @@ internal class CondaYamlFileRule(private val condaRule: LocalCondaRule,
lateinit var yamlFilePath: Path
private set
val envName: String = "file_${Math.random()}.yaml"
val envName: String = Math.random().toString()
override fun before() {
val fullPathOnTarget = condaRule.condaPathOnTarget
val command = PyCondaCommand(fullPathOnTarget, null, null)
val condaEnvRequest = NewCondaEnvRequest.EmptyNamedEnv(languageLevel, envName)
runBlocking {
val env = PyCondaEnvIdentity.NamedEnv(condaEnvRequest.envName)
val yamlFileText = runBlocking {
PyCondaEnv.createEnv(command, condaEnvRequest).getOrThrow()
}
val targetReq = LocalTargetEnvironmentRequest()
val builder = TargetedCommandLineBuilder(targetReq).apply {
setExePath(fullPathOnTarget)
addParameter("env")
addParameter("export")
addParameter("-n")
addParameter(envName)
}
val targetEnv = targetReq.prepareEnvironment(TargetProgressIndicator.EMPTY)
val yaml = targetEnv.createProcess(builder.build()).let { runBlocking { it.getResultStdoutStr() } }.getOrThrow()
CondaExecutor.exportEnvironmentFile(Path.of(fullPathOnTarget), env)
}.getOrThrow()
val file = File.createTempFile("ijconda", ".yaml")
file.writeText(yaml)
file.writeText(yamlFileText)
yamlFilePath = file.toPath()
}
@@ -1,7 +1,6 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.env.python.conda
import com.intellij.execution.processTools.getResultStdoutStr
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.registry.Registry
@@ -10,16 +9,14 @@ import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.common.timeoutRunBlocking
import com.jetbrains.getPythonVersion
import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.asKotlinResult
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.target.ui.PyAddCondaPanelModel
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import kotlinx.coroutines.test.runTest
import com.jetbrains.python.target.ui.PyAddCondaPanelModel
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.not
@@ -90,11 +87,9 @@ class PyAddCondaPanelModelTest {
Assert.assertNotNull("Bad conda name didn't lead to validation", model.getValidationError())
model.newEnvNameRwProperty.set(condaName)
val mockReporter = MockReporter()
val sdk = model.onCondaCreateSdkClicked(coroutineContext, mockReporter, targetConfiguration = null).getOrThrow()
val sdk = model.onCondaCreateSdkClicked(coroutineContext, null, targetConfiguration = null).getOrThrow()
val newName = ((sdk.getOrCreateAdditionalData().flavorAndData.data as PyCondaFlavorData).env.envIdentity as PyCondaEnvIdentity.NamedEnv).envName
Assert.assertEquals("Wrong conda name", condaName, newName)
Assert.assertTrue("No output provided for sink", mockReporter.out.toString().isNotEmpty())
}
@Test
@@ -22,7 +22,6 @@ import com.jetbrains.python.sdk.conda.createCondaSdkAlongWithNewEnv
import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv
import com.jetbrains.python.sdk.flavors.conda.*
import com.jetbrains.python.sdk.getOrCreateAdditionalData
import kotlinx.coroutines.test.runTest
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.*
@@ -117,7 +116,7 @@ internal class PyCondaSdkTest {
}
@Test
fun testExecuteCommandOnSdk(): Unit = timeoutRunBlocking(20.seconds) {
fun testExecuteCommandOnSdk(): Unit = timeoutRunBlocking(60.seconds) {
val condaEnv = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow().first()
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(condaEnv.envIdentity, emptyList(), projectRule.project)
val request = LocalTargetEnvironmentRequest()
@@ -1,9 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.env.python.conda
import com.intellij.execution.processTools.getResultStdout
import com.intellij.execution.processTools.getResultStdoutStr
import com.intellij.execution.processTools.mapFlat
import com.intellij.execution.target.TargetProgressIndicator
import com.intellij.execution.target.TargetedCommandLineBuilder
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
@@ -12,15 +10,13 @@ import com.intellij.openapi.util.registry.Registry
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.common.timeoutRunBlocking
import com.jetbrains.getPythonVersion
import com.jetbrains.python.errorProcessing.asKotlinResult
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.conda.loadLocalPythonCondaPath
import com.jetbrains.python.conda.saveLocalPythonCondaPath
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.flavors.conda.*
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest.EmptyNamedEnv
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest.LocalEnvByLocalEnvironmentFile
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Before
import org.junit.Rule