diff --git a/python/pluginCore/resources/META-INF/plugin.xml b/python/pluginCore/resources/META-INF/plugin.xml index a9932707b227..7f84a1664962 100644 --- a/python/pluginCore/resources/META-INF/plugin.xml +++ b/python/pluginCore/resources/META-INF/plugin.xml @@ -414,8 +414,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of - + diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java index 9b33df4214e1..6ca12e94b658 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java @@ -9,7 +9,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Disposer; import com.intellij.util.ArrayUtil; -import com.jetbrains.python.packaging.common.PackageManagerHolder; +import com.jetbrains.python.packaging.management.PythonPackageManagerService; import com.jetbrains.python.packaging.ui.PyPackageManagementService; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.PythonSdkUtil; @@ -89,7 +89,7 @@ public class PyPackageManagersImpl extends PyPackageManagers { LOG.assertTrue(!Disposer.isDisposed((Disposable)sdk), "Requesting a package service for an already disposed SDK " + sdk + " (" + sdk.getClass() + ")"); } - return project.getService(PackageManagerHolder.class).bridgeForSdk(project, sdk); + return project.getService(PythonPackageManagerService.class).bridgeForSdk(project, sdk); } @Override diff --git a/python/src/com/jetbrains/python/packaging/common/util.kt b/python/src/com/jetbrains/python/packaging/common/util.kt index 1c252ee35238..0257bdff76cf 100644 --- a/python/src/com/jetbrains/python/packaging/common/util.kt +++ b/python/src/com/jetbrains/python/packaging/common/util.kt @@ -1,79 +1,21 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.packaging.common -import com.intellij.openapi.Disposable import com.intellij.openapi.application.writeIntentReadAction import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.NlsContexts import com.intellij.webcore.packaging.PackagesNotificationPanel import com.jetbrains.python.packaging.PyExecutionException import com.jetbrains.python.packaging.PyPIPackageRanking import com.jetbrains.python.packaging.PyPackagesNotificationPanel import com.jetbrains.python.packaging.bridge.PythonPackageManagementServiceBridge -import com.jetbrains.python.packaging.management.PythonPackageManager -import com.jetbrains.python.packaging.management.PythonPackageManagerProvider import com.jetbrains.python.packaging.ui.PyPackageManagementService -import com.jetbrains.python.sdk.PythonSdkAdditionalData -import com.jetbrains.python.sdk.getOrCreateAdditionalData import com.jetbrains.python.statistics.PyPackagesUsageCollector -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import java.util.* -import java.util.concurrent.ConcurrentHashMap - - -interface PackageManagerHolder { - fun forSdk(project: Project, sdk: Sdk): PythonPackageManager - - /** - * Provides an implementation bridge for Python package management operations - * specific to the given project and SDK. The bridge serves as a connection point - * to enable advanced management tasks, potentially extending or adapting functionalities - * provided by the [PythonPackageManager]. - */ - fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge - - fun getServiceScope(): CoroutineScope -} - -class PackageManagerHolderImpl(private val serviceScope: CoroutineScope) : PackageManagerHolder, Disposable { - private val cache = ConcurrentHashMap() - - private val bridgeCache = ConcurrentHashMap() - - /** - * Requires Sdk to be Python Sdk and have PythonSdkAdditionalData. - */ - override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager { - val cacheKey = (sdk.getOrCreateAdditionalData()).uuid - - return cache.computeIfAbsent(cacheKey) { - PythonPackageManagerProvider.EP_NAME.extensionList - .firstNotNullOf { it.createPackageManagerForSdk(project, sdk) } - } - } - - override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge { - val cacheKey = (sdk.sdkAdditionalData as PythonSdkAdditionalData).uuid - return bridgeCache.computeIfAbsent(cacheKey) { - val bridge = PythonPackageManagementServiceBridge(project, sdk) - Disposer.register(this@PackageManagerHolderImpl, bridge) - bridge - } - } - - override fun getServiceScope(): CoroutineScope = serviceScope - - override fun dispose() { - cache.clear() - } -} - @ApiStatus.Experimental interface PythonPackageManagementListener { diff --git a/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt index 10ce9b17ac60..4eda95eb6a0d 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt @@ -15,12 +15,14 @@ class CompositePythonPackageManager( ) : PythonPackageManager(project, sdk) { @Volatile override var installedPackages: List = emptyList() + + // TODO: composite one override var repositoryManager: PythonRepositoryManager = managers.first().repositoryManager private fun isInRepository(repositoryManager: PythonRepositoryManager, pkgName: String) = repositoryManager.allPackages().contains(pkgName) - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { val exceptionList = mutableListOf() for (manager in managers) { repositoryManager = manager.repositoryManager @@ -28,17 +30,15 @@ class CompositePythonPackageManager( if (!isInRepository(repositoryManager, specification.name)) continue val executionResult = manager.installPackage(specification, options) - val executionOutcome = executionResult.getOrElse { exceptionList.add(it) } - - if (executionResult.isSuccess) { - return Result.success(executionOutcome.toString()) - } + executionResult + .onSuccess { return Result.success(Unit) } + .onFailure { exceptionList.add(it) } } - return Result.failure(exceptionList.last()) + + return Result.failure(exceptionList.lastOrNull() ?: RuntimeException("No package managers found for package $specification")) } - - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { val exceptionList = mutableListOf() for (manager in managers) { @@ -47,33 +47,32 @@ class CompositePythonPackageManager( if (!isInRepository(repositoryManager, specification.name)) continue val executionResult = manager.updatePackage(specification) - val executionOutcome = executionResult.getOrElse { exceptionList.add(it) } - - if (executionResult.isSuccess) { - return Result.success(executionOutcome.toString()) - } + executionResult + .onSuccess { return Result.success(Unit) } + .onFailure { exceptionList.add(it) } } - return Result.failure(exceptionList.last()) + return Result.failure(exceptionList.lastOrNull() ?: RuntimeException("No package managers found for package $specification")) } - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { val exceptionList = mutableListOf() for (manager in managers) { repositoryManager = manager.repositoryManager installedPackages = manager.installedPackages - if (!isInRepository(repositoryManager, pkg.name)) continue + if (!isInRepository(repositoryManager, pkg.name)) { + continue + } val executionResult = manager.uninstallPackage(pkg) - val executionOutcome = executionResult.getOrElse { exceptionList.add(it) } - if (executionResult.isSuccess) { - return Result.success(executionOutcome.toString()) - } + executionResult + .onSuccess { return Result.success(Unit) } + .onFailure { exceptionList.add(it) } } - return Result.failure(exceptionList.last()) + return Result.failure(exceptionList.lastOrNull() ?: RuntimeException("No package managers found for package $pkg")) } override suspend fun reloadPackagesCommand(): Result> { @@ -83,15 +82,11 @@ class CompositePythonPackageManager( repositoryManager = manager.repositoryManager installedPackages = manager.installedPackages - val executionResult = manager.reloadPackages() - val executionOutcome = executionResult.getOrElse { - exceptionList.add(it) - emptyList() - } - if (executionResult.isSuccess) { - return Result.success(executionOutcome) - } + manager.reloadPackages() + .onSuccess { return Result.success(it) } + .onFailure { exceptionList.add(it) } } - return Result.failure(exceptionList.last()) + + return Result.failure(exceptionList.lastOrNull() ?: RuntimeException("No package managers found")) } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt index 51855cad962f..acadbb06fe3c 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt @@ -32,25 +32,28 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pro override var installedPackages: List = emptyList() override val repositoryManager = CondaRepositoryManger(project, sdk) - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result = + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result = try { - Result.success(runConda("install", specification.buildInstallationString() + "-y" + options, message("conda.packaging.install.progress", specification.name))) + runConda("install", specification.buildInstallationString() + "-y" + options, message("conda.packaging.install.progress", specification.name)) + Result.success(Unit) } catch (ex: ExecutionException) { Result.failure(ex) } - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result = + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result = try { - Result.success(runConda("update", listOf(specification.name, "-y"), message("conda.packaging.update.progress", specification.name))) + runConda("update", listOf(specification.name, "-y"), message("conda.packaging.update.progress", specification.name)) + Result.success(Unit) } catch (ex: ExecutionException) { Result.failure(ex) } - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result = + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result = try { - Result.success(runConda("uninstall", listOf(pkg.name, "-y"), message("conda.packaging.uninstall.progress", pkg.name))) + runConda("uninstall", listOf(pkg.name, "-y"), message("conda.packaging.uninstall.progress", pkg.name)) + Result.success(Unit) } catch (ex: ExecutionException) { Result.failure(ex) diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt index da4f4e523ef9..db6f4d99f350 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt @@ -73,9 +73,9 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) { return Result.success(packages) } - protected abstract suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result - protected abstract suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result - protected abstract suspend fun uninstallPackageCommand(pkg: PythonPackage): Result + protected abstract suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result + protected abstract suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result + protected abstract suspend fun uninstallPackageCommand(pkg: PythonPackage): Result protected abstract suspend fun reloadPackagesCommand(): Result> internal suspend fun refreshPaths() { @@ -93,9 +93,9 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) { companion object { fun forSdk(project: Project, sdk: Sdk): PythonPackageManager { - val packageManagerHolder = project.service() - val manager = packageManagerHolder.forSdk(project, sdk) - packageManagerHolder.getServiceScope().launch(Dispatchers.IO) { + val pythonPackageManagerService = project.service() + val manager = pythonPackageManagerService.forSdk(project, sdk) + pythonPackageManagerService.getServiceScope().launch(Dispatchers.IO) { manager.repositoryManager.initCaches() } return manager diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerProvider.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerService.kt similarity index 55% rename from python/src/com/jetbrains/python/packaging/management/PythonPackageManagerProvider.kt rename to python/src/com/jetbrains/python/packaging/management/PythonPackageManagerService.kt index c292fa62cad1..76eff8ecc1c5 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerProvider.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerService.kt @@ -4,6 +4,8 @@ package com.jetbrains.python.packaging.management import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk +import com.jetbrains.python.packaging.bridge.PythonPackageManagementServiceBridge +import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.ApiStatus @ApiStatus.Experimental @@ -19,4 +21,19 @@ interface PythonPackageManagerProvider { companion object { val EP_NAME = ExtensionPointName.create("Pythonid.pythonPackageManagerProvider") } +} + +@ApiStatus.Experimental +interface PythonPackageManagerService { + fun forSdk(project: Project, sdk: Sdk): PythonPackageManager + + /** + * Provides an implementation bridge for Python package management operations + * specific to the given project and SDK. The bridge serves as a connection point + * to enable advanced management tasks, potentially extending or adapting functionalities + * provided by the [PythonPackageManager]. + */ + fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge + + fun getServiceScope(): CoroutineScope } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerServiceImpl.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerServiceImpl.kt new file mode 100644 index 000000000000..343b7c580574 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerServiceImpl.kt @@ -0,0 +1,46 @@ +// 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.packaging.management + +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.util.Disposer +import com.jetbrains.python.packaging.bridge.PythonPackageManagementServiceBridge +import com.jetbrains.python.sdk.PythonSdkAdditionalData +import com.jetbrains.python.sdk.getOrCreateAdditionalData +import kotlinx.coroutines.CoroutineScope +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +class PythonPackageManagerServiceImpl(private val serviceScope: CoroutineScope) : PythonPackageManagerService, Disposable { + private val cache = ConcurrentHashMap() + + private val bridgeCache = ConcurrentHashMap() + + /** + * Requires Sdk to be Python Sdk and have PythonSdkAdditionalData. + */ + override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager { + val cacheKey = (sdk.getOrCreateAdditionalData()).uuid + + return cache.computeIfAbsent(cacheKey) { + PythonPackageManagerProvider.EP_NAME.extensionList + .firstNotNullOf { it.createPackageManagerForSdk(project, sdk) } + } + } + + override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge { + val cacheKey = (sdk.sdkAdditionalData as PythonSdkAdditionalData).uuid + return bridgeCache.computeIfAbsent(cacheKey) { + val bridge = PythonPackageManagementServiceBridge(project, sdk) + Disposer.register(this@PythonPackageManagerServiceImpl, bridge) + bridge + } + } + + override fun getServiceScope(): CoroutineScope = serviceScope + + override fun dispose() { + cache.clear() + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt index 7b03b2cb052b..2ba3fb8dfdc0 100644 --- a/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt @@ -19,31 +19,40 @@ open class PipPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageMa override var installedPackages: List = emptyList() override val repositoryManager: PythonRepositoryManager = PipRepositoryManager(project, sdk) - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result = + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { try { - Result.success(runPackagingTool("install", specification.buildInstallationString() + options, PyBundle.message("python.packaging.install.progress", specification.name))) + runPackagingTool("install", specification.buildInstallationString() + options, PyBundle.message("python.packaging.install.progress", specification.name)) } catch (ex: ExecutionException) { - Result.failure(ex) + return Result.failure(ex) } - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result = + return Result.success(Unit) + } + + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { try { - Result.success(runPackagingTool("install", listOf("--upgrade") + specification.buildInstallationString(), PyBundle.message("python.packaging.update.progress", specification.name))) + runPackagingTool("install", listOf("--upgrade") + specification.buildInstallationString(), PyBundle.message("python.packaging.update.progress", specification.name)) } catch (ex: ExecutionException) { - Result.failure(ex) + return Result.failure(ex) } - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result = + return Result.success(Unit) + } + + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { try { - Result.success(runPackagingTool("uninstall", listOf(pkg.name), PyBundle.message("python.packaging.uninstall.progress", pkg.name))) + runPackagingTool("uninstall", listOf(pkg.name), PyBundle.message("python.packaging.uninstall.progress", pkg.name)) } catch (ex: ExecutionException) { - Result.failure(ex) + return Result.failure(ex) } - override suspend fun reloadPackagesCommand(): Result> = + return Result.success(Unit) + } + + override suspend fun reloadPackagesCommand(): Result> { try { val output = runPackagingTool("list", emptyList(), PyBundle.message("python.packaging.list.progress")) val packages = output.lineSequence() @@ -54,9 +63,11 @@ open class PipPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageMa } .sortedWith(compareBy(PythonPackage::name)) .toList() - Result.success(packages) + + return Result.success(packages) } catch (ex: ExecutionException) { - Result.failure(ex) + return Result.failure(ex) } + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt index bf5385602047..331f80786f19 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt @@ -18,13 +18,26 @@ class PoetryPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pr @Volatile private var outdatedPackages: Map = emptyMap() - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result = + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { poetryInstallPackage(sdk, specification.getVersionForPoetry(), options) + .onFailure { return Result.failure(it) } - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result = + return Result.success(Unit) + } + + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { poetryInstallPackage(sdk, specification.getVersionForPoetry(), emptyList()) + .onFailure { return Result.failure(it) } - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result = poetryUninstallPackage(sdk, pkg.name) + return Result.success(Unit) + } + + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { + poetryUninstallPackage(sdk, pkg.name) + .onFailure { return Result.failure(it) } + + return Result.success(Unit) + } override suspend fun reloadPackagesCommand(): Result> { val (installed, _) = poetryListPackages(sdk).getOrElse { diff --git a/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt b/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt index 6ce66db29a07..5e4d5d529d53 100644 --- a/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt @@ -21,7 +21,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, private val uv: UvLo @Volatile var outdatedPackages: Map = emptyMap() - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { val result = if (sdk.uvUsePackageManagement) { uv.installPackage(specification, emptyList()) } @@ -34,19 +34,19 @@ internal class UvPackageManager(project: Project, sdk: Sdk, private val uv: UvLo } // FIXME: refactor command return value, it's not used - return Result.success("") + return Result.success(Unit) } - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { installPackageCommand(specification, emptyList()).getOrElse { return Result.failure(it) } // FIXME: refactor command return value, it's not used - return Result.success("") + return Result.success(Unit) } - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { val result = if (sdk.uvUsePackageManagement) { uv.uninstallPackage(pkg) } @@ -59,7 +59,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, private val uv: UvLo } // FIXME: refactor command return value, it's not used - return Result.success("") + return Result.success(Unit) } override suspend fun reloadPackagesCommand(): Result> { diff --git a/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java index 903fdd3506ac..d26d4abadd06 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyPackageRequirementsInspectionTest.java @@ -7,8 +7,8 @@ import com.intellij.testFramework.ServiceContainerUtil; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.fixtures.PyInspectionTestCase; import com.jetbrains.python.packaging.PyRequirement; -import com.jetbrains.python.packaging.common.PackageManagerHolder; -import com.jetbrains.python.packaging.mocks.MockPackageManagerHolder; +import com.jetbrains.python.packaging.management.PythonPackageManagerService; +import com.jetbrains.python.packaging.management.TestPythonPackageManagerService; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.sdk.PythonSdkUtil; import com.jetbrains.python.sdk.pipenv.PipenvFilesUtilsKt; @@ -31,8 +31,8 @@ public class PyPackageRequirementsInspectionTest extends PyInspectionTestCase { ServiceContainerUtil.replaceService( myFixture.getProject(), - PackageManagerHolder.class, - new MockPackageManagerHolder(), + PythonPackageManagerService.class, + new TestPythonPackageManagerService(), myFixture.getProject() ); } diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt new file mode 100644 index 000000000000..9aea86941586 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt @@ -0,0 +1,114 @@ +// 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.packaging.management + +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.jetbrains.python.packaging.bridge.PythonPackageManagementServiceBridge +import com.jetbrains.python.packaging.common.PythonPackage +import com.jetbrains.python.packaging.common.PythonPackageDetails +import com.jetbrains.python.packaging.common.PythonPackageSpecification +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import org.jetbrains.annotations.TestOnly + +@TestOnly +class TestPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) { + + override var installedPackages: List = DEFAULT_PACKAGES.toMutableList() + + private var packageNames: List = emptyList() + private var packageDetails: PythonPackageDetails? = null + + override val repositoryManager: PythonRepositoryManager + get() = TestPythonRepositoryManager(project, sdk).withPackageNames(packageNames).withPackageDetails(packageDetails) + + override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { + return if (repositoryManager.allPackages().contains(specification.name)) { + installedPackages += PythonPackage(specification.name, specification.versionSpecs.orEmpty(), false) + Result.success(Unit) + } else { + Result.failure(Exception(PACKAGE_INSTALL_FAILURE_MESSAGE)) + } + } + + override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { + return Result.success(Unit) + } + + override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { + val packageToRemove = findPackageByName(pkg.name) + return if (packageToRemove != null) { + installedPackages -= packageToRemove + Result.success(Unit) + } else { + Result.failure(Exception(PACKAGE_UNINSTALL_FAILURE_MESSAGE)) + } + } + + override suspend fun reloadPackagesCommand(): Result> { + return Result.success(installedPackages) + } + + private fun findPackageByName(name: String): PythonPackage? { + return installedPackages.find { it.name == name } + } + + fun withPackageNames(packageNames: List): TestPythonPackageManager { + this.packageNames = packageNames + return this + } + + fun withPackageDetails(details: PythonPackageDetails?): TestPythonPackageManager { + this.packageDetails = details + return this + } + + companion object { + private val DEFAULT_PACKAGES = listOf( + PythonPackage(PIP_PACKAGE, EMPTY_STRING, false), + PythonPackage(SETUP_TOOLS_PACKAGE, EMPTY_STRING, false) + ) + + private const val PIP_PACKAGE = "pip" + private const val SETUP_TOOLS_PACKAGE = "setuptools" + private const val PACKAGE_INSTALL_FAILURE_MESSAGE = "Failed to install package" + private const val PACKAGE_UNINSTALL_FAILURE_MESSAGE = "No such package found" + private const val EMPTY_STRING = "" + } +} + +@TestOnly +class TestPythonPackageManagerService(): PythonPackageManagerService { + + override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager { + return TestPythonPackageManager(project, sdk) + } + + override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge { + return PythonPackageManagementServiceBridge(project, sdk) + } + + override fun getServiceScope(): CoroutineScope { + return CoroutineScope(Job()) + } +} + +@TestOnly +class TestPackageManagerProvider : PythonPackageManagerProvider { + private var packageNames: List = emptyList() + private var packageDetails: PythonPackageDetails? = null + + fun withPackageNames(packageNames: List): TestPackageManagerProvider { + this.packageNames = packageNames + return this + } + + fun withPackageDetails(details: PythonPackageDetails): TestPackageManagerProvider { + this.packageDetails = details + return this + } + + override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager { + return TestPythonPackageManager(project, sdk).withPackageNames(packageNames).withPackageDetails(packageDetails) + } +} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManagerProvider.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManagerProvider.kt deleted file mode 100644 index 64e68e3211a5..000000000000 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManagerProvider.kt +++ /dev/null @@ -1,27 +0,0 @@ -// 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.python.packaging.management - -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.Sdk -import com.jetbrains.python.packaging.common.PythonPackageDetails -import org.jetbrains.annotations.TestOnly - -@TestOnly -class TestPackageManagerProvider : PythonPackageManagerProvider { - private var packageNames: List = emptyList() - private var packageDetails: PythonPackageDetails? = null - - fun withPackageNames(packageNames: List): TestPackageManagerProvider { - this.packageNames = packageNames - return this - } - - fun withPackageDetails(details: PythonPackageDetails): TestPackageManagerProvider { - this.packageDetails = details - return this - } - - override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager { - return TestPythonPackageManager(project, sdk).withPackageNames(packageNames).withPackageDetails(packageDetails) - } -} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonPackageManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPythonPackageManager.kt deleted file mode 100644 index 726f8c07833b..000000000000 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonPackageManager.kt +++ /dev/null @@ -1,50 +0,0 @@ -// 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.python.packaging.management - -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.Sdk -import com.jetbrains.python.packaging.common.PythonPackage -import com.jetbrains.python.packaging.common.PythonPackageDetails -import com.jetbrains.python.packaging.common.PythonPackageSpecification -import org.jetbrains.annotations.TestOnly - -@TestOnly -class TestPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) { - private var packageNames: List = emptyList() - private var packageDetails: PythonPackageDetails? = null - - override var installedPackages: List = emptyList() - get() = TODO("Not yet implemented") - override val repositoryManager: PythonRepositoryManager - get() = TestPythonRepositoryManager(project, sdk).withPackageNames(packageNames).withPackageDetails(packageDetails) - - fun withPackageNames(packageNames: List): TestPythonPackageManager { - this.packageNames = packageNames - return this - } - - fun withPackageDetails(details: PythonPackageDetails?): TestPythonPackageManager { - this.packageDetails = details - return this - } - - override suspend fun reloadPackages(): Result> { - TODO("Not yet implemented") - } - - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { - TODO("Not yet implemented") - } - - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { - TODO("Not yet implemented") - } - - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { - TODO("Not yet implemented") - } - - override suspend fun reloadPackagesCommand(): Result> { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt index 36fc346e3c5f..fb19f3a68f0d 100644 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt +++ b/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt @@ -59,10 +59,8 @@ class TestPythonRepositoryManager(project: Project, sdk: Sdk) : PythonRepository } override suspend fun refreshCashes() { - TODO("Not yet implemented") } override suspend fun initCaches() { - TODO("Not yet implemented") } } \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManager.kt b/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManager.kt deleted file mode 100644 index 37dbd7e806d9..000000000000 --- a/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManager.kt +++ /dev/null @@ -1,66 +0,0 @@ -// 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.packaging.mocks - -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.Sdk -import com.jetbrains.python.packaging.common.PythonPackage -import com.jetbrains.python.packaging.common.PythonPackageSpecification -import com.jetbrains.python.packaging.management.PythonPackageManager -import com.jetbrains.python.packaging.management.PythonRepositoryManager -import com.jetbrains.python.packaging.pip.PipRepositoryManager -import org.jetbrains.annotations.TestOnly - -@TestOnly -class MockPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) { - - override var installedPackages: List = DEFAULT_PACKAGES.toMutableList() - - override val repositoryManager: PythonRepositoryManager = PipRepositoryManager(project, sdk) - - override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List): Result { - return if (repositoryManager.allPackages().contains(specification.name)) { - installedPackages += PythonPackage(specification.name, specification.versionSpecs.orEmpty(), false) - Result.success(PACKAGE_INSTALLED_MESSAGE) - } else { - Result.failure(Exception(PACKAGE_INSTALL_FAILURE_MESSAGE)) - } - } - - override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result { - return Result.success(PACKAGE_UPDATED_MESSAGE) - } - - override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result { - val packageToRemove = findPackageByName(pkg.name) - return if (packageToRemove != null) { - installedPackages -= packageToRemove - Result.success(PACKAGE_UNINSTALLED_MESSAGE) - } else { - Result.failure(Exception(PACKAGE_UNINSTALL_FAILURE_MESSAGE)) - } - } - - override suspend fun reloadPackagesCommand(): Result> { - return Result.success(installedPackages) - } - - private fun findPackageByName(name: String): PythonPackage? { - return installedPackages.find { it.name == name } - } - - companion object { - private val DEFAULT_PACKAGES = listOf( - PythonPackage(PIP_PACKAGE, EMPTY_STRING, false), - PythonPackage(SETUP_TOOLS_PACKAGE, EMPTY_STRING, false) - ) - - private const val PIP_PACKAGE = "pip" - private const val SETUP_TOOLS_PACKAGE = "setuptools" - private const val PACKAGE_INSTALLED_MESSAGE = "Successfully installed package" - private const val PACKAGE_INSTALL_FAILURE_MESSAGE = "Failed to install package" - private const val PACKAGE_UPDATED_MESSAGE = "Successfully updated package" - private const val PACKAGE_UNINSTALLED_MESSAGE = "Successfully uninstalled package" - private const val PACKAGE_UNINSTALL_FAILURE_MESSAGE = "No such package found" - private const val EMPTY_STRING = "" - } -} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManagerHolder.kt b/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManagerHolder.kt deleted file mode 100644 index 14ee6d3f6376..000000000000 --- a/python/testSrc/com/jetbrains/python/packaging/mocks/MockPackageManagerHolder.kt +++ /dev/null @@ -1,28 +0,0 @@ -// 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.packaging.mocks - -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.Sdk -import com.jetbrains.python.packaging.bridge.PythonPackageManagementServiceBridge -import com.jetbrains.python.packaging.common.PackageManagerHolder -import com.jetbrains.python.packaging.management.PythonPackageManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import org.jetbrains.annotations.TestOnly - - -@TestOnly -class MockPackageManagerHolder() : PackageManagerHolder { - - override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager { - return MockPythonPackageManager(project, sdk) - } - - override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge { - return PythonPackageManagementServiceBridge(project, sdk) - } - - override fun getServiceScope(): CoroutineScope { - return CoroutineScope(Job()) - } -} \ No newline at end of file