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

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) }