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

Fix compatibility with Flynt 0.78 and 1.0.0 #480

Merged
merged 4 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ console_scripts =

[options.extras_require]
flynt =
flynt>=0.76,<0.78
flynt>=0.76
isort =
isort>=5.0.1
color =
Expand All @@ -55,7 +55,7 @@ test =
black>=22.3.0
cryptography>=3.3.2 # through twine, fixes CVE-2020-36242
defusedxml>=0.7.1
flynt>=0.76,<0.78
flynt>=0.76
isort>=5.0.1
mypy>=0.990
pathspec # to test `gen_python_files` in `test_black_diff.py`
Expand Down
3 changes: 2 additions & 1 deletion src/darker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,9 @@ def main( # pylint: disable=too-many-locals,too-many-branches,too-many-statemen
validate_config_output_mode(config)

setup_logging(args.log_level)
# Make sure we don't get excessive debug log output from Black
# Make sure we don't get excessive debug log output from Black and Flynt
logging.getLogger("blib2to3.pgen2.driver").setLevel(logging.WARNING)
logging.getLogger("flynt.transform.transform").setLevel(logging.CRITICAL)

show_config_if_debug(config, config_nondefault, args.log_level)

Expand Down
81 changes: 68 additions & 13 deletions src/darker/fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
from pathlib import Path
from typing import Any
from typing import Any, Optional

from darker.exceptions import MissingPackageError
from darker.git import EditedLinenumsDiffer
Expand All @@ -11,14 +11,26 @@
try:
import flynt

flynt_fstringify_code_by_line = flynt.process.fstringify_code_by_line
flynt_version = tuple(map(int, flynt.__version__.split(".")))
if flynt_version >= (0, 78):
from flynt.state import State
else:
State = None # pylint: disable=invalid-name
if flynt_version < (1, 0, 0):
from flynt.process import fstringify_code_by_line
from flynt.pyproject_finder import find_pyproject_toml, parse_pyproject_toml
else:
from flynt.code_editor import fstringify_code_by_line
from flynt.utils.pyproject_finder import (
find_pyproject_toml,
parse_pyproject_toml,
)
except ImportError:
# `flynt` is an optional dependency. Prevent the `ImportError` if it's missing.
flynt = None
State = None

def flynt_fstringify_code_by_line( # type: ignore[misc]
*args: Any, **kwargs: Any
) -> str:
def fstringify_code_by_line(*args: Any, **kwargs: Any) -> str: # type: ignore[misc]
"""Fake `flynt.fstringify_code_by_line()` to use when `flynt` isn't installed"""
raise MissingPackageError(
"No module named 'flynt'. Please install the 'flynt' package before using"
Expand All @@ -42,9 +54,9 @@ def apply_flynt(
responsibility of the caller to filter output to modified lines only.

:param content: The contents of the Python source code file to sort imports in
:param src: The relative path to the file. This must be the actual path in the
repository, which may differ from the path given on the command line in
case of VSCode temporary files.
:param src: The path to the file relative to the repository root. This must be the
actual path in the repository, which may differ from the path given on
the command line in case of VSCode temporary files.
:param edited_linenums_differ: Helper for finding out which lines were edited
:return: Original Python source file contents with modifications from ``flynt``

Expand All @@ -56,18 +68,61 @@ def apply_flynt(
)
if not edited_linenums:
return content
return _call_flynt_fstringify(content)
state = _get_flynt_configuration(edited_linenums_differ.root / src)
return _call_flynt_fstringify(content, state)


def _get_flynt_configuration( # type: ignore[no-any-unimported]
src: Path,
) -> Optional[State]:
"""Read ``pyproject.toml`` Flynt configuration for the given Python file

:param src: The absolute path to the Python file to run Flynt on. This must be the
actual path in the repository, which may differ from the path given on
the command line in case of VSCode temporary files.
:return: A ``flynt`` configuration, or ``None`` for Flynt versions <0.78

def _call_flynt_fstringify(content: TextDocument) -> TextDocument:
"""Call ``flynt.process.fstringify_code_by_line()``, return result `TextDocument`
"""
if State is None: # flynt<0.78
return None
state = State(quiet=True)
toml_file = find_pyproject_toml((str(src),))
if toml_file:
cfg = parse_pyproject_toml(toml_file)
mapping = {
# (state attribute name, `pyproject.toml` option)
("aggressive", "aggressive"),
("len_limit", "line_length"),
("multiline", "not no_multiline"),
("transform_concat", "transform_concats"),
("transform_format", "transform_format"),
("transform_join", "transform_joins"),
("transform_percent", "transform_percent"),
}
for state_attr, cfg_option in mapping:
if cfg_option not in cfg:
continue
if cfg_option.startswith("not "):
value = not cfg[cfg_option[4:]]
else:
value = cfg[cfg_option]
setattr(state, state_attr, value)
return state


def _call_flynt_fstringify( # type: ignore[no-any-unimported]
content: TextDocument, state: Optional[State]
) -> TextDocument:
"""Call ``flynt.code_editor.fstringify_code_by_line()``, return ``TextDocument``

:param content: The contents of the Python source code file to fstringify
:param state: The ``flynt`` configuration to use, or ``None`` for ``flynt<0.78``
:return: Original Python source code contents with modifications from ``flynt``

"""
logger.debug("flynt.process.fstringify_code_by_line(code=...)")
result, _ = flynt_fstringify_code_by_line(content.string)
logger.debug("flynt.code_editor.fstringify_code_by_line(code=...)")
args = () if state is None else (state,) # `()` for flynt<0.78, (state,) for >=0.78
result, _ = fstringify_code_by_line(content.string, *args)
return TextDocument.from_str(
result,
encoding=content.encoding,
Expand Down
5 changes: 3 additions & 2 deletions src/darker/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def flynt_present(present: bool) -> Generator[None, None, None]:
if present:
# dummy module and function required by `fstring`:
# pylint: disable=no-member
fake_flynt_module.process = ModuleType("process") # type: ignore
fake_flynt_module.process.fstringify_code_by_line = None # type: ignore
fake_flynt_module.__version__ = "1.0.0" # type: ignore
fake_flynt_module.code_editor = ModuleType("process") # type: ignore
fake_flynt_module.code_editor.fstringify_code_by_line = None # type: ignore
yield
Loading