Skip to content

Commit

Permalink
Merge c5f303c into b517dfb
Browse files Browse the repository at this point in the history
  • Loading branch information
Shivansh-007 committed Jan 27, 2022
2 parents b517dfb + c5f303c commit fcd266f
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 25 deletions.
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 @@ -43,6 +43,7 @@
- Work around bug that causes unstable formatting in some cases in the presence of the
magic trailing comma (#2807)
- Deprecate the `black-primer` tool (#2809)
- Allow specifying `config` in config files (#2525)

### Packaging

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

black_config = config.get("config") or (
ctx.default_map.get("config") if ctx.default_map else None
)
if black_config:
black_config_path = Path(Path(value).parent, black_config).resolve()

try:
read_pyproject_toml(ctx, param, str(black_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.
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

0 comments on commit fcd266f

Please sign in to comment.