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 crash when isort isn't installed #132

Merged
merged 3 commits into from
May 16, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Added
Fixed
-----
- Git-related commands in the test suite now ignore the user's ``~/.gitconfig``.
- Now works again even if ``isort`` isn't installed


1.2.3_ - 2021-05-02
Expand Down
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ disallow_any_explicit = False

[mypy-darker.tests.*]
disallow_any_decorated = False
disallow_untyped_calls = False
disallow_untyped_defs = False

[mypy-darker.utils]
Expand Down
2 changes: 1 addition & 1 deletion src/darker/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
except ImportError:
isort = None # type: ignore
DESCRIPTION_PARTS.extend(
["", f"{ISORT_INSTRUCTION} to enable sorting of import definitions"]
["\n", "\n", f"{ISORT_INSTRUCTION} to enable sorting of import definitions"]
)
DESCRIPTION = "".join(DESCRIPTION_PARTS)

Expand Down
18 changes: 14 additions & 4 deletions src/darker/import_sorting.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import sys
from pathlib import Path
from typing import Optional
from typing import Any, Optional

from black import find_project_root

Expand All @@ -15,11 +15,21 @@

try:
import isort

# Work around Mypy problem
# https://github.com/python/mypy/issues/7030#issuecomment-504128883
isort_code = getattr(isort, "code")
samoylovfp marked this conversation as resolved.
Show resolved Hide resolved
except ImportError:
# `isort` is an optional dependency. Prevent the `ImportError` if it's missing.
isort = None # type: ignore
# Work around Mypy problem
# https://github.com/python/mypy/issues/7030#issuecomment-504128883
isort_code = getattr(isort, "code")

def isort_code(*args: Any, **kwargs: Any) -> str: # type: ignore[misc] # noqa: F821
"""Fake `isort.code()` function to use when `isort` isn't installed"""
raise ModuleNotFoundError(
"No module named 'isort'. Please install the 'isort' package before using"
" the --isort / -i option."
)


__all__ = ["apply_isort", "isort"]

Expand Down
15 changes: 0 additions & 15 deletions src/darker/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
"""Configuration and fixtures for the Pytest based test suite"""

import os
import sys
import types
from pathlib import Path
from subprocess import check_call
from typing import Dict, Optional
from unittest.mock import patch

import pytest
from black import find_project_root

from darker.git import _git_check_output_lines


@pytest.fixture
def without_isort():
with patch.dict(sys.modules, {"isort": None}):
yield


@pytest.fixture
def with_isort():
with patch.dict(sys.modules, {"isort": types.ModuleType("isort")}):
yield


class GitRepoFixture:
def __init__(self, root: Path, env: Dict[str, str]):
self.root = root
Expand Down
19 changes: 18 additions & 1 deletion src/darker/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import sys
from contextlib import contextmanager
from typing import Any, ContextManager, Dict, List, Union
from types import ModuleType
from typing import Any, ContextManager, Dict, List, Optional, Union
from unittest.mock import patch

import pytest
from _pytest.python_api import RaisesContext
Expand Down Expand Up @@ -53,3 +55,18 @@ def check(result):
assert result == expect

yield check


@contextmanager
def isort_present(present):
"""Context manager to remove or add the `isort` package temporarily for a test"""
if present:
# Inject a dummy `isort` package temporarily
fake_isort_module: Optional[ModuleType] = ModuleType("isort")
# dummy function required by `import_sorting`:
fake_isort_module.code = None # type: ignore
else:
# Remove the `isort` package temporarily
fake_isort_module = None
with patch.dict(sys.modules, {"isort": fake_isort_module}):
yield
38 changes: 23 additions & 15 deletions src/darker/tests/test_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from darker.__main__ import main
from darker.command_line import make_argument_parser, parse_command_line
from darker.git import RevisionRange
from darker.tests.helpers import filter_dict, raises_if_exception
from darker.tests.helpers import filter_dict, isort_present, raises_if_exception
from darker.utils import TextDocument, joinlines

pytestmark = pytest.mark.usefixtures("find_project_root_cache_clear")
Expand All @@ -29,8 +29,7 @@ def test_make_argument_parser(require_src, expect):
assert args.src == expect


