Continued to add support for Google protobuf protocol files. Added support for gRPC.

Tue, 14 Nov 2017 19:13:28 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 14 Nov 2017 19:13:28 +0100
changeset 5970
411f7ba577d5
parent 5969
584c21b6587a
child 5971
768aef1ec205

Continued to add support for Google protobuf protocol files. Added support for gRPC.

Preferences/ConfigurationPages/ProtobufPage.py file | annotate | diff | comparison | revisions
Preferences/ConfigurationPages/ProtobufPage.ui file | annotate | diff | comparison | revisions
Preferences/ProgramsDialog.py file | annotate | diff | comparison | revisions
Preferences/__init__.py file | annotate | diff | comparison | revisions
Project/ProjectProtocolsBrowser.py file | annotate | diff | comparison | revisions
--- a/Preferences/ConfigurationPages/ProtobufPage.py	Mon Nov 13 20:20:06 2017 +0100
+++ b/Preferences/ConfigurationPages/ProtobufPage.py	Tue Nov 14 19:13:28 2017 +0100
@@ -34,14 +34,21 @@
             "Press to select the Protobuf compiler via a file selection"
             " dialog."))
         
+        self.grpcPythonPicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.grpcPythonPicker.setToolTip(self.tr(
+            "Press to select the Python interpreter containing the grpc"
+            " compiler via a file selection dialog."))
+        
         # set initial values
         self.protocPicker.setText(Preferences.getProtobuf("protoc"))
+        self.grpcPythonPicker.setText(Preferences.getProtobuf("grpcPython"))
         
     def save(self):
         """
         Public slot to save the protobuf configuration.
         """
         Preferences.setProtobuf("protoc", self.protocPicker.text())
+        Preferences.setProtobuf("grpcPython", self.grpcPythonPicker.text())
     
 
 def create(dlg):
--- a/Preferences/ConfigurationPages/ProtobufPage.ui	Mon Nov 13 20:20:06 2017 +0100
+++ b/Preferences/ConfigurationPages/ProtobufPage.ui	Tue Nov 14 19:13:28 2017 +0100
@@ -10,7 +10,7 @@
     <height>490</height>
    </rect>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_3">
+  <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="headerLabel">
      <property name="text">
@@ -58,6 +58,32 @@
     </widget>
    </item>
    <item>
+    <widget class="QGroupBox" name="groupBox_3">
+     <property name="title">
+      <string>grpc Compiler</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <item>
+       <widget class="E5PathPicker" name="grpcPythonPicker" native="true">
+        <property name="focusPolicy">
+         <enum>Qt::StrongFocus</enum>
+        </property>
+        <property name="toolTip">
+         <string>Enter the path of the Python interpreter containing the grpc compiler.</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="textLabel1_6">
+        <property name="text">
+         <string>&lt;b&gt;Note:&lt;/b&gt; Leave this entry empty to use the Python interprter of eric.</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
     <spacer>
      <property name="orientation">
       <enum>Qt::Vertical</enum>
--- a/Preferences/ProgramsDialog.py	Mon Nov 13 20:20:06 2017 +0100
+++ b/Preferences/ProgramsDialog.py	Tue Nov 14 19:13:28 2017 +0100
@@ -15,6 +15,7 @@
 
 import os
 import re
+import sys
 
 from PyQt5.QtCore import pyqtSlot, Qt, QProcess
 from PyQt5.QtGui import QCursor
@@ -214,6 +215,13 @@
             exe += ".exe"
         self.__createProgramEntry(
             self.tr("Protobuf Compiler"), exe, '--version', 'libprotoc', -1)
+        # 5c. grpc
+        exe = Preferences.getProtobuf("grpcPython")
+        if not exe:
+            exe = sys.executable
+        self.__createProgramEntry(
+            self.tr("grpc Compiler"), exe, '--version', 'libprotoc', -1,
+            exeModule=['-m', 'grpc_tools.protoc'])
         
         # 6. do the spell checking entry
         try:
@@ -253,6 +261,8 @@
         pm = e5App().getObject("PluginManager")
         for info in pm.getPluginExeDisplayData():
             if info["programEntry"]:
