Skip to content

Commit

Permalink
pyproject.toml support (#893)
Browse files Browse the repository at this point in the history
* pyproject.toml support

* small refactoring

* add error handling

* fix document and add CHANGELOG entry

* add test for pyproject.toml as config file

* add install_requires on setup.cfg

* fix test function

* import toml conditionally

* raise import error
  • Loading branch information
hirosassa authored Mar 12, 2021
1 parent a53e340 commit 664e7c0
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
a custom number of blank lines between top-level imports and variable
definitions.
- Ignore end of line `# copybara:` directives when checking line length.
- Look at the 'pyproject.toml' file to see if it contains style information for
YAPF.
### Changed
- Do not scan exlcuded directories. Prior versions would scan an exluded
folder then exclude its contents on a file by file basis. Preventing the
Expand Down
19 changes: 11 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ Options::
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg file located in the same
directory as the source or one of its parent
directories (for stdin, the current directory is
used).
.style.yapf or setup.cfg or pyproject.toml file
located in the same directory as the source or one of
its parent directories (for stdin, the current
directory is used).
--style-help show style settings and exit; this output can be saved
to .style.yapf to make your settings permanent
--no-local-style don't search for local style definition
Expand Down Expand Up @@ -198,7 +198,9 @@ YAPF will search for the formatting style in the following manner:
directory or one of its parent directories.
3. In the ``[yapf]`` section of a ``setup.cfg`` file in either the current
directory or one of its parent directories.
4. In the ``[style]`` section of a ``~/.config/yapf/style`` file in your home
4. In the ``[tool.yapf]`` section of a ``pyproject.toml`` file in either the current
directory or one of its parent directories.
5. In the ``[style]`` section of a ``~/.config/yapf/style`` file in your home
directory.

If none of those files are found, the default style is used (PEP8).
Expand Down Expand Up @@ -351,9 +353,10 @@ Options::
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg file located in one of the
parent directories of the source file (or current
directory for stdin)
.style.yapf or setup.cfg or pyproject.toml file
located in the same directory as the source or one of
its parent directories (for stdin, the current
directory is used).
--binary BINARY location of binary to use for yapf

Knobs
Expand Down
4 changes: 2 additions & 2 deletions yapf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,10 @@ def _BuildParser():
action='store',
help=('specify formatting style: either a style name (for example "pep8" '
'or "google"), or the name of a file with style settings. The '
'default is pep8 unless a %s or %s file located in the same '
'default is pep8 unless a %s or %s or %s file located in the same '
'directory as the source or one of its parent directories '
'(for stdin, the current directory is used).' %
(style.LOCAL_STYLE, style.SETUP_CONFIG)))
(style.LOCAL_STYLE, style.SETUP_CONFIG, style.PYPROJECT_TOML)))
parser.add_argument(
'--style-help',
action='store_true',
Expand Down
22 changes: 21 additions & 1 deletion yapf/yapflib/file_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def GetExcludePatternsForDir(dirname):
def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE):
"""Return default style name for a given directory.
Looks for .style.yapf or setup.cfg in the parent directories.
Looks for .style.yapf or setup.cfg or pyproject.toml in the parent directories.
Arguments:
dirname: (unicode) The name of the directory.
Expand Down Expand Up @@ -97,6 +97,26 @@ def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE):
if config.has_section('yapf'):
return config_file

# See if we have a pyproject.toml file with a '[tool.yapf]' section.
config_file = os.path.join(dirname, style.PYPROJECT_TOML)
try:
fd = open(config_file)
except IOError:
pass # It's okay if it's not there.
else:
with fd:
try:
import toml
except ImportError:
raise errors.YapfError(
"toml package is needed for using pyproject.toml as a configuration file"
)

pyproject_toml = toml.load(config_file)
style_dict = pyproject_toml.get('tool', {}).get('yapf', None)
if style_dict is not None:
return config_file

if (not dirname or not os.path.basename(dirname) or
dirname == os.path.abspath(os.path.sep)):
break
Expand Down
35 changes: 30 additions & 5 deletions yapf/yapflib/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,19 +742,40 @@ def _CreateConfigParserFromConfigFile(config_filename):
'"{0}" is not a valid style or file path'.format(config_filename))
with open(config_filename) as style_file:
config = py3compat.ConfigParser()
if config_filename.endswith(PYPROJECT_TOML):
try:
import toml
except ImportError:
raise errors.YapfError(
"toml package is needed for using pyproject.toml as a configuration file"
)

pyproject_toml = toml.load(style_file)
style_dict = pyproject_toml.get("tool", {}).get("yapf", None)
if style_dict is None:
raise StyleConfigError(
'Unable to find section [tool.yapf] in {0}'.format(config_filename))
config.add_section('style')
for k, v in style_dict.items():
config.set('style', k, str(v))
return config

config.read_file(style_file)
if config_filename.endswith(SETUP_CONFIG):
if not config.has_section('yapf'):
raise StyleConfigError(
'Unable to find section [yapf] in {0}'.format(config_filename))
elif config_filename.endswith(LOCAL_STYLE):
if not config.has_section('style'):
raise StyleConfigError(
'Unable to find section [style] in {0}'.format(config_filename))
else:
return config

if config_filename.endswith(LOCAL_STYLE):
if not config.has_section('style'):
raise StyleConfigError(
'Unable to find section [style] in {0}'.format(config_filename))
return config

if not config.has_section('style'):
raise StyleConfigError(
'Unable to find section [style] in {0}'.format(config_filename))
return config


Expand Down Expand Up @@ -817,6 +838,10 @@ def _CreateStyleFromConfigParser(config):
# specified in the '[yapf]' section.
SETUP_CONFIG = 'setup.cfg'

# Style definition by local pyproject.toml file. Style should be specified
# in the '[tool.yapf]' section.
PYPROJECT_TOML = 'pyproject.toml'

# TODO(eliben): For now we're preserving the global presence of a style dict.
# Refactor this so that the style is passed around through yapf rather than
# being global.
Expand Down
15 changes: 15 additions & 0 deletions yapftests/file_resources_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ def test_setup_config(self):
self.assertEqual(setup_config,
file_resources.GetDefaultStyleForDir(test_dir))

def test_pyproject_toml(self):
# An empty pyproject.toml file should not be used
pyproject_toml = os.path.join(self.test_tmpdir, 'pyproject.toml')
open(pyproject_toml, 'w').close()

test_dir = os.path.join(self.test_tmpdir, 'dir1')
style_name = file_resources.GetDefaultStyleForDir(test_dir)
self.assertEqual(style_name, 'pep8')

# One with a '[tool.yapf]' section should be used
with open(pyproject_toml, 'w') as f:
f.write('[tool.yapf]\n')
self.assertEqual(pyproject_toml,
file_resources.GetDefaultStyleForDir(test_dir))

def test_local_style_at_root(self):
# Test behavior of files located on the root, and under root.
rootdir = os.path.abspath(os.path.sep)
Expand Down
21 changes: 21 additions & 0 deletions yapftests/style_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.
"""Tests for yapf.style."""

import os
import shutil
import tempfile
import textwrap
Expand Down Expand Up @@ -226,6 +227,26 @@ def testErrorUnknownStyleOption(self):
'Unknown style option'):
style.CreateStyleFromConfig(filepath)

def testPyprojectTomlNoYapfSection(self):
filepath = os.path.join(self.test_tmpdir, 'pyproject.toml')
_ = open(filepath, 'w')
with self.assertRaisesRegexp(style.StyleConfigError,
'Unable to find section'):
style.CreateStyleFromConfig(filepath)

def testPyprojectTomlParseYapfSection(self):
cfg = textwrap.dedent(u'''\
[tool.yapf]
based_on_style = "pep8"
continuation_indent_width = 40
''')
filepath = os.path.join(self.test_tmpdir, 'pyproject.toml')
with open(filepath, 'w') as f:
f.write(cfg)
cfg = style.CreateStyleFromConfig(filepath)
self.assertTrue(_LooksLikePEP8Style(cfg))
self.assertEqual(cfg['CONTINUATION_INDENT_WIDTH'], 40)


class StyleFromDict(unittest.TestCase):

Expand Down

0 comments on commit 664e7c0

Please sign in to comment.