Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 8d8b319

Browse files
authored
Enable full toml configuration and pyproject.toml (#534)
* fix missing inline_comment_prefixes * implement TomlParser * add pyproject.toml support * add tool.pydocstyle section support * implement variable config_name for tests * run all env tests for tox.ini and pyproject.toml * exclude ini-specific tests for toml * add toml dependency * support [] lists in toml config * add tests for [] lists in toml configs * nitpicks & code quality improvements * nitpicks & code quality improvements 2 * only consider tool.* sections in toml files * add file extension in test_config_path * make toml an optional dependency * document the toml configuration support * Update release_notes.rst
1 parent 837c0c2 commit 8d8b319

File tree

6 files changed

+242
-18
lines changed

6 files changed

+242
-18
lines changed

docs/release_notes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Release Notes
44
**pydocstyle** version numbers follow the
55
`Semantic Versioning <http://semver.org/>`_ specification.
66

7+
Current Development Version
8+
---------------------------
9+
10+
New Features
11+
12+
* Enable full toml configuration and pyproject.toml (#534).
713

814
6.0.0 - March 18th, 2021
915
---------------------------

docs/snippets/config.rst

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
``pydocstyle`` supports *ini*-like configuration files.
2-
In order for ``pydocstyle`` to use it, it must be named one of the following
3-
options, and have a ``[pydocstyle]`` section.
1+
``pydocstyle`` supports *ini*-like and *toml* configuration files.
2+
In order for ``pydocstyle`` to use a configuration file automatically, it must
3+
be named one of the following options.
44

55
* ``setup.cfg``
66
* ``tox.ini``
77
* ``.pydocstyle``
88
* ``.pydocstyle.ini``
99
* ``.pydocstylerc``
1010
* ``.pydocstylerc.ini``
11+
* ``pyproject.toml``
1112

1213
When searching for a configuration file, ``pydocstyle`` looks for one of the
13-
file specified above *in that exact order*. If a configuration file was not
14-
found, it keeps looking for one up the directory tree until one is found or
15-
uses the default configuration.
14+
file specified above *in that exact order*. *ini*-like configuration files must
15+
have a ``[pydocstyle]`` section while *toml* configuration files must have a
16+
``[tool.pydocstyle]`` section. If a configuration file was not found,
17+
``pydocstyle`` keeps looking for one up the directory tree until one is found
18+
or uses the default configuration.
19+
20+
.. note::
21+
22+
*toml* configuration file support is only enabled if the ``toml`` python
23+
package is installed. You can ensure that this is the case by installing
24+
the ``pydocstyle[toml]`` optional dependency.
1625

1726
.. note::
1827

requirements/runtime.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
snowballstemmer==1.2.1
2+
toml==0.10.2

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
requirements = [
88
'snowballstemmer',
99
]
10+
extra_requirements = {
11+
'toml': ['toml'],
12+
}
1013

1114

1215
setup(
@@ -36,6 +39,7 @@
3639
package_dir={'': 'src'},
3740
package_data={'pydocstyle': ['data/*.txt']},
3841
install_requires=requirements,
42+
extras_require=extra_requirements,
3943
entry_points={
4044
'console_scripts': [
4145
'pydocstyle = pydocstyle.cli:main',

src/pydocstyle/config.py

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@
22

33
import copy
44
import itertools
5+
import operator
56
import os
67
from collections import namedtuple
78
from collections.abc import Set
8-
from configparser import RawConfigParser
9+
from configparser import NoOptionError, NoSectionError, RawConfigParser
10+
from functools import reduce
911
from re import compile as re
1012

1113
from .utils import __version__, log
1214
from .violations import ErrorRegistry, conventions
1315

16+
try:
17+
import toml
18+
except ImportError: # pragma: no cover
19+
toml = None # type: ignore
20+
1421

1522
def check_initialized(method):
1623
"""Check that the configuration object was initialized."""
@@ -23,6 +30,109 @@ def _decorator(self, *args, **kwargs):
2330
return _decorator
2431

2532

33+
class TomlParser:
34+
"""ConfigParser that partially mimics RawConfigParser but for toml files.
35+
36+
See RawConfigParser for more info. Also, please note that not all
37+
RawConfigParser functionality is implemented, but only the subset that is
38+
currently used by pydocstyle.
39+
"""
40+
41+
def __init__(self):
42+
"""Create a toml parser."""
43+
self._config = {}
44+
45+
def read(self, filenames, encoding=None):
46+
"""Read and parse a filename or an iterable of filenames.
47+
48+
Files that cannot be opened are silently ignored; this is
49+
designed so that you can specify an iterable of potential
50+
configuration file locations (e.g. current directory, user's
51+
home directory, systemwide directory), and all existing
52+
configuration files in the iterable will be read. A single
53+
filename may also be given.
54+
55+
Return list of successfully read files.
56+
"""
57+
if isinstance(filenames, (str, bytes, os.PathLike)):
58+
filenames = [filenames]
59+
read_ok = []
60+
for filename in filenames:
61+
try:
62+
with open(filename, encoding=encoding) as fp:
63+
if not toml:
64+
log.warning(
65+
"The %s configuration file was ignored, "
66+
"because the `toml` package is not installed.",
67+
filename,
68+
)
69+
continue
70+
self._config.update(toml.load(fp))
71+
except OSError:
72+
continue
73+
if isinstance(filename, os.PathLike):
74+
filename = os.fspath(filename)
75+
read_ok.append(filename)
76+
return read_ok
77+
78+
def _get_section(self, section, allow_none=False):
79+
try:
80+
current = reduce(
81+
operator.getitem,
82+
section.split('.'),
83+
self._config['tool'],
84+
)
85+
except KeyError:
86+
current = None
87+
88+
if isinstance(current, dict):
89+
return current
90+
elif allow_none:
91+
return None
92+
else:
93+
raise NoSectionError(section)
94+
95+
def has_section(self, section):
96+
"""Indicate whether the named section is present in the configuration."""
97+
return self._get_section(section, allow_none=True) is not None
98+
99+
def options(self, section):
100+
"""Return a list of option names for the given section name."""
101+
current = self._get_section(section)
102+
return list(current.keys())
103+
104+
def get(self, section, option, *, _conv=None):
105+
"""Get an option value for a given section."""
106+
d = self._get_section(section)
107+
option = option.lower()
108+
try:
109+
value = d[option]
110+
except KeyError:
111+
raise NoOptionError(option, section)
112+
113+
if isinstance(value, dict):
114+
raise TypeError(
115+
f"Expected {section}.{option} to be an option, not a section."
116+
)
117+
118+
# toml should convert types automatically
119+
# don't manually convert, just check, that the type is correct
120+
if _conv is not None and not isinstance(value, _conv):
121+
raise TypeError(
122+
f"The type of {section}.{option} should be {_conv}"
123+
)
124+
125+
return value
126+
127+
def getboolean(self, section, option):
128+
"""Get a boolean option value for a given section."""
129+
return self.get(section, option, _conv=bool)
130+
131+
def getint(self, section, option):
132+
"""Get an integer option value for a given section."""
133+
return self.get(section, option, _conv=int)
134+
135+
26136
class ConfigurationParser:
27137
"""Responsible for parsing configuration from files and CLI.
28138
@@ -85,6 +195,7 @@ class ConfigurationParser:
85195
'.pydocstyle.ini',
86196
'.pydocstylerc',
87197
'.pydocstylerc.ini',
198+
'pyproject.toml',
88199
# The following is deprecated, but remains for backwards compatibility.
89200
'.pep257',
90201
)
@@ -310,7 +421,10 @@ def _read_configuration_file(self, path):
310421
Returns (options, should_inherit).
311422
312423
"""
313-
parser = RawConfigParser(inline_comment_prefixes=('#', ';'))
424+
if path.endswith('.toml'):
425+
parser = TomlParser()
426+
else:
427+
parser = RawConfigParser(inline_comment_prefixes=('#', ';'))
314428
options = None
315429
should_inherit = True
316430

@@ -433,7 +547,10 @@ def _get_config_file_in_folder(cls, path):
433547
path = os.path.dirname(path)
434548

435549
for fn in cls.PROJECT_CONFIG_FILES:
436-
config = RawConfigParser()
550+
if fn.endswith('.toml'):
551+
config = TomlParser()
552+
else:
553+
config = RawConfigParser(inline_comment_prefixes=('#', ';'))
437554
full_path = os.path.join(path, fn)
438555
if config.read(full_path) and cls._get_section_name(config):
439556
return full_path
@@ -552,8 +669,10 @@ def _get_set(value_str):
552669
file.
553670
554671
"""
672+
if isinstance(value_str, str):
673+
value_str = value_str.split(",")
555674
return cls._expand_error_codes(
556-
{x.strip() for x in value_str.split(",")} - {""}
675+
{x.strip() for x in value_str} - {""}
557676
)
558677

559678
for opt in optional_set_options:

0 commit comments

Comments
 (0)