@pytest.fixture
def darker_help_output(capsys):
def get_darker_help_output(capsys):
"""Test for ``--help`` option output"""
# Make sure the description is re-rendered since its content depends on whether
# isort is installed or not:
Expand Down Expand Up @@ -232,22 +231,31 @@ def test_parse_command_line(
assert modified_cfg[modified_option] == expect_modified_value


def test_help_description_without_isort_package(without_isort, darker_help_output):
assert (
"Please run `pip install 'darker[isort]'` to enable sorting of import "
"definitions" in darker_help_output
)
def test_help_description_without_isort_package(capsys):
"""``darker --help`` description shows how to add ``isort`` if it's not present"""
with isort_present(False):

assert (
"Please run `pip install 'darker[isort]'` to enable sorting of import "
"definitions" in get_darker_help_output(capsys)
)


def test_help_isort_option_without_isort_package(without_isort, darker_help_output):
assert (
"Please run `pip install 'darker[isort]'` to enable usage of this option."
in darker_help_output
)
def test_help_isort_option_without_isort_package(capsys):
"""``--isort`` option help text shows how to install `isort`` if it's not present"""
with isort_present(False):

assert (
"Please run `pip install 'darker[isort]'` to enable usage of this option."
in get_darker_help_output(capsys)
)


def test_help_with_isort_package(capsys):
"""``darker --help`` omits ``isort`` installation instructions if it is installed"""
with isort_present(True):

def test_help_with_isort_package(with_isort, darker_help_output):
assert "Please run" not in darker_help_output
assert "Please run" not in get_darker_help_output(capsys)


@pytest.mark.parametrize(
Expand Down
25 changes: 22 additions & 3 deletions src/darker/tests/test_import_sorting.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
"""Tests for :mod:`darker.import_sorting`"""

from importlib import reload
from pathlib import Path
from textwrap import dedent

import pytest
from black import find_project_root

from darker.import_sorting import apply_isort
import darker.import_sorting
from darker.tests.helpers import isort_present
from darker.utils import TextDocument

ORIGINAL_SOURCE = ("import sys", "import os")
ISORTED_SOURCE = ("import os", "import sys")


@pytest.mark.parametrize("present", [True, False])
def test_import_sorting_importable_with_and_without_isort(present):
"""Make sure ``import darker.import_sorting`` works with and without ``isort``"""
try:
with isort_present(present):

# Import when `isort` has been removed temporarily
reload(darker.import_sorting)
finally:
# Re-import after restoring `isort` so other tests won't be affected
reload(darker.import_sorting)


@pytest.mark.parametrize("encoding", ["utf-8", "iso-8859-1"])
@pytest.mark.parametrize("newline", ["\n", "\r\n"])
def test_apply_isort(encoding, newline):
"""Import sorting is applied correctly, with encoding and newline intact"""
result = apply_isort(
result = darker.import_sorting.apply_isort(
TextDocument.from_lines(ORIGINAL_SOURCE, encoding=encoding, newline=newline),
Path("test1.py"),
)
Expand Down Expand Up @@ -69,5 +86,7 @@ def test_isort_config(monkeypatch, tmpdir, line_length, settings_file, expect):
content = "from module import ab, cd, ef, gh, ij, kl, mn, op, qr, st, uv, wx, yz"
config = str(tmpdir / settings_file) if settings_file else None

actual = apply_isort(TextDocument.from_str(content), Path("test1.py"), config)
actual = darker.import_sorting.apply_isort(
TextDocument.from_str(content), Path("test1.py"), config
)
assert actual.string == expect
10 changes: 7 additions & 3 deletions src/darker/tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Unit tests for :mod:`darker.__main__`"""

# pylint: disable=unused-argument

import logging
import os
import re
Expand All @@ -13,14 +15,16 @@
import darker.__main__
import darker.import_sorting
from darker.git import RevisionRange
from darker.tests.helpers import isort_present
from darker.utils import TextDocument
from darker.verification import NotEquivalentError


def test_isort_option_without_isort(git_repo, without_isort, caplog):
def test_isort_option_without_isort(git_repo, caplog):
"""Without isort, provide isort install instructions and error"""
# pylint: disable=unused-argument
with patch.object(darker.__main__, "isort", None), pytest.raises(SystemExit):
with isort_present(False), patch.object(
darker.__main__, "isort", None
), pytest.raises(SystemExit):

darker.__main__.main(["--isort", "."])

Expand Down