+                if "exeModule" not in info:
+                    info["exeModule"] = None
                 self.__createProgramEntry(
                     info["header"],
                     info["exe"],
@@ -261,6 +271,7 @@
                     versionPosition=info["versionPosition"],
                     version=info["version"],
                     versionCleanup=info["versionCleanup"],
+                    exeModule=info["exeModule"],
                 )
             else:
                 self.__createEntry(
@@ -277,14 +288,15 @@
     def __createProgramEntry(self, description, exe,
                              versionCommand="", versionStartsWith="",
                              versionPosition=0, version="",
-                             versionCleanup=None, versionRe=None):
+                             versionCleanup=None, versionRe=None,
+                             exeModule=None):
         """
         Private method to generate a program entry.
         
         @param description descriptive text (string)
         @param exe name of the executable program (string)
         @param versionCommand command line switch to get the version info
-            (string) if this is empty, the given version will be shown.
+            (str). If this is empty, the given version will be shown.
         @param versionStartsWith start of line identifying version info
             (string)
         @param versionPosition index of part containing the version info
@@ -294,6 +306,9 @@
             start and stop for the version string (tuple of integers)
         @keyparam versionRe regexp to determine the line identifying version
             info (string). Takes precedence over versionStartsWith.
+        @keyparam exeModule list of command line parameters to execute a module
+            with the program given in exe (e.g. to execute a Python module)
+            (list of str)
         @return version string of detected or given version (string)
         """
         itmList = self.programsList.findItems(
@@ -320,7 +335,11 @@
                    versionPosition:
                     proc = QProcess()
                     proc.setProcessChannelMode(QProcess.MergedChannels)
-                    proc.start(exe, [versionCommand])
+                    if exeModule:
+                        args = exeModule[:] + [versionCommand]
+                    else:
+                        args = [versionCommand]
+                    proc.start(exe, args)
                     finished = proc.waitForFinished(10000)
                     if finished:
                         output = str(proc.readAllStandardOutput(),
@@ -345,7 +364,12 @@
                             version = self.tr("(unknown)")
                     else:
                         version = self.tr("(not executable)")
-                QTreeWidgetItem(itm, [exe, version])
+                if exeModule:
+                    QTreeWidgetItem(itm, [
+                        "{0} {1}".format(exe, " ".join(exeModule)),
+                        version])
+                else:
+                    QTreeWidgetItem(itm, [exe, version])
                 itm.setExpanded(True)
             else:
                 itm.setText(1, self.tr("(not found)"))
--- a/Preferences/__init__.py	Mon Nov 13 20:20:06 2017 +0100
+++ b/Preferences/__init__.py	Tue Nov 14 19:13:28 2017 +0100
@@ -1284,7 +1284,8 @@
     
     # defaults for protobuf related stuff
     protobufDefaults = {
-        "protoc": ""
+        "protoc": "",
+        "grpcPython": "",
     }
     
     # defaults for user related stuff
--- a/Project/ProjectProtocolsBrowser.py	Mon Nov 13 20:20:06 2017 +0100
+++ b/Project/ProjectProtocolsBrowser.py	Tue Nov 14 19:13:28 2017 +0100
@@ -16,6 +16,7 @@
 
 import os
 import glob
+import sys
 
 from PyQt5.QtCore import QThread, pyqtSignal, QProcess
 from PyQt5.QtWidgets import QDialog, QApplication, QMenu
@@ -61,13 +62,6 @@
         @param parent parent widget of this browser
         @type QWidget
         """
-        self.__protoc = Preferences.getProtobuf("protoc")
-        if self.__protoc == "":
-            self.__protoc = Utilities.isWindowsPlatform() and \
-                "protoc.exe" or "protoc"
-        if not Utilities.isinpath(self.__protoc):
-            self.__protoc = None
-        
         ProjectBaseBrowser.__init__(self, project,
                                     ProjectBrowserProtocolsType, parent)
         
@@ -96,12 +90,19 @@
         self.dirMultiMenuActions = []
         
         self.sourceMenu = QMenu(self)
-        if self.__protoc is not None:
-            self.sourceMenu.addAction(
-                self.tr('Compile protocol'), self.__compileProtocol)
-            self.sourceMenu.addAction(
-                self.tr('Compile all protocols'),
-                self.__compileAllProtocols)
+        self.sourceMenu.addAction(
+            self.tr('Compile protocol'), self.__compileProtocol)
+        self.sourceMenu.addAction(
+            self.tr('Compile all protocols'),
+            self.__compileAllProtocols)
+        self.sourceMenu.addSeparator()
+        self.sourceMenu.addAction(
+            self.tr('Compile protocol as grpc'),
+            lambda: self.__compileProtocol(grpc=True))
+        self.sourceMenu.addAction(
+            self.tr('Compile all protocols as grpc'),
+            lambda: self.__compileAllProtocols(grpc=True))
+        self.sourceMenu.addSeparator()
         self.sourceMenu.addAction(self.tr('Open'), self._openItem)
         self.sourceMenu.addSeparator()
         act = self.sourceMenu.addAction(
@@ -133,12 +134,19 @@
             self.tr('Configure Protobuf...'), self.__configureProtobuf)
 
         self.menu = QMenu(self)
-        if self.__protoc is not None:
-            self.menu.addAction(
-                self.tr('Compile protocol'), self.__compileProtocol)
-            self.menu.addAction(
-                self.tr('Compile all protocols'),
-                self.__compileAllProtocols)
+        self.menu.addAction(
+            self.tr('Compile protocol'), self.__compileProtocol)
+        self.menu.addAction(
+            self.tr('Compile all protocols'),
+            self.__compileAllProtocols)
+        self.menu.addSeparator()
+        self.menu.addAction(
+            self.tr('Compile protocol as grpc'),
+            lambda: self.__compileProtocol(grpc=True))
+        self.menu.addAction(
+            self.tr('Compile all protocols as grpc'),
+            lambda: self.__compileAllProtocols(grpc=True))
+        self.menu.addSeparator()
         self.menu.addAction(self.tr('Open'), self._openItem)
         self.menu.addSeparator()
         self.menu.addAction(
@@ -157,11 +165,14 @@
             self.tr('Configure Protobuf...'), self.__configureProtobuf)
 
         self.backMenu = QMenu(self)
-        if self.__protoc is not None:
-            self.backMenu.addAction(
-                self.tr('Compile all protocols'),
-                self.__compileAllProtocols)
-            self.backMenu.addSeparator()
+        self.backMenu.addAction(
+            self.tr('Compile all protocols'),
+            self.__compileAllProtocols)
+        self.backMenu.addSeparator()
+        self.backMenu.addAction(
+            self.tr('Compile all protocols as grpc'),
+            lambda: self.__compileAllProtocols(grpc=True))
+        self.backMenu.addSeparator()
         self.backMenu.addAction(
             self.tr('Add protocols...'), self.project.addProtoFiles)
         self.backMenu.addAction(
@@ -179,10 +190,14 @@
 
         # create the menu for multiple selected files
         self.multiMenu = QMenu(self)
-        if self.__protoc is not None:
-            self.multiMenu.addAction(
-                self.tr('Compile protocols'),
-                self.__compileSelectedProtocols)
+        self.multiMenu.addAction(
+            self.tr('Compile protocols'),
+            self.__compileSelectedProtocols)
+        self.multiMenu.addSeparator()
+        self.multiMenu.addAction(
+            self.tr('Compile protocols as grpc'),
+            lambda: self.__compileSelectedProtocols(grpc=True))
+        self.multiMenu.addSeparator()
         self.multiMenu.addAction(self.tr('Open'), self._openItem)
         self.multiMenu.addSeparator()
         act = self.multiMenu.addAction(
@@ -202,11 +217,13 @@
             self.tr('Configure Protobuf...'), self.__configureProtobuf)
 
         self.dirMenu = QMenu(self)
-        if self.__protoc is not None:
-            self.dirMenu.addAction(
-                self.tr('Compile all protocols'),
-                self.__compileAllProtocols)
-            self.dirMenu.addSeparator()
+        self.dirMenu.addAction(
+            self.tr('Compile all protocols'),
+            self.__compileAllProtocols)
+        self.dirMenu.addSeparator()
+        self.dirMenu.addAction(
+            self.tr('Compile all protocols as grpc'),
+            lambda: self.__compileAllProtocols(grpc=True))
         act = self.dirMenu.addAction(
             self.tr('Remove from project'), self._removeFile)
         self.dirMenuActions.append(act)
@@ -233,11 +250,13 @@
             self.tr('Configure Protobuf...'), self.__configureProtobuf)
         
         self.dirMultiMenu = QMenu(self)
-        if self.__protoc is not None:
-            self.dirMultiMenu.addAction(
-                self.tr('Compile all protocols'),
-                self.__compileAllProtocols)
-            self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Compile all protocols'),
+            self.__compileAllProtocols)
+        self.dirMultiMenu.addSeparator()
+        self.dirMultiMenu.addAction(
+            self.tr('Compile all protocols as grpc'),
+            lambda: self.__compileAllProtocols(grpc=True))
         self.dirMultiMenu.addAction(
             self.tr('Add protocols...'), self.project.addProtoFiles)
         self.dirMultiMenu.addAction(
@@ -437,6 +456,33 @@
     ##  Methods to handle the various compile commands
     ###########################################################################
     
+    def __getCompilerCommand(self, grpc):
+        """
+        Private method to get the compiler command.
+        
+        @param grpc flag indicating to get a grpc command
+        @type bool
+        @return tuple giving the executable and its parameter list
+        @rtype tuple of (str, list of str)
+        """
+        exe = None
+        exeArgs = []
+        
+        if grpc:
+            exe = Preferences.getProtobuf("grpcPython")
+            if exe == "":
+                exe = sys.executable
+            exeArgs = ['-m', 'grpc_tools.protoc']
+        else:
+            exe = Preferences.getProtobuf("protoc")
+            if exe == "":
+                exe = Utilities.isWindowsPlatform() and \
+                    "protoc.exe" or "protoc"
+            if not Utilities.isinpath(exe):
+                exe = None
+        
+        return exe, exeArgs
+    
     def __readStdout(self):
         """
         Private slot to handle the readyReadStandardOutput signal of the
@@ -471,18 +517,24 @@
             s += error
             self.appendStderr.emit(s)
         
-    def __compileProtocolDone(self, exitCode, exitStatus):
+    def __compileProtoDone(self, exitCode, exitStatus, grpc):
         """
         Private slot to handle the finished signal of the protoc process.
         
-        @param exitCode exit code of the process (integer)
-        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        @param exitCode exit code of the process
+        @type int
+        @param exitStatus exit status of the process
+        @type QProcess.ExitStatus
+        @param grpc flag indicating to compile as grpc files
+        @type bool
         """
         self.__compileRunning = False
         ui = e5App().getObject("UserInterface")
         if exitStatus == QProcess.NormalExit and exitCode == 0:
             path = os.path.dirname(self.__protoFile)
             fileList = glob.glob(os.path.join(path, "*_pb2.py"))
+            if grpc:
+                fileList += glob.glob(os.path.join(path, "*_pb2_grpc.py"))
             for file in fileList:
                 self.project.appendFile(file)
             if not self.noDialog and not ui.notificationsEnabled():
@@ -514,64 +566,87 @@
                         "The compilation of the protocol file failed."))
         self.compileProc = None
         
