From 398916bc071f394fe79ba59e6020767805e80355 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sun, 19 Apr 2020 23:52:36 +0900 Subject: [PATCH 01/11] support acceptable type --- resources/META-INF/plugin.xml | 1 + .../pydantic/PydanticConfigService.kt | 3 + .../koxudaxi/pydantic/PydanticInitializer.kt | 11 +- .../pydantic/PydanticTypeCheckerInspection.kt | 108 +++++++++++++----- .../typecheckerinspection/acceptableType.py | 12 ++ .../acceptableTypeDisable.py | 12 ++ .../acceptableTypeInvalid.py | 11 ++ .../acceptableTypeWarning.py | 12 ++ .../PydanticTypeCheckerInspectionTest.kt | 23 ++++ 9 files changed, 162 insertions(+), 31 deletions(-) create mode 100644 testData/typecheckerinspection/acceptableType.py create mode 100644 testData/typecheckerinspection/acceptableTypeDisable.py create mode 100644 testData/typecheckerinspection/acceptableTypeInvalid.py create mode 100644 testData/typecheckerinspection/acceptableTypeWarning.py diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 45643bf2..ca87d01e 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -7,6 +7,7 @@

version 0.1.3

Features

version 0.1.2

diff --git a/src/com/koxudaxi/pydantic/PydanticConfigService.kt b/src/com/koxudaxi/pydantic/PydanticConfigService.kt index a21162fd..2fa6cd82 100644 --- a/src/com/koxudaxi/pydantic/PydanticConfigService.kt +++ b/src/com/koxudaxi/pydantic/PydanticConfigService.kt @@ -15,6 +15,9 @@ class PydanticConfigService : PersistentStateComponent { var pyprojectToml: String? = null var parsableTypeMap = mutableMapOf>() var parsableTypeHighlightType: ProblemHighlightType = ProblemHighlightType.WARNING + var acceptableTypeMap = mutableMapOf>() + var acceptableTypeHighlightType: ProblemHighlightType = ProblemHighlightType.WEAK_WARNING + override fun getState(): PydanticConfigService { return this } diff --git a/src/com/koxudaxi/pydantic/PydanticInitializer.kt b/src/com/koxudaxi/pydantic/PydanticInitializer.kt index 7c2e4333..eb5badb8 100644 --- a/src/com/koxudaxi/pydantic/PydanticInitializer.kt +++ b/src/com/koxudaxi/pydantic/PydanticInitializer.kt @@ -52,6 +52,7 @@ class PydanticInitializer : StartupActivity { } else { configService.parsableTypeMap.clear() configService.parsableTypeHighlightType = ProblemHighlightType.WARNING + configService.acceptableTypeHighlightType = ProblemHighlightType.WEAK_WARNING } } @@ -64,11 +65,15 @@ class PydanticInitializer : StartupActivity { configService.parsableTypeMap = temporaryParsableTypeMap } - configService.parsableTypeHighlightType = getHighlightLevel(table, "parsable-type-highlight") + configService.parsableTypeHighlightType = getHighlightLevel(table, "parsable-type-highlight", ProblemHighlightType.WARNING) + configService.acceptableTypeHighlightType = getHighlightLevel(table, "acceptable-type-highlight", ProblemHighlightType.WEAK_WARNING) } - private fun getHighlightLevel(table: TomlTable, path: String): ProblemHighlightType { + private fun getHighlightLevel(table: TomlTable, path: String, default: ProblemHighlightType): ProblemHighlightType { return when (table.get(path) as? String) { + "warning" -> { + ProblemHighlightType.WARNING + } "weak_warning" -> { ProblemHighlightType.WEAK_WARNING } @@ -76,7 +81,7 @@ class PydanticInitializer : StartupActivity { ProblemHighlightType.INFORMATION } else -> { - ProblemHighlightType.WARNING + default } } } diff --git a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt index b86bf84b..2ef1f411 100644 --- a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt +++ b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt @@ -1,9 +1,9 @@ package com.koxudaxi.pydantic import com.intellij.codeInspection.LocalInspectionToolSession -import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.PsiElementVisitor import com.jetbrains.python.codeInsight.typing.matchingProtocolDefinitions @@ -45,19 +45,39 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { .forEach { mapping: PyArgumentsMapping -> analyzeCallee(callSite, mapping) } } + private fun getParsableTypeFromTypeMap(typeForParameter: PyType, cache: MutableMap): PyType? { + return getTypeFromTypeMap( + { project: Project -> PydanticConfigService.getInstance(project).parsableTypeMap }, + typeForParameter, + cache + ) + } - private fun getParsableType(typeForParameter: PyType): PyType? { - val pyTypes: MutableSet = mutableSetOf(typeForParameter) - val project = holder!!.project - getPyClassTypeByPyTypes(typeForParameter).toSet().forEach { type -> - val classQName: String? = type.classQName + private fun getAcceptableTypeFromTypeMap(typeForParameter: PyType, cache: MutableMap): PyType? { + return getTypeFromTypeMap( + { project: Project -> PydanticConfigService.getInstance(project).acceptableTypeMap }, + typeForParameter, + cache + ) + } - PydanticConfigService.getInstance(project).parsableTypeMap[classQName]?.mapNotNull { - createPyClassTypeImpl(it, project, myTypeEvalContext) - ?: createPyClassTypeImpl(it, project, myTypeEvalContext) - }?.toCollection(pyTypes) + private fun getTypeFromTypeMap(getTypeMap: (project: Project) -> (MutableMap>), typeForParameter: PyType, cache: MutableMap): PyType? { + return when { + cache.containsKey(typeForParameter) -> { + cache[typeForParameter] + } + else -> { + val project = holder!!.project + val typeMap = getTypeMap(project) + val unionType = PyUnionType.union(getPyClassTypeByPyTypes(typeForParameter).toSet().flatMap { type -> + typeMap[type.classQName]?.mapNotNull { + createPyClassTypeImpl(it, project, myTypeEvalContext) + } as? List ?: listOf() + }) + cache[typeForParameter] = unionType + unionType + } } - return PyUnionType.union(pyTypes) } private fun analyzeCallee(callSite: PyCallSiteExpression, mapping: PyArgumentsMapping) { @@ -65,32 +85,64 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { val receiver = callSite.getReceiver(callableType.callable) val substitutions = PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext) val mappedParameters = mapping.mappedParameters + val cachedParsableTypeMap = mutableMapOf() + val cachedAcceptableTypeMap = mutableMapOf() for ((argument, parameter) in PyCallExpressionHelper.getRegularMappedParameters(mappedParameters)) { val expected = parameter.getArgumentType(myTypeEvalContext) - val parsableType = expected?.let { getParsableType(it) } val actual = promoteToLiteral(argument, expected, myTypeEvalContext) val strictMatched = matchParameterAndArgument(expected, actual, argument, substitutions) val strictResult = AnalyzeArgumentResult(argument, expected, substituteGenerics(expected, substitutions), actual, strictMatched) if (!strictResult.isMatched) { val expectedType = PythonDocumentationProvider.getTypeName(strictResult.expectedType, myTypeEvalContext) val actualType = PythonDocumentationProvider.getTypeName(strictResult.actualType, myTypeEvalContext) - val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) - val parsableResult = AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched) - if (parsableResult.isMatched) { - registerProblem( - argument, - String.format("Field is of type '%s', '%s' may not be parsable to '%s'", - expectedType, - actualType, - expectedType), - pydanticConfigService.parsableTypeHighlightType - ) - } else { - registerProblem(argument, String.format("Expected type '%s', got '%s' instead", - expectedType, - actualType) - ) + val parsableType = expected?.let { getParsableTypeFromTypeMap(it, cachedParsableTypeMap) } + if (parsableType != null) { + val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) + val parsableResult = AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched) + if (parsableResult.isMatched) { + registerProblem( + argument, + String.format("Field is of type '%s', '%s' may not be parsable to '%s'", + expectedType, + actualType, + expectedType), + pydanticConfigService.parsableTypeHighlightType + ) + continue + } else { + registerProblem(argument, String.format("Expected type '%s', got '%s' instead", + expectedType, + actualType) + ) + continue + } + } + val acceptableType = expected?.let { getAcceptableTypeFromTypeMap(it, cachedAcceptableTypeMap) } + if (acceptableType != null) { + val acceptableMatched = matchParameterAndArgument(acceptableType, actual, argument, substitutions) + val acceptableResult = AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched) + if (acceptableResult.isMatched) { + registerProblem( + argument, + String.format("Field is of type '%s', '%s' is set as an acceptable type in pyproject.toml", + expectedType, + actualType, + expectedType), + pydanticConfigService.acceptableTypeHighlightType + ) + continue + } else { + registerProblem(argument, String.format("Expected type '%s', got '%s' instead", + expectedType, + actualType) + ) + continue + } } + registerProblem(argument, String.format("Expected type '%s', got '%s' instead", + expectedType, + actualType) + ) } } } diff --git a/testData/typecheckerinspection/acceptableType.py b/testData/typecheckerinspection/acceptableType.py new file mode 100644 index 00000000..a652429b --- /dev/null +++ b/testData/typecheckerinspection/acceptableType.py @@ -0,0 +1,12 @@ +from builtins import * +from typing import Union + +from pydantic import BaseModel + + +class A(BaseModel): + a: str + + +A(a=str('123')) +A(a=int(123)) diff --git a/testData/typecheckerinspection/acceptableTypeDisable.py b/testData/typecheckerinspection/acceptableTypeDisable.py new file mode 100644 index 00000000..4c544405 --- /dev/null +++ b/testData/typecheckerinspection/acceptableTypeDisable.py @@ -0,0 +1,12 @@ +from builtins import * +from typing import Union + +from pydantic import BaseModel + + +class A(BaseModel): + a: str + + +A(a=str('123')) +A(a=int(123)) diff --git a/testData/typecheckerinspection/acceptableTypeInvalid.py b/testData/typecheckerinspection/acceptableTypeInvalid.py new file mode 100644 index 00000000..1c410a84 --- /dev/null +++ b/testData/typecheckerinspection/acceptableTypeInvalid.py @@ -0,0 +1,11 @@ +from builtins import * +from typing import Union + +from pydantic import BaseModel + + +class A(BaseModel): + a: str + +A(a=str('123')) +A(a=bytes(123)) diff --git a/testData/typecheckerinspection/acceptableTypeWarning.py b/testData/typecheckerinspection/acceptableTypeWarning.py new file mode 100644 index 00000000..f565a65e --- /dev/null +++ b/testData/typecheckerinspection/acceptableTypeWarning.py @@ -0,0 +1,12 @@ +from builtins import * +from typing import Union + +from pydantic import BaseModel + + +class A(BaseModel): + a: str + + +A(a=str('123')) +A(a=int(123)) diff --git a/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt b/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt index 7fa13db4..b8c1581a 100644 --- a/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt +++ b/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt @@ -34,6 +34,29 @@ open class PydanticTypeCheckerInspectionTest : PydanticInspectionBase() { doTest() } + fun testAcceptableType() { + val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) + pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("builtins.int") + doTest() + } + fun testAcceptableTypeWarning() { + val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) + pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("builtins.int") + pydanticConfigService.acceptableTypeHighlightType = ProblemHighlightType.WARNING + doTest() + } + fun testAcceptableTypeDisable() { + val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) + pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("builtins.int") + pydanticConfigService.acceptableTypeHighlightType = ProblemHighlightType.INFORMATION + doTest() + } + + fun testAcceptableTypeInvalid() { + val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) + pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("int") + doTest() + } fun testField() { doTest() } From d1d6d6ebd7b3256215183ee58add7a1c32166c15 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 00:43:20 +0900 Subject: [PATCH 02/11] fix edge case --- .../pydantic/PydanticTypeCheckerInspection.kt | 84 ++++++++++--------- .../acceptableTypeDisable.py | 6 +- .../parsableTypeDisable.py | 6 +- .../PydanticTypeCheckerInspectionTest.kt | 4 +- 4 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt index 2ef1f411..49fb53f1 100644 --- a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt +++ b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt @@ -95,48 +95,50 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { if (!strictResult.isMatched) { val expectedType = PythonDocumentationProvider.getTypeName(strictResult.expectedType, myTypeEvalContext) val actualType = PythonDocumentationProvider.getTypeName(strictResult.actualType, myTypeEvalContext) - val parsableType = expected?.let { getParsableTypeFromTypeMap(it, cachedParsableTypeMap) } - if (parsableType != null) { - val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) - val parsableResult = AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched) - if (parsableResult.isMatched) { - registerProblem( - argument, - String.format("Field is of type '%s', '%s' may not be parsable to '%s'", - expectedType, - actualType, - expectedType), - pydanticConfigService.parsableTypeHighlightType - ) - continue - } else { - registerProblem(argument, String.format("Expected type '%s', got '%s' instead", - expectedType, - actualType) - ) - continue + if (expected is PyType) { + val parsableType = getParsableTypeFromTypeMap(expected, cachedParsableTypeMap) + if (parsableType != null) { + val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) + val parsableResult = AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched) + if (parsableResult.isMatched) { + registerProblem( + argument, + String.format("Field is of type '%s', '%s' may not be parsable to '%s'", + expectedType, + actualType, + expectedType), + pydanticConfigService.parsableTypeHighlightType + ) + continue + } else { + registerProblem(argument, String.format("Expected type '%s', got '%s' instead", + expectedType, + actualType) + ) + continue + } } - } - val acceptableType = expected?.let { getAcceptableTypeFromTypeMap(it, cachedAcceptableTypeMap) } - if (acceptableType != null) { - val acceptableMatched = matchParameterAndArgument(acceptableType, actual, argument, substitutions) - val acceptableResult = AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched) - if (acceptableResult.isMatched) { - registerProblem( - argument, - String.format("Field is of type '%s', '%s' is set as an acceptable type in pyproject.toml", - expectedType, - actualType, - expectedType), - pydanticConfigService.acceptableTypeHighlightType - ) - continue - } else { - registerProblem(argument, String.format("Expected type '%s', got '%s' instead", - expectedType, - actualType) - ) - continue + val acceptableType = getAcceptableTypeFromTypeMap(expected, cachedAcceptableTypeMap) + if (acceptableType != null) { + val acceptableMatched = matchParameterAndArgument(acceptableType, actual, argument, substitutions) + val acceptableResult = AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched) + if (acceptableResult.isMatched) { + registerProblem( + argument, + String.format("Field is of type '%s', '%s' is set as an acceptable type in pyproject.toml", + expectedType, + actualType, + expectedType), + pydanticConfigService.acceptableTypeHighlightType + ) + continue + } else { + registerProblem(argument, String.format("Expected type '%s', got '%s' instead", + expectedType, + actualType) + ) + continue + } } } registerProblem(argument, String.format("Expected type '%s', got '%s' instead", diff --git a/testData/typecheckerinspection/acceptableTypeDisable.py b/testData/typecheckerinspection/acceptableTypeDisable.py index 4c544405..2e60eb16 100644 --- a/testData/typecheckerinspection/acceptableTypeDisable.py +++ b/testData/typecheckerinspection/acceptableTypeDisable.py @@ -6,7 +6,7 @@ class A(BaseModel): a: str + b: str - -A(a=str('123')) -A(a=int(123)) +A(a=str('123'), b=str('123')) +A(a=int(123), b=int(123)) diff --git a/testData/typecheckerinspection/parsableTypeDisable.py b/testData/typecheckerinspection/parsableTypeDisable.py index 4c544405..2e60eb16 100644 --- a/testData/typecheckerinspection/parsableTypeDisable.py +++ b/testData/typecheckerinspection/parsableTypeDisable.py @@ -6,7 +6,7 @@ class A(BaseModel): a: str + b: str - -A(a=str('123')) -A(a=int(123)) +A(a=str('123'), b=str('123')) +A(a=int(123), b=int(123)) diff --git a/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt b/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt index b8c1581a..80b1bcde 100644 --- a/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt +++ b/testSrc/com/koxudaxi/pydantic/PydanticTypeCheckerInspectionTest.kt @@ -30,7 +30,7 @@ open class PydanticTypeCheckerInspectionTest : PydanticInspectionBase() { fun testParsableTypeInvalid() { val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) - pydanticConfigService.parsableTypeMap["builtins.str"] = arrayListOf("int") + pydanticConfigService.parsableTypeMap["builtins.str"] = arrayListOf("builtins.int") doTest() } @@ -54,7 +54,7 @@ open class PydanticTypeCheckerInspectionTest : PydanticInspectionBase() { fun testAcceptableTypeInvalid() { val pydanticConfigService = PydanticConfigService.getInstance(myFixture!!.project) - pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("int") + pydanticConfigService.acceptableTypeMap["builtins.str"] = arrayListOf("builtins.int") doTest() } fun testField() { From f7d9a9f304cf549755faadf8cdf9c4e5fce60593 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 01:16:04 +0900 Subject: [PATCH 03/11] support acceptable-types in toml --- .../koxudaxi/pydantic/PydanticInitializer.kt | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticInitializer.kt b/src/com/koxudaxi/pydantic/PydanticInitializer.kt index eb5badb8..d89c2434 100644 --- a/src/com/koxudaxi/pydantic/PydanticInitializer.kt +++ b/src/com/koxudaxi/pydantic/PydanticInitializer.kt @@ -50,21 +50,36 @@ class PydanticInitializer : StartupActivity { if (configFile is VirtualFile) { loadPyprojecToml(configFile, configService) } else { - configService.parsableTypeMap.clear() - configService.parsableTypeHighlightType = ProblemHighlightType.WARNING - configService.acceptableTypeHighlightType = ProblemHighlightType.WEAK_WARNING + clear(configService) } } + private fun clear(configService: PydanticConfigService) { + configService.parsableTypeMap.clear() + configService.acceptableTypeMap.clear() + configService.parsableTypeHighlightType = ProblemHighlightType.WARNING + configService.acceptableTypeHighlightType = ProblemHighlightType.WEAK_WARNING + } + private fun loadPyprojecToml(config: VirtualFile, configService: PydanticConfigService) { val result: TomlParseResult = Toml.parse(config.inputStream) - val table = result.getTableOrEmpty("tool.pydantic-pycharm-plugin") ?: return + val table = result.getTableOrEmpty("tool.pydantic-pycharm-plugin") + if (table.isEmpty) { + clear(configService) + return + } + val temporaryParsableTypeMap = getTypeMap("parsable-types", table) if (configService.parsableTypeMap != temporaryParsableTypeMap) { configService.parsableTypeMap = temporaryParsableTypeMap } + val temporaryAcceptableTypeMap = getTypeMap("acceptable-types", table) + if (configService.acceptableTypeMap != temporaryAcceptableTypeMap) { + configService.acceptableTypeMap = temporaryAcceptableTypeMap + } + configService.parsableTypeHighlightType = getHighlightLevel(table, "parsable-type-highlight", ProblemHighlightType.WARNING) configService.acceptableTypeHighlightType = getHighlightLevel(table, "acceptable-type-highlight", ProblemHighlightType.WEAK_WARNING) } From b8e0c60b514ce45a908922e68a139b9da4dd26a0 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 02:06:37 +0900 Subject: [PATCH 04/11] fix type check logic --- .../pydantic/PydanticTypeCheckerInspection.kt | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt index 49fb53f1..33689018 100644 --- a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt +++ b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt @@ -99,8 +99,7 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { val parsableType = getParsableTypeFromTypeMap(expected, cachedParsableTypeMap) if (parsableType != null) { val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) - val parsableResult = AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched) - if (parsableResult.isMatched) { + if (AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched).isMatched) registerProblem( argument, String.format("Field is of type '%s', '%s' may not be parsable to '%s'", @@ -109,20 +108,12 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { expectedType), pydanticConfigService.parsableTypeHighlightType ) - continue - } else { - registerProblem(argument, String.format("Expected type '%s', got '%s' instead", - expectedType, - actualType) - ) - continue - } + continue } val acceptableType = getAcceptableTypeFromTypeMap(expected, cachedAcceptableTypeMap) if (acceptableType != null) { val acceptableMatched = matchParameterAndArgument(acceptableType, actual, argument, substitutions) - val acceptableResult = AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched) - if (acceptableResult.isMatched) { + if (AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched).isMatched) registerProblem( argument, String.format("Field is of type '%s', '%s' is set as an acceptable type in pyproject.toml", @@ -131,14 +122,7 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { expectedType), pydanticConfigService.acceptableTypeHighlightType ) - continue - } else { - registerProblem(argument, String.format("Expected type '%s', got '%s' instead", - expectedType, - actualType) - ) - continue - } + continue } } registerProblem(argument, String.format("Expected type '%s', got '%s' instead", From 44b555e0555713ac0d2788ae90960309adea1403 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 02:32:43 +0900 Subject: [PATCH 05/11] fix invalid logic --- .../koxudaxi/pydantic/PydanticTypeCheckerInspection.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt index 33689018..0c72bc56 100644 --- a/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt +++ b/src/com/koxudaxi/pydantic/PydanticTypeCheckerInspection.kt @@ -99,7 +99,7 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { val parsableType = getParsableTypeFromTypeMap(expected, cachedParsableTypeMap) if (parsableType != null) { val parsableMatched = matchParameterAndArgument(parsableType, actual, argument, substitutions) - if (AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched).isMatched) + if (AnalyzeArgumentResult(argument, parsableType, substituteGenerics(parsableType, substitutions), actual, parsableMatched).isMatched) { registerProblem( argument, String.format("Field is of type '%s', '%s' may not be parsable to '%s'", @@ -108,12 +108,13 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { expectedType), pydanticConfigService.parsableTypeHighlightType ) - continue + continue + } } val acceptableType = getAcceptableTypeFromTypeMap(expected, cachedAcceptableTypeMap) if (acceptableType != null) { val acceptableMatched = matchParameterAndArgument(acceptableType, actual, argument, substitutions) - if (AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched).isMatched) + if (AnalyzeArgumentResult(argument, acceptableType, substituteGenerics(acceptableType, substitutions), actual, acceptableMatched).isMatched) { registerProblem( argument, String.format("Field is of type '%s', '%s' is set as an acceptable type in pyproject.toml", @@ -122,7 +123,8 @@ class PydanticTypeCheckerInspection : PyTypeCheckerInspection() { expectedType), pydanticConfigService.acceptableTypeHighlightType ) - continue + continue + } } } registerProblem(argument, String.format("Expected type '%s', got '%s' instead", From ba9ce9023bc4ec89c92684c18864cd446fb808b8 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 02:53:42 +0900 Subject: [PATCH 06/11] update README --- README.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef904d44..ed518c4a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ * Support same features as `pydantic.BaseModel` * (After PyCharm 2020.1 and this plugin version 0.1.0, PyCharm treats `pydantic.dataclasses.dataclass` as third-party dataclass.) -### Inspection for type-checking +### Inspection for type-checking (Experimental) **In version 0.1.1, This feature is broken. Please use it in [0.1.2](https://github.com/koxudaxi/pydantic-pycharm-plugin/releases/tag/0.1.2) or later.** This plugin provides an inspection for type-checking, which is compatible with pydantic. @@ -36,7 +36,7 @@ Don't use this type checker with a builtin type checker same time. ![inspection 1](https://raw.githubusercontent.com/koxudaxi/pydantic-pycharm-plugin/master/docs/inspection1.png) -### Parsable Type +### Parsable Type (Experimental) Pydantic has lots of support for coercing types. However, PyCharm gives a message saying only `Expected type "x," got "y" instead:` When you set parsable-type on a type, then the message will be changed to `Field is of type "x", "y" may not be parsable to "x"` @@ -55,7 +55,40 @@ exapmle: str = ["int", "float"] # datetime.datetime field may parse int -datetime.datetime = [ "int" ] +"datetime.datetime" = [ "int" ] + +# your_module.your_type field may parse str +"your_module.your_type" = [ "str" ] + +# You can set higlith level (default is "warning") +# You can select it from "warning", "weak_warning", "disable" +acceptable-type-highlight = "warning" +``` + +### Acceptable Type (Experimental) +This feature is in version 0.1.3 or later. +Pydantic can always parse a few types to other types. For example, `int` to `str.` It always succeeds. +You can set it as an acceptable type. The message is `Field is of type 'x', 'y' is set as an acceptable type in pyproject.toml`. +Also, you would disable the message to set "disable" on `acceptable-type-highlight`. + +#### Set acceptable-type in pyproject.toml +You should create `pyproject.toml` in your project root. +And, you define acceptable-type like a example. + +exapmle: + +```toml +[tool.pydantic-pycharm-plugin.parsable-types] + +# str field accepts to parse int and float +str = ["int", "float"] + +# datetime.datetime field may parse int +"datetime.datetime" = [ "int" ] + +# You can set higlith level (default is "weak_warning") +# You can select it from "warning", "weak_warning", "disable" +acceptable-type-highlight = "disable" ``` #### Related issues From 1a6b2c71707b7920552e2d9087f516402dba8f04 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 02:59:21 +0900 Subject: [PATCH 07/11] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed518c4a..473128f2 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,8 @@ acceptable-type-highlight = "warning" ``` ### Acceptable Type (Experimental) -This feature is in version 0.1.3 or later. +**This feature is in version [0.1.3](https://github.com/koxudaxi/pydantic-pycharm-plugin/releases/tag/0.1.3) or later.** + Pydantic can always parse a few types to other types. For example, `int` to `str.` It always succeeds. You can set it as an acceptable type. The message is `Field is of type 'x', 'y' is set as an acceptable type in pyproject.toml`. Also, you would disable the message to set "disable" on `acceptable-type-highlight`. From 49b188c4897cde82c6c2ef5aadbf1aa585590ac2 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 03:05:00 +0900 Subject: [PATCH 08/11] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 473128f2..90885441 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,9 @@ acceptable-type-highlight = "warning" ### Acceptable Type (Experimental) **This feature is in version [0.1.3](https://github.com/koxudaxi/pydantic-pycharm-plugin/releases/tag/0.1.3) or later.** -Pydantic can always parse a few types to other types. For example, `int` to `str.` It always succeeds. +Pydantic can always parse a few types to other types. For example, `int` to `str`. It always succeeds. You can set it as an acceptable type. The message is `Field is of type 'x', 'y' is set as an acceptable type in pyproject.toml`. -Also, you would disable the message to set "disable" on `acceptable-type-highlight`. +Also,You may want to disable the message.You can do it, by setting "disable" on `acceptable-type-highlight`. #### Set acceptable-type in pyproject.toml You should create `pyproject.toml` in your project root. From 040ee63fea8c589acced1aa6e8f7eec2ef65917b Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 11:48:49 +0900 Subject: [PATCH 09/11] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90885441..4b7d5806 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ str = ["int", "float"] # You can set higlith level (default is "warning") # You can select it from "warning", "weak_warning", "disable" -acceptable-type-highlight = "warning" +parsable-type-highlight = "warning" ``` ### Acceptable Type (Experimental) @@ -79,7 +79,7 @@ And, you define acceptable-type like a example. exapmle: ```toml -[tool.pydantic-pycharm-plugin.parsable-types] +[tool.pydantic-pycharm-plugin.acceptable-types] # str field accepts to parse int and float str = ["int", "float"] From d76ba4a6d601605baeedc7f5e7531ce208f5f692 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 11:57:53 +0900 Subject: [PATCH 10/11] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4b7d5806..04c00581 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ str = ["int", "float"] # your_module.your_type field may parse str "your_module.your_type" = [ "str" ] +[tool.pydantic-pycharm-plugin] # You can set higlith level (default is "warning") # You can select it from "warning", "weak_warning", "disable" parsable-type-highlight = "warning" @@ -87,6 +88,7 @@ str = ["int", "float"] # datetime.datetime field may parse int "datetime.datetime" = [ "int" ] +[tool.pydantic-pycharm-plugin] # You can set higlith level (default is "weak_warning") # You can select it from "warning", "weak_warning", "disable" acceptable-type-highlight = "disable" From 41da1e2e1310c8be9eedae11a17f39083838a1b0 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Mon, 20 Apr 2020 12:06:50 +0900 Subject: [PATCH 11/11] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 04c00581..40f9882a 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,9 @@ str = ["int", "float"] # You can set higlith level (default is "warning") # You can select it from "warning", "weak_warning", "disable" parsable-type-highlight = "warning" + +## If you set acceptable-type-highlight then, you have to set it at same depth. +acceptable-type-highlight = "disable" ``` ### Acceptable Type (Experimental) @@ -92,6 +95,9 @@ str = ["int", "float"] # You can set higlith level (default is "weak_warning") # You can select it from "warning", "weak_warning", "disable" acceptable-type-highlight = "disable" + +# If you set parsable-type-highlight then, you have to set it at same depth. +parsable-type-highlight = "warning" ``` #### Related issues