Skip to content

Commit

Permalink
Merge pull request #227 from koxudaxi/support_custom_root
Browse files Browse the repository at this point in the history
Support custom root field
  • Loading branch information
koxudaxi committed Dec 17, 2020
2 parents 5a147c4 + 8918276 commit c900df7
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 4 deletions.
7 changes: 6 additions & 1 deletion resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<idea-plugin url="https://github.com/koxudaxi/pydantic-pycharm-plugin" require-restart="true">
<id>com.koxudaxi.pydantic</id>
<name>Pydantic</name>
<version>0.1.17</version>
<version>0.1.18</version>
<vendor email="koaxudai@gmail.com">Koudai Aono @koxudaxi</vendor>
<change-notes><![CDATA[
<h2>version 0.1.18</h2>
<p>Features</p>
<ul>
<li>Support custom root field [#227]</li>
</ul>
<h2>version 0.1.17</h2>
<p>Features</p>
<ul>
Expand Down
4 changes: 3 additions & 1 deletion src/com/koxudaxi/pydantic/Pydantic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ val CONFIG_TYPES = mapOf(
"keep_untouched" to ConfigType.LIST_PYTYPE
)

const val CUSTOM_ROOT_FIELD = "__root__"

fun getPyClassByPyCallExpression(pyCallExpression: PyCallExpression, includeDataclass: Boolean, context: TypeEvalContext): PyClass? {
val callee = pyCallExpression.callee ?: return null
val pyType = when (val type = context.getType(callee)) {
Expand Down Expand Up @@ -290,7 +292,7 @@ fun isValidField(field: PyTargetExpression): Boolean {
}

fun isValidFieldName(name: String?): Boolean {
return name?.startsWith('_') == false
return name?.let { !it.startsWith('_') || it == CUSTOM_ROOT_FIELD } ?: false
}

fun getConfigValue(name: String, value: Any?, context: TypeEvalContext): Any? {
Expand Down
13 changes: 12 additions & 1 deletion src/com/koxudaxi/pydantic/PydanticInspection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class PydanticInspection : PyInspection() {
if (pydanticConfigService.currentWarnUntypedFields) {
inspectWarnUntypedFields(node)
}
inspectCustomRootField(node)
inspectReadOnlyProperty(node)
}

Expand Down Expand Up @@ -115,11 +116,21 @@ class PydanticInspection : PyInspection() {
val pyClass = getPyClassByAttribute(node) ?: return
if (!isPydanticModel(pyClass, true, myTypeEvalContext)) return
if (node.annotation != null) return
if ((node.leftHandSideExpression as? PyTargetExpressionImpl)?.text?.startsWith("_") == true) return
if (!isValidFieldName((node.leftHandSideExpression as? PyTargetExpressionImpl)?.text)) return
registerProblem(node,
"Untyped fields disallowed", ProblemHighlightType.WARNING)

}

private fun inspectCustomRootField(node: PyAssignmentStatement) {
val pyClass = getPyClassByAttribute(node) ?: return
if (!isPydanticModel(pyClass, true, myTypeEvalContext)) return
val fieldName = (node.leftHandSideExpression as? PyTargetExpressionImpl)?.text ?: return
if (fieldName.startsWith('_')) return
pyClass.findClassAttribute("__root__", true, myTypeEvalContext) ?: return
registerProblem(node,
"__root__ cannot be mixed with other fields", ProblemHighlightType.WARNING)
}
}

// override fun createOptionsPanel(): JComponent? {
Expand Down
2 changes: 1 addition & 1 deletion src/com/koxudaxi/pydantic/PydanticTypeProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class PydanticTypeProvider : PyTypeProviderBase() {
}
argumentWithoutModelName
.filter { it is PyKeywordArgument || (it as? PyStarArgumentImpl)?.isKeyword == true }
.filterNot { it.name?.startsWith("_") == true || it.name == "model_name" }
.filter { isValidFieldName(it.name) || it.name != "model_name" }
.forEach {
val parameter = fieldToParameter(it, context, hashMapOf(), typed)!!
parameter.name?.let { name ->
Expand Down
7 changes: 7 additions & 0 deletions testData/completion/fieldCustomRoot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from builtins import *
from pydantic import BaseModel

class A(BaseModel):
__root__: str

A().<caret>
12 changes: 12 additions & 0 deletions testData/completion/keywordArgumentCustomRoot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from builtins import *

from pydantic import BaseModel


class A(BaseModel):
__root__: str

class B(A):
hij: str

A(<caret>)
29 changes: 29 additions & 0 deletions testData/inspection/customRoot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydantic import BaseModel


class A(BaseModel):
__root__ = 'xyz'


class B(BaseModel):
a = 'xyz'


class C(BaseModel):
__root__ = 'xyz'
<warning descr="__root__ cannot be mixed with other fields">b = 'xyz'</warning>


class D(BaseModel):
__root__ = 'xyz'
_c = 'xyz'
__c = 'xyz'

class E:
__root__ = 'xyz'
e = 'xyz'

def f():
__root__ = 'xyz'
g = 'xyz'

16 changes: 16 additions & 0 deletions testSrc/com/koxudaxi/pydantic/PydanticCompletionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ open class PydanticCompletionTest : PydanticTestCase() {
)
}

fun testKeywordArgumentCustomRoot() {
doFieldTest(
listOf(
Pair("__root__=", "str A")
)
)
}

fun testKeywordArgumentDot() {
doFieldTest(
listOf(Pair("___slots__", "BaseModel"))
Expand Down Expand Up @@ -445,6 +453,14 @@ open class PydanticCompletionTest : PydanticTestCase() {
)
)
}
fun testFieldCustomRoot() {
doFieldTest(
listOf(
Pair("__root__", "str A"),
Pair("___slots__", "BaseModel")
)
)
}

fun testFieldOptional() {
doFieldTest(
Expand Down
4 changes: 4 additions & 0 deletions testSrc/com/koxudaxi/pydantic/PydanticInspectionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ open class PydanticInspectionTest : PydanticInspectionBase() {
pydanticConfigService.warnUntypedFields = true
doTest()
}

fun testCustomRoot() {
doTest()
}
}

0 comments on commit c900df7

Please sign in to comment.