Skip to content

Commit

Permalink
Merge pull request #759 from akaihola/drop-black-dependency
Browse files Browse the repository at this point in the history
Drop black dependency
  • Loading branch information
akaihola authored Jan 1, 2025
2 parents 6f5b4cf + 93aebe6 commit dc7cb4d
Show file tree
Hide file tree
Showing 18 changed files with 499 additions and 277 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Added
other formatters in the future. There's also a dummy ``none`` formatter plugin.
- ``--formatter=none`` now skips running Black. This is useful when you only want to run
Isort or Flynt_.
- Black_ is no longer installed by default. Use ``pip install 'darker[black]'`` to get
Black support.

Removed
-------
Expand Down
23 changes: 16 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ How?

To install or upgrade, use::

pip install --upgrade darker~=2.1.1
pip install --upgrade darker[black]~=2.1.1

Or, if you're using Conda_ for package management::

conda install -c conda-forge darker~=2.1.1 isort
conda install -c conda-forge darker~=2.1.1 black isort
conda update -c conda-forge darker

..
Expand All @@ -146,6 +146,8 @@ Or, if you're using Conda_ for package management::
specifier for Darker. See `Guarding against Black compatibility breakage`_ for more
information.

*New in version 3.0.0:* Black is no longer installed by default.

The ``darker <myfile.py>`` or ``darker <directory>`` command
reads the original file(s),
formats them using Black_,
Expand Down Expand Up @@ -478,7 +480,7 @@ PyCharm/IntelliJ IDEA

1. Install ``darker``::

$ pip install darker
$ pip install 'darker[black]'

2. Locate your ``darker`` installation folder.

Expand Down Expand Up @@ -540,7 +542,7 @@ Visual Studio Code

1. Install ``darker``::

$ pip install darker
$ pip install 'darker[black]'

2. Locate your ``darker`` installation folder.

Expand Down Expand Up @@ -683,16 +685,20 @@ other reformatter tools you use to known compatible versions, for example:
Using arguments
---------------

You can provide arguments, such as enabling isort, by specifying ``args``.
Note the inclusion of the isort Python package under ``additional_dependencies``:
You can provide arguments, such as disabling Darker or enabling isort,
by specifying ``args``.
Note the absence of Black and the inclusion of the isort Python package
under ``additional_dependencies``:

.. code-block:: yaml
- repo: https://github.com/akaihola/darker
rev: v2.1.1
hooks:
- id: darker
args: [--isort]
args:
- --formatter=none
- --isort
additional_dependencies:
- isort~=5.9
Expand Down Expand Up @@ -779,6 +785,9 @@ The ``lint:`` option.
Removed the ``lint:`` option and moved it into the GitHub action
of the Graylint_ package.

*New in version 3.0.0:*
Black is now explicitly installed when running the action.


Syntax highlighting
===================
Expand Down
2 changes: 1 addition & 1 deletion action/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True) # nosec

req = ["darker[color,isort]"]
req = ["darker[black,color,isort]"]
if VERSION:
if VERSION.startswith("@"):
req[0] = f"git+https://github.com/akaihola/darker{VERSION}#egg={req[0]}"
Expand Down
16 changes: 9 additions & 7 deletions action/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,23 +91,25 @@ def test_creates_virtualenv(tmp_path, main_patch):


@pytest.mark.kwparametrize(
dict(run_main_env={}, expect=["darker[color,isort]"]),
dict(run_main_env={}, expect=["darker[black,color,isort]"]),
dict(
run_main_env={"INPUT_VERSION": "1.5.0"}, expect=["darker[color,isort]==1.5.0"]
run_main_env={"INPUT_VERSION": "1.5.0"},
expect=["darker[black,color,isort]==1.5.0"],
),
dict(
run_main_env={"INPUT_VERSION": "@master"},
expect=[
"git+https://github.com/akaihola/darker@master#egg=darker[color,isort]"
"git+https://github.com/akaihola/darker"
"@master#egg=darker[black,color,isort]"
],
),
dict(
run_main_env={"INPUT_LINT": "dummy"},
expect=["darker[color,isort]"],
expect=["darker[black,color,isort]"],
),
dict(
run_main_env={"INPUT_LINT": "dummy,foobar"},
expect=["darker[color,isort]"],
expect=["darker[black,color,isort]"],
),
)
def test_installs_packages(tmp_path, main_patch, run_main_env, expect):
Expand Down Expand Up @@ -208,15 +210,15 @@ def test_error_if_pip_fails(tmp_path, capsys):
run_module("main")

