Skip to content

Commit

Permalink
config: Handle rich (non-string) types in TOML file
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dbaty authored and PCManticore committed May 16, 2020
1 parent ffb354a commit 489508c
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,5 @@ contributors:
* Yang Yang: contributor

* Andrew J. Simmons (anjsimmo): contributor

* Damien Baty: contributor
9 changes: 9 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ Release date: TBA

Close #3604

* 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::

enable = [
"use-symbolic-message-instead",
"useless-suppression",
]

Close #3538


What's New in Pylint 2.5.2?
===========================
Expand Down
11 changes: 10 additions & 1 deletion pylint/config/option_manager_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def read_config_file(self, config_file=None, verbose=None):
raise OSError("The config file {:s} doesn't exist!".format(config_file))

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

if config_file.endswith(".toml"):
Expand All @@ -274,6 +274,15 @@ def read_config_file(self, config_file=None, verbose=None):
pass
else:
for section, values in sections_values.items():
# TOML has rich types, convert values to
# strings as ConfigParser expects.
for option, value in values.items():
if isinstance(value, bool):
values[option] = "yes" if value else "no"
elif isinstance(value, int):
values[option] = str(value)
elif isinstance(value, list):
values[option] = ",".join(value)
parser._sections[section.upper()] = values
else:
# Use this encoding in order to strip the BOM marker, if any.
Expand Down
102 changes: 79 additions & 23 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,93 @@
# pylint: disable=missing-module-docstring, missing-function-docstring, protected-access
import unittest.mock

import pylint.lint


def test_can_read_toml(tmp_path):
config_file = tmp_path / "pyproject.toml"
def check_configuration_file_reader(config_file):
"""Initialize pylint with the given configuration file and check that
what we initialized the linter with what was expected.
"""
args = ["--rcfile", str(config_file), __file__]
# If we used `pytest.raises(SystemExit)`, the `runner` variable
# would not be accessible outside the `with` block.
with unittest.mock.patch("sys.exit") as mocked_exit:
# Do not actually run checks, that could be slow. Do not mock
# `Pylinter.check`: it calls `Pylinter.initialize` which is
# needed to properly set up messages inclusion/exclusion
# in `_msg_states`, used by `is_message_enabled`.
with unittest.mock.patch("pylint.lint.pylinter.check_parallel"):
runner = pylint.lint.Run(args)

# "logging-not-lazy" and "logging-format-interpolation"
expected_disabled = {"W1201", "W1202"}
for msgid in expected_disabled:
assert not runner.linter.is_message_enabled(msgid)
assert runner.linter.config.jobs == 10
assert runner.linter.config.reports

mocked_exit.assert_called_once_with(0)
return runner


def test_can_read_ini(tmp_path):
# Check that we can read the "regular" INI .pylintrc file
config_file = tmp_path / ".pylintrc"
config_file.write_text(
"[tool.pylint.'messages control']\n"
"disable='all'\n"
"enable='missing-module-docstring'\n"
"jobs=10\n"
"""
[messages control]
disable = logging-not-lazy,logging-format-interpolation
jobs = 10
reports = yes
"""
)
linter = pylint.lint.PyLinter()
linter.global_set_option = unittest.mock.MagicMock()
linter.read_config_file(str(config_file))

assert linter.global_set_option.called_with("disable", "all")
assert linter.global_set_option.called_with("enable", "missing-module-docstring")
assert linter.global_set_option.called_with("jobs", 10)
check_configuration_file_reader(config_file)


def test_can_read_setup_cfg(tmp_path):
# Check that we can read a setup.cfg (which is an INI file where
# section names are prefixed with "pylint."
config_file = tmp_path / "setup.cfg"
config_file.write_text(
"[pylint.messages control]\n"
"disable=all\n"
"enable=missing-module-docstring\n"
"jobs=10\n"
"""
[pylint.messages control]
disable = logging-not-lazy,logging-format-interpolation
jobs = 10
reports = yes
"""
)
linter = pylint.lint.PyLinter()
linter.global_set_option = unittest.mock.MagicMock()
linter.read_config_file(str(config_file))
check_configuration_file_reader(config_file)

assert linter.global_set_option.called_with("disable", "all")
assert linter.global_set_option.called_with("enable", "missing-module-docstring")
assert linter.global_set_option.called_with("jobs", 10)

def test_can_read_toml(tmp_path):
# Check that we can read a TOML file where lists and integers are
# expressed as strings.
config_file = tmp_path / "pyproject.toml"
config_file.write_text(
"""
[tool.pylint."messages control"]
disable = "logging-not-lazy,logging-format-interpolation"
jobs = "10"
reports = "yes"
"""
)
check_configuration_file_reader(config_file)


def test_can_read_toml_rich_types(tmp_path):
# Check that we can read a TOML file where lists, integers and
# booleans are expressed as such (and not as strings), using TOML
# type system.
config_file = tmp_path / "pyproject.toml"
config_file.write_text(
"""
[tool.pylint."messages control"]
disable = [
"logging-not-lazy",
"logging-format-interpolation",
]
jobs = 10
reports = true
"""
)
check_configuration_file_reader(config_file)

0 comments on commit 489508c

Please sign in to comment.