Skip to content

Commit 36e6bf3

Browse files
hetmankpRichardk2n
authored andcommitted
Create new config option 'config_names'
This configuration option allows us to specify additional configuration file names under which the mypy config could be found.
1 parent a4e3750 commit 36e6bf3

File tree

3 files changed

+73
-26
lines changed

3 files changed

+73
-26
lines changed

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Configuration
3939
``dmypy_status_file`` (Default is ``.dmypy.json``) specifies which status file dmypy should use.
4040
This modifies the ``--status-file`` option passed to ``dmypy`` given ``dmypy`` is active.
4141

42+
``config_sub_paths`` (default is ``[]``) specifies sub paths under which the mypy configuration file may be found.
43+
For each directory searched for the mypy config file, this also searches the sub paths specified here
44+
4245
This project supports the use of ``pyproject.toml`` for configuration. It is in fact the preferred way. Using that your configuration could look like this:
4346

4447
::
@@ -91,6 +94,15 @@ With ``dmypy_status_file`` your config could look like this:
9194
"dmypy_status_file": ".custom_dmypy_status_file.json"
9295
}
9396

97+
With ``config_sub_paths`` your config could look like this:
98+
99+
::
100+
101+
{
102+
"enabled": True,
103+
"config_sub_paths": [".config"]
104+
}
105+
94106
Developing
95107
-------------
96108

pylsp_mypy/plugin.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def init(workspace: str) -> Dict[str, str]:
358358

359359
configuration = {}
360360
path = findConfigFile(
361-
workspace, ["pylsp-mypy.cfg", "mypy-ls.cfg", "mypy_ls.cfg", "pyproject.toml"], False
361+
workspace, [], ["pylsp-mypy.cfg", "mypy-ls.cfg", "mypy_ls.cfg", "pyproject.toml"], False
362362
)
363363
if path:
364364
if "pyproject.toml" in path:
@@ -367,16 +367,17 @@ def init(workspace: str) -> Dict[str, str]:
367367
with open(path) as file:
368368
configuration = ast.literal_eval(file.read())
369369

370+
configSubPaths = configuration.get("config_sub_paths", [])
370371
mypyConfigFile = findConfigFile(
371-
workspace, ["mypy.ini", ".mypy.ini", "pyproject.toml", "setup.cfg"], True
372+
workspace, configSubPaths, ["mypy.ini", ".mypy.ini", "pyproject.toml", "setup.cfg"], True
372373
)
373374
mypyConfigFileMap[workspace] = mypyConfigFile
374375

375376
log.info("mypyConfigFile = %s configuration = %s", mypyConfigFile, configuration)
376377
return configuration
377378

378379

