Skip to content

Commit 8c0dece

Browse files
[python] PY-86999: Survive broken pyproject.toml.
`com.intellij.python.pyproject.PyProjectToml.Companion.parse` used to return Error in case of broken file so we simply ignored it. We now return `TomlTable` itself even in case of error. So `com.intellij.python.pyproject.model.internal.pyProjectToml.TomFileToolsKt.readFile` is only null (which means module is skipped) if there is `IOException` reading `pyproject.toml` (either file doesn't exist or unreadable). GitOrigin-RevId: 94fa7f46516450a9b0922679aa0d9d12571fae4f
1 parent 0ed3c5e commit 8c0dece

9 files changed

Lines changed: 56 additions & 62 deletions

File tree

python/ide/impl/src/com/intellij/pycharm/community/ide/impl/configuration/PyUvSdkConfiguration.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import com.intellij.python.pyproject.model.api.SuggestedSdk
1717
import com.intellij.python.pyproject.model.api.suggestSdk
1818
import com.jetbrains.python.errorProcessing.PyResult
1919
import com.jetbrains.python.onSuccess
20-
import com.jetbrains.python.orLogException
2120
import com.jetbrains.python.sdk.*
2221
import com.jetbrains.python.sdk.configuration.*
2322
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
@@ -74,8 +73,7 @@ internal class PyUvSdkConfiguration : PyProjectTomlConfigurationExtension {
7473
}
7574
} ?: return EnvCheckerResult.CannotConfigure
7675
val tomlContentResult = withContext(Dispatchers.Default) { PyProjectToml.parse(tomlFileContent) }
77-
val tomlContent = tomlContentResult.orLogException(logger) ?: return EnvCheckerResult.CannotConfigure
78-
val project = tomlContent.project ?: return EnvCheckerResult.CannotConfigure
76+
val project = tomlContentResult.project ?: return EnvCheckerResult.CannotConfigure
7977
project.name ?: module.name
8078
}
8179

