Skip to content

Commit

Permalink
Fix detecting validators decorated methods
Browse files Browse the repository at this point in the history
  • Loading branch information
koxudaxi committed Sep 22, 2020
1 parent 25c7b2f commit faf3b72
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 11 deletions.
1 change: 1 addition & 0 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<h2>version 0.1.14</h2>
<p>BugFixes</p>
<ul>
<li>Fix detecting validators decorated methods [#196]</li>
<li>Remove stub deletion error [#190]</li>
</ul>
<h2>version 0.1.13</h2>
Expand Down
48 changes: 37 additions & 11 deletions src/com/koxudaxi/pydantic/Pydantic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ import java.util.regex.Pattern

const val BASE_MODEL_Q_NAME = "pydantic.main.BaseModel"
const val DATA_CLASS_Q_NAME = "pydantic.dataclasses.dataclass"
const val VALIDATOR_Q_NAME = "pydantic.validator"
const val ROOT_VALIDATOR_Q_NAME = "pydantic.root_validator"
const val DATA_CLASS_SHORT_Q_NAME = "pydantic.dataclass"
const val VALIDATOR_Q_NAME = "pydantic.class_validators.validator"
const val VALIDATOR_SHORT_Q_NAME = "pydantic.validator"
const val ROOT_VALIDATOR_Q_NAME = "pydantic.class_validators.root_validator"
const val ROOT_VALIDATOR_SHORT_Q_NAME = "pydantic.root_validator"
const val SCHEMA_Q_NAME = "pydantic.schema.Schema"
const val FIELD_Q_NAME = "pydantic.fields.Field"
const val DATACLASS_FIELD_Q_NAME = "dataclasses.field"
const val DEPRECATED_SCHEMA_Q_NAME = "pydantic.fields.Schema"
const val BASE_SETTINGS_Q_NAME = "pydantic.env_settings.BaseSettings"
const val VERSION_Q_NAME = "pydantic.version.VERSION"
const val BASE_CONFIG_Q_NAME = "pydantic.BaseConfig"
const val BASE_CONFIG_Q_NAME = "pydantic.main.BaseConfig"
const val DATACLASS_MISSING = "dataclasses.MISSING"
const val CON_BYTES_Q_NAME = "pydantic.types.conbytes"
const val CON_DECIMAL_Q_NAME = "pydantic.types.condecimal"
Expand All @@ -50,6 +53,30 @@ val BASE_CONFIG_QUALIFIED_NAME = QualifiedName.fromDottedString(BASE_CONFIG_Q_NA

val BASE_MODEL_QUALIFIED_NAME = QualifiedName.fromDottedString(BASE_MODEL_Q_NAME)

val VALIDATOR_QUALIFIED_NAME = QualifiedName.fromDottedString(VALIDATOR_Q_NAME)

val VALIDATOR_SHORT_QUALIFIED_NAME = QualifiedName.fromDottedString(VALIDATOR_SHORT_Q_NAME)

val ROOT_VALIDATOR_QUALIFIED_NAME = QualifiedName.fromDottedString(ROOT_VALIDATOR_Q_NAME)

val ROOT_VALIDATOR_SHORT_QUALIFIED_NAME = QualifiedName.fromDottedString(ROOT_VALIDATOR_SHORT_Q_NAME)

val DATA_CLASS_QUALIFIED_NAME = QualifiedName.fromDottedString(DATA_CLASS_Q_NAME)

val DATA_CLASS_SHORT_QUALIFIED_NAME = QualifiedName.fromDottedString(DATA_CLASS_SHORT_Q_NAME)

val DATA_CLASS_QUALIFIED_NAMES = listOf(
DATA_CLASS_QUALIFIED_NAME,
DATA_CLASS_SHORT_QUALIFIED_NAME
)

val VALIDATOR_QUALIFIED_NAMES = listOf(
VALIDATOR_QUALIFIED_NAME,
VALIDATOR_SHORT_QUALIFIED_NAME,
ROOT_VALIDATOR_QUALIFIED_NAME,
ROOT_VALIDATOR_SHORT_QUALIFIED_NAME
)

val VERSION_SPLIT_PATTERN: Pattern = Pattern.compile("[.a-zA-Z]")!!

val pydanticVersionCache: HashMap<String, KotlinVersion> = hashMapOf()
Expand Down Expand Up @@ -99,17 +126,16 @@ internal fun isBaseSetting(pyClass: PyClass, context: TypeEvalContext): Boolean
return pyClass.isSubclass(BASE_SETTINGS_Q_NAME, context)
}

internal fun hasDecorator(pyDecoratable: PyDecoratable, refName: String): Boolean {
pyDecoratable.decoratorList?.decorators?.mapNotNull { it.callee as? PyReferenceExpression }?.forEach {
PyResolveUtil.resolveImportedElementQNameLocally(it).forEach { decoratorQualifiedName ->
if (decoratorQualifiedName == QualifiedName.fromDottedString(refName)) return true
internal fun hasDecorator(pyDecoratable: PyDecoratable, refNames: List<QualifiedName>): Boolean {
return pyDecoratable.decoratorList?.decorators?.mapNotNull { it.callee as? PyReferenceExpression }?.any {
PyResolveUtil.resolveImportedElementQNameLocally(it).any { decoratorQualifiedName ->
refNames.any { refName -> decoratorQualifiedName == refName }
}
}
return false
} ?: false
}

internal fun isPydanticDataclass(pyClass: PyClass): Boolean {
return hasDecorator(pyClass, DATA_CLASS_Q_NAME)
return hasDecorator(pyClass, DATA_CLASS_QUALIFIED_NAMES)
}

internal fun isPydanticSchema(pyClass: PyClass, context: TypeEvalContext): Boolean {
Expand All @@ -134,7 +160,7 @@ internal fun isDataclassMissing(pyTargetExpression: PyTargetExpression): Boolean
}

internal fun isValidatorMethod(pyFunction: PyFunction): Boolean {
return hasDecorator(pyFunction, VALIDATOR_Q_NAME) || hasDecorator(pyFunction, ROOT_VALIDATOR_Q_NAME)
return hasDecorator(pyFunction, VALIDATOR_QUALIFIED_NAMES)
}

internal fun isConfigClass(pyClass: PyClass): Boolean {
Expand Down
10 changes: 10 additions & 0 deletions testData/ignoreinspection/validatorFullPath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pydantic import BaseModel
from pydantic.class_validators import validator


class A(BaseModel):
a: str

@validator('a')
def vali<caret>date_a(cls):
pass
36 changes: 36 additions & 0 deletions testData/inspection/rootValidatorSelf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@ class A(BaseModel):
d: str
e: str

@root_validator('a')
def validate_a(<weak_warning descr="Usually first parameter of such methods is named 'cls'">self</weak_warning>):
pass

@root_validator('b')
def validate_b(<weak_warning descr="Usually first parameter of such methods is named 'cls'">fles</weak_warning>):
pass

@root_validator('c')
def validate_b(*args):
pass

@root_validator('d')
def validate_c(**kwargs):
pass

@root_validator('e')
def validate_e<error descr="Method must have a first parameter, usually called 'cls'">()</error>:
pass

def dummy(self):
pass

@check
def task(self):
pass

from pydantic.class_validators import root_validator

class B(BaseModel):
a: str
b: str
c: str
d: str
e: str

@root_validator('a')
def validate_a(<weak_warning descr="Usually first parameter of such methods is named 'cls'">self</weak_warning>):
pass
Expand Down
4 changes: 4 additions & 0 deletions testSrc/com/koxudaxi/pydantic/PydanticIgnoreInspectionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ open class PydanticIgnoreInspectionTest : PydanticTestCase() {
doTest(true)
}

fun testValidatorFullPath() {
doTest(true)
}

fun testBaseModel() {
doTest(false)
}
Expand Down

0 comments on commit faf3b72

Please sign in to comment.