assert main_patch.subprocess.run.call_args_list[-1] == call(
[ANY, "-m", "pip", "install", "darker[color,isort]"],
[ANY, "-m", "pip", "install", "darker[black,color,isort]"],
check=False,
stdout=PIPE,
stderr=STDOUT,
encoding="utf-8",
)
assert (
capsys.readouterr().out.splitlines()[-1]
== "::error::Failed to install darker[color,isort]."
== "::error::Failed to install darker[black,color,isort]."
)
main_patch.sys.exit.assert_called_once_with(42)

Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ package_dir =
packages = find:
install_requires =
# NOTE: remember to keep `constraints-oldest.txt` in sync with these
black>=22.3.0
darkgraylib~=2.1.0
toml>=0.10.0
typing_extensions>=4.0.1
Expand All @@ -52,6 +51,8 @@ console_scripts =
darker = darker.__main__:main_with_error_handling

[options.extras_require]
black =
black>=22.3.0
flynt =
flynt>=0.76
isort =
Expand Down
3 changes: 1 addition & 2 deletions src/darker/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from functools import partial
from typing import List, Optional, Tuple

from black import TargetVersion

import darkgraylib.command_line
from darker import help as hlp
from darker.config import (
Expand All @@ -15,6 +13,7 @@
DarkerConfig,
OutputMode,
)
from darker.configuration.target_version import TargetVersion
from darker.formatters import get_formatter_names
from darker.version import __version__
from darkgraylib.command_line import add_parser_argument
Expand Down
1 change: 1 addition & 0 deletions src/darker/configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Configuration and command line handling."""
19 changes: 19 additions & 0 deletions src/darker/configuration/target_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Data structures configuring Darker and formatter plugin behavior."""

from enum import Enum


class TargetVersion(Enum):
"""Python version numbers."""

PY33 = 3
PY34 = 4
PY35 = 5
PY36 = 6
PY37 = 7
PY38 = 8
PY39 = 9
PY310 = 10
PY311 = 11
PY312 = 12
PY313 = 13
153 changes: 111 additions & 42 deletions src/darker/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,9 @@

from __future__ import annotations

import inspect
from typing import TYPE_CHECKING, Collection

from black import (
DEFAULT_EXCLUDES,
DEFAULT_INCLUDES,
Report,
err,
find_user_pyproject_toml,
gen_python_files,
re_compile_maybe_verbose,
)
import re
from functools import lru_cache
from typing import TYPE_CHECKING, Collection, Iterable, Iterator, Pattern

from darkgraylib.files import find_project_root

Expand All @@ -25,22 +16,116 @@

def find_pyproject_toml(path_search_start: tuple[str, ...]) -> str | None:
"""Find the absolute filepath to a pyproject.toml if it exists"""

path_project_root = find_project_root(path_search_start)
path_pyproject_toml = path_project_root / "pyproject.toml"
if path_pyproject_toml.is_file():
return str(path_pyproject_toml)
return None


DEFAULT_EXCLUDE_RE = re.compile(
r"/(\.direnv"
r"|\.eggs"
r"|\.git"
r"|\.hg"
r"|\.ipynb_checkpoints"
r"|\.mypy_cache"
r"|\.nox"
r"|\.pytest_cache"
r"|\.ruff_cache"
r"|\.tox"
r"|\.svn"
r"|\.venv"
r"|\.vscode"
r"|__pypackages__"
r"|_build"
r"|buck-out"
r"|build"
r"|dist"
r"|venv)/"
)
DEFAULT_INCLUDE_RE = re.compile(r"(\.pyi?|\.ipynb)$")


