Repository: xonsh/xonsh

Issue:
A trailing comma in the import list raises syntax error
A trailing comma in the import list raises syntax error (it is valid Python)

```
$ from x import (y, z,)
.
  File "<string>", line None
SyntaxError: <xonsh-code>:1:20: ('code: )',)
from x import (y, z,)
                    ^
```
I have tried and failed to fix it my self :( 

I thought it should be handled by this: 
https://github.com/xonsh/xonsh/blob/master/xonsh/parsers/base.py#L1471-L1478

Or am I wrong here...


Discussion:

Patch:
diff --git a/docs/sidebar.rst b/docs/sidebar.rst
index 5b266896..33aa81ae 100644
--- a/docs/sidebar.rst
+++ b/docs/sidebar.rst
@@ -6,4 +6,15 @@
     :maxdepth: 3
 
     the xonsh shell <index>
+    intro
+    comparison
+    installation
+    dependencies
+    news
+    guides
+    setup
+    contributing
+    development
     API <api/index>
+    contact
+    links
\ No newline at end of file
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 276d2799..662c6817 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -52,7 +52,7 @@ simple:
           xonsh input. This follows the Python convention and helps trick
           syntax highlighting, though ``$`` is more traditional for shells.
 
-Since this is just Python, we are able to import modules, print values,
+Since this is just Python, we are able import modules, print values,
 and use other built-in Python functionality:
 
 .. code-block:: xonshcon
diff --git a/news/fix-flake8-errors.rst b/news/fix-flake8-errors.rst
deleted file mode 100644
index 12250063..00000000
--- a/news/fix-flake8-errors.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-**Added:**
-
-* <news item>
-
-**Changed:**
-
-* <news item>
-
-**Deprecated:**
-
-* <news item>
-
-**Removed:**
-
-* <news item>
-
-**Fixed:**
-
-* Flake8 errors
-
-**Security:**
-
-* <news item>
diff --git a/news/docs_sidebar_dedupe.rst b/news/fix-import-names.rst
similarity index 55%
rename from news/docs_sidebar_dedupe.rst
rename to news/fix-import-names.rst
index 790d30b1..9cd1a2e5 100644
--- a/news/docs_sidebar_dedupe.rst
+++ b/news/fix-import-names.rst
@@ -16,7 +16,10 @@
 
 **Fixed:**
 
-* <news item>
+* xonsh can now properly parse import statements with trailing comma within
+  parentheses, e.g.::
+  
+    from x import (y, z,)
 
 **Security:**
 
diff --git a/news/fix-unclosed-scandir-iterator.rst b/news/fix-unclosed-scandir-iterator.rst
deleted file mode 100644
index 4ac45245..00000000
--- a/news/fix-unclosed-scandir-iterator.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-**Added:**
-
-* <news item>
-
-**Changed:**
-
-* <news item>
-
-**Deprecated:**
-
-* <news item>
-
-**Removed:**
-
-* <news item>
-
-**Fixed:**
-
-* ResourceWarning: unclosed scandir iterator in imphooks.py
-
-**Security:**
-
-* <news item>
diff --git a/news/fix-use-of-formatargspec.rst b/news/fix-use-of-formatargspec.rst
deleted file mode 100644
index f81efd18..00000000
--- a/news/fix-use-of-formatargspec.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-**Added:**
-
-* <news item>
-
-**Changed:**
-
-* <news item>
-
-**Deprecated:**
-
-* <news item>
-
-**Removed:**
-
-* <news item>
-
-**Fixed:**
-
-* Removed use of deprecated ``inspect.formatargspec()`` for ``inspect.signature()``
-
-**Security:**
-
-* <news item>
diff --git a/xonsh/dirstack.py b/xonsh/dirstack.py
index 7c8de2c5..b882065f 100644
--- a/xonsh/dirstack.py
+++ b/xonsh/dirstack.py
@@ -39,7 +39,7 @@ def _unc_check_enabled() -> bool:
         )
         wval, wtype = winreg.QueryValueEx(key, "DisableUNCCheck")
         winreg.CloseKey(key)
-    except OSError:
+    except OSError as e:
         pass
 
     if wval is None:
