Skip to content

Commit

Permalink
Ignore __init__ method if parameter is only only **kwargs argumen…
Browse files Browse the repository at this point in the history
…ts. (#798)

* Fix incorrect init interpretation

* Fix unittest
  • Loading branch information
koxudaxi committed Sep 12, 2023
1 parent 2a9bc63 commit b90b88b
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 2 deletions.
18 changes: 17 additions & 1 deletion docs/ignore-init-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,21 @@ The option has to be defined in pyproject.toml
```toml
[tool.pydantic-pycharm-plugin]
ignore-init-method-arguments = true

```

!!! info
**This feature is in version 0.4.9 or later**

If a third-party library provides a model that extends BaseModel, it may override the `__init__` method, as in `__init__(self, **kwargs)`.
If this is the case, the plugin user should set `ignore-init-method-arguments = true` to ignore the `__init__` method argument.
But it is difficult to tell if the library is using BaseModel or not.

The plugin ignore the `__init__` method if argument is only `**kwargs`. the option is provided as `ignore-init-method-keyword-arguments`.
This option is enabled by default, so if you create a model that inherits from BaseModel with a method like `__init__(self, **kwargs)` defined, ignore this `init` argument.

If you want to disable this option, please put the following setting in `pyproject.toml`.

```toml
[tool.pydantic-pycharm-plugin].
ignore-init-method-keyword-arguments = true
```
26 changes: 26 additions & 0 deletions docs/ignore-init-keyword-arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Ignore `__init__` method arguments


!!! info
**This feature is in version 0.4.8 or later**

You can write `__init__` method on a model for adding some logic.

However, default arguments on `__init__` method will be overridden, And you will lose autocompletion for `__init__` methods by the plugin.

![options_init](init_arguments.png)

`ignore-init-method-arguments` option resolves this problem.
The option ignore arguments on `__init__` method.

![options_ignore_init](ignore_init_arguments.png)


The option has to be defined in pyproject.toml

```toml
[tool.pydantic-pycharm-plugin]
ignore-init-method-keyword-arguments = false

```

19 changes: 18 additions & 1 deletion src/com/koxudaxi/pydantic/Pydantic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -783,11 +783,28 @@ internal fun getQualifiedName(pyExpression: PyExpression, context: TypeEvalConte
}

fun getPydanticModelInit(pyClass: PyClass, context: TypeEvalContext): PyFunction? {
if (PydanticConfigService.getInstance(pyClass.project).ignoreInitMethodArguments) return null
val pydanticConfigService = PydanticConfigService.getInstance(pyClass.project)
if (pydanticConfigService.ignoreInitMethodArguments) return null
val pyFunction = pyClass.findInitOrNew(true, context) ?: return null
if (pyFunction.name != PyNames.INIT) return null
val containingClass = pyFunction.containingClass ?: return null
if (!isPydanticModel(containingClass, false, context)) return null

if (!pydanticConfigService.ignoreInitMethodKeywordArguments) return pyFunction
val callParameters = pyFunction.getParameters(context)
.filterNot { parameter -> parameter.isSelf }
val callParametersWithoutKeywordContainer = callParameters.filterNot {
parameter -> parameter.isKeywordContainer
}
val hasKeywordContainer = callParametersWithoutKeywordContainer.size != callParameters.size
if (hasKeywordContainer) {
val hasNonPositionalContainer = callParametersWithoutKeywordContainer.any {
parameter -> !parameter.isPositionalContainer
}
if (!hasNonPositionalContainer) {
return null
}
}
return pyFunction
}

Expand Down
1 change: 1 addition & 0 deletions src/com/koxudaxi/pydantic/PydanticConfigService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PydanticConfigService : PersistentStateComponent<PydanticConfigService> {
var acceptableTypeMap = mapOf<String, List<String>>()
var acceptableTypeHighlightType: ProblemHighlightType = ProblemHighlightType.WEAK_WARNING
var ignoreInitMethodArguments: Boolean = false
var ignoreInitMethodKeywordArguments: Boolean = true
val currentInitTyped: Boolean
get() = this.mypyInitTyped ?: this.initTyped
val currentWarnUntypedFields: Boolean
Expand Down
2 changes: 2 additions & 0 deletions src/com/koxudaxi/pydantic/PydanticInitializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class PydanticInitializer : ProjectActivity {
getHighlightLevel(table, "acceptable-type-highlight", ProblemHighlightType.WEAK_WARNING)

configService.ignoreInitMethodArguments = table.getBoolean("ignore-init-method-arguments") ?: false
configService.ignoreInitMethodKeywordArguments =
table.getBoolean("ignore-init-method-keyword-arguments") ?: true
}

private fun getHighlightLevel(table: TomlTable, path: String, default: ProblemHighlightType): ProblemHighlightType {
Expand Down
16 changes: 16 additions & 0 deletions testData/completion/keywordArgumentInitArgsKwargs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@


from pydantic import BaseModel

class Z(BaseModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class A(Z):
abc: str
cde: str
efg: str

class B(A):
hij: str

A(<caret>)
16 changes: 16 additions & 0 deletions testData/completion/keywordArgumentInitArgsKwargsDisable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@


from pydantic import BaseModel

class Z(BaseModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class A(Z):
abc: str
cde: str
efg: str

class B(A):
hij: str

A(<caret>)
16 changes: 16 additions & 0 deletions testData/completion/keywordArgumentInitKwargs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@


from pydantic import BaseModel

class Z(BaseModel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class A(Z):
abc: str
cde: str
efg: str

class B(A):
hij: str

A(<caret>)
16 changes: 16 additions & 0 deletions testData/completion/keywordArgumentInitPosition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@


from pydantic import BaseModel

class Z(BaseModel):
def __init__(self, xyz: str):
super().__init__()
class A(Z):
abc: str
cde: str
efg: str

class B(A):
hij: str

A(<caret>)
2 changes: 2 additions & 0 deletions testData/initializer/pyprojecttoml/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ parsable-type-highlight = "weak_warning"
acceptable-type-highlight = "warning"

ignore-init-method-arguments = true
ignore-init-method-keyword-arguments = false


[tool.pydantic-pycharm-plugin.parsable-types]
## datetime.datetime field may parse int
Expand Down
37 changes: 37 additions & 0 deletions testSrc/com/koxudaxi/pydantic/PydanticCompletionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,43 @@ open class PydanticCompletionTest : PydanticTestCase() {
)
}

fun testKeywordArgumentInitArgsKwargs() {
doFieldTest(
listOf(
Pair("abc=", "str A"),
Pair("cde=", "str A"),
Pair("efg=", "str A")
)
)
}
fun testKeywordArgumentInitArgsKwargsDisable() {
val config = PydanticConfigService.getInstance(myFixture!!.project)
config.ignoreInitMethodKeywordArguments = false
try {
doFieldTest(
emptyList(
)
)}
finally {
config.ignoreInitMethodKeywordArguments = true
}
}
fun testKeywordArgumentInitKwargs() {
doFieldTest(
listOf(
Pair("abc=", "str A"),
Pair("cde=", "str A"),
Pair("efg=", "str A")
)
)
}

fun testKeywordArgumentInitPosition() {
doFieldTest(
emptyList()
)
}

fun testdataclassKeywordArgument() {
doFieldTest(
listOf(
Expand Down
2 changes: 2 additions & 0 deletions testSrc/com/koxudaxi/pydantic/PydanticInitializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ open class PydanticInitializerTest : PydanticTestCase() {
assertEquals(this.pydanticConfigService.parsableTypeHighlightType, ProblemHighlightType.WEAK_WARNING)
assertEquals(this.pydanticConfigService.acceptableTypeHighlightType, ProblemHighlightType.WARNING)
assertEquals(this.pydanticConfigService.ignoreInitMethodArguments, true)
assertEquals(this.pydanticConfigService.ignoreInitMethodKeywordArguments, false)
}
}
}
Expand Down Expand Up @@ -96,6 +97,7 @@ open class PydanticInitializerTest : PydanticTestCase() {
assertEquals(this.pydanticConfigService.parsableTypeHighlightType, ProblemHighlightType.WARNING)
assertEquals(this.pydanticConfigService.acceptableTypeHighlightType, ProblemHighlightType.WEAK_WARNING)
assertEquals(this.pydanticConfigService.ignoreInitMethodArguments, false)
assertEquals(this.pydanticConfigService.ignoreInitMethodKeywordArguments, true)
}
}
}
Expand Down

0 comments on commit b90b88b

Please sign in to comment.