python/python-pyproject/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ jvm_library(
5656
associates = [":pyproject"],
5757
deps = [
5858
"@lib//:kotlin-stdlib",
59+
"//libraries/assertj-core",
5960
"//python/openapi:community",
6061
"//python/openapi:community_test_lib",
6162
"//python/python-psi-impl:psi-impl",

python/python-pyproject/intellij.python.pyproject.iml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<orderEntry type="inheritedJdk" />
1313
<orderEntry type="sourceFolder" forTests="false" />
1414
<orderEntry type="library" name="kotlin-stdlib" level="project" />
15+
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
1516
<orderEntry type="module" module-name="intellij.python.community" />
1617
<orderEntry type="module" module-name="intellij.python.psi.impl" />
1718
<orderEntry type="module" module-name="intellij.platform.core" />

python/python-pyproject/src/com/intellij/python/pyproject/PyProjectToml.kt

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ package com.intellij.python.pyproject
44
import com.intellij.openapi.module.Module
55
import com.intellij.openapi.vfs.VirtualFile
66
import com.jetbrains.python.Result
7-
import com.jetbrains.python.Result.Companion.success
87
import com.jetbrains.python.sdk.findAmongRoots
98
import kotlinx.coroutines.Dispatchers
109
import kotlinx.coroutines.withContext
1110
import org.apache.tuweni.toml.Toml
1211
import org.apache.tuweni.toml.TomlParseError
12+
import org.apache.tuweni.toml.TomlParseResult
1313
import org.apache.tuweni.toml.TomlTable
1414
import org.jetbrains.annotations.ApiStatus.Internal
1515
import java.nio.file.Path
@@ -78,7 +78,7 @@ data class PyProjectToml(
7878
/**
7979
* An instance of [TomlTable] provided by the TOML parser.
8080
*/
81-
val toml: TomlTable,
81+
val toml: TomlParseResult,
8282
) {
8383
/**
8484
* Gets a specific tool from an object implementing [PyProjectToolFactory].
@@ -103,9 +103,10 @@ data class PyProjectToml(
103103

104104
companion object {
105105
/**
106+
* TODO: REDOC
106107
* Attempts to parse [inputStream] and construct an instance of [PyProjectToml].
107108
* On success, returns an instance of [Result.Success] with an instance of [PyProjectToml].
108-
* On failure, returns an instance of [Result.Failure] with a list of [TomlParseError]s.
109+
* On failure, returns an instance of [Result.Failure] with a list of [TomlParseError]s and [TomlTable] itself.
109110
*
110111
* Example:
111112
*
@@ -115,18 +116,15 @@ data class PyProjectToml(
115116
* val hatch = pyProject.getTool(HatchPyProject)
116117
* ```
117118
*/
118-
fun parse(tomlFileContent: String): Result<PyProjectToml, List<TomlParseError>> {
119+
fun parse(tomlFileContent: String): PyProjectToml {
119120
val issues = mutableListOf<PyProjectIssue>()
120121
val toml = Toml.parse(tomlFileContent)
121122

122-
if (toml.hasErrors()) {
123-
return Result.failure(toml.errors())
124-
}
125123

126124
val projectTable = toml.safeGet<TomlTable>(PY_PROJECT_TOML_PROJECT).getOrIssue(issues)
127125

128126
if (projectTable == null) {
129-
return success(PyProjectToml(null, issues, toml))
127+
return PyProjectToml(null, issues, toml)
130128
}
131129

132130
val name = projectTable.safeGet<String>("name").getOrIssue(issues) {
@@ -205,33 +203,31 @@ data class PyProjectToml(
205203
val guiScripts = projectTable.parseMap("gui-scripts", issues)
206204
val urls = projectTable.parseMap("urls", issues)
207205

208-
return success(
209-
PyProjectToml(
210-
PyProjectTable(
211-
name,
212-
version,
213-
requiresPython,
214-
authors,
215-
maintainers,
216-
description,
217-
readme,
218-
license,
219-
licenseFiles,
220-
keywords,
221-
classifiers,
222-
dynamic,
223-
PyProjectDependencies(
224-
projectDependencies,
225-
devDependencies,
226-
optionalDependencies
227-
),
228-
scripts,
229-
guiScripts,
230-
urls,
206+
return PyProjectToml(
207+
PyProjectTable(
208+
name,
209+
version,
210+
requiresPython,
211+
authors,
212+
maintainers,
213+
description,
214+
readme,
215+
license,
216+
licenseFiles,
217+
keywords,
218+
classifiers,
219+
dynamic,
220+
PyProjectDependencies(
221+
projectDependencies,
222+
devDependencies,
223+
optionalDependencies
231224
),
232-
issues,
233-
toml,
234-
)
225+
scripts,
226+
guiScripts,
227+
urls,
228+
),
229+
issues,
230+
toml,
235231
)
236232
}
237233

python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/tomFileTools.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,13 @@ private suspend fun readFile(file: Path): PyProjectToml? {
110110
logger.warn("Can't read $file", e)
111111
return null
112112
}
113-
return when (val r = withContext(Dispatchers.Default) { PyProjectToml.parse(content) }) {
114-
is Result.Failure -> {
115-
logger.warn("Errors on $file: ${r.error.joinToString(", ")}")
116-
null
113+
return withContext(Dispatchers.Default) {
114+
val toml = PyProjectToml.parse(content)
115+
val errors = toml.issues.joinToString(", ")
116+
if (errors.isNotBlank()) {
117+
logger.warn("Errors on $file: $errors")
117118
}
118-
is Result.Success -> r.result
119+
toml
119120
}
120121
}
121122

python/python-pyproject/src/com/intellij/python/pyproject/model/internal/workspaceBridge/workspaceTools.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,14 @@ private suspend fun generatePyProjectTomlEntries(
132132
participatedTools.add(toolAndName.first.id)
133133
}
134134
}
135-
if (projectNameAsString != null) {
136-
if (projectNameAsString in usedNamed) {
137-
projectNameAsString = "$projectNameAsString@${usedNamed.size}"
138-
}
139-
usedNamed.add(projectNameAsString)
135+
if (projectNameAsString == null) {
136+
projectNameAsString = root.name
137+
}
138+
if (projectNameAsString in usedNamed) {
139+
projectNameAsString = "$projectNameAsString@${usedNamed.size}"
140140
}
141-
val projectName = ProjectName(projectNameAsString ?: "${root.name}@${tomlFile.hashCode()}")
141+
usedNamed.add(projectNameAsString)
142+
val projectName = ProjectName(projectNameAsString)
142143
val sourceRootsAndTools = Tool.EP.extensionList.flatMap { tool -> tool.getSrcRoots(toml.toml, root).map { Pair(tool, it) } }.toSet()
143144
val sourceRoots = sourceRootsAndTools.map { it.second }.toSet() + findSrc(root)
144145
participatedTools.addAll(sourceRootsAndTools.map { it.first.id })

python/python-pyproject/test/com/intellij/python/junit5Tests/unit/pyproject/PyProjectTomlTest.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import com.intellij.python.pyproject.*
44
import com.intellij.python.pyproject.PyProjectIssue.*
55
import com.intellij.python.pyproject.TomlTableSafeGetError.RequiredValueMissing
66
import com.intellij.python.pyproject.TomlTableSafeGetError.UnexpectedType
7-
import com.jetbrains.python.Result
8-
import com.jetbrains.python.getOrThrow
9-
import com.jetbrains.python.isFailure
107
import org.apache.tuweni.toml.TomlArray
118
import org.apache.tuweni.toml.TomlTable
9+
import org.assertj.core.api.Assertions
1210
import org.junit.jupiter.api.Assertions.assertEquals
1311
import org.junit.jupiter.api.Test
1412
import org.junit.jupiter.params.ParameterizedTest
@@ -26,8 +24,7 @@ class PyProjectTomlTest {
2624
val result = PyProjectToml.Companion.parse(configContents)
2725

2826
// THEN
29-
assert(result.isFailure)
30-
assert((result as Result.Failure).error.isNotEmpty())
27+
Assertions.assertThat(result.toml.errors()).isNotEmpty()
3128
}
3229

3330
@Test
@@ -45,7 +42,7 @@ class PyProjectTomlTest {
4542
bar="test bar"
4643
baz="test baz"
4744
""".trimIndent()
48-
val pyproject = PyProjectToml.Companion.parse(configContents).orThrow()
45+
val pyproject = PyProjectToml.Companion.parse(configContents)
4946

5047
// WHEN
5148
val testTool = pyproject.getTool(TestPyProject)
@@ -69,7 +66,7 @@ class PyProjectTomlTest {
6966
""".trimIndent()
7067

7168
// WHEN
72-
val pyproject = PyProjectToml.Companion.parse(configContents).orThrow()
69+
val pyproject = PyProjectToml.Companion.parse(configContents)
7370
val testTool = pyproject.getTool(TestPyProject)
7471

7572
// THEN
@@ -95,7 +92,7 @@ class PyProjectTomlTest {
9592
""".trimIndent()
9693

9794
// WHEN
98-
val pyproject = PyProjectToml.Companion.parse(configContents).orThrow()
95+
val pyproject = PyProjectToml.Companion.parse(configContents)
9996
val testTool = pyproject.getTool(TestPyProject)
10097

10198
// THEN
@@ -114,7 +111,7 @@ class PyProjectTomlTest {
114111
name="Some project"
115112
version="1.2.3"
116113
""".trimIndent()
117-
val pyproject = PyProjectToml.Companion.parse(configContents).orThrow()
114+
val pyproject = PyProjectToml.Companion.parse(configContents)
118115

119116
// WHEN
120117
val testTool = pyproject.getTool(TestPyProject)
@@ -128,7 +125,7 @@ class PyProjectTomlTest {
128125
@MethodSource("parseTestCases")
129126
fun parseTests(name: String, pyprojectToml: String, expectedProjectTable: PyProjectTable?, expectedIssues: List<PyProjectIssue>) {
130127
val result = PyProjectToml.Companion.parse(pyprojectToml)
131-
val unwrapped = result.getOrThrow()
128+
val unwrapped = result
132129

133130
assertEquals(expectedProjectTable, unwrapped.project)
134131
assertEquals(expectedIssues, unwrapped.issues)

python/src/com/jetbrains/python/poetry/sdk/evolution/PoetrySelectSdkProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ internal class PoetrySelectSdkProvider() : EvoSelectSdkProvider {
4646

4747

4848
val (projectName, requiresPython) = withContext(Dispatchers.IO) {
49-
val toml = PyProjectToml.parse(pyProjectTomlFile.readText()).getOrNull()
50-
(toml?.project?.name) to (toml?.project?.requiresPython)
49+
val toml = PyProjectToml.parse(pyProjectTomlFile.readText())
50+
(toml.project?.name) to (toml.project?.requiresPython)
5151
}
5252
val poetryVirtualenvsPath = runPoetry(pyProjectTomlFile.parent.toNioPath(), "config", "virtualenvs.path")
5353
.getOr { return@EvoTreeLazyNodeElement it }.let { Path(it.trim()) }

python/src/com/jetbrains/python/sdk/add/v2/uv/EnvironmentCreatorUv.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import com.intellij.util.ui.AsyncProcessIcon
2020
import com.jetbrains.python.PyBundle.message
2121
import com.jetbrains.python.errorProcessing.ErrorSink
2222
import com.jetbrains.python.errorProcessing.PyResult
23-
import com.jetbrains.python.getOrNull
2423
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
2524
import com.jetbrains.python.sdk.add.v2.*
2625
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
@@ -146,7 +145,7 @@ internal class EnvironmentCreatorUv<P : PathHolder>(
146145

147146
val pythonVersions = withContext(Dispatchers.IO) {
148147
val versionRequest = if (pyProjectTomlPath.exists()) {
149-
PyProjectToml.parse(pyProjectTomlPath.readText()).getOrNull()?.project?.requiresPython
148+
PyProjectToml.parse(pyProjectTomlPath.readText()).project?.requiresPython
150149
}
151150
else {
152151
null

0 commit comments

Comments
 (0)