diff --git a/xonsh/imphooks.py b/xonsh/imphooks.py
index a0ff63b1..e83f06f8 100644
--- a/xonsh/imphooks.py
+++ b/xonsh/imphooks.py
@@ -84,7 +84,7 @@ class XonshImportHook(MetaPathFinder, SourceLoader):
                 continue
             if not os.path.isdir(p) or not os.access(p, os.R_OK):
                 continue
-            if fname not in {x.name for x in scandir(p)}:
+            if fname not in (x.name for x in scandir(p)):
                 continue
             spec = ModuleSpec(fullname, self)
             self._filenames[fullname] = os.path.join(p, fname)
diff --git a/xonsh/inspectors.py b/xonsh/inspectors.py
index f14f8fcb..3245a6b2 100644
--- a/xonsh/inspectors.py
+++ b/xonsh/inspectors.py
@@ -354,7 +354,7 @@ class Inspector(object):
         exception is suppressed.
         """
         try:
-            hdef = oname + inspect.signature(*getargspec(obj))
+            hdef = oname + inspect.formatargspec(*getargspec(obj))
             return cast_unicode(hdef)
         except:  # pylint:disable=bare-except
             return None
diff --git a/xonsh/parsers/base.py b/xonsh/parsers/base.py
index 4e33232a..06ba6806 100644
--- a/xonsh/parsers/base.py
+++ b/xonsh/parsers/base.py
@@ -1425,7 +1425,7 @@ class BaseParser(object):
         p[0] = p[1]
 
     def p_import_from_post_paren(self, p):
-        """import_from_post : LPAREN import_as_names RPAREN"""
+        """import_from_post : LPAREN import_as_names"""
         p[0] = p[2]
 
     def p_import_from(self, p):
@@ -1459,6 +1459,10 @@ class BaseParser(object):
         """
         p[0] = [p[2]]
 
+    def p_comma_import_as_name_tail(self, p):
+        """comma_import_as_name : comma_opt RPAREN"""
+        p[0] = list()
+
     def p_dotted_as_name(self, p):
         """dotted_as_name : dotted_name as_name_opt"""
         p0 = ast.alias(name=p[1], asname=p[2])
@@ -1469,8 +1473,7 @@ class BaseParser(object):
         p[0] = [p[2]]
 
     def p_import_as_names(self, p):
-        """import_as_names : import_as_name comma_import_as_name_list_opt comma_opt
-        """
+        """import_as_names : import_as_name comma_import_as_name_list_opt"""
         p1, p2 = p[1], p[2]
         p0 = [p1]
         if p2 is not None:
diff --git a/xonsh/proc.py b/xonsh/proc.py
index 1a50d4fd..e182fd42 100644
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -1406,7 +1406,7 @@ class ProcProxyThread(threading.Thread):
                 r = self.f(self.args, sp_stdin, sp_stdout, sp_stderr, spec, spec.stack)
         except SystemExit as e:
             r = e.code if isinstance(e.code, int) else int(bool(e.code))
-        except OSError:
+        except OSError as e:
             status = still_writable(self.c2pwrite) and still_writable(self.errwrite)
             if status:
                 # stdout and stderr are still writable, so error must
diff --git a/xontrib/bashisms.py b/xontrib/bashisms.py
index e9d6be67..fa27cb8c 100644
--- a/xontrib/bashisms.py
+++ b/xontrib/bashisms.py
@@ -32,7 +32,8 @@ def custom_keybindings(bindings, **kw):
     def last_command_exists():
         return len(__xonsh__.history) > 0
 
-    @handler(Keys.Escape, '.', filter=last_command_exists & insert_mode)
+    @handler(Keys.Escape, '.', filter=last_command_exists &
+             insert_mode)
     def recall_last_arg(event):
         arg = __xonsh__.history[-1].cmd.split()[-1]
         event.current_buffer.insert_text(arg)


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 864788a1..09adb810 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -1486,6 +1486,14 @@ def test_from_x_import_star():
     check_stmts("from x import *", False)
 
 
+def test_from_x_import_group_x_y_z():
+    check_stmts("from x import (x, y, z)", False)
+
+
+def test_from_x_import_group_x_y_z_comma():
+    check_stmts("from x import (x, y, z,)", False)
+
+
 def test_from_x_import_y_as_z():
     check_stmts("from x import y as z", False)