From ff86ab4b8226a1ed66d3368c413fa5e3eefb0fec Mon Sep 17 00:00:00 2001 From: Timur Malanin Date: Thu, 10 Apr 2025 13:11:48 +0000 Subject: [PATCH] PY-79792 Refactored repository handling in Python packaging subsystem. Unified repository management by removing redundant methods (packagesFromRepository, allPackages) and standardized package retrieval via getPackages from PyPackageRepository. Updated related classes to align with the new CompositePythonRepositoryManager and PythonRepositoryManager interfaces. Added Test. PY-80168 Replace latest text with real latest version. GitOrigin-RevId: f9d826a84d469c75a0c66f34a308cdde16c2f5b0 --- python/intellij.python.community.impl.iml | 3 + .../PythonPackageManagementServiceBridge.kt | 105 +++++++----------- .../python/packaging/common/packages.kt | 2 +- .../conda/CompositePythonPackageManager.kt | 2 +- .../conda/CompositePythonRepositoryManager.kt | 32 ++---- .../packaging/conda/CondaRepositoryManger.kt | 11 +- .../python/packaging/conda/common.kt | 14 ++- .../management/PythonPackageManagerExt.kt | 2 +- .../management/PythonRepositoryManager.kt | 32 +++--- .../pip/PipBasedRepositoryManager.kt | 34 +++--- .../packaging/pip/PipRepositoryManager.kt | 5 +- .../InstalledPyPackagedRepository.kt | 2 +- .../repository/PyPackageRepository.kt | 90 +++++++++------ .../repository/PyPackageRepositoryUtil.kt | 12 +- .../repository/PyRepositoriesList.kt | 7 +- .../repository/PyRepositoryListItem.kt | 21 +++- .../PyPackagingToolWindowService.kt | 32 +++++- .../details/PyPackageDescriptionController.kt | 33 ++++-- .../PyRunAnythingPackageProvider.kt | 2 +- .../management/TestPackageManager.kt | 3 +- .../management/TestPythonRepositoryManager.kt | 18 +-- 21 files changed, 254 insertions(+), 208 deletions(-) diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml index 18ed8f1b6000..1405e070e978 100644 --- a/python/intellij.python.community.impl.iml +++ b/python/intellij.python.community.impl.iml @@ -145,5 +145,8 @@ + + + \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/bridge/PythonPackageManagementServiceBridge.kt b/python/src/com/jetbrains/python/packaging/bridge/PythonPackageManagementServiceBridge.kt index 42b77a9ed04e..5bdf0b22129a 100644 --- a/python/src/com/jetbrains/python/packaging/bridge/PythonPackageManagementServiceBridge.kt +++ b/python/src/com/jetbrains/python/packaging/bridge/PythonPackageManagementServiceBridge.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.asContextElement import com.intellij.openapi.components.service +import com.intellij.openapi.progress.runBlockingCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.util.CatchingConsumer @@ -15,90 +16,65 @@ import com.intellij.webcore.packaging.RepoPackage import com.jetbrains.python.PyBundle import com.jetbrains.python.packaging.PyPackagingSettings import com.jetbrains.python.packaging.common.* -import com.jetbrains.python.packaging.conda.CondaPackage import com.jetbrains.python.packaging.conda.CondaPackageCache -import com.jetbrains.python.packaging.conda.CondaPackageManager -import com.jetbrains.python.packaging.conda.CondaPackageRepository import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.packaging.management.packagesByRepository -import com.jetbrains.python.packaging.management.runPackagingTool import com.jetbrains.python.packaging.repository.PyPIPackageRepository import com.jetbrains.python.packaging.repository.PyPackageRepository +import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService import com.jetbrains.python.packaging.ui.PyPackageManagementService -import kotlinx.coroutines.* +import com.jetbrains.python.sdk.conda.isConda +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch -class PythonPackageManagementServiceBridge(project: Project,sdk: Sdk) : PyPackageManagementService(project, sdk), Disposable { +class PythonPackageManagementServiceBridge(project: Project, sdk: Sdk) : PyPackageManagementService(project, sdk), Disposable { - private val scope = CoroutineScope(Dispatchers.IO) + private val scope = project.service().serviceScope private val manager: PythonPackageManager get() = PythonPackageManager.forSdk(project, sdk) - var useConda = true - + var useConda: Boolean = true val isConda: Boolean - get() = manager is CondaPackageManager - + get() = sdk.isConda() override fun getInstalledPackagesList(): List { - if (manager.installedPackages.isEmpty()) runBlocking { + if (manager.installedPackages.isEmpty()) runBlockingCancellable { manager.reloadPackages() } - if (isConda) { - if (useConda) { - return manager.installedPackages.asSequence() - .filterIsInstance() - .filter { it.installedWithPip != useConda } - .map { InstalledPackage(it.name, it.version) } - .toList() - } - else { - return runBlocking { - val result = runPackagingOperationOrShowErrorDialog(sdk, PyBundle.message("python.packaging.operation.failed.title")) { - val output = manager.runPackagingTool("list", emptyList(), PyBundle.message("python.packaging.list.progress")) - val packages = output.lineSequence() - .filter { it.isNotBlank() } - .map { - val line = it.split("\t") - PythonPackage(line[0], line[1], isEditableMode = false) - } - .sortedWith(compareBy(PythonPackage::name)) - .toList() - Result.success(packages) - } - return@runBlocking if (result.isSuccess) { - result.getOrThrow().map { InstalledPackage(it.name, it.version) } - } else emptyList() - } - } - } return manager.installedPackages.map { InstalledPackage(it.name, it.version) } } override fun getAllPackages(): List { - if (isConda && useConda) { - val settings = PyPackagingSettings.getInstance(project) - val cache = service() - return manager - .repositoryManager - .packagesFromRepository(CondaPackageRepository) - .asSequence() - .map { RepoPackage(it, null, settings.selectLatestVersion(cache[it] ?: emptyList())) } - .toMutableList() + val packagesWithRepositories = manager.repositoryManager.packagesByRepository() + return packagesWithRepositories + .flatMap { (repository, packages) -> + packages.asSequence().map { pkg -> + createRepoPackage(pkg, repository) + } + } + .toList() + } + + private fun createRepoPackage(pkg: String, repository: PyPackageRepository): RepoPackage { + val repositoryUrl = when { + repository.isCustom -> repository.repositoryUrl + else -> null } + val latestVersion = getLatestVersion(pkg) + return RepoPackage(pkg, repositoryUrl, latestVersion) + } - val hasRepositories = manager - .repositoryManager - .repositories - .any { it !is PyPIPackageRepository && it !is CondaPackageRepository } + // TODO unify logic of retrieving package versions for pypi and conda + private fun getLatestVersion(pkg: String): String? { + if (!isConda || !useConda) return null - return manager - .repositoryManager - .packagesByRepository() - .filterNot { it.first is CondaPackageRepository } - .flatMap { (repo, pkgs) -> pkgs.asSequence().map { RepoPackage(it, if (hasRepositories) repo.repositoryUrl else null) } } - .toMutableList() + val settings = PyPackagingSettings.getInstance(project) + val cache = service() + val versions = cache[pkg] ?: emptyList() + return settings.selectLatestVersion(versions) } override fun getAllPackagesCached(): List { @@ -106,7 +82,7 @@ class PythonPackageManagementServiceBridge(project: Project,sdk: Sdk) : PyPackag } override fun reloadAllPackages(): List { - return runBlocking { + return runBlockingCancellable { manager.repositoryManager.refreshCaches() allPackages } @@ -185,15 +161,12 @@ class PythonPackageManagementServiceBridge(project: Project,sdk: Sdk) : PyPackag } } - private fun findRepositoryForPackage(name: String): PyPackageRepository { - if (manager is CondaPackageManager && useConda) return CondaPackageRepository - return manager + private fun findRepositoryForPackage(name: String): PyPackageRepository = + manager .repositoryManager .packagesByRepository() - .filterNot { it.first is CondaPackageRepository || it.first is PyPIPackageRepository } .firstOrNull { (_, packages) -> name in packages } ?.first ?: PyPIPackageRepository - } private fun buildDescription(details: PythonPackageDetails): String { return buildString { @@ -235,6 +208,6 @@ class PythonPackageManagementServiceBridge(project: Project,sdk: Sdk) : PyPackag } companion object { - var runningUnderOldUI = false + var runningUnderOldUI: Boolean = false } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/common/packages.kt b/python/src/com/jetbrains/python/packaging/common/packages.kt index 5b279cf8cf77..04ff4ccf225d 100644 --- a/python/src/com/jetbrains/python/packaging/common/packages.kt +++ b/python/src/com/jetbrains/python/packaging/common/packages.kt @@ -99,7 +99,7 @@ interface PythonPackageSpecification { } if (repository != null && repository != PyPIPackageRepository) { add("--index-url") - add(repository!!.urlForInstallation) + add(repository!!.urlForInstallation.toString()) } } } diff --git a/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt index d34371a38658..6e278b9dca0c 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CompositePythonPackageManager.kt @@ -19,7 +19,7 @@ internal class CompositePythonPackageManager( override var installedPackages: List = emptyList() override var repositoryManager: PythonRepositoryManager = - CompositePythonRepositoryManager(project, sdk, managers.map { it.repositoryManager }) + CompositePythonRepositoryManager(project, managers.map { it.repositoryManager }, sdk) private val managerNames = managers.joinToString { it.javaClass.simpleName } diff --git a/python/src/com/jetbrains/python/packaging/conda/CompositePythonRepositoryManager.kt b/python/src/com/jetbrains/python/packaging/conda/CompositePythonRepositoryManager.kt index 4fc0bf478bed..4f463647c27f 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CompositePythonRepositoryManager.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CompositePythonRepositoryManager.kt @@ -6,13 +6,13 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.jetbrains.python.PyBundle import com.jetbrains.python.packaging.PyPackageVersion +import com.jetbrains.python.packaging.PyPackageVersionComparator import com.jetbrains.python.packaging.common.EmptyPythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageSpecification import com.jetbrains.python.packaging.management.PythonPackageManagerService import com.jetbrains.python.packaging.management.PythonRepositoryManager import com.jetbrains.python.packaging.repository.PyPackageRepository -import io.github.z4kn4fein.semver.toVersion import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore @@ -21,32 +21,16 @@ import kotlinx.coroutines.sync.withPermit import java.util.concurrent.atomic.AtomicBoolean internal class CompositePythonRepositoryManager( - project: Project, - sdk: Sdk, + override val project: Project, private val managers: List, -) : PythonRepositoryManager(project, sdk) { + @Deprecated("Don't use sdk from here") override val sdk: Sdk +) : PythonRepositoryManager { override val repositories: List = managers.flatMap { it.repositories } - override fun allPackages(): Set { - return managers.fold(emptySet()) { acc, manager -> acc + manager.allPackages() } - } - - override fun packagesFromRepository(repository: PyPackageRepository): Set { - return findPackagesInRepository(repository) - ?: error("No packages for requested repository in cache") - } - - private fun findPackagesInRepository(repository: PyPackageRepository): Set? { - for (manager in managers) { - val packages = manager.packagesFromRepository(repository) - if (packages.isNotEmpty()) { - return packages - } - } - return null - } + override fun allPackages(): Set = + repositories.flatMap { it.getPackages() }.toSet() override suspend fun getPackageDetails(pkg: PythonPackageSpecification): PythonPackageDetails { for (manager in managers) { @@ -61,9 +45,7 @@ internal class CompositePythonRepositoryManager( var latestVersion: PyPackageVersion? = null for (manager in managers) { val version = manager.getLatestVersion(spec) - if (version != null && - (latestVersion == null || version.presentableText.toVersion() > latestVersion.presentableText.toVersion()) - ) { + if (version != null && (latestVersion == null || PyPackageVersionComparator.compare(version, latestVersion) > 0)) { latestVersion = version } } diff --git a/python/src/com/jetbrains/python/packaging/conda/CondaRepositoryManger.kt b/python/src/com/jetbrains/python/packaging/conda/CondaRepositoryManger.kt index bbee8e9d9da3..e268a74bbc6f 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CondaRepositoryManger.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CondaRepositoryManger.kt @@ -16,17 +16,14 @@ import com.jetbrains.python.packaging.repository.PyPackageRepository import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal -internal class CondaRepositoryManger(project: Project, sdk: Sdk) : PipBasedRepositoryManager(project, sdk) { +internal class CondaRepositoryManger( + override val project: Project, + @Deprecated("Don't use sdk from here") override val sdk: Sdk +) : PipBasedRepositoryManager() { override val repositories: List get() = listOf(CondaPackageRepository) + super.repositories - override fun allPackages(): Set = service().packages - - override fun packagesFromRepository(repository: PyPackageRepository): Set { - return if (repository is CondaPackageRepository) service().packages else super.packagesFromRepository(repository) - } - override fun buildPackageDetails(rawInfo: String?, spec: PythonPackageSpecification): PythonPackageDetails { if (spec is CondaPackageSpecification) { val versions = service()[spec.name] ?: error("No conda package versions in cache") diff --git a/python/src/com/jetbrains/python/packaging/conda/common.kt b/python/src/com/jetbrains/python/packaging/conda/common.kt index 1f79268805fd..28433215e6ba 100644 --- a/python/src/com/jetbrains/python/packaging/conda/common.kt +++ b/python/src/com/jetbrains/python/packaging/conda/common.kt @@ -23,7 +23,7 @@ class CondaPackageSpecification(name: String, relation: PyRequirementRelation? = null) : PythonPackageSpecificationBase(name, version, relation, CondaPackageRepository) { override val repository: PyPackageRepository = CondaPackageRepository override var versionSpecs: String? = null - get() = if (field != null) "${field}" else if (version != null) "${relation?.presentableText ?: "="}$version" else "" + get() = if (field != null) "${field}" else if (version != null) "${relation?.presentableText ?: "=="}$version" else "" override fun buildInstallationString(): List { return listOf("$name$versionSpecs") @@ -42,8 +42,18 @@ class CondaPackageDetails(override val name: String, } } -object CondaPackageRepository : PyPackageRepository("Conda", "", "") { +object CondaPackageRepository : PyPackageRepository("Conda", null, null) { override fun createPackageSpecification(packageName: String, version: String?, relation: PyRequirementRelation?): PythonPackageSpecification { return CondaPackageSpecification(packageName, version, relation) } + + override fun createForcedSpecPackageSpecification(packageName: String, versionSpecs: String?): PythonPackageSpecification { + val spec = CondaPackageSpecification(packageName, null, null) + spec.versionSpecs = versionSpecs + return spec + } + + override fun getPackages(): Set { + return service().packages + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerExt.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerExt.kt index 9c525ef9ae0f..3c35e8f10284 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerExt.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManagerExt.kt @@ -141,7 +141,7 @@ private val proxyString: String? } fun PythonRepositoryManager.packagesByRepository(): Sequence>> { - return repositories.asSequence().map { it to packagesFromRepository(it) } + return repositories.asSequence().map { it to it.getPackages() } } fun PythonPackageManager.isInstalled(name: String): Boolean { diff --git a/python/src/com/jetbrains/python/packaging/management/PythonRepositoryManager.kt b/python/src/com/jetbrains/python/packaging/management/PythonRepositoryManager.kt index a465395492d8..86f4d4ac5a10 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonRepositoryManager.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonRepositoryManager.kt @@ -8,23 +8,25 @@ import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageSpecification import com.jetbrains.python.packaging.repository.PyPackageRepository import org.jetbrains.annotations.ApiStatus +import java.io.IOException @ApiStatus.Experimental -abstract class PythonRepositoryManager(val project: Project, val sdk: Sdk) { - abstract val repositories: List +interface PythonRepositoryManager { + @Deprecated("Don't use sdk from here") + val sdk: Sdk + val project: Project + val repositories: List - abstract fun allPackages(): Set + fun allPackages(): Set + fun searchPackages(query: String): Map> + fun searchPackages(query: String, repository: PyPackageRepository): List - abstract fun packagesFromRepository(repository: PyPackageRepository): Set - abstract suspend fun getPackageDetails(pkg: PythonPackageSpecification): PythonPackageDetails - abstract suspend fun getLatestVersion(spec: PythonPackageSpecification): PyPackageVersion? + suspend fun getPackageDetails(pkg: PythonPackageSpecification): PythonPackageDetails + suspend fun getLatestVersion(spec: PythonPackageSpecification): PyPackageVersion? + fun buildPackageDetails(rawInfo: String?, spec: PythonPackageSpecification): PythonPackageDetails - abstract suspend fun refreshCaches() - - abstract suspend fun initCaches() - - abstract fun buildPackageDetails(rawInfo: String?, spec: PythonPackageSpecification): PythonPackageDetails - - abstract fun searchPackages(query: String, repository: PyPackageRepository): List - abstract fun searchPackages(query: String): Map> -} \ No newline at end of file + @Throws(IOException::class) + suspend fun refreshCaches() + @Throws(IOException::class) + suspend fun initCaches() +} diff --git a/python/src/com/jetbrains/python/packaging/pip/PipBasedRepositoryManager.kt b/python/src/com/jetbrains/python/packaging/pip/PipBasedRepositoryManager.kt index e92109398edb..9b79496cfc1c 100644 --- a/python/src/com/jetbrains/python/packaging/pip/PipBasedRepositoryManager.kt +++ b/python/src/com/jetbrains/python/packaging/pip/PipBasedRepositoryManager.kt @@ -5,25 +5,30 @@ import com.github.benmanes.caffeine.cache.Caffeine import com.google.gson.Gson import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.text.StringUtil import com.intellij.util.io.HttpRequests import com.jetbrains.python.PyBundle -import com.jetbrains.python.packaging.* +import com.jetbrains.python.packaging.PyPIPackageUtil +import com.jetbrains.python.packaging.PyPackageVersion +import com.jetbrains.python.packaging.PyPackageVersionComparator +import com.jetbrains.python.packaging.PyPackageVersionNormalizer import com.jetbrains.python.packaging.cache.PythonSimpleRepositoryCache import com.jetbrains.python.packaging.common.EmptyPythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageSpecification import com.jetbrains.python.packaging.common.PythonSimplePackageDetails import com.jetbrains.python.packaging.management.PythonRepositoryManager -import com.jetbrains.python.packaging.management.packagesByRepository -import com.jetbrains.python.packaging.repository.* +import com.jetbrains.python.packaging.normalizePackageName +import com.jetbrains.python.packaging.repository.PyEmptyPackagePackageRepository +import com.jetbrains.python.packaging.repository.PyPIPackageRepository +import com.jetbrains.python.packaging.repository.PyPackageRepositories +import com.jetbrains.python.packaging.repository.PyPackageRepository +import com.jetbrains.python.packaging.repository.withBasicAuthorization import org.jetbrains.annotations.ApiStatus import java.time.Duration @ApiStatus.Experimental -internal abstract class PipBasedRepositoryManager(project: Project, sdk: Sdk) : PythonRepositoryManager(project, sdk) { +internal abstract class PipBasedRepositoryManager() : PythonRepositoryManager { override val repositories: List get() = listOf(PyPIPackageRepository) + service().repositories @@ -34,9 +39,8 @@ internal abstract class PipBasedRepositoryManager(project: Project, sdk: Sdk) : .expireAfterWrite(Duration.ofHours(1)) .build { // todo[akniazev] make it possible to show info from several repos - val repositoryUrl = it.repository?.repositoryUrl ?: PyPIPackageRepository.repositoryUrl!! + val repositoryUrl = it.repository?.repositoryUrl ?: PyPIPackageRepository.repositoryUrl ?: "" val result = runCatching { - val packageDetailsUrl = PyPIPackageUtil.buildDetailsUrl(repositoryUrl, it.name) HttpRequests.request(packageDetailsUrl) .withBasicAuthorization(it.repository) @@ -72,7 +76,7 @@ internal abstract class PipBasedRepositoryManager(project: Project, sdk: Sdk) : if (rawInfo == null) { val versions = tryParsingVersionsFromPage(spec.name, spec.repository?.repositoryUrl) val repository = if (spec.repository !is PyEmptyPackagePackageRepository) spec.repository else PyPIPackageRepository - val repositoryName = repository?.name ?: PyPIPackageRepository.name!! + val repositoryName = repository?.name ?: PyPIPackageRepository.name if (versions != null) { return PythonSimplePackageDetails( spec.name, @@ -133,14 +137,8 @@ internal abstract class PipBasedRepositoryManager(project: Project, sdk: Sdk) : service().refresh() } - override fun allPackages(): Set { - return packagesByRepository().fold(emptySet()) { acc, manager -> acc + manager.second } - } - - override fun packagesFromRepository(repository: PyPackageRepository): Set { - return if (repository is PyPIPackageRepository) service().packages - else service()[repository] ?: error("No packages for requested repository in cache") - } + override fun allPackages(): Set = + repositories.flatMap { it.getPackages() }.toSet() override suspend fun getPackageDetails(pkg: PythonPackageSpecification): PythonPackageDetails { return packageDetailsCache[pkg] @@ -152,7 +150,7 @@ internal abstract class PipBasedRepositoryManager(project: Project, sdk: Sdk) : override fun searchPackages(query: String, repository: PyPackageRepository): List { val normalizedQuery = normalizePackageName(query) - return packagesFromRepository(repository).filter { StringUtil.containsIgnoreCase(normalizePackageName(it), normalizedQuery) } + return repository.getPackages().filter { StringUtil.containsIgnoreCase(normalizePackageName(it), normalizedQuery) } } override fun searchPackages(query: String): Map> { diff --git a/python/src/com/jetbrains/python/packaging/pip/PipRepositoryManager.kt b/python/src/com/jetbrains/python/packaging/pip/PipRepositoryManager.kt index 91c6012a6dcb..0ea3738da1b5 100644 --- a/python/src/com/jetbrains/python/packaging/pip/PipRepositoryManager.kt +++ b/python/src/com/jetbrains/python/packaging/pip/PipRepositoryManager.kt @@ -6,4 +6,7 @@ import com.intellij.openapi.projectRoots.Sdk import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal -internal class PipRepositoryManager(project: Project, sdk: Sdk) : PipBasedRepositoryManager(project, sdk) \ No newline at end of file +internal class PipRepositoryManager( + override val project: Project, + @Deprecated("Don't use sdk from here") override val sdk: Sdk, +) : PipBasedRepositoryManager() \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/repository/InstalledPyPackagedRepository.kt b/python/src/com/jetbrains/python/packaging/repository/InstalledPyPackagedRepository.kt index 1344b0a6385e..87790ece67a1 100644 --- a/python/src/com/jetbrains/python/packaging/repository/InstalledPyPackagedRepository.kt +++ b/python/src/com/jetbrains/python/packaging/repository/InstalledPyPackagedRepository.kt @@ -3,4 +3,4 @@ package com.jetbrains.python.packaging.repository import com.jetbrains.python.PyBundle -class InstalledPyPackagedRepository : PyPackageRepository(PyBundle.message("python.toolwindow.packages.installed.label"), "", "") {} \ No newline at end of file +class InstalledPyPackagedRepository : PyPackageRepository(PyBundle.message("python.toolwindow.packages.installed.label"), null, null) \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/repository/PyPackageRepository.kt b/python/src/com/jetbrains/python/packaging/repository/PyPackageRepository.kt index 5f1da78fcf7d..c2d1f8590925 100644 --- a/python/src/com/jetbrains/python/packaging/repository/PyPackageRepository.kt +++ b/python/src/com/jetbrains/python/packaging/repository/PyPackageRepository.kt @@ -5,70 +5,86 @@ import com.intellij.credentialStore.CredentialAttributes import com.intellij.credentialStore.Credentials import com.intellij.credentialStore.generateServiceName import com.intellij.ide.passwordSafe.PasswordSafe -import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.service import com.intellij.util.xmlb.annotations.Transient +import com.jetbrains.python.packaging.cache.PythonSimpleRepositoryCache import com.jetbrains.python.packaging.common.PythonPackageSpecification import com.jetbrains.python.packaging.common.PythonSimplePackageSpecification +import com.jetbrains.python.packaging.conda.CondaPackageRepository import com.jetbrains.python.packaging.requirement.PyRequirementRelation +import org.apache.http.client.utils.URIBuilder import org.jetbrains.annotations.ApiStatus +import java.net.URL @ApiStatus.Internal -open class PyPackageRepository() : BaseState() { - var name by string("") - var repositoryUrl by string("") - var authorizationType by enum(PyPackageRepositoryAuthenticationType.NONE) - var login by string("") +open class PyPackageRepository() { - val urlForInstallation: String - get() { - val fullUrl = repositoryUrl!! - if (login != null && login!!.isNotBlank()) { - val password = getPassword() - if (password != null) { - val protocol = fullUrl.substringBefore("//") - val url = fullUrl.substringAfter("//") - return "$protocol//${encodeCredentialsForUrl(login!!, password)}@$url" - } - } - return fullUrl - } + var name: String = "" + internal set + var repositoryUrl: String? = null + internal set + var login: String? = null + internal set + var authorizationType: PyPackageRepositoryAuthenticationType = PyPackageRepositoryAuthenticationType.NONE + internal set + constructor(name: String, repositoryUrl: String?, login: String?) : this() { + this.name = name + this.repositoryUrl = repositoryUrl + this.login = login + } + + internal val isCustom: Boolean + get() = this !is PyPIPackageRepository && this !is CondaPackageRepository + + private val serviceName: String + get() = generateServiceName(SUBSYSTEM_NAME, name) + + val urlForInstallation: URL + get() = repositoryUrl?.let { baseUrl -> + val userLogin = login.takeUnless { it.isNullOrBlank() } ?: return URL(baseUrl) + val userPassword = getPassword() ?: return URL(baseUrl) + buildAuthenticatedUrl(baseUrl, userLogin, userPassword) + } ?: URL("") + + private fun buildAuthenticatedUrl(baseUrl: String, login: String, password: String): URL = + URIBuilder(baseUrl).setUserInfo(login, password).build().toURL() @Transient fun getPassword(): String? { - val serviceName = generateServiceName("PyCharm", name!!) val attributes = CredentialAttributes(serviceName, login) return PasswordSafe.instance.getPassword(attributes) } fun setPassword(pass: String?) { - val serviceName = generateServiceName("PyCharm", name!!) val attributes = CredentialAttributes(serviceName, login) PasswordSafe.instance.set(attributes, Credentials(login, pass)) } fun clearCredentials() { - val serviceName = generateServiceName("PyCharm", name!!) val attributes = CredentialAttributes(serviceName, login) PasswordSafe.instance.set(attributes, null) } - constructor(name: String, repositoryUrl: String, username: String) : this() { - this.name = name - this.repositoryUrl = repositoryUrl - this.login = username - } + open fun createPackageSpecification( + packageName: String, + version: String? = null, + relation: PyRequirementRelation? = null, + ): PythonPackageSpecification = + PythonSimplePackageSpecification(packageName, version, this, relation) - open fun createPackageSpecification(packageName: String, - version: String? = null, - relation: PyRequirementRelation? = null): PythonPackageSpecification { - return PythonSimplePackageSpecification(packageName, version, this, relation) - } + open fun createForcedSpecPackageSpecification( + packageName: String, + versionSpecs: String? = null, + ): PythonPackageSpecification = + PythonSimplePackageSpecification(packageName, null, this).apply { + this.versionSpecs = versionSpecs + } - open fun createForcedSpecPackageSpecification(packageName: String, - versionSpecs: String? = null): PythonPackageSpecification { - val spec = PythonSimplePackageSpecification(packageName, null, this) - spec.versionSpecs = versionSpecs - return spec + open fun getPackages(): Set = + service()[this] ?: emptySet() + + companion object { + private const val SUBSYSTEM_NAME = "PyCharm" } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/repository/PyPackageRepositoryUtil.kt b/python/src/com/jetbrains/python/packaging/repository/PyPackageRepositoryUtil.kt index 3529104c4768..0394b182f7c8 100644 --- a/python/src/com/jetbrains/python/packaging/repository/PyPackageRepositoryUtil.kt +++ b/python/src/com/jetbrains/python/packaging/repository/PyPackageRepositoryUtil.kt @@ -2,13 +2,15 @@ @file:JvmName("PyPackageRepositoryUtil") package com.jetbrains.python.packaging.repository +import com.intellij.openapi.components.service import com.intellij.util.io.HttpRequests import com.intellij.util.io.RequestBuilder import com.jetbrains.python.packaging.PyPIPackageUtil +import com.jetbrains.python.packaging.pip.PypiPackageCache import org.jetbrains.annotations.ApiStatus import java.net.URLEncoder import java.nio.charset.StandardCharsets -import java.util.* +import java.util.Base64 @ApiStatus.Experimental internal fun RequestBuilder.withBasicAuthorization(repository: PyPackageRepository?): RequestBuilder { @@ -37,7 +39,11 @@ internal fun encodeCredentialsForUrl(login: String, password: String): String { } @ApiStatus.Experimental -object PyEmptyPackagePackageRepository : PyPackageRepository("empty repository", "", "") +object PyEmptyPackagePackageRepository : PyPackageRepository("empty repository", null, null) @ApiStatus.Experimental -object PyPIPackageRepository : PyPackageRepository("PyPI", PyPIPackageUtil.PYPI_LIST_URL, "") \ No newline at end of file +object PyPIPackageRepository : PyPackageRepository("PyPI", PyPIPackageUtil.PYPI_LIST_URL, null) { + override fun getPackages(): Set { + return service().packages + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/repository/PyRepositoriesList.kt b/python/src/com/jetbrains/python/packaging/repository/PyRepositoriesList.kt index 7686fdeece1e..23263b258243 100644 --- a/python/src/com/jetbrains/python/packaging/repository/PyRepositoriesList.kt +++ b/python/src/com/jetbrains/python/packaging/repository/PyRepositoriesList.kt @@ -20,8 +20,9 @@ class PyRepositoriesList(val project: Project) : MasterDetailsComponent() { init { initTree() - service().repositories - .map { MyNode(PyRepositoryListItem(it)) } + service() + .repositories + .map { MyNode(PyRepositoryListItem(it, project)) } .forEach { addNode(it, myRoot) } } @@ -34,7 +35,7 @@ class PyRepositoriesList(val project: Project) : MasterDetailsComponent() { PyBundle.message("python.packaging.repository.form.default.name"), remainingRepositories().map { it.name }.toMutableList()) - val newNode = MyNode(PyRepositoryListItem(PyPackageRepository(uniqueName, "", ""))) + val newNode = MyNode(PyRepositoryListItem(PyPackageRepository(uniqueName, null, null), project)) addNode(newNode, myRoot) selectNodeInTree(newNode) } diff --git a/python/src/com/jetbrains/python/packaging/repository/PyRepositoryListItem.kt b/python/src/com/jetbrains/python/packaging/repository/PyRepositoryListItem.kt index df63d44343b6..2d18657aaf8a 100644 --- a/python/src/com/jetbrains/python/packaging/repository/PyRepositoryListItem.kt +++ b/python/src/com/jetbrains/python/packaging/repository/PyRepositoryListItem.kt @@ -1,7 +1,9 @@ // 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.repository +import com.intellij.openapi.components.service import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.openapi.project.Project import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.util.NlsSafe import com.intellij.ui.components.JBTextField @@ -10,6 +12,8 @@ import com.intellij.ui.dsl.builder.bindText import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import com.jetbrains.python.PyBundle.message +import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService +import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus import java.awt.BorderLayout import java.awt.Dimension @@ -17,7 +21,7 @@ import javax.swing.JComponent import javax.swing.JPanel @ApiStatus.Internal -internal class PyRepositoryListItem(val repository: PyPackageRepository) : NamedConfigurable(true, null) { +internal class PyRepositoryListItem(val repository: PyPackageRepository, private val project: Project) : NamedConfigurable(true, null) { @NlsSafe private var currentName = repository.name!! private var password = repository.getPassword() @@ -25,7 +29,7 @@ internal class PyRepositoryListItem(val repository: PyPackageRepository) : Named private val propertyGraph = PropertyGraph() private val urlProperty = propertyGraph.lazyProperty { repository.repositoryUrl ?: "" } private val loginProperty = propertyGraph.lazyProperty { repository.login ?: "" } - private val passwordProperty = propertyGraph.lazyProperty { repository.getPassword() ?: "" } + private val passwordProperty = propertyGraph.lazyProperty { getPassword(repository) } private val authorizationTypeProperty = propertyGraph.lazyProperty { repository.authorizationType } override fun getDisplayName(): String { @@ -87,7 +91,7 @@ internal class PyRepositoryListItem(val repository: PyPackageRepository) : Named .bindText(urlProperty) } row(message("python.packaging.repository.form.authorization")) { - segmentedButton(PyPackageRepositoryAuthenticationType.values().toList()) { text = it.text } + segmentedButton(PyPackageRepositoryAuthenticationType.entries) { text = it.text } .bind(authorizationTypeProperty) } val row1 = row(message("python.packaging.repository.form.login")) { @@ -95,7 +99,7 @@ internal class PyRepositoryListItem(val repository: PyPackageRepository) : Named .bindText(loginProperty) }.visible(repository.authorizationType != PyPackageRepositoryAuthenticationType.NONE) val row2 = row(message("python.packaging.repository.form.password")) { - passwordField().applyToComponent { text = repository.getPassword() } + passwordField().applyToComponent { text = getPassword(repository) } .apply { component.preferredSize = Dimension(250, component.preferredSize.height) } .bindText(passwordProperty) }.visible(repository.authorizationType != PyPackageRepositoryAuthenticationType.NONE) @@ -109,4 +113,13 @@ internal class PyRepositoryListItem(val repository: PyPackageRepository) : Named mainPanel.add(repositoryForm, BorderLayout.CENTER) return mainPanel } + + private fun getPassword(repository: PyPackageRepository): String { + val toolWindowService = project.service() + var retrievedPassword: String? = null + toolWindowService.serviceScope.launch { + retrievedPassword = repository.getPassword() + } + return retrievedPassword ?: "" + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt index 85c1163c36d6..b11be7faa3b1 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt @@ -22,7 +22,12 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.platform.ide.progress.withBackgroundProgress import com.intellij.platform.util.progress.reportRawProgress import com.jetbrains.python.PyBundle.message -import com.jetbrains.python.packaging.* +import com.jetbrains.python.packaging.PyPIPackageRanking +import com.jetbrains.python.packaging.PyPIPackageUtil +import com.jetbrains.python.packaging.PyPackageService +import com.jetbrains.python.packaging.PyPackageVersion +import com.jetbrains.python.packaging.PyPackageVersionComparator +import com.jetbrains.python.packaging.PyPackageVersionNormalizer import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageManagementListener import com.jetbrains.python.packaging.common.PythonPackageSpecification @@ -30,13 +35,30 @@ import com.jetbrains.python.packaging.common.runPackagingOperationOrShowErrorDia import com.jetbrains.python.packaging.conda.CondaPackage import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.packaging.management.packagesByRepository -import com.jetbrains.python.packaging.repository.* +import com.jetbrains.python.packaging.normalizePackageName +import com.jetbrains.python.packaging.repository.PyEmptyPackagePackageRepository +import com.jetbrains.python.packaging.repository.PyPackageRepositories +import com.jetbrains.python.packaging.repository.PyPackageRepository +import com.jetbrains.python.packaging.repository.PyRepositoriesList +import com.jetbrains.python.packaging.repository.checkValid import com.jetbrains.python.packaging.statistics.PythonPackagesToolwindowStatisticsCollector -import com.jetbrains.python.packaging.toolwindow.model.* +import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage +import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage +import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage +import com.jetbrains.python.packaging.toolwindow.model.PyInvalidRepositoryViewData +import com.jetbrains.python.packaging.toolwindow.model.PyPackagesViewData import com.jetbrains.python.sdk.PythonSdkUtil import com.jetbrains.python.sdk.pythonSdk import com.jetbrains.python.statistics.modules -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jetbrains.annotations.Nls @Service(Service.Level.PROJECT) @@ -353,7 +375,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou return sortPackagesForRepo(manager.repositoryManager.searchPackages(currentQuery, repository), currentQuery, repository, skipItems) } else { - val packagesFromRepo = manager.repositoryManager.packagesFromRepository(repository) + val packagesFromRepo = repository.getPackages() val page = packagesFromRepo.asSequence().limitResultAndFilterOutInstalled(repository, skipItems) return PyPackagesViewData(repository, page, moreItems = packagesFromRepo.size - (PACKAGES_LIMIT + skipItems)) } diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDescriptionController.kt b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDescriptionController.kt index c38721eb1805..371c1c963098 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDescriptionController.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDescriptionController.kt @@ -5,6 +5,7 @@ import com.intellij.ide.BrowserUtil import com.intellij.ide.plugins.newui.OneLineProgressIndicator import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.UiDataProvider +import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service import com.intellij.openapi.observable.properties.AtomicBooleanProperty import com.intellij.openapi.observable.properties.AtomicProperty @@ -24,7 +25,11 @@ import com.intellij.ui.JBColor import com.intellij.ui.SideBorder import com.intellij.ui.components.JBComboBoxLabel import com.intellij.ui.components.JBOptionButton -import com.intellij.ui.dsl.builder.* +import com.intellij.ui.dsl.builder.BottomGap +import com.intellij.ui.dsl.builder.RightGap +import com.intellij.ui.dsl.builder.TopGap +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.panel import com.intellij.ui.jcef.JCEFHtmlPanel import com.jetbrains.python.PyBundle.message import com.jetbrains.python.packaging.PyPackageUtil @@ -38,19 +43,25 @@ import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents import com.jetbrains.python.packaging.utils.PyPackageCoroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jetbrains.annotations.Nls import java.awt.BorderLayout import java.awt.Font import java.awt.event.ActionEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent -import javax.swing.* +import javax.swing.AbstractAction +import javax.swing.Action +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.SwingConstants class PyPackageDescriptionController(val project: Project) : Disposable { private val latestText: String get() = message("python.toolwindow.packages.latest.version.label") - val service = project.service() + val service: PyPackagingToolWindowService = project.service() internal val selectedPackage = AtomicProperty(null) private val isManagement = AtomicBooleanProperty(false) @@ -114,23 +125,26 @@ class PyPackageDescriptionController(val project: Project) : Disposable { private val rightPanel = panel { row { cell(progressIndicatorComponent).gap(RightGap.SMALL).visibleIf(progressEnabledProperty) - versionSelector.apply { versionSelector.text = packageVersionProperty.get() addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { - val versions = listOf(latestText) + (selectedPackageDetails.get()?.availableVersions ?: emptyList()) + val availableVersions = selectedPackageDetails.get()?.availableVersions ?: emptyList() + val latestVersion = availableVersions.first() + val versions = listOf(latestText) + availableVersions JBPopupFactory.getInstance().createListPopup( object : BaseListPopupStep(null, versions) { override fun onChosen(@NlsContexts.Label selectedValue: String, finalChoice: Boolean): PopupStep<*>? { packageVersionProperty.set(selectedValue) - suggestInstallPackage(selectedValue) + val effectiveVersion = if (selectedValue == latestText) latestVersion else selectedValue + suggestInstallPackage(effectiveVersion) return FINAL_CHOICE } }, 8).showUnderneathOf(this@apply) } }) } + packageVersionProperty.afterChange { versionSelector.text = it } @@ -165,7 +179,7 @@ class PyPackageDescriptionController(val project: Project) : Disposable { add(htmlPanel.component, BorderLayout.CENTER) } - val wrappedComponent = UiDataProvider.wrapComponent(component, UiDataProvider {}) + val wrappedComponent: JComponent = UiDataProvider.wrapComponent(component, UiDataProvider {}) override fun dispose() {} @@ -182,6 +196,7 @@ class PyPackageDescriptionController(val project: Project) : Disposable { private fun updatePackageVersion(newVersion: String) { val details = selectedPackageDetails.get() ?: return val newVersionSpec = details.toPackageSpecification(newVersion) + println(newVersionSpec.versionSpecs) val pyPackagingToolWindowService = PyPackagingToolWindowService.getInstance(project) PyPackageCoroutine.launch(project, Dispatchers.IO) { pyPackagingToolWindowService.installPackage(newVersionSpec) @@ -218,7 +233,9 @@ class PyPackageDescriptionController(val project: Project) : Disposable { actionPerformed() } finally { - progressEnabledProperty.set(false) + withContext(Dispatchers.EDT) { + progressEnabledProperty.set(false) + } progressIndicator.stop() } } diff --git a/python/src/com/jetbrains/python/run/runAnything/PyRunAnythingPackageProvider.kt b/python/src/com/jetbrains/python/run/runAnything/PyRunAnythingPackageProvider.kt index ca53778762b7..bbd511130301 100644 --- a/python/src/com/jetbrains/python/run/runAnything/PyRunAnythingPackageProvider.kt +++ b/python/src/com/jetbrains/python/run/runAnything/PyRunAnythingPackageProvider.kt @@ -39,7 +39,7 @@ abstract class PyRunAnythingPackageProvider : RunAnythingCommandLineProvider() { initCaches(packageManager) if (isInstall) { val packageRepository = getPackageRepository(dataContext) ?: return emptySequence() - return packageManager.repositoryManager.packagesFromRepository(packageRepository).filter { + return packageRepository.getPackages().filter { it.startsWith(commandLine.toComplete) }.asSequence() } diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt index b1a1206dff1f..1fb951ce6b0a 100644 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt +++ b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt @@ -8,7 +8,6 @@ import com.jetbrains.python.packaging.common.PythonPackage import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageSpecification import com.jetbrains.python.packaging.common.PythonSimplePackageDetails -import com.jetbrains.python.packaging.repository.PyEmptyPackagePackageRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import org.jetbrains.annotations.TestOnly @@ -95,7 +94,7 @@ class TestPythonPackageManagerService(val installedPackages: List return TestPythonPackageManager(project, sdk) .withPackageInstalled(installedPackages) .withPackageNames(installedPackages.map { it.name }) - .withPackageDetails(PythonSimplePackageDetails(installedPackages.first().name, listOf(installedPackages.first().version),PyEmptyPackagePackageRepository)) + .withPackageDetails(PythonSimplePackageDetails(installedPackages.first().name, listOf(installedPackages.first().version), TestPackageRepository(installedPackages.map { it.name }.toSet()))) } override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge { diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt index 23f78932debc..83e030d86c60 100644 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt +++ b/python/testSrc/com/jetbrains/python/packaging/management/TestPythonRepositoryManager.kt @@ -6,12 +6,14 @@ import com.intellij.openapi.projectRoots.Sdk import com.jetbrains.python.packaging.PyPackageVersion import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonPackageSpecification -import com.jetbrains.python.packaging.repository.PyEmptyPackagePackageRepository import com.jetbrains.python.packaging.repository.PyPackageRepository import org.jetbrains.annotations.TestOnly @TestOnly -class TestPythonRepositoryManager(project: Project, sdk: Sdk) : PythonRepositoryManager(project, sdk) { +internal class TestPythonRepositoryManager( + override val project: Project, + @Deprecated("Don't use sdk from here") override val sdk: Sdk +) : PythonRepositoryManager { private var packageNames: Set = emptySet() private var packageDetails: PythonPackageDetails? = null @@ -39,16 +41,12 @@ class TestPythonRepositoryManager(project: Project, sdk: Sdk) : PythonRepository } override val repositories: List - get() = listOf(PyEmptyPackagePackageRepository) + get() = listOf(TestPackageRepository(packageNames)) override fun allPackages(): Set { return packageNames } - override fun packagesFromRepository(repository: PyPackageRepository): Set { - return packageNames - } - override suspend fun getPackageDetails(pkg: PythonPackageSpecification): PythonPackageDetails { assert(packageDetails != null) return packageDetails!! @@ -63,4 +61,10 @@ class TestPythonRepositoryManager(project: Project, sdk: Sdk) : PythonRepository override suspend fun initCaches() { } +} + +internal class TestPackageRepository(private val packages: Set): PyPackageRepository("test repository", null, null) { + override fun getPackages(): Set { + return packages + } } \ No newline at end of file