mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-80189 Introduced a unified PythonPackageRequiresExtractor interface for parsing package requirements for Python SDKs. Added implementations for Poetry and UV tools, including the necessary changes to handle normalized package names. Added corresponding unit tests to validate the extraction functionality.
GitOrigin-RevId: add74cf74974af6608d88546d86fe72900eee271
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2e311368af
commit
a54f307202
@@ -778,6 +778,10 @@
|
||||
<extensionPoint qualifiedName="com.jetbrains.python.configuration.pyIntegratedToolsTestPanelCustomizer"
|
||||
interface="com.jetbrains.python.configuration.PyIntegratedToolsTestPanelCustomizer"
|
||||
dynamic="true"/>
|
||||
|
||||
<extensionPoint qualifiedName="Pythonid.PythonPackageRequiresExtractorProvider"
|
||||
interface="com.jetbrains.python.packaging.packageRequires.PythonPackageRequiresExtractorProvider"
|
||||
dynamic="true"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="Pythonid">
|
||||
@@ -880,6 +884,9 @@
|
||||
<pythonPackageManagerProvider implementation="com.jetbrains.python.packaging.pip.PipPackageManagerProvider" order="last"/>
|
||||
<pythonPackageManagerProvider implementation="com.jetbrains.python.packaging.conda.CondaPackageManagerProvider"/>
|
||||
|
||||
<PythonPackageRequiresExtractorProvider implementation="com.jetbrains.python.poetry.packaging.PoetryRequiresExtractorProvider"/>
|
||||
<PythonPackageRequiresExtractorProvider implementation="com.jetbrains.python.uv.packaging.UvPackageRequiresExtractorProvider"/>
|
||||
|
||||
<systemPythonProvider implementation="com.jetbrains.python.sdk.uv.UvSystemPythonProvider"/>
|
||||
|
||||
<inspectionExtension implementation="com.jetbrains.python.sdk.configuration.PyInterpreterInspectionSuppressor"/>
|
||||
|
||||
@@ -6,14 +6,24 @@ import com.jetbrains.python.packaging.pyRequirementVersionSpec
|
||||
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementRelation
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
@ApiStatus.Internal
|
||||
@JvmInline
|
||||
value class NormalizedPythonPackageName private constructor(val name: String) {
|
||||
companion object {
|
||||
fun from(name: String): NormalizedPythonPackageName =
|
||||
NormalizedPythonPackageName(normalizePackageName(name))
|
||||
}
|
||||
}
|
||||
|
||||
open class PythonPackage(name: String, val version: String, val isEditableMode: Boolean) {
|
||||
companion object {
|
||||
private const val HASH_MULTIPLIER = 31
|
||||
}
|
||||
|
||||
val name: String = normalizePackageName(name)
|
||||
val name: String = NormalizedPythonPackageName.from(name).name
|
||||
val presentableName: String = name
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// 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.packageRequires
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface PythonPackageRequiresExtractor {
|
||||
|
||||
suspend fun extract(pkg: PythonPackage, module: Module): List<NormalizedPythonPackageName>
|
||||
|
||||
companion object {
|
||||
fun forSdk(sdk: Sdk): PythonPackageRequiresExtractor? =
|
||||
PythonPackageRequiresExtractorProvider.EP_NAME.extensionList.firstNotNullOf { it.createExtractor(sdk) }
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface PythonPackageRequiresExtractorProvider {
|
||||
|
||||
fun createExtractor(sdk: Sdk): PythonPackageRequiresExtractor?
|
||||
|
||||
companion object {
|
||||
val EP_NAME: ExtensionPointName<PythonPackageRequiresExtractorProvider> = ExtensionPointName.create<PythonPackageRequiresExtractorProvider>("Pythonid.PythonPackageRequiresExtractorProvider")
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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.poetry.packaging
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.packageRequires.PythonPackageRequiresExtractor
|
||||
import com.jetbrains.python.packaging.packageRequires.PythonPackageRequiresExtractorProvider
|
||||
import com.jetbrains.python.sdk.poetry.isPoetry
|
||||
import com.jetbrains.python.sdk.poetry.runPoetryWithSdk
|
||||
|
||||
internal class PoetryPackageRequiresExtractor(private val sdk: Sdk) : PythonPackageRequiresExtractor {
|
||||
|
||||
override suspend fun extract(pkg: PythonPackage, module: Module): List<NormalizedPythonPackageName> {
|
||||
val output = runPoetryWithSdk(sdk, "show", pkg.name)
|
||||
val data = output.getOrElse {
|
||||
thisLogger().info("extracting requires for package ${pkg.name}: error. Output: \n${it.stackTraceToString()}")
|
||||
return emptyList()
|
||||
}
|
||||
return parsePackageData(data.lines())
|
||||
}
|
||||
|
||||
private fun parsePackageData(lines: List<String>): List<NormalizedPythonPackageName> {
|
||||
val packages = mutableListOf<NormalizedPythonPackageName>()
|
||||
|
||||
fun parseDependencyLine(line: String): NormalizedPythonPackageName? {
|
||||
val depLine = line.removePrefix(DEPENDENCY_PREFIX).trim()
|
||||
val name = depLine.split(" ", limit = 2).let { parts ->
|
||||
parts.getOrNull(0)?.trim()
|
||||
}
|
||||
return name?.let { NormalizedPythonPackageName.Companion.from(name) }
|
||||
}
|
||||
|
||||
var inDependenciesSection = false
|
||||
for (line in lines) {
|
||||
val trimmedLine = line.trim()
|
||||
when {
|
||||
trimmedLine == DEPENDENCIES_MARKER -> inDependenciesSection = true
|
||||
inDependenciesSection && line.startsWith(DEPENDENCY_PREFIX) -> parseDependencyLine(line)?.let { packages.add(it) }
|
||||
inDependenciesSection && trimmedLine.isEmpty() -> break
|
||||
}
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DEPENDENCIES_MARKER = "dependencies"
|
||||
const val DEPENDENCY_PREFIX = " - "
|
||||
}
|
||||
}
|
||||
|
||||
private class PoetryRequiresExtractorProvider: PythonPackageRequiresExtractorProvider {
|
||||
override fun createExtractor(sdk: Sdk): PythonPackageRequiresExtractor? {
|
||||
if (!sdk.isPoetry) return null
|
||||
return PoetryPackageRequiresExtractor(sdk)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// 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.errorProcessing.PyExecResult
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
@@ -34,6 +35,7 @@ interface UvLowLevel {
|
||||
|
||||
suspend fun listPackages(): PyExecResult<List<PythonPackage>>
|
||||
suspend fun listOutdatedPackages(): PyResult<List<PythonOutdatedPackage>>
|
||||
suspend fun listPackageRequirements(name: PythonPackage): PyResult<List<NormalizedPythonPackageName>>
|
||||
|
||||
suspend fun isProjectSynced(inexact: Boolean): PyExecResult<Boolean>
|
||||
suspend fun isScriptSynced(inexact: Boolean, scriptPath: Path): PyExecResult<ScriptSyncCheckResult>
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.errorProcessing.failure
|
||||
import com.jetbrains.python.onFailure
|
||||
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
@@ -127,6 +128,13 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun listPackageRequirements(name: PythonPackage): PyResult<List<NormalizedPythonPackageName>> {
|
||||
val out = uvCli.runUv(cwd, "pip", "show", name.name)
|
||||
.getOr { return it }
|
||||
|
||||
return PyExecResult.success(parsePackageRequirements(out))
|
||||
}
|
||||
|
||||
override suspend fun installPackage(name: PythonPackageInstallRequest, options: List<String>): PyExecResult<Unit> {
|
||||
uvCli.runUv(cwd, "pip", "install", name.formatPackageName(), *options.toTypedArray())
|
||||
.onFailure { return PyResult.failure(it) }
|
||||
@@ -235,11 +243,26 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
|
||||
|
||||
override suspend fun sync(): PyExecResult<String> {
|
||||
return uvCli.runUv(cwd, "sync")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun lock(): PyExecResult<String> {
|
||||
return uvCli.runUv(cwd, "lock")
|
||||
}
|
||||
|
||||
private fun parsePackageRequirements(input: String): List<NormalizedPythonPackageName> {
|
||||
val requiresLine = input.lines().find { it.startsWith(REQUIRES_LINE_PREFIX) } ?: return emptyList()
|
||||
|
||||
return requiresLine
|
||||
.removePrefix(REQUIRES_LINE_PREFIX)
|
||||
.split(",")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { NormalizedPythonPackageName.from(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUIRES_LINE_PREFIX = "Requires:"
|
||||
}
|
||||
}
|
||||
|
||||
fun createUvLowLevel(cwd: Path, uvCli: UvCli = createUvCli()): UvLowLevel {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.uv.packaging
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.packageRequires.PythonPackageRequiresExtractor
|
||||
import com.jetbrains.python.packaging.packageRequires.PythonPackageRequiresExtractorProvider
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.uv.UvSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvCli
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class UvPackageRequiresExtractor(private val uvWorkingDirectory: Path?) : PythonPackageRequiresExtractor {
|
||||
override suspend fun extract(pkg: PythonPackage, module: Module): List<NormalizedPythonPackageName> {
|
||||
val uvWorkingDirectory = uvWorkingDirectory ?: Path.of(module.basePath!!)
|
||||
val uv = createUvLowLevel(uvWorkingDirectory, createUvCli())
|
||||
return uv.listPackageRequirements(pkg).getOr {
|
||||
thisLogger().info("extracting requires for package ${pkg.name}: error. Output: \n${it.error}")
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UvPackageRequiresExtractorProvider: PythonPackageRequiresExtractorProvider {
|
||||
override fun createExtractor(sdk: Sdk): PythonPackageRequiresExtractor? {
|
||||
val data = sdk.sdkAdditionalData as? UvSdkAdditionalData ?: return null
|
||||
return UvPackageRequiresExtractor(data.uvWorkingDirectory)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user