Skip to content

Commit ffb6006

Browse files
dbatyPCManticore
authored andcommitted
config: Handle rich (non-string) types in TOML file
Fixes #3538 Before that, we had to use strings in a TOML configuration file, like this: enable = "use-symbolic-message-instead,useless-suppression" jobs = "10" suggestion-mode = "no" TOML supports rich types like list, integer and boolean. They make for a more readable and less error-prone file. We can now express the same configuration like this: enable = [ "use-symbolic-message-instead", "useless-suppression", ] jobs = 10 suggestion-mode = false
1 parent c4c96ec commit ffb6006

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

CONTRIBUTORS.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,5 @@ contributors:
381381
* Yang Yang: contributor
382382

383383
* Andrew J. Simmons (anjsimmo): contributor
384+
385+
* Damien Baty: contributor

ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ Release date: TBA
2323

2424
Close #3547
2525

26+
* In a TOML configuration file, it's now possible to use rich (non-string) types, such as list, integer or boolean instead of strings. For example, one can now define a *list* of message identifiers to enable like this::
27+
28+
enable = [
29+
"use-symbolic-message-instead",
30+
"useless-suppression",
31+
]
32+
33+
Close #3538
34+
2635

2736
What's New in Pylint 2.5.2?
2837
===========================

pylint/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ def read_config_file(self, config_file=None, verbose=None):
741741
raise OSError("The config file {:s} doesn't exist!".format(config_file))
742742

743743
use_config_file = config_file and os.path.exists(config_file)
744-
if use_config_file:
744+
if use_config_file: # pylint: disable=too-many-nested-blocks
745745
parser = self.cfgfile_parser
746746

747747
if config_file.endswith(".toml"):
@@ -754,6 +754,15 @@ def read_config_file(self, config_file=None, verbose=None):
754754
pass
755755
else:
756756
for section, values in sections_values.items():
757+
# TOML has rich types, convert values to
758+
# strings as ConfigParser expects.
759+
for option, value in values.items():
760+
if isinstance(value, bool):
761+
values[option] = "yes" if value else "no"
762+
elif isinstance(value, int):
763+
values[option] = str(value)
764+
elif isinstance(value, list):
765+
values[option] = ",".join(value)
757766
parser._sections[section.upper()] = values
758767
else:
759768
# Use this encoding in order to strip the BOM marker, if any.

tests/test_config.py

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,93 @@
1+
# pylint: disable=missing-module-docstring, missing-function-docstring, protected-access
12
import unittest.mock
23

34
import pylint.lint
45

56

6-
def test_can_read_toml(tmp_path):
7-
config_file = tmp_path / "pyproject.toml"
7+
def check_configuration_file_reader(config_file):
8+
"""Initialize pylint with the given configuration file and check that
9+
what we initialized the linter with what was expected.
10+
"""
11+
args = ["--rcfile", str(config_file), __file__]
12+
# If we used `pytest.raises(SystemExit)`, the `runner` variable
13+
# would not be accessible outside the `with` block.
14+
with unittest.mock.patch("sys.exit") as mocked_exit:
15+
# Do not actually run checks, that could be slow. Do not mock
16+
# `Pylinter.check`: it calls `Pylinter.initialize` which is
17+
# needed to properly set up messages inclusion/exclusion
18+
# in `_msg_states`, used by `is_message_enabled`.
19+
with unittest.mock.patch("pylint.lint.pylinter.check_parallel"):
20+
runner = pylint.lint.Run(args)
21+
22+
# "logging-not-lazy" and "logging-format-interpolation"
23+
expected_disabled = {"W1201", "W1202"}
24+
for msgid in expected_disabled:
25+
assert not runner.linter.is_message_enabled(msgid)
26+
assert runner.linter.config.jobs == 10
27+
assert runner.linter.config.reports
28+
29+
mocked_exit.assert_called_once_with(0)
30+
return runner
31+
32+
33+
def test_can_read_ini(tmp_path):
34+
# Check that we can read the "regular" INI .pylintrc file
35+
config_file = tmp_path / ".pylintrc"
836
config_file.write_text(
9-
"[tool.pylint.'messages control']\n"
10-
"disable='all'\n"
11-
"enable='missing-module-docstring'\n"
12-
"jobs=10\n"
37+
"""
38+
[messages control]
39+
disable = logging-not-lazy,logging-format-interpolation
40+
jobs = 10
41+
reports = yes
42+
"""
1343
)
14-
linter = pylint.lint.PyLinter()
15-
linter.global_set_option = unittest.mock.MagicMock()
16-
linter.read_config_file(str(config_file))
17-
18-
assert linter.global_set_option.called_with("disable", "all")
19-
assert linter.global_set_option.called_with("enable", "missing-module-docstring")
20-
assert linter.global_set_option.called_with("jobs", 10)
44+
check_configuration_file_reader(config_file)
2145

2246

2347
def test_can_read_setup_cfg(tmp_path):
48+
# Check that we can read a setup.cfg (which is an INI file where
49+
# section names are prefixed with "pylint."
2450
config_file = tmp_path / "setup.cfg"
2551
config_file.write_text(
26-
"[pylint.messages control]\n"
27-
"disable=all\n"
28-
"enable=missing-module-docstring\n"
29-
"jobs=10\n"
52+
"""
53+
[pylint.messages control]
54+
disable = logging-not-lazy,logging-format-interpolation
55+
jobs = 10
56+
reports = yes
57+
"""
3058
)
31-
linter = pylint.lint.PyLinter()
32-
linter.global_set_option = unittest.mock.MagicMock()
33-
linter.read_config_file(str(config_file))
59+
check_configuration_file_reader(config_file)
3460

35-
assert linter.global_set_option.called_with("disable", "all")
36-
assert linter.global_set_option.called_with("enable", "missing-module-docstring")
37-
assert linter.global_set_option.called_with("jobs", 10)
61+
62+
def test_can_read_toml(tmp_path):
63+
# Check that we can read a TOML file where lists and integers are
64+
# expressed as strings.
65+
config_file = tmp_path / "pyproject.toml"
66+
config_file.write_text(
67+
"""
68+
[tool.pylint."messages control"]
69+
disable = "logging-not-lazy,logging-format-interpolation"
70+
jobs = "10"
71+
reports = "yes"
72+
"""
73+
)
74+
check_configuration_file_reader(config_file)
75+
76+
77+
def test_can_read_toml_rich_types(tmp_path):
78+
# Check that we can read a TOML file where lists, integers and
79+
# booleans are expressed as such (and not as strings), using TOML
80+
# type system.
81+
config_file = tmp_path / "pyproject.toml"
82+
config_file.write_text(
83+
"""
84+
[tool.pylint."messages control"]
85+
disable = [
86+
"logging-not-lazy",
87+
"logging-format-interpolation",
88+
]
89+
jobs = 10
90+
reports = true
91+
"""
92+
)
93+
check_configuration_file_reader(config_file)

0 commit comments

Comments
 (0)