PY-87290 Collect dependencies from groups and tool specific keys

These changes support collecting python project dependencies from PEP
621 standard pyproject.toml locations (including dependency groups).
Also a support for tool specific keys to collect their dependencies was
added as well.


(cherry picked from commit cc376c1696b3ddd435e113cfa39b547d60f0f791)

IJ-MR-193200

GitOrigin-RevId: 6d87d4983755525322ca0a4f2c1aedc4510a817b
This commit is contained in:
Alexey Katsman
2026-02-24 17:50:58 +01:00
committed by intellij-monorepo-bot
parent 03f5e63efe
commit e81dfd761d
9 changed files with 382 additions and 44 deletions

View File

@@ -4,6 +4,7 @@ package com.intellij.python.hatch.impl
import com.intellij.openapi.util.NlsSafe
import com.intellij.python.common.tools.ToolId
import com.intellij.python.hatch.icons.PythonHatchIcons
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
@@ -26,5 +27,10 @@ internal class HatchTool : Tool {
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? = null
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo? = null
override suspend fun getProjectStructure(
entries: Map<ProjectName, PyProjectTomlProject>,
rootIndex: Map<Directory, ProjectName>,
): ProjectStructureInfo? = null
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = emptyList()
}

View File

@@ -6,6 +6,7 @@ import com.intellij.openapi.util.NlsSafe
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
import com.intellij.python.community.impl.poetry.common.POETRY_UI_INFO
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
@@ -24,6 +25,14 @@ internal class PoetryTool : Tool {
override suspend fun getProjectName(projectToml: TomlTable): @NlsSafe String? =
projectToml.getString("tool.poetry.name")
override suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo? = null
override suspend fun getProjectStructure(
entries: Map<ProjectName, PyProjectTomlProject>,
rootIndex: Map<Directory, ProjectName>,
): ProjectStructureInfo? = null
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = listOf(
TomlDependencySpecification.PathDependency("tool.poetry.dependencies"),
TomlDependencySpecification.PathDependency("tool.poetry.dev-dependencies"),
TomlDependencySpecification.GroupPathDependency("tool.poetry.group", "dependencies"),
)
}

View File

