Skip to content

Commit

Permalink
basic support for uv env & package manager; PY-75983
Browse files Browse the repository at this point in the history
GitOrigin-RevId: 2597e4de17e167d8a0b0038190b5127a9dc4b155
  • Loading branch information
asorotsky authored and intellij-monorepo-bot committed Nov 15, 2024
1 parent 6de9588 commit 9b76b13
Show file tree
Hide file tree
Showing 26 changed files with 976 additions and 46 deletions.
1 change: 1 addition & 0 deletions python/intellij.python.community.impl.iml
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,6 @@
<orderEntry type="library" name="kotlinx-datetime-jvm" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide.remote" />
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
</component>
</module>
5 changes: 5 additions & 0 deletions python/pluginCore/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of

<extensions defaultExtensionNs="com.intellij">
<localInspection language="TOML" enabledByDefault="true" implementationClass="com.jetbrains.python.sdk.poetry.PoetryPackageVersionsInspection" key="INSP.poetry.package.versions.display.name" bundle="messages.PyBundle" groupKey="INSP.GROUP.python" suppressId="PoetryPackageVersions" shortName="PoetryPackageVersionsInspection"/>
<localInspection language="TOML" enabledByDefault="true" implementationClass="com.jetbrains.python.sdk.uv.UvPackageVersionsInspection" key="INSP.poetry.package.versions.display.name" bundle="messages.PyBundle" groupKey="INSP.GROUP.python" suppressId="UvPackageVersions" shortName="UvPackageVersionsInspection"/>

<fileType name="Requirements.txt"
implementationClass="com.jetbrains.python.requirements.RequirementsFileType"
Expand Down Expand Up @@ -830,6 +831,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
<pyAddSdkProvider implementation="com.jetbrains.python.sdk.poetry.PyAddPoetrySdkProvider"/>
<pythonFlavorProvider implementation="com.jetbrains.python.sdk.poetry.PyPoetrySdkFlavorProvider"/>

<pythonFlavorProvider implementation="com.jetbrains.python.sdk.uv.UvSdkFlavorProvider"/>

<!-- SDK Flavors -->
<pythonSdkFlavor implementation="com.jetbrains.python.sdk.flavors.conda.CondaEnvSdkFlavor"/>
<pythonSdkFlavor implementation="com.jetbrains.python.sdk.flavors.JythonSdkFlavor"/>
Expand Down Expand Up @@ -858,6 +861,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
<packageManagerProvider implementation="com.jetbrains.python.sdk.poetry.PyPoetryPackageManagerProvider"/>
<pythonPackageManagerProvider implementation="com.jetbrains.python.sdk.poetry.PoetryPackageManagerProvider"/>

<pySdkProvider implementation="com.jetbrains.python.sdk.uv.UvSdkProvider"/>
<pythonPackageManagerProvider implementation="com.jetbrains.python.sdk.uv.UvPackageManagerProvider"/>

