Skip to content

Commit 6b5b5fb

Browse files
authored
Merge pull request #1 from asford/master
Add dmypy support via mypy.api.run_dmypy
2 parents 333125b + 4d7d342 commit 6b5b5fb

File tree

2 files changed

+79
-7
lines changed

2 files changed

+79
-7
lines changed

README.rst

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ Install into the same virtualenv as python-lsp-server itself.
2424
Configuration
2525
-------------
2626

27-
``live_mode`` (default is True) provides type checking as you type. This writes to a tempfile every time a check is done.
27+
``live_mode`` (default is True) provides type checking as you type.
28+
This writes to a tempfile every time a check is done. Turning off ``live_mode`` means you must save your changes for mypy diagnostics to update correctly.
2829

29-
Turning off ``live_mode`` means you must save your changes for mypy diagnostics to update correctly.
30+
``dmypy`` (default is False) executes via ``dmypy run`` rather than ``mypy``.
31+
This uses the ``dmypy`` daemon and may dramatically improve the responsiveness of the ``pylsp`` server, however this currently does not work in ``live_mode``. Enabling this disables ``live_mode``, even for conflicting configs.
3032

31-
Depending on your editor, the configuration (found in a file called mypy-ls.cfg in your workspace or a parent directory) should be roughly like this:
33+
``strict`` (default is False) refers to the ``strict`` option of ``mypy``.
34+
This option often is too strict to be useful.
35+
36+
Depending on your editor, the configuration (found in a file called mypy-ls.cfg in your workspace or a parent directory) should be roughly like this for a standard configuration:
3237

3338
::
3439

@@ -38,6 +43,17 @@ Depending on your editor, the configuration (found in a file called mypy-ls.cfg
3843
"strict": False
3944
}
4045

46+
With ``dmypy`` enabled your config should look like this:
47+
48+
::
49+
50+
{
51+
"enabled": True,
52+
"live_mode": False,
53+
"dmypy": True,
54+
"strict": False
55+
}
56+
4157
Developing
4258
-------------
4359

mypy_ls/plugin.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pylsp.config.config import Config
1818
from typing import Optional, Dict, Any, IO, List
1919
import atexit
20+
import collections
2021

2122
line_pattern: str = r"((?:^[a-z]:)?[^:]+):(?:(\d+):)?(?:(\d+):)? (\w+): (.*)"
2223

@@ -26,6 +27,13 @@
2627

2728
tmpFile: Optional[IO[str]] = None
2829

30+
# In non-live-mode the file contents aren't updated.
31+
# Returning an empty diagnostic clears the diagnostic result,
32+
# so store a cache of last diagnostics for each file a-la the pylint plugin,
33+
# so we can return some potentially-stale diagnostics.
34+
# https://github.com/python-lsp/python-lsp-server/blob/v1.0.1/pylsp/plugins/pylint_lint.py#L55-L62
35+
last_diagnostics: Dict[str, List] = collections.defaultdict(list)
36+
2937

3038
def parse_line(
3139
line: str, document: Optional[Document] = None
@@ -115,33 +123,73 @@ def pylsp_lint(
115123
116124
"""
117125
settings = config.plugin_settings("mypy-ls")
126+
log.info(
127+
"lint settings = %s document.path = %s is_saved = %s",
128+
settings,
129+
document.path,
130+
is_saved,
131+
)
132+
118133
live_mode = settings.get("live_mode", True)
119-
args = ["--incremental", "--show-column-numbers", "--follow-imports", "silent"]
134+
dmypy = settings.get("dmypy", False)
135+
136+
if dmypy and live_mode:
137+
# dmypy can only be efficiently run on files that have been saved, see:
138+
# https://github.com/python/mypy/issues/9309
139+
log.warning("live_mode is not supported with dmypy, disabling")
140+
live_mode = False
141+
142+
args = ["--show-column-numbers"]
120143

121144
global tmpFile
122145
if live_mode and not is_saved and tmpFile:
146+
log.info("live_mode tmpFile = %s", live_mode)
123147
tmpFile = open(tmpFile.name, "w")
124148
tmpFile.write(document.source)
125149
tmpFile.close()
126150
args.extend(["--shadow-file", document.path, tmpFile.name])
127-
elif not is_saved:
128-
return []
151+
elif not is_saved and document.path in last_diagnostics:
152+
# On-launch the document isn't marked as saved, so fall through and run
153+
# the diagnostics anyway even if the file contents may be out of date.
154+
log.info(
155+
"non-live, returning cached diagnostics len(cached) = %s",
156+
last_diagnostics[document.path],
157+
)
158+
return last_diagnostics[document.path]
129159

130160
if mypyConfigFile:
131161
args.append("--config-file")
132162
args.append(mypyConfigFile)
163+
133164
args.append(document.path)
165+
134166
if settings.get("strict", False):
135167
args.append("--strict")
136168

137-
report, errors, _ = mypy_api.run(args)
169+
if not dmypy:
170+
args.extend(["--incremental", "--follow-imports", "silent"])
171+
172+
log.info("executing mypy args = %s", args)
173+
report, errors, _ = mypy_api.run(args)
174+
else:
175+
args = ["run", "--"] + args
176+
177+
log.info("executing dmypy args = %s", args)
178+
report, errors, _ = mypy_api.run_dmypy(args)
179+
180+
log.debug("report:\n%s", report)
181+
log.debug("errors:\n%s", errors)
138182

139183
diagnostics = []
140184
for line in report.splitlines():
185+
log.debug("parsing: line = %r", line)
141186
diag = parse_line(line, document)
142187
if diag:
143188
diagnostics.append(diag)
144189

190+
log.info("mypy-ls len(diagnostics) = %s", len(diagnostics))
191+
192+
last_diagnostics[document.path] = diagnostics
145193
return diagnostics
146194

147195

@@ -181,20 +229,28 @@ def init(workspace: str) -> Dict[str, str]:
181229
182230
"""
183231
# On windows the path contains \\ on linux it contains / all the code works with /
232+
log.info("init workspace = %s", workspace)
184233
workspace = workspace.replace("\\", "/")
234+
185235
configuration = {}
186236
path = findConfigFile(workspace, "mypy-ls.cfg")
187237
if path:
188238
with open(path) as file:
189239
configuration = eval(file.read())
240+
190241
global mypyConfigFile
191242
mypyConfigFile = findConfigFile(workspace, "mypy.ini")
243+
if not mypyConfigFile:
244+
mypyConfigFile = findConfigFile(workspace, ".mypy.ini")
245+
192246
if ("enabled" not in configuration or configuration["enabled"]) and (
193247
"live_mode" not in configuration or configuration["live_mode"]
194248
):
195249
global tmpFile
196250
tmpFile = tempfile.NamedTemporaryFile("w", delete=False)
197251
tmpFile.close()
252+
253+
log.info("mypyConfigFile = %s configuration = %s", mypyConfigFile, configuration)
198254
return configuration
199255

200256

0 commit comments

Comments
 (0)