@@ -35,6 +35,9 @@ sealed class TomlTableSafeGetError {
* On a missing value, returns an instance of [Result.Success] with a value of null.
* On a present value with the valid type, returns an instance of [Result.Success] with that value.
*
* @param key is the key in a table
* @param unquotedDottedKey specifies whether a key should be treated as a dotted key without quotes
*
* Example:
*
* ```kotlin
@@ -46,8 +49,10 @@ sealed class TomlTableSafeGetError {
* ```
*/
@Internal
inline fun <reified T> TomlTable.safeGet(key: String): Result<T?, TomlTableSafeGetError.UnexpectedType> {
val value = get("\"$key\"")
inline fun <reified T> TomlTable.safeGet(
key: String, unquotedDottedKey: Boolean = false,
): Result<T?, TomlTableSafeGetError.UnexpectedType> {
val value = if (unquotedDottedKey) get(key) else get("\"$key\"")
if (value == null) {
return success(null)
@@ -101,6 +106,9 @@ internal inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T
* On a missing value, returns an instance of [Result.Success] with a value of null.
* On a present value with the valid type, returns an instance of [Result.Success] with a [List] of type [T].
*
* @param key is the key in a table
* @param unquotedDottedKey specifies whether a key should be treated as a dotted key without quotes
*
* Example:
*
* ```kotlin
@@ -116,8 +124,10 @@ internal inline fun <reified T> TomlTable.safeGetRequired(key: String): Result<T
* ```
*/
@Internal
inline fun <reified T> TomlTable.safeGetArr(key: String): Result<List<T>?, TomlTableSafeGetError.UnexpectedType> {
val array = safeGet<TomlArray>(key).getOr { return it }
inline fun <reified T> TomlTable.safeGetArr(
key: String, unquotedDottedKey: Boolean = false,
): Result<List<T>?, TomlTableSafeGetError.UnexpectedType> {
val array = safeGet<TomlArray>(key, unquotedDottedKey).getOr { return it }
if (array == null) {
return success(null)

View File

@@ -6,12 +6,14 @@ import com.intellij.python.pyproject.PyProjectToml
import com.intellij.python.pyproject.model.spi.ProjectDependencies
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
import com.intellij.python.pyproject.safeGetArr
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.Result
import com.jetbrains.python.venvReader.Directory
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.tuweni.toml.TomlTable
import java.io.IOException
import java.net.URI
import java.net.URISyntaxException
@@ -81,23 +83,6 @@ suspend fun walkFileSystemNoTomlContent(
}
}
suspend fun getPEP621Deps(
entries: Map<ProjectName, PyProjectTomlProject>,
rootIndex: Map<Directory, ProjectName>,
): ProjectDependencies = withContext(Dispatchers.Default) {
val deps = entries.asSequence().associate { (name, entry) ->
val deps = getDependenciesFromToml(entry.pyProjectToml).mapNotNull { dir ->
rootIndex[dir] ?: run {
logger.warn("Can't find project for dir $dir")
null
}
}.toSet()
Pair(name, deps)
}
ProjectDependencies(deps) // No workspace info (yet)
}
private val logger = fileLogger()
private suspend fun readFile(file: Path): PyProjectToml? {
@@ -118,16 +103,93 @@ private suspend fun readFile(file: Path): PyProjectToml? {
}
}
@RequiresBackgroundThread
private fun getDependenciesFromToml(projectToml: PyProjectToml): Set<Directory> {
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
val moduleDependencies = depsFromFile
.mapNotNull { depSpec ->
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return@mapNotNull null
val (_, depUri) = match.destructured
return@mapNotNull parseDepUri(depUri)
suspend fun getDependenciesFromToml(
entries: Map<ProjectName, PyProjectTomlProject>,
rootIndex: Map<Directory, ProjectName>,
tomlDependencySpecifications: List<TomlDependencySpecification>,
): ProjectDependencies = withContext(Dispatchers.Default) {
val deps = entries.asSequence().associate { (name, entry) ->
val depsPaths = collectAllDependencies(entry, tomlDependencySpecifications)
val deps = processDependenciesWithRootIndex(depsPaths, rootIndex)
Pair(name, deps)
}
ProjectDependencies(deps)
}
private fun processDependenciesWithRootIndex(dependencies: Sequence<Directory>, rootIndex: Map<Directory, ProjectName>): Set<ProjectName> =
dependencies.mapNotNull { dir ->
rootIndex[dir] ?: run {
logger.warn("Can't find project for dir $dir")
null
}
return moduleDependencies.toSet()
}.toSet()
@RequiresBackgroundThread
private fun collectAllDependencies(
entry: PyProjectTomlProject, tomlDependencySpecifications: List<TomlDependencySpecification>,
): Sequence<Directory> = sequence {
yieldAll(getDependenciesFromProject(entry.pyProjectToml))
yieldAll(getDependenciesFromPep735Groups(entry.pyProjectToml.toml))
yieldAll(getToolSpecificDependencies(entry.root, entry.pyProjectToml.toml, tomlDependencySpecifications))
}
@RequiresBackgroundThread
private fun getToolSpecificDependencies(
root: Path, tomlTable: TomlTable, tomlDependencySpecifications: List<TomlDependencySpecification>,
): Sequence<Directory> {
return tomlDependencySpecifications.asSequence().flatMap { specification ->
when (specification) {
is TomlDependencySpecification.PathDependency -> tomlTable.getTable(specification.tomlKey)?.let {
getToolSpecificDependenciesFromTomlTable(root, it)
} ?: emptySet()
is TomlDependencySpecification.Pep621Dependency -> {
val deps = tomlTable.safeGetArr<String>(specification.tomlKey, unquotedDottedKey = true).successOrNull ?: emptyList()
deps.asSequence().mapNotNull(::parsePep621Dependency).toSet()
}
is TomlDependencySpecification.GroupPathDependency -> {
val groups = tomlTable.getTable(specification.tomlKeyToGroup) ?: return@flatMap emptySet()
groups.keySet().flatMap { group ->
groups.getTable("${group}.${specification.tomlKeyFromGroupToPath}")?.let {
getToolSpecificDependenciesFromTomlTable(root, it)
} ?: emptySet()
}
}
}
}
}
@RequiresBackgroundThread
private fun getToolSpecificDependenciesFromTomlTable(root: Path, tomlTable: TomlTable): Set<Directory> {
return tomlTable.keySet().asSequence().mapNotNull {
tomlTable.getString("${it}.path")?.let { depPathString -> parseDepFromPathString(root, depPathString) }
}.toSet()
}
@RequiresBackgroundThread
private fun getDependenciesFromPep735Groups(tomlTable: TomlTable): Sequence<Directory> {
val groups = tomlTable.getTable("dependency-groups") ?: return emptySequence()
return groups.keySet().asSequence().flatMap { group ->
val deps = groups.safeGetArr<String>(group).successOrNull ?: emptyList()
deps.asSequence().mapNotNull(::parsePep621Dependency)
}
}
@RequiresBackgroundThread
private fun getDependenciesFromProject(projectToml: PyProjectToml): Sequence<Directory> {
val depsFromFile = projectToml.project?.dependencies?.project ?: emptyList()
return depsFromFile.asSequence().mapNotNull(::parsePep621Dependency)
}
private fun parsePep621Dependency(depSpec: String): Path? {
val match = PEP_621_PATH_DEPENDENCY.matchEntire(depSpec) ?: return null
val (_, depUri) = match.destructured
return parseDepUri(depUri)
}
sealed interface TomlDependencySpecification {
data class PathDependency(val tomlKey: String) : TomlDependencySpecification
data class Pep621Dependency(val tomlKey: String) : TomlDependencySpecification
data class GroupPathDependency(val tomlKeyToGroup: String, val tomlKeyFromGroupToPath: String) : TomlDependencySpecification
}
@@ -145,4 +207,13 @@ private fun parseDepUri(depUri: String): Path? =
catch (e: URISyntaxException) {
logger.info("Dep $depUri can't be parsed", e)
null
}
}
private fun parseDepFromPathString(root: Path, depPathString: String): Path? =
try {
root.resolve(depPathString).normalize()
}
catch (e: InvalidPathException) {
logger.info("Dep $depPathString points to wrong path", e)
null
}

View File

@@ -36,7 +36,7 @@ import com.intellij.python.pyproject.PyProjectToml
import com.intellij.python.pyproject.model.internal.PY_PROJECT_SYSTEM_ID
import com.intellij.python.pyproject.model.internal.PyProjectTomlBundle
import com.intellij.python.pyproject.model.internal.pyProjectToml.FSWalkInfoWithToml
import com.intellij.python.pyproject.model.internal.pyProjectToml.getPEP621Deps
import com.intellij.python.pyproject.model.internal.pyProjectToml.getDependenciesFromToml
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
import com.intellij.python.pyproject.model.spi.Tool
@@ -188,8 +188,8 @@ private suspend fun generatePyProjectTomlEntries(
val entriesByName = entries.associateBy { it.name }
val namesByDir = entries.associate { Pair(it.root, it.name) }
val allNames = entriesByName.keys
var dependencies = getPEP621Deps(entriesByName, namesByDir)
for (tool in Tool.EP.extensionList) {
var dependencies = getDependenciesFromToml(entriesByName, namesByDir, tools.flatMap { it.getTomlDependencySpecifications() })
for (tool in tools) {
// Tool provides deps and workspace members
val toolSpecificInfo = tool.getProjectStructure(entriesByName, namesByDir)
// Tool-agnostic pep621 deps

View File

@@ -3,6 +3,7 @@ package com.intellij.python.pyproject.model.spi
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.util.NlsSafe
import com.intellij.python.common.tools.ToolId
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
import com.jetbrains.python.PyToolUIInfo
import com.jetbrains.python.venvReader.Directory
import org.apache.tuweni.toml.TomlTable
@@ -25,10 +26,18 @@ interface Tool {
* All project names must be taken from provided data (use [rootIndex] to get name by directory).
* If tool doesn't provide any specific structure (i.e: no dependencies except those described in pyproject.toml spec, no workspaces) return `null`
*/
suspend fun getProjectStructure(entries: Map<ProjectName, PyProjectTomlProject>, rootIndex: Map<Directory, ProjectName>): ProjectStructureInfo?
suspend fun getProjectStructure(
entries: Map<ProjectName, PyProjectTomlProject>,
rootIndex: Map<Directory, ProjectName>,
): ProjectStructureInfo?
/**
* Tool that supports build systems might return additional src directories
*/
suspend fun getSrcRoots(toml: TomlTable, projectRoot: Directory): Set<Directory>
}
/**
* Tool-specific toml sections where dependencies may be specified
*/
fun getTomlDependencySpecifications(): List<TomlDependencySpecification>
}

View File

@@ -0,0 +1,227 @@
package com.intellij.python.junit5Tests.unit.pyproject
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
import com.intellij.python.pyproject.model.internal.pyProjectToml.getDependenciesFromToml
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
import com.intellij.testFramework.common.timeoutRunBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import kotlin.io.path.createDirectories
internal class GetDependenciesFromTomlTest {
private data class TestProject(
override val pyProjectToml: PyProjectToml,
override val root: Path,
) : PyProjectTomlProject
@Test
fun `pep 621 project dependencies`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val libDir = tempDir.resolve("lib").createDirectories()
val libUri = libDir.toUri()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
dependencies = ["lib @ $libUri"]
""".trimIndent())
val mainName = ProjectName("main")
val libName = ProjectName("lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(libDir to libName)
val result = getDependenciesFromToml(entries, rootIndex, emptyList())
assertThat(result.map[mainName]).containsExactly(libName)
}
@Test
fun `pep 735 dependency groups`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val libDir = tempDir.resolve("lib").createDirectories()
val testLibDir = tempDir.resolve("test-lib").createDirectories()
val libUri = libDir.toUri()
val testLibUri = testLibDir.toUri()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
[dependency-groups]
dev = ["lib @ $libUri"]
test = ["test-lib @ $testLibUri"]
""".trimIndent())
val mainName = ProjectName("main")
val libName = ProjectName("lib")
val testLibName = ProjectName("test-lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(libDir to libName, testLibDir to testLibName)
val result = getDependenciesFromToml(entries, rootIndex, emptyList())
assertThat(result.map[mainName]).containsExactlyInAnyOrder(libName, testLibName)
}
@Test
fun `tool PathDependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val libDir = tempDir.resolve("lib").createDirectories()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
[tool.poetry.dependencies]
lib = {path = "../lib"}
""".trimIndent())
val mainName = ProjectName("main")
val libName = ProjectName("lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(libDir to libName)
val specs = listOf(TomlDependencySpecification.PathDependency("tool.poetry.dependencies"))
val result = getDependenciesFromToml(entries, rootIndex, specs)
assertThat(result.map[mainName]).containsExactly(libName)
}
@Test
fun `tool Pep621Dependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val libDir = tempDir.resolve("lib").createDirectories()
val libUri = libDir.toUri()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
[tool.uv]
dev-dependencies = ["lib @ $libUri"]
""".trimIndent())
val mainName = ProjectName("main")
val libName = ProjectName("lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(libDir to libName)
val specs = listOf(TomlDependencySpecification.Pep621Dependency("tool.uv.dev-dependencies"))
val result = getDependenciesFromToml(entries, rootIndex, specs)
assertThat(result.map[mainName]).containsExactly(libName)
}
@Test
fun `tool GroupPathDependency`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val libDir = tempDir.resolve("lib").createDirectories()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
[tool.poetry.group.test.dependencies]
lib = {path = "../lib"}
""".trimIndent())
val mainName = ProjectName("main")
val libName = ProjectName("lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(libDir to libName)
val specs = listOf(TomlDependencySpecification.GroupPathDependency("tool.poetry.group", "dependencies"))
val result = getDependenciesFromToml(entries, rootIndex, specs)
assertThat(result.map[mainName]).containsExactly(libName)
}
@Test
fun `non-path dependencies are ignored`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
dependencies = ["pytest>=7", "requests"]
[dependency-groups]
dev = ["black>=23"]
""".trimIndent())
val mainName = ProjectName("main")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val result = getDependenciesFromToml(entries, emptyMap(), emptyList())
assertThat(result.map[mainName]).isEmpty()
}
@Test
fun `empty and missing sections`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
""".trimIndent())
val mainName = ProjectName("main")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val specs = listOf(
TomlDependencySpecification.PathDependency("tool.poetry.dependencies"),
TomlDependencySpecification.Pep621Dependency("tool.uv.dev-dependencies"),
TomlDependencySpecification.GroupPathDependency("tool.poetry.group", "dependencies"),
)
val result = getDependenciesFromToml(entries, emptyMap(), specs)
assertThat(result.map[mainName]).isEmpty()
}
@Test
fun `mixed sources collected together`(@TempDir tempDir: Path): Unit = timeoutRunBlocking {
val mainDir = tempDir.resolve("main").createDirectories()
val pep621Lib = tempDir.resolve("pep621-lib").createDirectories()
val groupLib = tempDir.resolve("group-lib").createDirectories()
val toolLib = tempDir.resolve("tool-lib").createDirectories()
val pep621Uri = pep621Lib.toUri()
val groupUri = groupLib.toUri()
val toml = PyProjectToml.parse("""
[project]
name = "main"
version = "1.0"
dependencies = ["pep621-lib @ $pep621Uri"]
[dependency-groups]
dev = ["group-lib @ $groupUri"]
[tool.poetry.dependencies]
tool-lib = {path = "../tool-lib"}
""".trimIndent())
val mainName = ProjectName("main")
val pep621Name = ProjectName("pep621-lib")
val groupName = ProjectName("group-lib")
val toolName = ProjectName("tool-lib")
val entries = mapOf(mainName to TestProject(toml, mainDir))
val rootIndex = mapOf(pep621Lib to pep621Name, groupLib to groupName, toolLib to toolName)
val specs = listOf(TomlDependencySpecification.PathDependency("tool.poetry.dependencies"))
val result = getDependenciesFromToml(entries, rootIndex, specs)
assertThat(result.map[mainName]).containsExactlyInAnyOrder(pep621Name, groupName, toolName)
}
}

View File

@@ -30,7 +30,7 @@ class PyProjectTomlTest {
val configContents = "[proj"
// WHEN
val result = PyProjectToml.Companion.parse(configContents)
val result = PyProjectToml.parse(configContents)
// THEN
Assertions.assertThat(result.toml.errors()).isNotEmpty()
@@ -51,7 +51,7 @@ class PyProjectTomlTest {
bar="test bar"
baz="test baz"
""".trimIndent()
val pyproject = PyProjectToml.Companion.parse(configContents)
val pyproject = PyProjectToml.parse(configContents)
// WHEN
val testTool = pyproject.getTool(TestPyProject)
@@ -75,7 +75,7 @@ class PyProjectTomlTest {
""".trimIndent()
// WHEN
val pyproject = PyProjectToml.Companion.parse(configContents)
val pyproject = PyProjectToml.parse(configContents)
val testTool = pyproject.getTool(TestPyProject)
// THEN
@@ -101,7 +101,7 @@ class PyProjectTomlTest {
""".trimIndent()
// WHEN
val pyproject = PyProjectToml.Companion.parse(configContents)
val pyproject = PyProjectToml.parse(configContents)
val testTool = pyproject.getTool(TestPyProject)
// THEN
@@ -120,7 +120,7 @@ class PyProjectTomlTest {
name="Some project"
version="1.2.3"
""".trimIndent()
val pyproject = PyProjectToml.Companion.parse(configContents)
val pyproject = PyProjectToml.parse(configContents)
// WHEN
val testTool = pyproject.getTool(TestPyProject)
@@ -133,7 +133,7 @@ class PyProjectTomlTest {
@ParameterizedTest(name = "{0}")
@MethodSource("parseTestCases")
fun parseTests(name: String, pyprojectToml: String, expectedProjectTable: PyProjectTable?, expectedIssues: List<PyProjectIssue>) {
val result = PyProjectToml.Companion.parse(pyprojectToml)
val result = PyProjectToml.parse(pyprojectToml)
val unwrapped = result
assertEquals(expectedProjectTable, unwrapped.project)

View File

@@ -7,6 +7,7 @@ import com.intellij.openapi.util.getPathMatcher
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.uv.common.UV_TOOL_ID
import com.intellij.python.community.impl.uv.common.UV_UI_INFO
import com.intellij.python.pyproject.model.internal.pyProjectToml.TomlDependencySpecification
import com.intellij.python.pyproject.model.spi.ProjectDependencies
import com.intellij.python.pyproject.model.spi.ProjectName
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
@@ -97,6 +98,11 @@ internal class UvTool : Tool {
}
override fun getTomlDependencySpecifications(): List<TomlDependencySpecification> = listOf(
TomlDependencySpecification.PathDependency("tool.uv.sources"),
TomlDependencySpecification.Pep621Dependency("tool.uv.dev-dependencies"),
)
@RequiresBackgroundThread
private fun getWorkspaceMembers(toml: TomlTable): WorkspaceInfo? {
val workspace = toml.getTable("tool.uv.workspace") ?: return null