379-
def findConfigFile(path: str, names: List[str], mypy: bool) -> Optional[str]:
380+
def findConfigFile(path: str, configSubPaths: List[str], names: List[str], mypy: bool) -> Optional[str]:
380381
"""
381382
Search for a config file.
382383
@@ -387,6 +388,8 @@ def findConfigFile(path: str, names: List[str], mypy: bool) -> Optional[str]:
387388
----------
388389
path : str
389390
The path where the search starts.
391+
configSubPaths : List[str]
392+
Additional sub search paths in which mypy configs might be located
390393
names : List[str]
391394
The file to be found (or alternative names).
392395
mypy : bool
@@ -401,26 +404,27 @@ def findConfigFile(path: str, names: List[str], mypy: bool) -> Optional[str]:
401404
start = Path(path).joinpath(names[0]) # the join causes the parents to include path
402405
for parent in start.parents:
403406
for name in names:
404-
file = parent.joinpath(name)
405-
if file.is_file():
406-
if file.name in ["mypy-ls.cfg", "mypy_ls.cfg"]:
407-
raise NameError(
408-
f"{str(file)}: {file.name} is no longer supported, you should rename your "
409-
"config file to pylsp-mypy.cfg or preferably use a pyproject.toml instead."
410-
)
411-
if file.name == "pyproject.toml":
412-
configPresent = (
413-
toml.load(file).get("tool", {}).get("mypy" if mypy else "pylsp-mypy")
414-
is not None
415-
)
416-
if not configPresent:
417-
continue
418-
if file.name == "setup.cfg":
419-
config = ConfigParser()
420-
config.read(str(file))
421-
if "mypy" not in config:
422-
continue
423-
return str(file)
407+
for subPath in [""] + configSubPaths:
408+
file = parent.joinpath(subPath).joinpath(name)
409+
if file.is_file():
410+
if file.name in ["mypy-ls.cfg", "mypy_ls.cfg"]:
411+
raise NameError(
412+
f"{str(file)}: {file.name} is no longer supported, you should rename your "
413+
"config file to pylsp-mypy.cfg or preferably use a pyproject.toml instead."
414+
)
415+
if file.name == "pyproject.toml":
416+
configPresent = (
417+
toml.load(file).get("tool", {}).get("mypy" if mypy else "pylsp-mypy")
418+
is not None
419+
)
420+
if not configPresent:
421+
continue
422+
if file.name == "setup.cfg":
423+
config = ConfigParser()
424+
config.read(str(file))
425+
if "mypy" not in config:
426+
continue
427+
return str(file)
424428
# No config file found in the whole directory tree
425429
# -> check mypy default locations for mypy config
426430
if mypy:

test/test_plugin.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from pylsp_mypy import plugin
1414

15-
DOC_URI = f"file:/{Path(__file__)}"
15+
DOC_URI = f"file:/{Path(__file__)}" #TODO using these file as a document is a bad idea as tests can break by adding new tests
1616
DOC_TYPE_ERR = """{}.append(3)
1717
"""
1818
TYPE_ERR_MSG = '"Dict[<nothing>, <nothing>]" has no attribute "append" [attr-defined]'
@@ -69,8 +69,7 @@ def test_plugin(workspace, last_diagnostics_monkeypatch):
6969

7070

7171
def test_parse_full_line(workspace):
72-
doc = Document(DOC_URI, workspace)
73-
diag = plugin.parse_line(TEST_LINE, doc)
72+
diag = plugin.parse_line(TEST_LINE) #TODO parse a document here
7473
assert diag["message"] == '"Request" has no attribute "id"'
7574
assert diag["range"]["start"] == {"line": 278, "character": 7}
7675
assert diag["range"]["end"] == {"line": 278, "character": 8}
@@ -253,3 +252,35 @@ def test_dmypy_status_file(tmpdir, last_diagnostics_monkeypatch, workspace):
253252
)
254253

255254
assert statusFile.exists()
255+
256+
257+
def test_config_sub_paths(tmpdir, last_diagnostics_monkeypatch):
258+
DOC_SOURCE = """
259+
def foo():
260+
return
261+
unreachable = 1
262+
"""
263+
DOC_ERR_MSG = "Statement is unreachable [unreachable]"
264+
265+
config_sub_paths = [".config"]
266+
267+
# Initialize workspace.
268+
ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
269+
ws._config = Config(ws.root_uri, {}, 0, {})
270+
271+
# Create configuration file for workspace.
272+
plugin_config = tmpdir.join("pyproject.toml")
273+
plugin_config.write(f"[tool.pylsp-mypy]\nenabled = true\nconfig_sub_paths = {config_sub_paths}")
274+
config_dir = tmpdir.mkdir(".config")
275+
mypy_config = config_dir.join("mypy.ini")
276+
mypy_config.write("[mypy]\nwarn_unreachable = True\ncheck_untyped_defs = True")
277+
278+
# Update settings for workspace.
279+
plugin.pylsp_settings(ws._config)
280+
281+
# Test document to make sure it uses .config/mypy.ini configuration.
282+
doc = Document(DOC_URI, ws, DOC_SOURCE)
283+
diags = plugin.pylsp_lint(ws._config, ws, doc, is_saved=False)
284+
assert len(diags) == 1
285+
diag = diags[0]
286+
assert diag["message"] == DOC_ERR_MSG

0 commit comments

Comments
 (0)