-    def __compileProto(self, fn, noDialog=False, progress=None):
+    def __compileProto(self, fn, noDialog=False, progress=None, grpc=False):
         """
         Private method to compile a .proto file to Python.
 
-        @param fn filename of the .proto file to be compiled (string)
-        @param noDialog flag indicating silent operations (boolean)
-        @param progress reference to the progress dialog (E5ProgressDialog)
-        @return reference to the compile process (QProcess)
+        @param fn filename of the .proto file to be compiled
+        @type str
+        @param noDialog flag indicating silent operations
+        @type bool
+        @param progress reference to the progress dialog
+        @type E5ProgressDialog
+        @param grpc flag indicating to compile as grpc files
+        @type bool
+        @return reference to the compile process
+        @rtype QProcess
         """
-        self.compileProc = QProcess()
-        args = []
-        
-        fn = os.path.join(self.project.ppath, fn)
-        self.__protoFile = fn
-        
-        srcPath = os.path.dirname(fn)
-        args.append("--proto_path={0}".format(srcPath))
-        args.append("--python_out={0}".format(srcPath))
-        args.append(fn)
-        
-        self.compileProc.finished.connect(self.__compileProtocolDone)
-        self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
-        self.compileProc.readyReadStandardError.connect(self.__readStderr)
-        
-        self.noDialog = noDialog
-        self.compileProc.start(self.__protoc, args)
-        procStarted = self.compileProc.waitForStarted(5000)
-        if procStarted:
-            self.__compileRunning = True
-            return self.compileProc
+        exe, exeArgs = self.__getCompilerCommand(grpc)
+        if exe:
+            self.compileProc = QProcess()
+            args = []
+            
+            fn = os.path.join(self.project.ppath, fn)
+            self.__protoFile = fn
+            
+            srcPath = os.path.dirname(fn)
+            args.append("--proto_path={0}".format(srcPath))
+            args.append("--python_out={0}".format(srcPath))
+            if grpc:
+                args.append("--grpc_python_out={0}".format(srcPath))
+            args.append(fn)
+            
+            self.compileProc.finished.connect(
+                lambda c, s: self.__compileProtoDone(c, s, grpc))
+            self.compileProc.readyReadStandardOutput.connect(self.__readStdout)
+            self.compileProc.readyReadStandardError.connect(self.__readStderr)
+            
+            self.noDialog = noDialog
+            self.compileProc.start(exe, exeArgs + args)
+            procStarted = self.compileProc.waitForStarted(5000)
+            if procStarted:
+                self.__compileRunning = True
+                return self.compileProc
+            else:
+                self.__compileRunning = False
+                if progress is not None:
+                    progress.cancel()
+                E5MessageBox.critical(
+                    self,
+                    self.tr('Process Generation Error'),
+                    self.tr(
+                        '<p>Could not start {0}.<br>'
+                        'Ensure that it is in the search path.</p>'
+                    ).format(exe))
+                return None
         else:
