[gnome-code-assistance] python: improve performance of the pep8 check



commit a7326287018d3ef2bb79c8232d3daf4212c03b83
Author: Elad Alfassa <elad fedoraproject org>
Date:   Fri Nov 17 21:53:10 2017 +0200

    python: improve performance of the pep8 check
    
    This patch significantly improves performance of the pep8 checks,
    from ~400ms to ~30ms per run, by using pep8 directly instead of
    launching a subprocess.
    
    It also adds support for pycodestyle, which is the new name for
    the pep8 tool (since 2016)
    
    https://bugzilla.gnome.org/show_bug.cgi?id=790514

 backends/python/__init__.py |   99 ++++++++++++++++++++++++++++++-------------
 1 files changed, 69 insertions(+), 30 deletions(-)
---
diff --git a/backends/python/__init__.py b/backends/python/__init__.py
index 7776614..aa3c28a 100644
--- a/backends/python/__init__.py
+++ b/backends/python/__init__.py
@@ -1,6 +1,6 @@
 # gnome code assistance python backend
 # Copyright (C) 2013  Jesse van den Kieboom <jessevdk gnome org>
-# Copyright (C) 2014  Elad Alfassa <elad fedoraproject org>
+# Copyright (C) 2014, 2017  Elad Alfassa <elad fedoraproject org>
 # Copyright (C) 2015  Igor Gnatenko <ignatenko src gnome org>
 # Copyright (C) 2017  Luke Benstead <kazade gmail com>
 #
@@ -19,8 +19,19 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
 import ast
-import subprocess
-import re
+
+try:
+    import pycodestyle
+    HAS_PYCODESTYLE = True
+except ImportError:
+    try:
+        # pep8 has been renamed to "pycodestyle". Maybe the user only has
+        # the old version installed, so let's try loading it before we give up
+        import pep8 as pycodestyle
+        HAS_PYCODESTYLE = True
+    except ImportError:
+        # No pep8 or pycodestyle installed
+        HAS_PYCODESTYLE = False
 
 try:
     from pylint import lint
@@ -120,55 +131,83 @@ class Pyflakes(object):
         return reporter.diagnostics
 
 
+class PyCodeStyle(object):
+    """ PyCodeStyle / PEP8 backend """
+    def __init__(self, source, path):
+        self.source = source
+        self.path = path
+
+    def run(self):
+        class PyCodeStyleReporter(pycodestyle.BaseReport):
+            """
+                Custom reporter, nested as parent class will not have been imported
+                if pycodestyle/pep8 wasn't available
+            """
+            def __init__(self, *args):
+                super().__init__(*args)
+                self.diagnostics = []
+
+            def error(self, line_number, offset, text, check):
+                errorcode = super().error(line_number, offset, text, check)
+                if errorcode:
+                    loc = types.SourceLocation(line=line_number, column=offset + 1)
+                    severity = types.Diagnostic.Severity.INFO
+                    self.diagnostics.append(types.Diagnostic(severity=severity,
+                                                             locations=[loc.to_range()],
+                                                             message=text))
+
+        style_guide = pycodestyle.StyleGuide(reporter=PyCodeStyleReporter)
+        reporter = style_guide.options.report
+        style_guide.input_file(self.path, lines=self.source.splitlines(True))
+        return reporter.diagnostics
+
+
 class Service(transport.Service):
     language = 'python'
 
     def parse(self, doc, options):
         doc.diagnostics = []
+        use_pylint = HAS_PYLINT and "pylint" in options and options["pylint"]
+
+        with open(doc.data_path) as f:
+            source = f.read()
+
+        if not use_pylint and not HAS_PYFLAKES:
+            # both pylint and pyflakes warn about syntax errors, so only need
+            # ast.parse() if we can't use either of them
+            try:
+                ast.parse(source, doc.path)
+            except SyntaxError as e:
+                loc = types.SourceLocation(line=e.lineno, column=e.offset)
+                severity = types.Diagnostic.Severity.ERROR
 
-        try:
-            with open(doc.data_path) as f:
-                source = f.read()
-
-            ast.parse(source, doc.path)
-        except SyntaxError as e:
-            loc = types.SourceLocation(line=e.lineno, column=e.offset)
-            severity = types.Diagnostic.Severity.ERROR
-
-            doc.diagnostics.append(types.Diagnostic(severity=severity, locations=[loc.to_range()], 
message=e.msg))
-
-        # PEP8 checks
-        try:
-            p = subprocess.Popen(['pep8', doc.data_path], stdout=subprocess.PIPE)
-            for line in iter(p.stdout.readline, ''):
-                if not line:
-                    break
-                line = line.decode()
-                result = re.search(':(\d*):(\d*): (.*)', line).groups()
-                loc = types.SourceLocation(line=result[0], column=result[1])
-                severity = types.Diagnostic.Severity.INFO
-                doc.diagnostics.append(types.Diagnostic(severity=severity, locations=[loc.to_range()], 
message=result[2]))
-        except FileNotFoundError:
-            # PEP8 is not installed. Do nothing.
-            pass
+                doc.diagnostics.append(types.Diagnostic(severity=severity, locations=[loc.to_range()], 
message=e.msg))
+
+        # Pycodestyle / PEP8 checks
+        if HAS_PYCODESTYLE or True:
+            pycodestyle_checker = PyCodeStyle(source, doc.path)
+            for diagnostic in pycodestyle_checker.run():
+                doc.diagnostics.append(diagnostic)
 
-        if HAS_PYLINT and "pylint" in options and options["pylint"]:
+        # Pylint checks (if present and enabled)
+        if use_pylint:
             pylint = PyLint(doc.data_path)
             diagnostics = pylint.run()
 
             for diag in diagnostics:
                 doc.diagnostics.append(diag)
 
+        # Pyflakes check (if present)
         if HAS_PYFLAKES:
             pyflakes = Pyflakes(doc.data_path)
             diagnostics = pyflakes.run()
             for diag in diagnostics:
                 doc.diagnostics.append(diag)
 
-
 class Document(transport.Document, transport.Diagnostics):
     pass
 
+
 def run():
     transport.Transport(Service, Document).run()
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]