From 2623269dab4de9d47750b1a8af5ae44240e13cf6 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:50:45 -0800 Subject: [PATCH] Ignore pyproject.toml missing tool.black section (#4204) Fixes #2863 This is pretty desirable in a monorepo situation where you have configuration in the root since it will mean you don't have to reconfigure every project. The good news for backward compatibility is that `find_project_root` continues to stop at any git or hg root, so in all cases repo root coincides with a pyproject.toml missing tool.black, we'll continue to have the project root as before and end up using default config (i.e. we're unlikely to randomly start using the user config). The other thing we need to be a little careful about is that changing find_project_root logic affects what `exclude` is relative to. Since we only change in cases where there is no config, this only applies where users were using `exclude` via command line arg (and had pyproject.toml missing tool.black in a dir that was not repo root). Finally, for the few who could be affected, the fix is to put an empty `[tool.black]` in pyproject.toml --- CHANGES.md | 7 ++++++- docs/usage_and_configuration/the_basics.md | 9 +++++---- src/black/files.py | 13 ++++++++++--- tests/test_black.py | 18 ++++++++++++++++-- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4fd030d..a3ffba6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,7 +16,12 @@ ### Configuration - +- _Black_ now ignores `pyproject.toml` that is missing a `tool.black` section when + discovering project root and configuration. Since _Black_ continues to use version + control as an indicator of project root, this is expected to primarily change behavior + for users in a monorepo setup (desirably). If you wish to preserve previous behavior, + simply add an empty `[tool.black]` to the previously discovered `pyproject.toml` + (#4204) ### Packaging diff --git a/docs/usage_and_configuration/the_basics.md b/docs/usage_and_configuration/the_basics.md index dc9d9a6..61c5245 100644 --- a/docs/usage_and_configuration/the_basics.md +++ b/docs/usage_and_configuration/the_basics.md @@ -456,10 +456,11 @@ of tools like [Poetry](https://python-poetry.org/), ### Where _Black_ looks for the file -By default _Black_ looks for `pyproject.toml` starting from the common base directory of -all files and directories passed on the command line. If it's not there, it looks in -parent directories. It stops looking when it finds the file, or a `.git` directory, or a -`.hg` directory, or the root of the file system, whichever comes first. +By default _Black_ looks for `pyproject.toml` containing a `[tool.black]` section +starting from the common base directory of all files and directories passed on the +command line. If it's not there, it looks in parent directories. It stops looking when +it finds the file, or a `.git` directory, or a `.hg` directory, or the root of the file +system, whichever comes first. If you're formatting standard input, _Black_ will look for configuration starting from the current working directory. diff --git a/src/black/files.py b/src/black/files.py index 1eb8745..960f13e 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -42,6 +42,12 @@ import colorama # noqa: F401 +@lru_cache +def _load_toml(path: Union[Path, str]) -> Dict[str, Any]: + with open(path, "rb") as f: + return tomllib.load(f) + + @lru_cache def find_project_root( srcs: Sequence[str], stdin_filename: Optional[str] = None @@ -84,7 +90,9 @@ def find_project_root( return directory, ".hg directory" if (directory / "pyproject.toml").is_file(): - return directory, "pyproject.toml" + pyproject_toml = _load_toml(directory / "pyproject.toml") + if "black" in pyproject_toml.get("tool", {}): + return directory, "pyproject.toml" return directory, "file system root" @@ -117,8 +125,7 @@ def parse_pyproject_toml(path_config: str) -> Dict[str, Any]: If parsing fails, will raise a tomllib.TOMLDecodeError. """ - with open(path_config, "rb") as f: - pyproject_toml = tomllib.load(f) + pyproject_toml = _load_toml(path_config) config: Dict[str, Any] = pyproject_toml.get("tool", {}).get("black", {}) config = {k.replace("--", "").replace("-", "_"): v for k, v in config.items()} diff --git a/tests/test_black.py b/tests/test_black.py index 123ea0b..f876d36 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1668,9 +1668,9 @@ def test_find_project_root(self) -> None: src_dir.mkdir() root_pyproject = root / "pyproject.toml" - root_pyproject.touch() + root_pyproject.write_text("[tool.black]", encoding="utf-8") src_pyproject = src_dir / "pyproject.toml" - src_pyproject.touch() + src_pyproject.write_text("[tool.black]", encoding="utf-8") src_python = src_dir / "foo.py" src_python.touch() @@ -1693,6 +1693,20 @@ def test_find_project_root(self) -> None: (src_dir.resolve(), "pyproject.toml"), ) + src_sub = src_dir / "sub" + src_sub.mkdir() + + src_sub_pyproject = src_sub / "pyproject.toml" + src_sub_pyproject.touch() # empty + + src_sub_python = src_sub / "bar.py" + + # we skip src_sub_pyproject since it is missing the [tool.black] section + self.assertEqual( + black.find_project_root((src_sub_python,)), + (src_dir.resolve(), "pyproject.toml"), + ) + @patch( "black.files.find_user_pyproject_toml", )