WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
15d1375
feat(config): Support `--config` in pyproject.toml
Shivansh-007 Oct 2, 2021
4911d68
Add changelog entry
Shivansh-007 Oct 7, 2021
9b8bfcf
Just overwrite the configuration, if `config` found
Shivansh-007 Oct 8, 2021
2d9d11c
Treat `--config` and `pyproject.toml` in the same way
Shivansh-007 Oct 8, 2021
e1e2ce9
Make config path relative to original TOML's location
Shivansh-007 Oct 9, 2021
7097049
Chain black configurations using recursion
Shivansh-007 Oct 15, 2021
e082f38
Separate test for checking black replaces config with linked one
Shivansh-007 Oct 21, 2021
55f0278
Make the root config values higher level than the linked configs
Shivansh-007 Nov 16, 2021
8b0eff7
Document feature behaviour
Shivansh-007 Nov 16, 2021
9898473
Move all TOML configs to a separate data dir
Shivansh-007 Jan 14, 2022
aa14650
Handle recursion error when configs mutually include each other
Shivansh-007 Jan 14, 2022
f22622c
Improve docs wording
Shivansh-007 Jan 14, 2022
c5f303c
Merge remote-tracking branch 'upstream/main' into feat/pyproject-config
Shivansh-007 Jan 27, 2022
87f1210
Apply felix's suggestions
Shivansh-007 Jan 28, 2022
e3a25a3
Merge remote-tracking branch 'upstream/main' into feat/pyproject-config
Shivansh-007 Jan 28, 2022
3d3403d
Merge branch 'feat/pyproject-config' of https://github.com/Shivansh-0…
Shivansh-007 Jan 28, 2022
7ed5e3f
Merge branch 'main' into feat/pyproject-config
JelleZijlstra Jan 29, 2022
cb36ae6
Reword docs and changelog
Shivansh-007 Jan 29, 2022
cb5846e
Merge branch 'feat/pyproject-config' of https://github.com/Shivansh-0…
Shivansh-007 Jan 29, 2022
f4d6a50
Merge remote-tracking branch 'upstream/main' into feat/pyproject-config
Shivansh-007 Mar 11, 2022
d9776b8
Fix merge
Shivansh-007 Mar 11, 2022
e8b70a0
Merge branch 'main' into feat/pyproject-config
JelleZijlstra Apr 5, 2022
4f3b87c
Merge branch 'main' into feat/pyproject-config
JelleZijlstra Dec 18, 2022
c98f8b3
isort
JelleZijlstra Dec 18, 2022
eb627aa
Merge branch 'main' into feat/pyproject-config
JelleZijlstra Dec 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
.venv
.coverage
.coverage.*
_build
.DS_Store
.vscode
docs/_static/pypi.svg
.tox
__pycache__

# Packaging artifacts
Expand All @@ -21,6 +18,13 @@ src/_black_version.py