@lru_cache
def _cached_resolve(path: Path) -> Path:
return path.resolve()


def _resolves_outside_root_or_cannot_stat(path: Path, root: Path) -> bool:
"""Return whether path is a symlink that points outside the root directory.
Also returns True if we failed to resolve the path.
This function has been adapted from Black 24.10.0.
"""
try:
path_user_pyproject_toml = find_user_pyproject_toml()
return (
str(path_user_pyproject_toml)
if path_user_pyproject_toml.is_file()
else None
)
except (PermissionError, RuntimeError) as e:
# We do not have access to the user-level config directory, so ignore it.
err(f"Ignoring user configuration directory due to {e!r}")
return None
resolved_path = _cached_resolve(path)
except OSError:
return True
try:
resolved_path.relative_to(root)
except ValueError:
return True
return False


def _path_is_excluded(
normalized_path: str,
pattern: Pattern[str] | None,
) -> bool:
"""Return whether the path is excluded by the pattern.
This function has been adapted from Black 24.10.0.
"""
match = pattern.search(normalized_path) if pattern else None
return bool(match and match.group(0))


def _gen_python_files(
paths: Iterable[Path],
root: Path,
exclude: Pattern[str],
extend_exclude: Pattern[str] | None,
force_exclude: Pattern[str] | None,
) -> Iterator[Path]:
"""Generate all files under ``path`` whose paths are not excluded.
This function has been adapted from Black 24.10.0.
"""
if not root.is_absolute():
message = f"`root` must be absolute, not {root}"
raise ValueError(message)
for child in paths:
if not child.is_absolute():
message = f"`child` must be absolute, not {child}"
raise ValueError(message)
root_relative_path = child.relative_to(root).as_posix()

# Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options.
root_relative_path = f"/{root_relative_path}"
if child.is_dir():
root_relative_path = f"{root_relative_path}/"

if any(
_path_is_excluded(root_relative_path, x)
for x in [exclude, extend_exclude, force_exclude]
) or _resolves_outside_root_or_cannot_stat(child, root):
continue

if child.is_dir():
yield from _gen_python_files(
child.iterdir(), root, exclude, extend_exclude, force_exclude
)

elif child.is_file():
include_match = DEFAULT_INCLUDE_RE.search(root_relative_path)
if include_match:
yield child


def filter_python_files(
Expand All @@ -58,32 +143,16 @@ def filter_python_files(
``black_config``, relative to ``root``.
"""
sig = inspect.signature(gen_python_files)
# those two exist and are required in black>=21.7b1.dev9
kwargs = {"verbose": False, "quiet": False} if "verbose" in sig.parameters else {}
# `gitignore=` was replaced with `gitignore_dict=` in black==22.10.1.dev19+gffaaf48
for param in sig.parameters:
if param == "gitignore":
kwargs[param] = None # type: ignore[assignment]
elif param == "gitignore_dict":
kwargs[param] = {} # type: ignore[assignment]
absolute_paths = {p.resolve() for p in paths}
directories = {p for p in absolute_paths if p.is_dir()}
files = {p for p in absolute_paths if p not in directories}
files_from_directories = set(
gen_python_files(
_gen_python_files(
directories,
root,
include=DEFAULT_INCLUDE_RE,
exclude=formatter.get_exclude(DEFAULT_EXCLUDE_RE),
extend_exclude=formatter.get_extend_exclude(),
force_exclude=formatter.get_force_exclude(),
report=Report(),
**kwargs, # type: ignore[arg-type]
formatter.get_exclude(DEFAULT_EXCLUDE_RE),
formatter.get_extend_exclude(),
formatter.get_force_exclude(),
)
)
return {p.resolve().relative_to(root) for p in files_from_directories | files}


DEFAULT_EXCLUDE_RE = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
DEFAULT_INCLUDE_RE = re_compile_maybe_verbose(DEFAULT_INCLUDES)
Loading

0 comments on commit dc7cb4d

Please sign in to comment.