<pythonPackageManagerProvider implementation="com.jetbrains.python.packaging.pip.PipPackageManagerProvider" order="last"/>
<pythonPackageManagerProvider implementation="com.jetbrains.python.packaging.conda.CondaPackageManagerProvider"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<body>
<p>Reports outdated versions of packages in <code>[dependencies]</code> and <code>[dev-dependencies]</code>
sections of <code>pyproject.toml</code>.
</p>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"id": "PoetryPackageVersionsInspection",
"codeQualityCategory": "Sanity"
},
{
"id": "UvPackageVersionsInspection",
"codeQualityCategory": "Sanity"
},
{
"id": "PyStubPackagesCompatibilityInspection",
"codeQualityCategory": "Sanity"
Expand Down
17 changes: 16 additions & 1 deletion python/pluginResources/messages/PyBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ python.console=Python Console
python.console.history.root=Python Consoles
python.console.run.anything.provider=Runs Python Console
python.console.not.supported=Python console for {0} interpreter is not supported
python.console.toolbar.action.available.non.interactive=The action is not available for non-interactive shell
python.console.toolbar.action.available.non.interactive=The action is not available for non-interactive shell

runcfg.unittest.dlg.test_function_title=Function
run.configuration.remote.debug.name=Python Remote Debug
Expand Down Expand Up @@ -395,6 +395,20 @@ python.sdk.poetry.install.packages.from.toml.checkbox.text=Install packages from
python.sdk.poetry.dialog.message.poetry.interpreter.has.been.already.added=Poetry interpreter has been already added, select ''{0}''
python.sdk.poetry.dialog.add.new.environment.in.project.checkbox=Create an in-project environment

# UV
python.sdk.dialog.message.creating.virtual.environments.based.on.uv.environments.not.supported=Creating virtual environments based on UV environments is not supported
python.sdk.dialog.title.setting.up.uv.environment=Setting up UV environment
python.sdk.inspection.message.uv.interpreter.associated.with.another.project=Uv interpreter is associated with another {0}: {1}
python.sdk.inspection.message.uv.interpreter.not.associated.with.any.project=Uv interpreter is not associated with any {0}
python.sdk.intention.family.name.install.requirements.from.uv.lock=Install requirements from uv.lock
python.sdk.quickfix.use.uv.name=Use UV interpreter
python.sdk.uv.associated.module=Associated module:
python.sdk.uv.associated.project=Associated project:
python.sdk.uv.environment.panel.title=Uv Environment
python.sdk.uv.executable.not.found=UV executable is not found
python.sdk.uv.executable=Uv executable:
python.sdk.uv.install.packages.from.toml.checkbox.text=Install packages from pyproject.toml

python.sdk.pipenv.has.been.selected=Pipenv interpreter has been already added, select ''{0}'' in your interpreters list
python.sdk.there.is.no.interpreter=No interpreter
python.sdk.no.interpreter.configured.warning=No Python interpreter configured for the project
Expand Down Expand Up @@ -541,6 +555,7 @@ sdk.create.custom.virtualenv=Virtualenv
sdk.create.custom.conda=Conda
sdk.create.custom.pipenv=Pipenv
sdk.create.custom.poetry=Poetry
sdk.create.custom.uv=Uv
sdk.create.custom.python=Python

sdk.rendering.detected.grey.text=detected in the system
Expand Down
4 changes: 4 additions & 0 deletions python/src/com/jetbrains/python/packaging/common/packages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ open class PythonPackage(val name: String, val version: String, val isEditableMo
}
}

open class PythonOutdatedPackage(name: String, version: String, isEditableMode: Boolean, val latestVersion: String)
: PythonPackage(name, version, isEditableMode)
{}

interface PythonPackageDetails {

val name: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
ProjectJdkTable.getInstance().allJdks.asList(),
model.myProjectPathFlows.projectPathWithDefault.first().toString(),
homePath,
false).getOrElse { return Result.failure(it) }
false)
.getOrElse { return Result.failure(it) }
newSdk.persist()

module?.excludeInnerVirtualEnv(newSdk)
model.addInterpreter(newSdk)

return Result.success(newSdk)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.jetbrains.python.statistics.InterpreterType
import java.nio.file.Path


class PipEnvNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("pipenv", model) {
class EnvironmentCreatorPip(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("pipenv", model) {
override val interpreterType: InterpreterType = InterpreterType.PIPENV
override val executable: ObservableMutableProperty<String> = model.state.pipenvExecutable
override val installationScript: Path? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.nio.file.Path

class PoetryNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("poetry", model) {
class EnvironmentCreatorPoetry(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("poetry", model) {
override val interpreterType: InterpreterType = InterpreterType.POETRY
override val executable: ObservableMutableProperty<String> = model.state.poetryExecutable
override val installationScript = PythonHelpersLocator.findPathInHelpers("pycharm_poetry_installer.py")
Expand Down
43 changes: 43 additions & 0 deletions python/src/com/jetbrains/python/sdk/add/v2/EnvironmentCreatorUv.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.add.v2

import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.uv.uvPath
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
import com.jetbrains.python.statistics.InterpreterType
import java.nio.file.Path

class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("uv", model) {
override val interpreterType: InterpreterType = InterpreterType.UV
override val executable: ObservableMutableProperty<String> = model.state.uvExecutable
// FIXME: support uv installation
override val installationScript = null

override fun onShown() {
// FIXME: validate base interpreters against pyprojecttoml version. See poetry
basePythonComboBox.setItems(model.baseInterpreters)
}

override fun savePathToExecutableToProperties() {
PropertiesComponent.getInstance().uvPath = Path.of(executable.get())
}

override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> {
if (module == null) {
// FIXME: should not happen, proper error
return Result.failure(Exception("module is null"))
}

val python = homePath?.let { Path.of(it) }
return setupUvSdkUnderProgress(module, Path.of(projectPath), baseSdks, python)
}

override suspend fun detectExecutable() {
model.detectUvExecutable()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.isDirectory

class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
class EnvironmentCreatorVenv(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
private lateinit var versionComboBox: PythonInterpreterComboBox

private val locationValidationFailed = propertyGraph.property(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
private val existingInterpreterManager = propertyGraph.property(PYTHON)

private val newInterpreterCreators = mapOf(
VIRTUALENV to PythonNewVirtualenvCreator(model),
CONDA to CondaNewEnvironmentCreator(model, errorSink),
PIPENV to PipEnvNewEnvironmentCreator(model),
POETRY to PoetryNewEnvironmentCreator(model, moduleOrProject),
VIRTUALENV to EnvironmentCreatorVenv(model),
CONDA to CondaNewEnvironmentCreator(model, errorSink),
PIPENV to EnvironmentCreatorPip(model),
POETRY to EnvironmentCreatorPoetry(model, moduleOrProject),
UV to EnvironmentCreatorUv(model, moduleOrProject),
)

private val existingInterpreterSelectors = mapOf(
Expand All @@ -52,15 +53,6 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
navigator.existingEnvManager = existingInterpreterManager
}

// todo delete this. testing busy state
//existingInterpreterManager.afterChange {
// model.scope.launch {
// model.interpreterLoading.value = true
// delay(5000)
// model.interpreterLoading.value = false
// }
//}

with(outerPanel) {
buttonsGroup {
row(message("sdk.create.custom.env.creation.type")) {
Expand Down
2 changes: 2 additions & 0 deletions python/src/com/jetbrains/python/sdk/add/v2/common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.pipenv.PIPENV_ICON
import com.jetbrains.python.sdk.poetry.POETRY_ICON
import com.jetbrains.python.sdk.uv.UV_ICON
import com.jetbrains.python.statistics.InterpreterTarget
import kotlinx.coroutines.CoroutineScope
import javax.swing.Icon
Expand Down Expand Up @@ -58,6 +59,7 @@ enum class PythonSupportedEnvironmentManagers(val nameKey: String, val icon: Ico
CONDA("sdk.create.custom.conda", PythonIcons.Python.Anaconda),
POETRY("sdk.create.custom.poetry", POETRY_ICON),
PIPENV("sdk.create.custom.pipenv", PIPENV_ICON),
UV("sdk.create.custom.uv", UV_ICON),
PYTHON("sdk.create.custom.python", com.jetbrains.python.psi.icons.PythonPsiApiIcons.Python)
}

Expand Down
48 changes: 21 additions & 27 deletions python/src/com/jetbrains/python/sdk/add/v2/models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package com.jetbrains.python.sdk.add.v2

import com.intellij.execution.target.TargetEnvironmentConfiguration
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.fileChooser.FileChooser
Expand All @@ -22,8 +21,9 @@ import com.jetbrains.python.sdk.conda.suggestCondaPath
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.sdk.pipenv.pipEnvPath
import com.jetbrains.python.sdk.poetry.poetryPath
import com.jetbrains.python.sdk.pipenv.getPipEnvExecutable
import com.jetbrains.python.sdk.poetry.getPoetryExecutable
import com.jetbrains.python.sdk.uv.getUvExecutable
import com.jetbrains.python.util.ErrorSink
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -94,11 +94,6 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams) {
withContext(uiContext) {
state.condaExecutable.set(suggestedCondaLocalPath?.toString().orEmpty())
}

//val environments = suggestedCondaPath?.let { PyCondaEnv.getEnvs(executor, suggestedCondaPath).getOrLogException(
// PythonAddInterpreterPresenter.LOG) }
//baseConda = environments?.find { env -> env.envIdentity.let { it is PyCondaEnvIdentity.UnnamedEnv && it.isBase } }

}
}

Expand Down Expand Up @@ -172,34 +167,32 @@ abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModel
super.initialize()
detectPoetryExecutable()
detectPipEnvExecutable()
detectUvExecutable()
}

suspend fun detectPoetryExecutable() {
// todo this is local case, fix for targets
val savedPath = PropertiesComponent.getInstance().poetryPath
if (savedPath != null) {
state.poetryExecutable.set(savedPath)
}
else {
com.jetbrains.python.sdk.poetry.detectPoetryExecutable().getOrNull()?.let {
withContext(Dispatchers.EDT) {
state.poetryExecutable.set(it.pathString)
}
// FIXME: support targets
getPoetryExecutable().getOrNull()?.let {
withContext(Dispatchers.EDT) {
state.poetryExecutable.set(it.pathString)
}
}
}

suspend fun detectPipEnvExecutable() {
// todo this is local case, fix for targets
val savedPath = PropertiesComponent.getInstance().pipEnvPath
if (savedPath != null) {
state.pipenvExecutable.set(savedPath)
// FIXME: support targets
getPipEnvExecutable().getOrNull()?.let {
withContext(Dispatchers.EDT) {
state.pipenvExecutable.set(it.pathString)
}
}
else {
com.jetbrains.python.sdk.pipenv.detectPipEnvExecutable().getOrNull()?.let {
withContext(Dispatchers.EDT) {
state.pipenvExecutable.set(it.pathString)
}
}

suspend fun detectUvExecutable() {
// FIXME: support targets
getUvExecutable()?.pathString?.let {
withContext(Dispatchers.EDT) {
state.uvExecutable.set(it)
}
}
}
Expand Down Expand Up @@ -289,6 +282,7 @@ class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(pro
val baseInterpreter: ObservableMutableProperty<PythonSelectableInterpreter?> = propertyGraph.property(null)
val newCondaEnvName: ObservableMutableProperty<String> = propertyGraph.property("")
val poetryExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
val uvExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
val pipenvExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
val venvPath: ObservableMutableProperty<String> = propertyGraph.property("")
val inheritSitePackages = propertyGraph.property(false)
Expand Down
21 changes: 21 additions & 0 deletions python/src/com/jetbrains/python/sdk/uv/Uv.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2000-2024 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.packaging.common.PythonPackage
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
import com.jetbrains.python.packaging.common.PythonPackageSpecification
import java.nio.file.Path

interface UvCli {
suspend fun runUv(workingDir: Path, vararg args: String): Result<String>
}

interface UvLowLevel {
suspend fun initializeEnvironment(init: Boolean, python: Path?): Result<Path>

suspend fun listPackages(): Result<List<PythonPackage>>
suspend fun listOutdatedPackages(): Result<List<PythonOutdatedPackage>>

suspend fun installPackage(name: PythonPackageSpecification, options: List<String>): Result<Unit>
suspend fun uninstallPackage(name: PythonPackage): Result<Unit>
}
Loading

0 comments on commit 9b76b13

Please sign in to comment.