.dmypy.json
*.swp
.hypothesis/
venv/
.ipynb_checkpoints/

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.hypothesis/
.pytest_cache/
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ and the first release covered by our new stability policy.
- Enable Python 3.10+ by default, without any extra need to specify
`--target-version=py310`. (#2758)
- Make passing `SRC` or `--code` mandatory and mutually exclusive (#2804)
- Allow specifying `config` in config files (#2525)

### Output

Expand Down
34 changes: 34 additions & 0 deletions docs/usage_and_configuration/the_basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,40 @@ project.
"No". _Black_ is all about sensible defaults. Applying those defaults will have your
code in compliance with many other _Black_ formatted projects.

### Linked Configuration feature

You can use the `config = ` field to include configuration options from another
configuration file. Values set in the original configuration file have precedence over
those in the included file.

Suppose `pyproject.toml` contains:

```{code-block} toml
---
lineno-start: 1
emphasize-lines: 2,3
---
[tools.black]
config = "linked_config.toml"
color = true
```

And `linked_config.toml` contains:

```{code-block} toml
---
lineno-start: 1
emphasize-lines: 2
---
[tools.black]
color = false
line-length = 88
target-version = ['py36', 'py37', 'py38']
```

The resulting value for `color` would be `true` as specified in the first config
(`pyproject.toml`).

### What on Earth is a `pyproject.toml` file?

[PEP 518](https://www.python.org/dev/peps/pep-0518/) defines `pyproject.toml` as a
Expand Down
44 changes: 30 additions & 14 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,28 +127,44 @@ def read_pyproject_toml(

if not config:
return None

inner_config = config.get("config") or (
ctx.default_map.get("config") if ctx.default_map else None
)
if inner_config:
inner_config_path = (Path(value).parent / inner_config).resolve()

try:
read_pyproject_toml(ctx, param, str(inner_config_path))
except RecursionError as e:
raise click.FileError(
filename=value, hint=f"Error reading configuration file: {e}"
) from None

del config["config"]

if ctx.default_map:
ctx.default_map.update(config)
config = ctx.default_map.copy()
else:
# Sanitize the values to be Click friendly. For more information please see:
# https://github.com/psf/black/issues/1458
# https://github.com/pallets/click/issues/1567
config = {
k: str(v) if not isinstance(v, (list, dict)) else v
for k, v in config.items()
}
if ctx.default_map:
config.update(ctx.default_map)

# Sanitize the values to be Click friendly. For more information please see:
# https://github.com/psf/black/issues/1458
# https://github.com/pallets/click/issues/1567
default_map: Dict[str, Any] = {
k: str(v) if not isinstance(v, (list, dict)) else v for k, v in config.items()
}

target_version = config.get("target_version")
target_version = default_map.get("target_version")
if target_version is not None and not isinstance(target_version, list):
raise click.BadOptionUsage(
"target-version", "Config key target-version must be a list"
)

default_map: Dict[str, Any] = {}
if ctx.default_map:
default_map.update(ctx.default_map)
default_map.update(config)

ctx.default_map = default_map
return value
return str(value)


def target_version_option_callback(
Expand Down
9 changes: 9 additions & 0 deletions tests/data/toml_configs/_black_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tool.black]
verbose = 1
--check = "no"
diff = "y"
color = true
line-length = 88
target-version = ["py36", "py37", "py38"]
exclude='\.pyi?$'
include='\.py?$'
File renamed without changes.
2 changes: 2 additions & 0 deletions tests/data/toml_configs/invalid_test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.black]
config = "tests/bazqux.toml"
2 changes: 2 additions & 0 deletions tests/data/toml_configs/recursion_1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.black]
config = "recursion_2.toml"
2 changes: 2 additions & 0 deletions tests/data/toml_configs/recursion_2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.black]
config = "recursion_1.toml"
File renamed without changes.
4 changes: 4 additions & 0 deletions tests/data/toml_configs/test_replace.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[tool.black]
config = "_black_config.toml"
verbose = 0
color = false
43 changes: 36 additions & 7 deletions tests/test_black.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
PROJECT_ROOT,
PY36_VERSIONS,
THIS_DIR,
TOML_CONFIG_DIR,
BlackBaseTestCase,
assert_format,
change_directory,
Expand Down Expand Up @@ -123,7 +124,7 @@ def invokeBlack(
) -> None:
runner = BlackRunner()
if ignore_config:
args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
args = ["--verbose", "--config", str(TOML_CONFIG_DIR / "empty.toml"), *args]
result = runner.invoke(black.main, args, catch_exceptions=False)
assert result.stdout_bytes is not None
assert result.stderr_bytes is not None
Expand Down Expand Up @@ -175,7 +176,7 @@ def test_piping_diff(self) -> None:
)
source, _ = read_data("expression.py")
expected, _ = read_data("expression.diff")
config = THIS_DIR / "data" / "empty_pyproject.toml"
config = TOML_CONFIG_DIR / "empty_pyproject.toml"
args = [
"-",
"--fast",
Expand All @@ -193,7 +194,7 @@ def test_piping_diff(self) -> None:

def test_piping_diff_with_color(self) -> None:
source, _ = read_data("expression.py")
config = THIS_DIR / "data" / "empty_pyproject.toml"
config = TOML_CONFIG_DIR / "empty_pyproject.toml"
args = [
"-",
"--fast",
Expand Down Expand Up @@ -252,7 +253,7 @@ def test_expression_ff(self) -> None:

def test_expression_diff(self) -> None:
source, _ = read_data("expression.py")
config = THIS_DIR / "data" / "empty_pyproject.toml"
config = TOML_CONFIG_DIR / "empty_pyproject.toml"
expected, _ = read_data("expression.diff")
tmp_file = Path(black.dump_to_file(source))
diff_header = re.compile(
Expand All @@ -279,7 +280,7 @@ def test_expression_diff(self) -> None:

def test_expression_diff_with_color(self) -> None:
source, _ = read_data("expression.py")
config = THIS_DIR / "data" / "empty_pyproject.toml"
config = TOML_CONFIG_DIR / "empty_pyproject.toml"
expected, _ = read_data("expression.diff")
tmp_file = Path(black.dump_to_file(source))
try:
Expand Down Expand Up @@ -1283,7 +1284,7 @@ def test_invalid_config_return_code(self) -> None:
tmp_file.unlink()

def test_parse_pyproject_toml(self) -> None:
test_toml_file = THIS_DIR / "test.toml"
test_toml_file = TOML_CONFIG_DIR / "test.toml"
config = black.parse_pyproject_toml(str(test_toml_file))
self.assertEqual(config["verbose"], 1)
self.assertEqual(config["check"], "no")
Expand All @@ -1296,7 +1297,7 @@ def test_parse_pyproject_toml(self) -> None:
self.assertEqual(config["include"], r"\.py?$")

def test_read_pyproject_toml(self) -> None:
test_toml_file = THIS_DIR / "test.toml"
test_toml_file = TOML_CONFIG_DIR / "test.toml"
fake_ctx = FakeContext()
black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
config = fake_ctx.default_map
Expand All @@ -1309,6 +1310,34 @@ def test_read_pyproject_toml(self) -> None:
self.assertEqual(config["exclude"], r"\.pyi?$")
self.assertEqual(config["include"], r"\.py?$")

def test_black_replace_config(self) -> None:
test_toml_file = TOML_CONFIG_DIR / "test_replace.toml"
fake_ctx = FakeContext()
black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
config = fake_ctx.default_map
# `0` in `test_replace.toml` and `1` in `_black_config.toml`.
# Should be `0` as the root config is on a higher level than linked configs
self.assertEqual(config["verbose"], "0")
self.assertEqual(config["check"], "no")
self.assertEqual(config["diff"], "y")
self.assertEqual(config["color"], "False")
self.assertEqual(config["line_length"], "88")
self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
self.assertEqual(config["exclude"], r"\.pyi?$")
self.assertEqual(config["include"], r"\.py?$")

def test_invalid_black_config(self) -> None:
test_toml_file = TOML_CONFIG_DIR / "invalid_test.toml"
fake_ctx = FakeContext()
with self.assertRaises(click.exceptions.FileError):
black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))

def test_recursion_black_config(self) -> None:
test_toml_file = TOML_CONFIG_DIR / "recursion_1.toml"
fake_ctx = FakeContext()
with self.assertRaises(click.exceptions.FileError):
black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))

@pytest.mark.incompatible_with_mypyc
def test_find_project_root(self) -> None:
with TemporaryDirectory() as workspace:
Expand Down
1 change: 1 addition & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

THIS_DIR = Path(__file__).parent
DATA_DIR = THIS_DIR / "data"
TOML_CONFIG_DIR = DATA_DIR / "toml_configs"
PROJECT_ROOT = THIS_DIR.parent
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
DETERMINISTIC_HEADER = "[Deterministic header]"
Expand Down