Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support mypy.ini #110

Merged
merged 5 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ allprojects {
dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.apache.tuweni:tuweni-toml:0.10.0"
compile group: 'org.ini4j', name: 'ini4j', version: '0.5.4'
testCompile group: 'junit', name: 'junit', version: '4.11'
}

Expand All @@ -79,5 +80,6 @@ sourceSets {
}
test {
java.srcDir 'testSrc'
resources.srcDir 'testData'
}
}
7 changes: 6 additions & 1 deletion resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<idea-plugin url="https://github.com/koxudaxi/pydantic-pycharm-plugin">
<id>com.koxudaxi.pydantic</id>
<name>Pydantic</name>
<version>0.1.3</version>
<version>0.1.4</version>
<vendor email="koaxudai@gmail.com">Koudai Aono @koxudaxi</vendor>
<change-notes><![CDATA[
<h2>version 0.1.4</h2>
<p>Features</p>
<ul>
<li>Support mypy.ini [#110] </li>
</ul>
<h2>version 0.1.3</h2>
<p>Features</p>
<ul>
Expand Down
4 changes: 2 additions & 2 deletions src/com/koxudaxi/pydantic/PydanticConfigPanel.form
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Type-checking for init [init_typed]"/>
<text value="Type-checking for init [If init_typed is in mypy.ini then, It is used for this config] "/>
</properties>
</component>
<hspacer id="18091">
Expand All @@ -49,7 +49,7 @@
</constraints>
<properties>
<borderPaintedFlat value="true"/>
<text value="Warning untyped fields [warn_untyped_fields]"/>
<text value="Warning untyped fields [If warn_untyped_fields is in mypy.ini then, It is used for this config]"/>
</properties>
</component>
<component id="d96bd" class="javax.swing.JTextPane" binding="ifEnabledRaiseATextPane" default-binding="true">
Expand Down
9 changes: 8 additions & 1 deletion src/com/koxudaxi/pydantic/PydanticConfigPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;

import static com.intellij.util.ui.JBUI.CurrentTheme.NewClassDialog.panelBackground;

public class PydanticConfigPanel {

Expand All @@ -23,6 +22,14 @@ public class PydanticConfigPanel {
this.initTypedCheckBox.setSelected(pydanticConfigService.getInitTyped());
this.warnUntypedFieldsCheckBox.setSelected(pydanticConfigService.getWarnUntypedFields());

boolean enableInitTypedCheckBox = pydanticConfigService.getMypyInitTyped() == null;
this.initTypedCheckBox.setEnabled(enableInitTypedCheckBox);
this.ifEnabledIncludeTheTextPane.setEnabled(enableInitTypedCheckBox);

boolean warnUntypedFieldsCheckBox = pydanticConfigService.getMypyWarnUntypedFields() == null;
this.warnUntypedFieldsCheckBox.setEnabled(warnUntypedFieldsCheckBox);
this.ifEnabledRaiseATextPane.setEnabled(warnUntypedFieldsCheckBox);

setHyperlinkHtml(this.textPane1, "See <a href=\"https://koxudaxi.github.io/pydantic-pycharm-plugin/\">documentation</a> for more details.</p>");
}

Expand Down
8 changes: 8 additions & 0 deletions src/com/koxudaxi/pydantic/PydanticConfigService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ import com.intellij.util.xmlb.XmlSerializerUtil
class PydanticConfigService : PersistentStateComponent<PydanticConfigService> {
var initTyped = true
var warnUntypedFields = false
var mypyInitTyped: Boolean? = null
var mypyWarnUntypedFields: Boolean? = null
var pyprojectToml: String? = null
var mypyIni: String? = null
var parsableTypeMap = mutableMapOf<String, List<String>>()
var parsableTypeHighlightType: ProblemHighlightType = ProblemHighlightType.WARNING
var acceptableTypeMap = mutableMapOf<String, List<String>>()
var acceptableTypeHighlightType: ProblemHighlightType = ProblemHighlightType.WEAK_WARNING

val currentInitTyped: Boolean
get() = this.mypyInitTyped ?: this.initTyped
val currentWarnUntypedFields: Boolean
get() = this.mypyWarnUntypedFields ?: this.warnUntypedFields

override fun getState(): PydanticConfigService {
return this
}
Expand Down
90 changes: 64 additions & 26 deletions src/com/koxudaxi/pydantic/PydanticInitializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,70 +15,108 @@ import org.apache.tuweni.toml.Toml
import org.apache.tuweni.toml.TomlArray
import org.apache.tuweni.toml.TomlParseResult
import org.apache.tuweni.toml.TomlTable
import org.ini4j.Ini
import org.ini4j.IniPreferences
import java.io.StringReader


class PydanticInitializer : StartupActivity {

private fun getDefaultPyProjectTomlPathPath(project: Project): String {
private fun getDefaultPyProjectTomlPath(project: Project): String {
return project.basePath + "/pyproject.toml"
}

private fun getDefaultMypyIniPath(project: Project): String {
return project.basePath + "/mypy.ini"
}

private fun initializeParsableTypeMap(project: Project, configService: PydanticConfigService) {
val pyprojectTomlDefault = configService.pyprojectToml ?: getDefaultPyProjectTomlPathPath(project)
private fun initializeFileLoader(project: Project, configService: PydanticConfigService) {
val defaultPyProjectToml = getDefaultPyProjectTomlPath(project)
val defaultMypyIni = getDefaultMypyIniPath(project)
when (val pyprojectToml = LocalFileSystem.getInstance()
.findFileByPath(configService.pyprojectToml ?: defaultPyProjectToml)
) {
is VirtualFile -> loadPyprojecToml(pyprojectToml, configService)
else -> clearPyProjectTomlConfig(configService)
}

when (val mypyIni = LocalFileSystem.getInstance()
.findFileByPath(configService.mypyIni ?: defaultMypyIni)
) {
is VirtualFile -> loadMypyIni(mypyIni, configService)
else -> clearMypyIniConfig(configService)
}
VirtualFileManager.getInstance().addAsyncFileListener(
{ events ->
object : AsyncFileListener.ChangeApplier {
override fun afterVfsChange() {
if (project.isDisposed) return
val configFile = events
val projectFiles = events
.asSequence()
.filter {
it is VFileContentChangeEvent || it is VFileMoveEvent || it is VFileCopyEvent
}
.mapNotNull { it.file }
.filter { ProjectFileIndex.getInstance(project).isInContent(it) }
.filter { it.path == configService.pyprojectToml ?: pyprojectTomlDefault }
.lastOrNull() ?: return
loadPyprojecToml(configFile, configService)
if (projectFiles.count() == 0) return
val pyprojectToml = configService.pyprojectToml ?: defaultPyProjectToml
val mypyIni = configService.mypyIni ?: defaultMypyIni
projectFiles.forEach {
when (it.path) {
pyprojectToml -> loadPyprojecToml(it, configService)
mypyIni -> loadMypyIni(it, configService)
}
}
}
}
},
{}
)
val configFile = LocalFileSystem.getInstance()
.findFileByPath(configService.pyprojectToml ?: pyprojectTomlDefault)
if (configFile is VirtualFile) {
loadPyprojecToml(configFile, configService)
} else {
clear(configService)
}
}

private fun clear(configService: PydanticConfigService) {
private fun clearMypyIniConfig(configService: PydanticConfigService) {
configService.mypyInitTyped = null
configService.mypyWarnUntypedFields = null
}

private fun clearPyProjectTomlConfig(configService: PydanticConfigService) {
configService.parsableTypeMap.clear()
configService.acceptableTypeMap.clear()
configService.parsableTypeHighlightType = ProblemHighlightType.WARNING
configService.acceptableTypeHighlightType = ProblemHighlightType.WEAK_WARNING
}

private fun fromIniBoolean(text: String?): Boolean? {
return when (text) {
"True" -> true
"False" -> false
else -> null
}
}

private fun loadMypyIni(config: VirtualFile, configService: PydanticConfigService) {
try {
val ini = Ini(config.inputStream)
val prefs = IniPreferences(ini)
val pydanticMypy = prefs.node("pydantic-mypy")
configService.mypyInitTyped = fromIniBoolean(pydanticMypy["init_typed", null])
configService.mypyWarnUntypedFields = fromIniBoolean(pydanticMypy["warn_untyped_fields", null])
} catch (t: Throwable) {
clearMypyIniConfig(configService)
}
}

private fun loadPyprojecToml(config: VirtualFile, configService: PydanticConfigService) {
val result: TomlParseResult = Toml.parse(config.inputStream)

val table = result.getTableOrEmpty("tool.pydantic-pycharm-plugin")
if (table.isEmpty) {
clear(configService)
clearPyProjectTomlConfig(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.parsableTypeMap = getTypeMap("parsable-types", table)
configService.acceptableTypeMap = getTypeMap("acceptable-types", table)

configService.parsableTypeHighlightType = getHighlightLevel(table, "parsable-type-highlight", ProblemHighlightType.WARNING)
configService.acceptableTypeHighlightType = getHighlightLevel(table, "acceptable-type-highlight", ProblemHighlightType.WEAK_WARNING)
Expand Down Expand Up @@ -122,6 +160,6 @@ class PydanticInitializer : StartupActivity {

override fun runActivity(project: Project) {
val configService = PydanticConfigService.getInstance(project)
initializeParsableTypeMap(project, configService)
initializeFileLoader(project, configService)
}
}
2 changes: 1 addition & 1 deletion src/com/koxudaxi/pydantic/PydanticInspection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class PydanticInspection : PyInspection() {
super.visitPyAssignmentStatement(node)

if (node == null) return
if (pydanticConfigService.warnUntypedFields) {
if (pydanticConfigService.currentWarnUntypedFields) {
inspectWarnUntypedFields(node)
}
inspectReadOnlyProperty(node)
Expand Down
2 changes: 1 addition & 1 deletion src/com/koxudaxi/pydantic/PydanticTypeProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class PydanticTypeProvider : PyTypeProviderBase() {
val clsType = (context.getType(pyClass) as? PyClassLikeType) ?: return null
val ellipsis = PyElementGenerator.getInstance(pyClass.project).createEllipsis()

val typed = !init || getInstance(pyClass.project).initTyped
val typed = !init || getInstance(pyClass.project).currentInitTyped
val collected = linkedMapOf<String, PyCallableParameter>()
val pydanticVersion = getPydanticVersion(pyClass.project, context)
val config = getConfig(pyClass, context, true)
Expand Down
19 changes: 19 additions & 0 deletions testData/initializer/mypyini/mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[mypy]
plugins = pydantic.mypy

follow_imports = silent
strict_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True

[pydantic-mypy]
init_forbid_extra = True
init_typed = False
warn_required_dynamic_aliases = True
warn_untyped_fields = True
19 changes: 19 additions & 0 deletions testData/initializer/mypyinibroken/mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[mypy]
plugins = pydantic.mypy

follow_imports = silent
strict_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True

[pydantic-mypy
init_forbid_extra = True
init_typed = False
warn_required_dynamic_aliases = True
warn_untyped_fields = True
4 changes: 4 additions & 0 deletions testData/initializer/mypyiniempty/mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[mypy]

follow_imports = silent
strict_optional = True
12 changes: 12 additions & 0 deletions testData/initializer/pyprojecttoml/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[tool.pydantic-pycharm-plugin]
# You can select higlith level from "warning", "weak_warning", "disable"
parsable-type-highlight = "weak_warning"
acceptable-type-highlight = "warning"

[tool.pydantic-pycharm-plugin.parsable-types]
## datetime.datetime field may parse int
"datetime.datetime" = ["int"]

[tool.pydantic-pycharm-plugin.acceptable-types]
# str field accepts to parse int and float
str = ["int", "float"]
12 changes: 12 additions & 0 deletions testData/initializer/pyprojecttomldisable/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[tool.pydantic-pycharm-plugin]
# You can select higlith level from "warning", "weak_warning", "disable"
#parsable-type-highlight = "disable"
acceptable-type-highlight = "disable"

[tool.pydantic-pycharm-plugin.parsable-types]
## datetime.datetime field may parse int
"datetime.datetime" = ["int"]

[tool.pydantic-pycharm-plugin.acceptable-types]
# str field accepts to parse int and float
str = ["int", "float"]
2 changes: 2 additions & 0 deletions testData/initializer/pyprojecttomlempty/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.others]
abc = "123"
Loading