Repository: rupert/pyls-black

Issue:
Regression on black 19.3b0
Test environment:

Python: python 3.6
OS: Ubuntu 18.04 64bit
IDE: atom-ide(ide-python extension)
pyls: 0.25

With newest version black 19.3b0, pyls-black is malfunctioning, the error log in atom suggests that FileMode has no attribute from_configuration. Downgrading to 18.9b0 works.

I suggest specifying a version dependency for black for the time being.

Relevant issue for vim plugin: [Link](https://github.com/ambv/black/issues/755)

Discussion:

Patch:
diff --git a/pyls_black/plugin.py b/pyls_black/plugin.py
index f3a41a0..6089f53 100644
--- a/pyls_black/plugin.py
+++ b/pyls_black/plugin.py
@@ -50,29 +50,22 @@ def format_document(document, range=None):
 
 
 def format_text(*, text, config):
-    line_length = config["line_length"]
-    fast = config["fast"]
-    mode = black.FileMode.from_configuration(
-        py36=config["py36"],
-        pyi=config["pyi"],
-        skip_string_normalization=config["skip_string_normalization"],
-        skip_numeric_underscore_normalization=config[
-            "skip_numeric_underscore_normalization"
-        ],
-    )
-    return black.format_file_contents(
-        text, line_length=line_length, fast=fast, mode=mode
+    mode = black.FileMode(
+        target_versions=config["target_version"],
+        line_length=config["line_length"],
+        is_pyi=config["pyi"],
+        string_normalization=not config["skip_string_normalization"],
     )
 
+    return black.format_file_contents(text, fast=config["fast"], mode=mode)
+
 
 def load_config(filename: str) -> Dict:
     defaults = {
         "line_length": 88,
         "fast": False,
-        "py36": False,
         "pyi": filename.endswith(".pyi"),
         "skip_string_normalization": False,
-        "skip_numeric_underscore_normalization": False,
     }
 
     root = black.find_project_root((filename,))
@@ -87,9 +80,26 @@ def load_config(filename: str) -> Dict:
     except (toml.TomlDecodeError, OSError):
         return defaults
 
-    config = pyproject_toml.get("tool", {}).get("black", {})
+    file_config = pyproject_toml.get("tool", {}).get("black", {})
+    file_config = {
+        key.replace("--", "").replace("-", "_"): value
+        for key, value in file_config.items()
+    }
+
     config = {
-        key.replace("--", "").replace("-", "_"): value for key, value in config.items()
+        key: file_config.get(key, default_value)
+        for key, default_value in defaults.items()
     }
 
-    return {**defaults, **config}
+    if file_config.get("target_version"):
+        target_version = set(
+            black.TargetVersion[x.upper()] for x in file_config["target_version"]
+        )
+    elif file_config.get("py36"):
+        target_version = black.PY36_VERSIONS
+    else:
+        target_version = set()
+
+    config["target_version"] = target_version
+
+    return config
diff --git a/pyproject.toml b/pyproject.toml
index af3bd7e..9e3dd3e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
 [tool.black]
-py36 = true
+target-version = ['py36', 'py37', 'py38']
 exclude = '''
 /(
 	  \.venv
diff --git a/setup.py b/setup.py
index ec2d408..4c7463c 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as f:
 
 setup(
     name="pyls-black",
-    version="0.4.2",
+    version="0.4.3",
     description="Black plugin for the Python Language Server",
     long_description=long_description,
     long_description_content_type="text/markdown",
@@ -13,7 +13,7 @@ setup(
     author="Rupert Bedford",
     author_email="rupert@rupertb.com",
     packages=find_packages(exclude=["tests"]),
-    install_requires=["python-language-server", "black==18.9b0", "toml"],
+    install_requires=["python-language-server", "black>=19.3b0", "toml"],
     extras_require={"dev": ["isort", "flake8", "pytest", "mypy", "pytest"]},
     entry_points={"pyls": ["pyls_black = pyls_black.plugin"]},
     classifiers=(


Test patch:
diff --git a/tests/fixtures/config/pyproject.toml b/tests/fixtures/config/pyproject.toml
index fe02bb4..c934350 100644
--- a/tests/fixtures/config/pyproject.toml
+++ b/tests/fixtures/config/pyproject.toml
@@ -1,7 +1,5 @@
 [tool.black]
 line-length = 20
 --fast = true
-py36 = true
 pyi = true
 skip-string-normalization = true
-skip-numeric-underscore-normalization = true
diff --git a/tests/fixtures/py36/pyproject.toml b/tests/fixtures/py36/pyproject.toml
new file mode 100644
index 0000000..1917f16
--- /dev/null
+++ b/tests/fixtures/py36/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.black]
+py36 = true
diff --git a/tests/fixtures/target_version/pyproject.toml b/tests/fixtures/target_version/pyproject.toml
new file mode 100644
index 0000000..8c5e387
--- /dev/null
+++ b/tests/fixtures/target_version/pyproject.toml
@@ -0,0 +1,2 @@
+[tool.black]
+target-version = ['py27']
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 4a32081..e15c48c 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -1,5 +1,6 @@
 from pathlib import Path
 
+import black
 import pytest
 from pyls.workspace import Document
 
@@ -161,24 +162,35 @@ def test_pyls_format_range_syntax_error(invalid_document):
 def test_load_config():
     config = load_config(str(fixtures_dir / "config" / "example.py"))
 
+    # TODO split into smaller tests
     assert config == {
         "line_length": 20,
-        "py36": True,
+        "target_version": set(),
         "pyi": True,
         "fast": True,
         "skip_string_normalization": True,
-        "skip_numeric_underscore_normalization": True,
     }
 
 
+def test_load_config_target_version():
+    config = load_config(str(fixtures_dir / "target_version" / "example.py"))
+
+    assert config["target_version"] == {black.TargetVersion.PY27}
+
+
+def test_load_config_py36():
+    config = load_config(str(fixtures_dir / "py36" / "example.py"))
+
+    assert config["target_version"] == black.PY36_VERSIONS
+
+
 def test_load_config_defaults():
     config = load_config(str(fixtures_dir / "example.py"))
 
     assert config == {
         "line_length": 88,
-        "py36": False,
+        "target_version": set(),
         "pyi": False,
         "fast": False,
         "skip_string_normalization": False,
-        "skip_numeric_underscore_normalization": False,
     }