Repository: timothycrosley/isort (python)

Issue:
Type-annotated "encoding" field crashes isort
This came up while trying to enforce isort imports in [typeshed](/python/typeshed). Please consider the following file:

```python
class X:
    encoding: str
```

(The actual file it stumbles over is [stdlib/3/io.pyi](https://github.com/python/typeshed/blob/master/stdlib/3/io.pyi).)

Running isort 4.3.21 with Python 3.7 against this fails with the following traceback:

```
Traceback (most recent call last):
  File "./.venv/bin/isort", line 10, in <module>
    sys.exit(main())
  File "/home/srittau/.venv/lib/python3.7/site-packages/isort/main.py", line 379, in main
    for sort_attempt in attempt_iterator:
  File "/home/srittau/.venv/lib/python3.7/site-packages/isort/main.py", line 377, in <genexpr>
    attempt_iterator = (sort_imports(file_name, **arguments) for file_name in file_names)
  File "/home/srittau/.venv/lib/python3.7/site-packages/isort/main.py", line 88, in sort_imports
    result = SortImports(file_name, **arguments)
  File "/home/srittau/.venv/lib/python3.7/site-packages/isort/isort.py", line 114, in __init__
    with io.open(file_path, encoding=file_encoding, newline='') as file_to_import_sort:
LookupError: unknown encoding: str
```

Pull request description:
Coding detection closer to PEP 263
* Use the regexp from the PEP
* Fix a bug that also checked the third line for coding

Closes #1027 

Patch:
diff --git a/isort/compat.py b/isort/compat.py
index 0af5d44a..9e3e6e52 100644
--- a/isort/compat.py
+++ b/isort/compat.py
@@ -12,17 +12,17 @@ from isort.isort import _SortImports
 
 def determine_file_encoding(file_path: Path, default: str = "utf-8") -> str:
     # see https://www.python.org/dev/peps/pep-0263/
-    pattern = re.compile(br"coding[:=]\s*([-\w.]+)")
+    pattern = re.compile(br"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
 
     coding = default
     with file_path.open("rb") as f:
         for line_number, line in enumerate(f, 1):
+            if line_number > 2:
+                break
             groups = re.findall(pattern, line)
             if groups:
                 coding = groups[0].decode("ascii")
                 break
-            if line_number > 2:
-                break
 
     return coding
 


Test patch:
diff --git a/test_isort.py b/test_isort.py
index ae5eee89..7ca611f4 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -2031,6 +2031,28 @@ def test_other_file_encodings(tmpdir) -> None:
         )
 
 
+def test_encoding_not_in_comment(tmpdir) -> None:
+    """Test that 'encoding' not in a comment is ignored"""
+    tmp_fname = tmpdir.join("test_encoding.py")
+    file_contents = "class Foo\n    coding: latin1\n\ns = u'ã'\n".format("utf8")
+    tmp_fname.write_binary(file_contents.encode("utf8"))
+    assert (
+        SortImports(file_path=str(tmp_fname), settings_path=os.getcwd()).output
+        == file_contents
+    )
+
+
+def test_encoding_not_in_first_two_lines(tmpdir) -> None:
+    """Test that 'encoding' not in the first two lines is ignored"""
+    tmp_fname = tmpdir.join("test_encoding.py")
+    file_contents = "\n\n# -*- coding: latin1\n\ns = u'ã'\n".format("utf8")
+    tmp_fname.write_binary(file_contents.encode("utf8"))
+    assert (
+        SortImports(file_path=str(tmp_fname), settings_path=os.getcwd()).output
+        == file_contents
+    )
+
+
 def test_comment_at_top_of_file() -> None:
     """Test to ensure isort correctly handles top of file comments"""
     test_input = (