Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pyproject.toml support #893

Merged
merged 10 commits into from
Mar 12, 2021
Merged
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
16 changes: 15 additions & 1 deletion yapf/yapflib/file_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import os
import re

import toml
from lib2to3.pgen2 import tokenize

from yapf.yapflib import errors
Expand Down Expand Up @@ -67,7 +68,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 +98,19 @@ 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:
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
42 changes: 29 additions & 13 deletions yapf/yapflib/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import re
import textwrap

import toml
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[help wanted] I added external dependency because there's no toml loader in standard libraries. Is this OK?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to be seamless. So if someone doesn't have the toml package installed, then we just skip this support all together. Is it possible to import the toml conditionally and if it fails to import set a flag or something?

Copy link
Contributor Author

@hirosassa hirosassa Feb 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you! Fixed this in 58b9034 and 04b1111


from yapf.yapflib import errors
from yapf.yapflib import py3compat

Expand Down Expand Up @@ -742,20 +744,30 @@ 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()
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))
if config_filename.endswith(PYPROJECT_TOML):
pyproject_toml = toml.load(style_file)
style_dict = pyproject_toml.get("tool", {}).get("yapf", {})
config.add_section('style')
for k, v in style_dict.items():
config.set('style', k, str(v))
return config
else:
if not config.has_section('style'):
raise StyleConfigError(
'Unable to find section [style] in {0}'.format(config_filename))
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))
return config
elif 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
else:
if not config.has_section('style'):
raise StyleConfigError(
'Unable to find section [style] in {0}'.format(config_filename))
return config


def _CreateStyleFromConfigParser(config):
Expand Down Expand Up @@ -817,6 +829,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
setup_config = os.path.join(self.test_tmpdir, 'pyproject.toml')
open(setup_config, '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(setup_config, 'w') as f:
f.write('[tool.yapf]\n')
self.assertEqual(setup_config,
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