-            self.__compileRunning = False
-            if progress is not None:
-                progress.cancel()
             E5MessageBox.critical(
                 self,
-                self.tr('Process Generation Error'),
-                self.tr(
-                    '<p>Could not start {0}.<br>'
-                    'Ensure that it is in the search path.</p>'
-                ).format(self.__protoc))
+                self.tr('Compiler Invalid'),
+                self.tr('The configured compiler is invalid.'))
             return None
         
-    def __compileProtocol(self):
+    def __compileProtocol(self, grpc=False):
         """
         Private method to compile a protocol to Python.
+        
+        @param grpc flag indicating to compile as grpc files
+        @type bool
         """
-        if self.__protoc is not None:
+        if self.__getCompilerCommand(grpc)[0] is not None:
             itm = self.model().item(self.currentIndex())
             fn2 = itm.fileName()
             fn = self.project.getRelativePath(fn2)
-            self.__compileProto(fn)
+            self.__compileProto(fn, grpc=grpc)
         
-    def __compileAllProtocols(self):
+    def __compileAllProtocols(self, grpc=False):
         """
         Private method to compile all protocols to Python.
+        
+        @param grpc flag indicating to compile as grpc files
+        @type bool
         """
-        if self.__protoc is not None:
+        if self.__getCompilerCommand(grpc)[0] is not None:
             numProtos = len(self.project.pdata["PROTOCOLS"])
             progress = E5ProgressDialog(
                 self.tr("Compiling Protocols..."),
@@ -586,7 +661,7 @@
                 progress.setValue(i)
                 if progress.wasCanceled():
                     break
-                proc = self.__compileProto(fn, True, progress)
+                proc = self.__compileProto(fn, True, progress, grpc=grpc)
                 if proc is not None:
                     while proc.state() == QProcess.Running:
                         QApplication.processEvents()
@@ -598,11 +673,14 @@
             
             progress.setValue(numProtos)
         
-    def __compileSelectedProtocols(self):
+    def __compileSelectedProtocols(self, grpc=False):
         """
         Private method to compile selected protocols to Python.
+        
+        @param grpc flag indicating to compile as grpc files
+        @type bool
         """
-        if self.__protoc is not None:
+        if self.__getCompilerCommand(grpc)[0] is not None:
             items = self.getSelectedItems()
             
             files = [self.project.getRelativePath(itm.fileName())
@@ -621,7 +699,7 @@
                 progress.setValue(i)
                 if progress.wasCanceled():
                     break
-                proc = self.__compileProto(fn, True, progress)
+                proc = self.__compileProto(fn, True, progress, grpc=grpc)
                 if proc is not None:
                     while proc.state() == QProcess.Running:
                         QApplication.processEvents()

eric ide

mercurial