diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 4d3f33e0..2bce0391 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -7,6 +7,7 @@
version 0.1.14
BugFixes
+ - Fix detecting validators decorated methods [#196]
- Remove stub deletion error [#190]
version 0.1.13
diff --git a/src/com/koxudaxi/pydantic/Pydantic.kt b/src/com/koxudaxi/pydantic/Pydantic.kt
index c774a55e..92237a46 100644
--- a/src/com/koxudaxi/pydantic/Pydantic.kt
+++ b/src/com/koxudaxi/pydantic/Pydantic.kt
@@ -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"
@@ -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 = hashMapOf()
@@ -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): 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 {
@@ -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 {
diff --git a/testData/ignoreinspection/validatorFullPath.py b/testData/ignoreinspection/validatorFullPath.py
new file mode 100644
index 00000000..8887342e
--- /dev/null
+++ b/testData/ignoreinspection/validatorFullPath.py
@@ -0,0 +1,10 @@
+from pydantic import BaseModel
+from pydantic.class_validators import validator
+
+
+class A(BaseModel):
+ a: str
+
+ @validator('a')
+ def validate_a(cls):
+ pass
diff --git a/testData/inspection/rootValidatorSelf.py b/testData/inspection/rootValidatorSelf.py
index db07abcc..fd2bab1b 100644
--- a/testData/inspection/rootValidatorSelf.py
+++ b/testData/inspection/rootValidatorSelf.py
@@ -12,6 +12,42 @@ class A(BaseModel):
d: str
e: str
+ @root_validator('a')
+ def validate_a(self):
+ pass
+
+ @root_validator('b')
+ def validate_b(fles):
+ pass
+
+ @root_validator('c')
+ def validate_b(*args):
+ pass
+
+ @root_validator('d')
+ def validate_c(**kwargs):
+ pass
+
+ @root_validator('e')
+ def validate_e():
+ 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(self):
pass
diff --git a/testSrc/com/koxudaxi/pydantic/PydanticIgnoreInspectionTest.kt b/testSrc/com/koxudaxi/pydantic/PydanticIgnoreInspectionTest.kt
index 6ca1ccec..6faa3e34 100644
--- a/testSrc/com/koxudaxi/pydantic/PydanticIgnoreInspectionTest.kt
+++ b/testSrc/com/koxudaxi/pydantic/PydanticIgnoreInspectionTest.kt
@@ -20,6 +20,10 @@ open class PydanticIgnoreInspectionTest : PydanticTestCase() {
doTest(true)
}
+ fun testValidatorFullPath() {
+ doTest(true)
+ }
+
fun testBaseModel() {
doTest(false)
}