Python: refactor PyError hierarchy, migrate to PyResult.

DO:
For upper-level (public) API use `PyResult`.
(Optionally) for low-level APIs inside your modules use python `Result<S, E>`.
Represent errors as `PyError` whenever possible.
Report `PyError` to `ErrorSink` at the top of your code.

DON'T:
Use `kotlin.Result`
Use `PyExecutionException`
Use any exception to represent user errors.

GitOrigin-RevId: 4ecf69e1fae8be9192cd33b90e0147c725a98964
This commit is contained in:
Ilya.Kazakevich
2025-04-29 00:43:56 +00:00
committed by intellij-monorepo-bot
parent d366245171
commit 803e270d45
74 changed files with 693 additions and 713 deletions
@@ -9,8 +9,8 @@ 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.PyError
import com.jetbrains.python.errorProcessing.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.PyResult
import io.github.z4kn4fein.semver.Version
import io.github.z4kn4fein.semver.VersionFormatException
import java.nio.file.Path
@@ -18,7 +18,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>): Result<T, ExecException> {
private suspend fun <T> HatchRuntime.executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): Result<T, ExecError> {
val errorHandlerTransformer: ProcessOutputTransformer<T> = { output ->
when {
output.exitCode !in 0..1 -> Result.failure(null)
@@ -38,7 +38,7 @@ private suspend fun <T> HatchRuntime.executeAndMatch(
expectedOutput: Regex,
outputContentSupplier: (ProcessOutput) -> String = ProcessOutput::getStdout,
transformer: (MatchResult) -> Result<T, @NlsSafe String?>,
): Result<T, ExecException> {
): Result<T, ExecError> {
return this.executeAndHandleErrors(*arguments) { processOutput ->
if (processOutput.exitCode != 0) return@executeAndHandleErrors Result.failure(null)
@@ -57,11 +57,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>): Result<T, ExecException> {
protected suspend fun <T> executeAndHandleErrors(vararg arguments: String, transformer: ProcessOutputTransformer<T>): Result<T, ExecError> {
return runtime.executeAndHandleErrors(*command, *arguments, transformer = transformer)
}
protected suspend fun <T> executeAndMatch(vararg arguments: String, expectedOutput: Regex, transformer: (MatchResult) -> Result<T, @NlsSafe String?>): Result<T, ExecException> {
protected suspend fun <T> executeAndMatch(vararg arguments: String, expectedOutput: Regex, transformer: (MatchResult) -> Result<T, @NlsSafe String?>): Result<T, ExecError> {
return runtime.executeAndMatch(*command, *arguments, expectedOutput = expectedOutput, transformer = transformer)
}
}
@@ -70,12 +70,12 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Build a project
*/
fun build(): Result<Unit, ExecException> = TODO()
fun build(): Result<Unit, ExecError> = TODO()
/**
* Remove build artifacts
*/
fun clean(): Result<Unit, ExecException> = TODO()
fun clean(): Result<Unit, ExecError> = TODO()
/**
* Manage the config file
@@ -95,7 +95,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Format and lint source code
*/
fun fmt(): Result<Unit, ExecException> = TODO()
fun fmt(): Result<Unit, ExecError> = TODO()
/**
* Create or initialize a project.
@@ -114,7 +114,7 @@ class HatchCli(private val runtime: HatchRuntime) {
*
* @param[initExistingProject] Initialize an existing project
*/
suspend fun new(projectName: String, location: Path? = null, initExistingProject: Boolean = false): Result<String, PyError> {
suspend fun new(projectName: String, location: Path? = null, initExistingProject: Boolean = false): PyResult<String> {
val options = listOf(
initExistingProject to "--init",
true to projectName,
@@ -136,7 +136,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Publish build artifacts
*/
fun publish(): Result<Unit, ExecException> = TODO()
fun publish(): Result<Unit, ExecError> = TODO()
/**
* Manage Python installations
@@ -146,7 +146,7 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Run commands within project environments
*/
suspend fun run(envName: String, vararg command: String): Result<String, ExecException> {
suspend fun run(envName: String, vararg command: String): Result<String, ExecError> {
val envRuntime = runtime.withEnv(HatchConstants.AppEnvVars.ENV to envName)
return envRuntime.executeAndHandleErrors("run", *command) { output ->
val scenario = output.stderr.trim()
@@ -170,14 +170,14 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Enter a shell within a project's environment
*/
fun shell(): Result<Unit, ExecException> = TODO()
fun shell(): Result<Unit, ExecError> = TODO()
data class HatchStatus(val project: String, val location: Path, val config: Path)
/**
* Show information about the current environment
*/
suspend fun status(): Result<HatchStatus, ExecException> {
suspend fun status(): Result<HatchStatus, ExecError> {
val expectedOutput = """^\[Project] - (.*)\n\[Location] - (.*)\n\[Config] - (.*)\n$""".toRegex()
return runtime.executeAndMatch("status", expectedOutput = expectedOutput, outputContentSupplier = { it.stderr }) { matchResult ->
@@ -194,14 +194,14 @@ class HatchCli(private val runtime: HatchRuntime) {
/**
* Run tests
*/
fun test(): Result<Unit, ExecException> = TODO()
fun test(): Result<Unit, ExecError> = TODO()
/**
* View a project's version.
*
* @return Project Version
*/
suspend fun getVersion(): Result<Version, ExecException> {
suspend fun getVersion(): Result<Version, ExecError> {
return runtime.executeAndHandleErrors("version") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdout?.trim()
?: return@executeAndHandleErrors Result.failure(null)
@@ -219,7 +219,7 @@ class HatchCli(private val runtime: HatchRuntime) {
*
* @return OldVersion to NewVersion as Pair
*/
suspend fun setVersion(desiredVersion: String): Result<Pair<Version, Version>, PyError> {
suspend fun setVersion(desiredVersion: String): PyResult<Pair<Version, Version>> {
val expectedOutput = """^Old: (.*)\nNew: (.*)\n$""".toRegex()
return runtime.executeAndMatch("version", desiredVersion, expectedOutput = expectedOutput, outputContentSupplier = { it.stderr }) { matchResult ->
@@ -4,7 +4,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.Result
import com.jetbrains.python.errorProcessing.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
/**
* Manage environment dependencies
@@ -13,28 +13,28 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
/**
* Open the config location in your file manager
*/
suspend fun explore(): Result<String, ExecException> {
suspend fun explore(): Result<String, ExecError> {
return executeAndHandleErrors("explore", transformer = ZeroCodeStdoutTransformer)
}
/**
* Show the location of the config file
*/
suspend fun find(): Result<String, ExecException> {
suspend fun find(): Result<String, ExecError> {
return executeAndHandleErrors("find", transformer = ZeroCodeStdoutTransformer)
}
/**
* Restore the config file to default settings
*/
suspend fun restore(): Result<String, ExecException> {
suspend fun restore(): Result<String, ExecError> {
return executeAndHandleErrors("restore", transformer = ZeroCodeStdoutTransformer)
}
/**
* Assign values to config file entries
*/
suspend fun set(key: String, value: String): Result<String, ExecException> {
suspend fun set(key: String, value: String): Result<String, ExecError> {
return executeAndHandleErrors("set", key, value, transformer = ZeroCodeStdoutTransformer)
}
@@ -43,7 +43,7 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
*
* @param all Do not scrub secret fields
*/
suspend fun show(all: Boolean? = null): Result<String, ExecException> {
suspend fun show(all: Boolean? = null): Result<String, ExecError> {
val options = listOf(all to "--all").makeOptions()
return executeAndHandleErrors("show", *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -51,7 +51,7 @@ class HatchConfig(runtime: HatchRuntime) : HatchCommand("config", runtime) {
/**
* Update the config file with any new fields
*/
suspend fun update(): Result<String, ExecException> {
suspend fun update(): Result<String, ExecError> {
return executeAndHandleErrors("update", transformer = ZeroCodeStdoutTransformer)
}
}
@@ -4,7 +4,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.Result
import com.jetbrains.python.errorProcessing.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
enum class Scope(val options: Array<String>) {
All(emptyArray()),
@@ -19,7 +19,7 @@ class HatchDep(runtime: HatchRuntime) : HatchCommand("dep", runtime) {
/**
* Output a hash of the currently defined dependencies
**/
suspend fun hash(scope: Scope = Scope.All): Result<String, ExecException> {
suspend fun hash(scope: Scope = Scope.All): Result<String, ExecError> {
return executeAndHandleErrors("hash", *scope.options, transformer = ZeroCodeStdoutTransformer)
}
@@ -38,7 +38,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): Result<String, ExecException> {
suspend fun requirements(scope: Scope = Scope.All, features: List<String>? = null): Result<String, ExecError> {
val options = features?.flatMap { listOf("--feature", it) }?.toTypedArray() ?: arrayOf("--all")
return executeAndHandleErrors("requirements", *scope.options, *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -46,7 +46,7 @@ class HatchDepShow(runtime: HatchRuntime) : HatchCommand(arrayOf("dep", "show"),
/**
* Enumerate dependencies in a tabular format.
*/
suspend fun table(scope: Scope = Scope.All): Result<String, ExecException> {
suspend fun table(scope: Scope = Scope.All): Result<String, ExecError> {
val options = listOf(null to "--lines", true to "--ascii").makeOptions()
return executeAndHandleErrors("table", *scope.options, *options, transformer = ZeroCodeStdoutTransformer)
}
@@ -5,7 +5,7 @@ import com.intellij.openapi.util.NlsSafe
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -127,7 +127,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
*
* @return true if created, false if already exists
*/
suspend fun create(envName: String? = null): Result<CreateResult, ExecException> {
suspend fun create(envName: String? = null): Result<CreateResult, ExecError> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("create", *arguments) {
val actualEnvName = envName ?: DEFAULT_ENV_NAME
@@ -145,7 +145,7 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
*
* @return path to environment
*/
suspend fun find(envName: String? = null): Result<PythonHomePath?, ExecException> {
suspend fun find(envName: String? = null): Result<PythonHomePath?, ExecError> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("find", *arguments) {
when (it.exitCode) {
@@ -178,9 +178,9 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* - [RemoveResult.NotExists] if the environment does not exist.
* - [RemoveResult.NotDefinedInConfig] if the environment is not defined in the project configuration.
* - [RemoveResult.CantRemoveActiveEnvironment] if the environment cannot be removed because it is currently active.
* - An error wrapped in [ExecException] in case of execution failure.
* - An error wrapped in [ExecError] in case of execution failure.
*/
suspend fun remove(envName: String? = null): Result<RemoveResult, ExecException> {
suspend fun remove(envName: String? = null): Result<RemoveResult, ExecError> {
val arguments = if (envName == null) emptyArray() else arrayOf(envName)
return executeAndHandleErrors("remove", *arguments) {
val actualEnvName = envName ?: DEFAULT_ENV_NAME
@@ -200,9 +200,9 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* @param envs A vararg parameter specifying the environment names to be displayed. If not provided, information for all environments is shown.
* @return A [Result] containing:
* - [HatchDetailedEnvironments] if operation is successful.
* - An error wrapped in [ExecException] if an execution failure occurs.
* - An error wrapped in [ExecError] if an execution failure occurs.
*/
suspend fun showWithDetails(vararg envs: String): Result<HatchDetailedEnvironments, ExecException> {
suspend fun showWithDetails(vararg envs: String): Result<HatchDetailedEnvironments, ExecError> {
return executeAndHandleErrors("show", "--json", *envs) { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdout
?: return@executeAndHandleErrors Result.failure(null)
@@ -227,9 +227,9 @@ class HatchEnv(runtime: HatchRuntime) : HatchCommand("env", runtime) {
* @param internal Optional parameter indicating whether to include internal environments. Defaults to false.
* @return A [Result] containing:
* - [HatchDetailedEnvironments] if operation is successful.
* - An error wrapped in [ExecException] if an execution failure occurs.
* - An error wrapped in [ExecError] if an execution failure occurs.
*/
suspend fun show(vararg envs: String, internal: Boolean = false): Result<HatchEnvironments, ExecException> {
suspend fun show(vararg envs: String, internal: Boolean = false): Result<HatchEnvironments, ExecError> {
val options = listOf(internal to "--internal").makeOptions()
return executeAndMatch("show", "--ascii", *options, *envs, expectedOutput = SHOW_RESPONSE_REGEX) { matchResult ->
@@ -3,7 +3,7 @@ package com.intellij.python.hatch.cli
import com.intellij.python.hatch.runtime.HatchRuntime
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@@ -61,7 +61,7 @@ class HatchProject(runtime: HatchRuntime) : HatchCommand("project", runtime) {
/**
* Display project metadata
*/
suspend fun metadata(): Result<Metadata, ExecException> {
suspend fun metadata(): Result<Metadata, ExecError> {
return executeAndHandleErrors("metadata") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdout
?: return@executeAndHandleErrors Result.failure(null)
@@ -3,10 +3,10 @@ package com.intellij.python.hatch.cli
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.io.NioFiles
import com.intellij.python.hatch.runtime.HatchRuntime
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.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
import java.nio.file.Path
/**
@@ -24,7 +24,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): Result<Path?, ExecException> {
suspend fun find(name: String, parent: Boolean? = null, dir: String? = null): Result<Path?, ExecError> {
val options = listOf(parent to "--parent").makeOptions() + buildDirOption(dir)
return executeAndHandleErrors("find", *options, name) { output ->
@@ -127,7 +127,7 @@ class HatchPython(runtime: HatchRuntime) : HatchCommand("python", runtime) {
private: Boolean? = null,
update: Boolean? = null,
dir: String? = null,
): Result<PythonInstallResponse, ExecException> {
): Result<PythonInstallResponse, ExecError> {
val options = listOf(update to "--update", private to "--private").makeOptions() + buildDirOption(dir)
return executeAndHandleErrors("install", *options, *names) { output ->
Result.success(parsePythonInstallCommandOutput(output))
@@ -142,7 +142,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): Result<PythonRemoveResponse, ExecException> {
suspend fun remove(vararg names: String = ALL_NAMES, dir: String? = null): Result<PythonRemoveResponse, ExecError> {
return executeAndHandleErrors("remove", *buildDirOption(dir), *names) { processOutput ->
val output = processOutput.stderr
val notInstalledRegex = Regex("""^Distribution is not installed: (.*)$""", RegexOption.MULTILINE)
@@ -163,7 +163,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): Result<ShowResponse, ExecException> {
suspend fun show(dir: String? = null): Result<ShowResponse, ExecError> {
val nameToVersionRegex = """\|\s+([^|\s]+)\s+\|\s+([^|\s]+)\s+\|""".toRegex()
fun parseNameToVersions(payload: String) = nameToVersionRegex.findAll(payload).associate {
val (name, version) = it.destructured
@@ -198,7 +198,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): Result<PythonInstallResponse, ExecException> {
suspend fun update(vararg names: String = ALL_NAMES, dir: String? = null): Result<PythonInstallResponse, ExecError> {
return executeAndHandleErrors("update", *buildDirOption(dir), *names) { output ->
Result.success(parsePythonInstallCommandOutput(output))
}
@@ -1,12 +1,12 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.hatch.cli
import com.intellij.python.hatch.runtime.HatchRuntime
import com.intellij.python.hatch.PyHatchBundle
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.PyError.ExecException
import com.jetbrains.python.errorProcessing.ExecError
/**
* Manage environment dependencies
@@ -16,7 +16,7 @@ class HatchSelf(runtime: HatchRuntime) : HatchCommand("self", runtime) {
/**
* Generate a pre-populated GitHub issue.
*/
suspend fun report(): Result<Url, ExecException> {
suspend fun report(): Result<Url, ExecError> {
return executeAndHandleErrors("report", "--no-open") { processOutput ->
val output = processOutput.takeIf { it.exitCode == 0 }?.stdout?.trim()
?: return@executeAndHandleErrors Result.failure(null)
@@ -33,10 +33,10 @@ class HatchSelf(runtime: HatchRuntime) : HatchCommand("self", runtime) {
/**
* Restore the installation
*/
fun restore(): Result<String, ExecException> = TODO()
fun restore(): Result<String, ExecError> = TODO()
/**
* Install the latest version
*/
fun update(): Result<String, ExecException> = TODO()
fun update(): Result<String, ExecError> = TODO()
}
@@ -10,11 +10,12 @@ import com.intellij.python.hatch.service.CliBasedHatchService
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.PythonHomePath
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyError
import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.basePath
import java.nio.file.Path
sealed class HatchError(message: @NlsSafe String) : PyError.Message(message)
sealed class HatchError(message: @NlsSafe String) : MessageError(message)
class HatchExecutableNotFoundHatchError(path: Path?) : HatchError(
PyHatchBundle.message("python.hatch.error.executable.is.not.found", path.toString())
@@ -79,25 +80,25 @@ data class ProjectStructure(
interface HatchService {
fun getWorkingDirectoryPath(): Path
suspend fun syncDependencies(envName: String): Result<String, PyError>
suspend fun syncDependencies(envName: String): PyResult<String>
suspend fun isHatchManagedProject(): Result<Boolean, PyError>
suspend fun isHatchManagedProject(): PyResult<Boolean>
suspend fun createNewProject(projectName: String): Result<ProjectStructure, PyError>
suspend fun createNewProject(projectName: String): PyResult<ProjectStructure>
/**
* param[basePythonBinaryPath] base python for environment, the one on the PATH should be used if null.
* param[envName] environment name to create, 'default' should be used if null.
*/
suspend fun createVirtualEnvironment(basePythonBinaryPath: PythonBinary? = null, envName: String? = null): Result<PythonVirtualEnvironment.Existing, PyError>
suspend fun createVirtualEnvironment(basePythonBinaryPath: PythonBinary? = null, envName: String? = null): PyResult<PythonVirtualEnvironment.Existing>
suspend fun findVirtualEnvironments(): Result<List<HatchVirtualEnvironment>, PyError>
suspend fun findVirtualEnvironments(): PyResult<List<HatchVirtualEnvironment>>
}
/**
* Hatch Service for working directory (where hatch.toml / pyproject.toml is usually placed)
*/
suspend fun Path.getHatchService(hatchExecutablePath: Path? = null): Result<HatchService, PyError> {
suspend fun Path.getHatchService(hatchExecutablePath: Path? = null): PyResult<HatchService> {
return CliBasedHatchService(hatchExecutablePath = hatchExecutablePath, workingDirectoryPath = this)
}
@@ -105,7 +106,7 @@ suspend fun Path.getHatchService(hatchExecutablePath: Path? = null): Result<Hatc
* Hatch Service for Module.
* Working directory considered as the module base path.
*/
suspend fun Module.getHatchService(hatchExecutablePath: Path? = null): Result<HatchService, PyError> {
suspend fun Module.getHatchService(hatchExecutablePath: Path? = null): PyResult<HatchService> {
val workingDirectoryPath = resolveHatchWorkingDirectory(this.project, this).getOr { return it }
return workingDirectoryPath.getHatchService(hatchExecutablePath = hatchExecutablePath)
}
@@ -115,7 +116,7 @@ suspend fun Module.getHatchService(hatchExecutablePath: Path? = null): Result<Ha
*/
fun PythonHomePath.getHatchEnvVirtualProjectPath(): Path = this.parent.parent
fun resolveHatchWorkingDirectory(project: Project, module: Module?): Result<Path, PyError> {
fun resolveHatchWorkingDirectory(project: Project, module: Module?): PyResult<Path> {
val pathString = module?.basePath ?: project.basePath
return when (val path = pathString?.let { Path.of(it) }) {
@@ -12,7 +12,8 @@ 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.PyError
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.resolvePythonBinary
import java.nio.file.Path
import kotlin.io.path.isDirectory
@@ -57,15 +58,15 @@ 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>): Result<T, PyError.ExecException> {
internal suspend fun <T> execute(vararg arguments: String, processOutputTransformer: ProcessOutputTransformer<T>): Result<T, ExecError> {
return execService.execute(hatchBinary, arguments.toList(), execOptions, processOutputTransformer)
}
internal suspend fun <T> executeInteractive(vararg arguments: String, eelProcessInteractiveHandler: EelProcessInteractiveHandler<T>): Result<T, PyError.ExecException> {
internal suspend fun <T> executeInteractive(vararg arguments: String, eelProcessInteractiveHandler: EelProcessInteractiveHandler<T>): Result<T, ExecError> {
return execService.executeInteractive(hatchBinary, arguments.toList(), execOptions, eelProcessInteractiveHandler)
}
internal suspend fun resolvePythonVirtualEnvironment(pythonHomePath: PythonHomePath): Result<PythonVirtualEnvironment, PyError> {
internal suspend fun resolvePythonVirtualEnvironment(pythonHomePath: PythonHomePath): PyResult<PythonVirtualEnvironment> {
val pythonVersion = pythonHomePath.takeIf { it.isDirectory() }?.resolvePythonBinary()?.let { pythonBinaryPath ->
execService.execGetStdout(Binary(pythonBinaryPath), listOf("--version")).getOr { return it }.trim()
}
@@ -15,7 +15,7 @@ import com.intellij.python.hatch.runtime.HatchRuntime
import com.intellij.python.hatch.runtime.createHatchRuntime
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyError
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
@@ -30,7 +30,7 @@ internal class CliBasedHatchService private constructor(
private val hatchRuntime: HatchRuntime,
) : HatchService {
companion object {
suspend operator fun invoke(workingDirectoryPath: Path, hatchExecutablePath: Path?): Result<CliBasedHatchService, PyError> {
suspend operator fun invoke(workingDirectoryPath: Path, hatchExecutablePath: Path?): PyResult<CliBasedHatchService> {
val hatchRuntime = createHatchRuntime(
hatchExecutablePath = hatchExecutablePath,
workingDirectoryPath = workingDirectoryPath,
@@ -51,13 +51,13 @@ internal class CliBasedHatchService private constructor(
override fun getWorkingDirectoryPath(): Path = workingDirectoryPath
override suspend fun syncDependencies(envName: String): Result<String, PyError> {
override suspend fun syncDependencies(envName: String): PyResult<String> {
return withContext(Dispatchers.IO) {
hatchRuntime.hatchCli().run(envName, "python", "--version")
}
}
override suspend fun isHatchManagedProject(): Result<Boolean, PyError> {
override suspend fun isHatchManagedProject(): PyResult<Boolean> {
val isHatchManaged = withContext(Dispatchers.IO) {
when {
workingDirectoryPath.resolve("hatch.toml").exists() -> true
@@ -72,7 +72,7 @@ internal class CliBasedHatchService private constructor(
}
override suspend fun findVirtualEnvironments(): Result<List<HatchVirtualEnvironment>, PyError> {
override suspend fun findVirtualEnvironments(): PyResult<List<HatchVirtualEnvironment>> {
val hatchEnv = hatchRuntime.hatchCli().env()
val environments: HatchEnvironments = hatchEnv.show().getOr { return it }
val virtualEnvironments = environments.getAvailableVirtualHatchEnvironments()
@@ -90,7 +90,7 @@ internal class CliBasedHatchService private constructor(
}
override suspend fun createNewProject(projectName: String): Result<ProjectStructure, PyError> {
override suspend fun createNewProject(projectName: String): PyResult<ProjectStructure> {
val eelApi = workingDirectoryPath.getEelDescriptor().upgrade()
val tempDir = eelApi.fs.createTemporaryDirectory(EelFileSystemApi.CreateTemporaryEntryOptions.Builder().build()).getOr { failure ->
return Result.failure(FileSystemOperationHatchError(failure.error))
@@ -108,7 +108,7 @@ internal class CliBasedHatchService private constructor(
))
}
override suspend fun createVirtualEnvironment(basePythonBinaryPath: PythonBinary?, envName: String?): Result<PythonVirtualEnvironment.Existing, PyError> {
override suspend fun createVirtualEnvironment(basePythonBinaryPath: PythonBinary?, envName: String?): PyResult<PythonVirtualEnvironment.Existing> {
val pythonBasedRuntime = basePythonBinaryPath?.let { path ->
hatchRuntime.withBasePythonBinaryPath(path).getOr { return it }
} ?: hatchRuntime