Exemplo n.º 1
0
def _main(args):
    # import pydm
    # app = QApplication([])
    # app = pydm.PyDMApplication()
    app = QApplication([])
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    from xicam.gui.windows import splash
    from xicam.core import msg

    if args.verbose in sys.argv:
        QErrorMessage.qtHandler()

    # start splash in subprocess
    splash_proc = QProcess()
    # splash_proc.started.connect(lambda: print('started splash'))
    # splash_proc.finished.connect(lambda: print('finished splashing'))
    log_file = msg.file_handler.baseFilename
    initial_length = os.path.getsize(log_file)
    splash_proc.start(sys.executable,
                      [splash.__file__, log_file,
                       str(initial_length)])

    from xicam.gui.windows.mainwindow import XicamMainWindow
    mainwindow = XicamMainWindow()
    while splash_proc.state() != QProcess.NotRunning:
        app.processEvents()
    # splash_proc.waitForFinished()
    mainwindow.show()

    # splash = splash.XicamSplashScreen(args=args)
    return sys.exit(app.exec_())
class LineProfilerWidget(QWidget):
    """
    Line profiler widget.
    """
    DATAPATH = get_conf_path('lineprofiler.results')
    VERSION = '0.0.1'
    redirect_stdio = Signal(bool)

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.setWindowTitle("Line profiler")

        self.output = None
        self.error_output = None

        self.use_colors = True

        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(
            self, icon=get_icon('run.png'),
            text=_("Profile by line"),
            tip=_("Run line profiler"),
            triggered=self.start, text_beside_icon=True)
        self.stop_button = create_toolbutton(
            self,
            icon=get_icon('terminate.png'),
            text=_("Stop"),
            tip=_("Stop current profiling"),
            text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        #self.filecombo.valid.connect(self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often.

        browse_button = create_toolbutton(
            self, icon=get_icon('fileopen.png'),
            tip=_('Select Python script'),
            triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(
            self, icon=get_icon('log.png'),
            text=_("Output"),
            text_beside_icon=True,
            tip=_("Show program's output"),
            triggered=self.show_log)

        self.datatree = LineProfilerDataTree(self)

        self.collapse_button = create_toolbutton(
            self,
            icon=get_icon('collapse.png'),
            triggered=lambda dD=-1: self.datatree.collapseAll(),
            tip=_('Collapse all'))
        self.expand_button = create_toolbutton(
            self,
            icon=get_icon('expand.png'),
            triggered=lambda dD=1: self.datatree.expandAll(),
            tip=_('Expand all'))

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.start_button.setEnabled(False)

        if not is_lineprofiler_installed():
            for widget in (self.datatree, self.filecombo, self.log_button,
                           self.start_button, self.stop_button, browse_button,
                           self.collapse_button, self.expand_button):
                widget.setDisabled(True)
            text = _(
                '<b>Please install the <a href="%s">line_profiler module</a></b>'
                ) % WEBSITE_URL
            self.datelabel.setText(text)
            self.datelabel.setOpenExternalLinks(True)
        else:
            pass  # self.show_data()

    def analyze(self, filename, wdir=None, args=None, pythonpath=None,
                use_colors=True):
        self.use_colors = use_colors
        if not is_lineprofiler_installed():
            return
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index = None  # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)

    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python script"), getcwd(),
            _("Python scripts")+" (*.py ; *.pyw)")
        self.redirect_stdio.emit(False)
        if filename:
            self.analyze(filename)

    def show_log(self):
        if self.output:
            TextEditor(self.output, title=_("Line profiler output"),
                       readonly=True, size=(700, 500)).exec_()

    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output, title=_("Line profiler output"),
                       readonly=True, size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath

        self.datelabel.setText(_('Profiling, please wait...'))

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(self.finished)
        self.stop_button.clicked.connect(self.process.kill)

        if pythonpath is not None:
            env = [to_text_string(_pth)
                   for _pth in self.process.systemEnvironment()]
            add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)

        self.output = ''
        self.error_output = ''

        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid
            # confusion with escape characters (otherwise, for example, '\t'
            # will be interpreted as a tabulation):
            filename = osp.normpath(filename).replace(os.sep, '/')
            p_args = ['-lvb', '-o', '"' + self.DATAPATH + '"',
                      '"' + filename + '"']
            if args:
                p_args.extend(programs.shell_split(args))
            executable = '"' + programs.find_program('kernprof') + '"'
            executable += ' ' + ' '.join(p_args)
            executable = executable.replace(os.sep, '/')
            self.process.start(executable)
        else:
            p_args = ['-lvb', '-o', self.DATAPATH, filename]
            if args:
                p_args.extend(programs.shell_split(args))
            executable = 'kernprof'
            self.process.start(executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(
            self.output is not None and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datatree.load_data(self.DATAPATH)
        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()
        self.datatree.show_tree()

        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                               time.localtime())
        self.datelabel.setText(date_text)
Exemplo n.º 3
0
class ProfilerWidget(QWidget):
    """
    Profiler widget
    """
    DATAPATH = get_conf_path('profiler.results')
    VERSION = '0.0.1'
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)
        
        self.setWindowTitle("Profiler")
        
        self.output = None
        self.error_output = None
        
        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None
        
        self.filecombo = PythonModulesComboBox(self)
        
        self.start_button = create_toolbutton(self, icon=ima.icon('run'),
                                    text=_("Profile"),
                                    tip=_("Run profiler"),
                                    triggered=lambda : self.start(),
                                    text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current profiling"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often. 

        browse_button = create_toolbutton(self, icon=ima.icon('fileopen'),
                                          tip=_('Select Python script'),
                                          triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self, icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Show program's output"),
                                            triggered=self.show_log)

        self.datatree = ProfilerDataTree(self)

        self.collapse_button = create_toolbutton(self,
                                                 icon=ima.icon('collapse'),
                                                 triggered=lambda dD:
                                                 self.datatree.change_view(-1),
                                                 tip=_('Collapse one level up'))
        self.expand_button = create_toolbutton(self,
                                               icon=ima.icon('expand'),
                                               triggered=lambda dD:
                                               self.datatree.change_view(1),
                                               tip=_('Expand one level down'))
                                
        self.save_button = create_toolbutton(self, text_beside_icon=True,
                                             text=_("Save data"),
                                             icon=ima.icon('filesave'),
                                             triggered=self.save_data,
                                             tip=_('Save profiling data'))
        self.load_button = create_toolbutton(self, text_beside_icon=True,
                            text=_("Load data"),
                            icon=ima.icon('fileimport'),
                            triggered=self.compare,
                            tip=_('Load profiling data for comparison'))
        self.clear_button = create_toolbutton(self, text_beside_icon=True,
                                              text=_("Clear comparison"),
                                              icon=ima.icon('editdelete'),
                                              triggered=self.clear)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        hlayout2.addWidget(self.save_button)
        hlayout2.addWidget(self.load_button)
        hlayout2.addWidget(self.clear_button)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)
        
        self.process = None
        self.set_running_state(False)
        self.start_button.setEnabled(False)
        self.clear_button.setEnabled(False)

        if not is_profiler_installed():
            # This should happen only on certain GNU/Linux distributions 
            # or when this a home-made Python build because the Python 
            # profilers are included in the Python standard library
            for widget in (self.datatree, self.filecombo,
                           self.start_button, self.stop_button):
                widget.setDisabled(True)
            url = 'http://docs.python.org/library/profile.html'
            text = '%s <a href=%s>%s</a>' % (_('Please install'), url,
                                             _("the Python profiler modules"))
            self.datelabel.setText(text)
        else:
            pass # self.show_data()
            
    def save_data(self):
        """Save data"""
        title = _( "Save profiler result")
        filename, _selfilter = getsavefilename(self, title,
                                               getcwd(),
                                               _("Profiler result")+" (*.Result)")
        if filename:
            self.datatree.save_data(filename)
            
    def compare(self):
        filename, _selfilter = getopenfilename(self, _("Select script to compare"),
                                               getcwd(), _("Profiler result")+" (*.Result)")
        if filename:
            self.datatree.compare(filename)
            self.show_data()
            self.clear_button.setEnabled(True)

    def clear(self):
        self.datatree.compare(None)
        self.datatree.hide_diff_cols(True)
        self.show_data()
        self.clear_button.setEnabled(False)

    def analyze(self, filename, wdir=None, args=None, pythonpath=None):
        if not is_profiler_installed():
            return
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index = None # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)
            
    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(self, _("Select Python script"),
                           getcwd(), _("Python scripts")+" (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)
        
    def show_log(self):
        if self.output:
            TextEditor(self.output, title=_("Profiler output"),
                       readonly=True, size=(700, 500)).exec_()
    
    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output, title=_("Profiler output"),
                       readonly=True, size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath
        
        self.datelabel.setText(_('Profiling, please wait...'))
        
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
                                          lambda: self.read_output(error=True))
        self.process.finished.connect(lambda ec, es=QProcess.ExitStatus:
                                      self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        if pythonpath is not None:
            env = [to_text_string(_pth)
                   for _pth in self.process.systemEnvironment()]
            baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)
        
        self.output = ''
        self.error_output = ''
        
        p_args = ['-m', 'cProfile', '-o', self.DATAPATH]
        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid 
            # confusion with escape characters (otherwise, for example, '\t' 
            # will be interpreted as a tabulation):
            p_args.append(osp.normpath(filename).replace(os.sep, '/'))
        else:
            p_args.append(filename)
        if args:
            p_args.extend(shell_split(args))
        executable = sys.executable
        if executable.endswith("spyder.exe"):
            # py2exe distribution
            executable = "python.exe"
        self.process.start(executable, p_args)
        
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
    
    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)
        
    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string( locale_codec.toUnicode(qba.data()) )
        if error:
            self.error_output += text
        else:
            self.output += text
        
    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)
                
    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()
        
    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()
        
        self.datatree.load_data(self.DATAPATH)
        self.datatree.show_tree()
            
        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                               time.localtime())
        self.datelabel.setText(date_text)
Exemplo n.º 4
0
class PylintWidget(QWidget):
    """
    Pylint widget
    """
    DATAPATH = get_conf_path('pylint.results')
    VERSION = '1.1.0'
    redirect_stdio = Signal(bool)
    start_analysis = Signal()

    def __init__(self,
                 parent,
                 max_entries=100,
                 options_button=None,
                 text_color=None,
                 prevrate_color=None):
        super().__init__(parent)

        self.setWindowTitle("Pylint")

        self.output = None
        self.error_output = None
        self.filename = None
        self.text_color = text_color
        self.prevrate_color = prevrate_color

        self.max_entries = max_entries
        self.rdata = []
        if osp.isfile(self.DATAPATH):
            try:
                data = pickle.loads(open(self.DATAPATH, 'rb').read())
                if data[0] == self.VERSION:
                    self.rdata = data[1:]
            except (EOFError, ImportError):
                pass

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(
            self,
            icon=ima.icon('run'),
            text=_("Analyze"),
            tip=_("Run analysis"),
            triggered=self.analyze_button_handler,
            text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current analysis"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        self.filecombo.valid.connect(self.check_new_file)

        browse_button = create_toolbutton(self,
                                          icon=ima.icon('fileopen'),
                                          tip=_('Select Python file'),
                                          triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self,
                                            icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Complete output"),
                                            triggered=self.show_log)
        self.treewidget = ResultsTree(self)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)
        if options_button:
            hlayout1.addWidget(options_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.show_data()

        if self.rdata:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())
            self.start_button.setEnabled(self.filecombo.is_valid())
        else:
            self.start_button.setEnabled(False)

    def check_new_file(self):
        fname = self.get_filename()
        if fname != self.filename:
            self.filename = fname
            self.show_data()

    def get_filename(self):
        """Get current filename in combobox."""
        return self.filecombo.currentText()

    @Slot(str)
    def set_filename(self, filename):
        """Set filename without performing code analysis."""
        filename = str(filename)  # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()

    def analyze(self, filename=None):
        """
        Perform code analysis for given `filename`.

        If `filename` is None default to current filename in combobox.
        """
        if filename is not None:
            self.set_filename(filename)

        if self.filecombo.is_valid():
            self.start()

    @Slot()
    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python file"), getcwd_or_home(),
            _("Python files") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)

    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.rdata = [(filename, data) for filename, data in self.rdata
                      if is_module_or_package(filename)]

    def get_filenames(self):
        return [filename for filename, _data in self.rdata]

    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.rdata):
            if fname == filename:
                return index, data
        else:
            return None, None

    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.rdata.pop(index)
        self.rdata.insert(0, (filename, data))
        self.save()

    def save(self):
        while len(self.rdata) > self.max_entries:
            self.rdata.pop(-1)
        pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2)

    @Slot()
    def show_log(self):
        if self.output:
            output_dialog = TextEditor(self.output,
                                       title=_("Pylint output"),
                                       parent=self,
                                       readonly=True)
            output_dialog.resize(700, 500)
            output_dialog.exec_()

    @Slot()
    def analyze_button_handler(self):
        """Try to start code analysis when Analyze button pressed."""
        self.start_analysis.emit()

    def get_pylintrc_path(self, filename):
        """Get the path to the most proximate pylintrc config to the file."""
        parent = self.parentWidget()
        if parent is not None:
            project_dir = parent.main.projects.get_active_project_path()
        else:
            project_dir = None
        search_paths = [
            osp.dirname(filename),  # File's directory
            getcwd_or_home(),  # Working directory
            project_dir,  # Project directory
            osp.expanduser("~"),  # Home directory
        ]
        return get_pylintrc_path(search_paths=search_paths)

    @Slot()
    def start(self):
        """Start the code analysis."""
        filename = str(self.filecombo.currentText())

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(getcwd_or_home())
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(
            lambda ec, es=QProcess.ExitStatus: self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        self.output = ''
        self.error_output = ''

        if PYLINT_VER is not None:
            pylint_args = [
                '-m', 'pylint', '--output-format=text', '--msg-template='
                "'{msg_id}:{symbol}:{line:3d},{column}: {msg}'"
            ]

        pylintrc_path = self.get_pylintrc_path(filename=filename)
        if pylintrc_path is not None:
            pylint_args += ['--rcfile={}'.format(pylintrc_path)]

        pylint_args.append(filename)
        processEnvironment = QProcessEnvironment()
        processEnvironment.insert("PYTHONIOENCODING", "utf8")
        self.process.setProcessEnvironment(processEnvironment)

        self.process.start(sys.executable, pylint_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = str(qba.data(), 'utf-8')
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        if not self.output:
            if self.error_output:
                QMessageBox.critical(self, _("Error"), self.error_output)
                print("pylint error:\n\n" + self.error_output, file=sys.stderr)
            return

        # Convention, Refactor, Warning, Error
        results = {'C:': [], 'R:': [], 'W:': [], 'E:': []}
        txt_module = '************* Module '

        module = ''  # Should not be needed - just in case something goes wrong
        for line in self.output.splitlines():
            if line.startswith(txt_module):
                # New module
                module = line[len(txt_module):]
                continue
            # Supporting option include-ids: ('R3873:' instead of 'R:')
            if not re.match(r'^[CRWE]+([0-9]{4})?:', line):
                continue
            items = {}
            idx_0 = 0
            idx_1 = 0
            key_names = ["msg_id", "message_name", "line_nb", "message"]
            for key_idx, key_name in enumerate(key_names):
                if key_idx == len(key_names) - 1:
                    idx_1 = len(line)
                else:
                    idx_1 = line.find(":", idx_0)

                if idx_1 < 0:
                    break
                item = line[(idx_0):idx_1]

                if not item:
                    break

                if key_name == "line_nb":
                    item = int(item.split(',')[0])

                items[key_name] = item
                idx_0 = idx_1 + 1
            else:
                pylint_item = (module, items["line_nb"], items["message"],
                               items["msg_id"], items["message_name"])
                results[line[0] + ':'].append(pylint_item)

        # Rate
        rate = None
        txt_rate = 'Your code has been rated at '
        i_rate = self.output.find(txt_rate)
        if i_rate > 0:
            i_rate_end = self.output.find('/10', i_rate)
            if i_rate_end > 0:
                rate = self.output[i_rate + len(txt_rate):i_rate_end]

        # Previous run
        previous = ''
        if rate is not None:
            txt_prun = 'previous run: '
            i_prun = self.output.find(txt_prun, i_rate_end)
            if i_prun > 0:
                i_prun_end = self.output.find('/10', i_prun)
                previous = self.output[i_prun + len(txt_prun):i_prun_end]

        filename = str(self.filecombo.currentText())
        self.set_data(filename, (time.localtime(), rate, previous, results))
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = str(self.filecombo.currentText())
        if not filename:
            return

        _index, data = self.get_data(filename)
        if data is None:
            text = _('Source code has not been rated yet.')
            self.treewidget.clear_results()
            date_text = ''
        else:
            datetime, rate, previous_rate, results = data
            if rate is None:
                text = _('Analysis did not succeed '
                         '(see output for more details).')
                self.treewidget.clear_results()
                date_text = ''
            else:
                text_style = "<span style=\'color: %s\'><b>%s </b></span>"
                rate_style = "<span style=\'color: %s\'><b>%s</b></span>"
                prevrate_style = "<span style=\'color: %s\'>%s</span>"
                color = "#FF0000"
                if float(rate) > 5.:
                    color = "#22AA22"
                elif float(rate) > 3.:
                    color = "#EE5500"
                text = _('Global evaluation:')
                text = ((text_style % (self.text_color, text)) +
                        (rate_style % (color, ('%s/10' % rate))))
                if previous_rate:
                    text_prun = _('previous run:')
                    text_prun = ' (%s %s/10)' % (text_prun, previous_rate)
                    text += prevrate_style % (self.prevrate_color, text_prun)
                self.treewidget.set_results(filename, results)
                date = time.strftime("%Y-%m-%d %H:%M:%S", datetime)
                date_text = text_style % (self.text_color, date)

        self.ratelabel.setText(text)
        self.datelabel.setText(date_text)
Exemplo n.º 5
0
class LSPClient(QObject, LSPMethodProviderMixIn):
    """Language Server Protocol v3.0 client implementation."""
    #: Signal to inform the editor plugin that the client has
    #  started properly and it's ready to be used.
    sig_initialize = Signal(dict, str)

    #: Signal to report internal server errors through Spyder's
    #  facilities.
    sig_server_error = Signal(str)

    #: Signal to warn the user when either the transport layer or the
    #  server went down
    sig_went_down = Signal(str)

    def __init__(self, parent,
                 server_settings={},
                 folder=getcwd_or_home(),
                 language='python'):
        QObject.__init__(self)
        self.manager = parent
        self.zmq_in_socket = None
        self.zmq_out_socket = None
        self.zmq_in_port = None
        self.zmq_out_port = None
        self.transport = None
        self.server = None
        self.stdio_pid = None
        self.notifier = None
        self.language = language

        self.initialized = False
        self.ready_to_close = False
        self.request_seq = 1
        self.req_status = {}
        self.watched_files = {}
        self.watched_folders = {}
        self.req_reply = {}
        self.server_unresponsive = False
        self.transport_unresponsive = False

        # Select a free port to start the server.
        # NOTE: Don't use the new value to set server_setttings['port']!!
        # That's not required because this doesn't really correspond to a
        # change in the config settings of the server. Else a server
        # restart would be generated when doing a
        # workspace/didChangeConfiguration request.
        if not server_settings['external']:
            self.server_port = select_port(
                default_port=server_settings['port'])
        else:
            self.server_port = server_settings['port']
        self.server_host = server_settings['host']

        self.external_server = server_settings.get('external', False)
        self.stdio = server_settings.get('stdio', False)

        # Setting stdio on implies that external_server is off
        if self.stdio and self.external_server:
            error = ('If server is set to use stdio communication, '
                     'then it cannot be an external server')
            logger.error(error)
            raise AssertionError(error)

        self.folder = folder
        self.configurations = server_settings.get('configurations', {})
        self.client_capabilites = CLIENT_CAPABILITES
        self.server_capabilites = SERVER_CAPABILITES
        self.context = zmq.Context()

        # To set server args
        self._server_args = server_settings.get('args', '')
        self._server_cmd = server_settings['cmd']

        # Save requests name and id. This is only necessary for testing.
        self._requests = []

    def _get_log_filename(self, kind):
        """
        Get filename to redirect server or transport logs to in
        debugging mode.

        Parameters
        ----------
        kind: str
            It can be "server" or "transport".
        """
        if get_debug_level() == 0:
            return None

        fname = '{0}_{1}_{2}.log'.format(kind, self.language, os.getpid())
        location = get_conf_path(osp.join('lsp_logs', fname))

        # Create directory that contains the file, in case it doesn't
        # exist
        if not osp.exists(osp.dirname(location)):
            os.makedirs(osp.dirname(location))

        return location

    @property
    def server_log_file(self):
        """
        Filename to redirect the server process stdout/stderr output.
        """
        return self._get_log_filename('server')

    @property
    def transport_log_file(self):
        """
        Filename to redirect the transport process stdout/stderr
        output.
        """
        return self._get_log_filename('transport')

    @property
    def server_args(self):
        """Arguments for the server process."""
        args = []
        if self.language == 'python':
            args += [sys.executable, '-m']
        args += [self._server_cmd]

        # Replace host and port placeholders
        host_and_port = self._server_args.format(
            host=self.server_host,
            port=self.server_port)
        if len(host_and_port) > 0:
            args += host_and_port.split(' ')

        if self.language == 'python' and get_debug_level() > 0:
            args += ['--log-file', self.server_log_file]
            if get_debug_level() == 2:
                args.append('-v')
            elif get_debug_level() == 3:
                args.append('-vv')

        return args

    @property
    def transport_args(self):
        """Arguments for the transport process."""
        args = [
            sys.executable,
            '-u',
            osp.join(LOCATION, 'transport', 'main.py'),
            '--folder', self.folder,
            '--transport-debug', str(get_debug_level())
        ]

        # Replace host and port placeholders
        host_and_port = '--server-host {host} --server-port {port} '.format(
            host=self.server_host,
            port=self.server_port)
        args += host_and_port.split(' ')

        # Add socket ports
        args += ['--zmq-in-port', str(self.zmq_out_port),
                 '--zmq-out-port', str(self.zmq_in_port)]

        # Adjustments for stdio/tcp
        if self.stdio:
            args += ['--stdio-server']
            if get_debug_level() > 0:
                args += ['--server-log-file', self.server_log_file]
            args += self.server_args
        else:
            args += ['--external-server']

        return args

    def create_transport_sockets(self):
        """Create PyZMQ sockets for transport."""
        self.zmq_out_socket = self.context.socket(zmq.PAIR)
        self.zmq_out_port = self.zmq_out_socket.bind_to_random_port(
            'tcp://{}'.format(LOCALHOST))
        self.zmq_in_socket = self.context.socket(zmq.PAIR)
        self.zmq_in_socket.set_hwm(0)
        self.zmq_in_port = self.zmq_in_socket.bind_to_random_port(
            'tcp://{}'.format(LOCALHOST))

    @Slot(QProcess.ProcessError)
    def handle_process_errors(self, error):
        """Handle errors with the transport layer or server processes."""
        self.sig_went_down.emit(self.language)

    def start_server(self):
        """Start server."""
        # This is not necessary if we're trying to connect to an
        # external server
        if self.external_server or self.stdio:
            return

        logger.info('Starting server: {0}'.format(' '.join(self.server_args)))

        # Create server process
        self.server = QProcess(self)
        env = self.server.processEnvironment()

        # Use local PyLS instead of site-packages one.
        if DEV or running_under_pytest():
            running_in_ci = bool(os.environ.get('CI'))
            if os.name != 'nt' or os.name == 'nt' and not running_in_ci:
                env.insert('PYTHONPATH', os.pathsep.join(sys.path)[:])

        # Adjustments for the Python language server.
        if self.language == 'python':
            # Set the PyLS current working to an empty dir inside
            # our config one. This avoids the server to pick up user
            # files such as random.py or string.py instead of the
            # standard library modules named the same.
            cwd = osp.join(get_conf_path(), 'lsp_paths', 'cwd')
            if not osp.exists(cwd):
                os.makedirs(cwd)

            # On Windows, some modules (notably Matplotlib)
            # cause exceptions if they cannot get the user home.
            # So, we need to pass the USERPROFILE env variable to
            # the PyLS.
            if os.name == "nt" and "USERPROFILE" in os.environ:
                env.insert("USERPROFILE", os.environ["USERPROFILE"])
        else:
            # There's no need to define a cwd for other servers.
            cwd = None

            # Most LSP servers spawn other processes, which may require
            # some environment variables.
            for var in os.environ:
                env.insert(var, os.environ[var])
            logger.info('Server process env variables: {0}'.format(env.keys()))

        # Setup server
        self.server.setProcessEnvironment(env)
        self.server.errorOccurred.connect(self.handle_process_errors)
        self.server.setWorkingDirectory(cwd)
        self.server.setProcessChannelMode(QProcess.MergedChannels)
        if self.server_log_file is not None:
            self.server.setStandardOutputFile(self.server_log_file)

        # Start server
        self.server.start(self.server_args[0], self.server_args[1:])

    def start_transport(self):
        """Start transport layer."""
        logger.info('Starting transport for {1}: {0}'
                    .format(' '.join(self.transport_args), self.language))

        # Create transport process
        self.transport = QProcess(self)
        env = self.transport.processEnvironment()

        # Most LSP servers spawn other processes other than Python, which may
        # require some environment variables
        if self.language != 'python' and self.stdio:
            for var in os.environ:
                env.insert(var, os.environ[var])
            logger.info('Transport process env variables: {0}'.format(
                env.keys()))

        self.transport.setProcessEnvironment(env)

        # Modifying PYTHONPATH to run transport in development mode or
        # tests
        if DEV or running_under_pytest():
            if running_under_pytest():
                env.insert('PYTHONPATH', os.pathsep.join(sys.path)[:])
            else:
                env.insert('PYTHONPATH', os.pathsep.join(sys.path)[1:])
            self.transport.setProcessEnvironment(env)

        # Set up transport
        self.transport.errorOccurred.connect(self.handle_process_errors)
        if self.stdio:
            self.transport.setProcessChannelMode(QProcess.SeparateChannels)
            if self.transport_log_file is not None:
                self.transport.setStandardErrorFile(self.transport_log_file)
        else:
            self.transport.setProcessChannelMode(QProcess.MergedChannels)
            if self.transport_log_file is not None:
                self.transport.setStandardOutputFile(self.transport_log_file)

        # Start transport
        self.transport.start(self.transport_args[0], self.transport_args[1:])

    def start(self):
        """Start client."""
        # NOTE: DO NOT change the order in which these methods are called.
        self.create_transport_sockets()
        self.start_server()
        self.start_transport()

        # Create notifier
        fid = self.zmq_in_socket.getsockopt(zmq.FD)
        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
        self.notifier.activated.connect(self.on_msg_received)

        # This is necessary for tests to pass locally!
        logger.debug('LSP {} client started!'.format(self.language))

    def stop(self):
        """Stop transport and server."""
        logger.info('Stopping {} client...'.format(self.language))
        if self.notifier is not None:
            self.notifier.activated.disconnect(self.on_msg_received)
            self.notifier.setEnabled(False)
            self.notifier = None
        if self.transport is not None:
            self.transport.kill()
        self.context.destroy()
        if self.server is not None:
            self.server.kill()

    def is_transport_alive(self):
        """Detect if transport layer is alive."""
        state = self.transport.state()
        return state != QProcess.NotRunning

    def is_stdio_alive(self):
        """Check if an stdio server is alive."""
        alive = True
        if not psutil.pid_exists(self.stdio_pid):
            alive = False
        else:
            try:
                pid_status = psutil.Process(self.stdio_pid).status()
            except psutil.NoSuchProcess:
                pid_status = ''
            if pid_status == psutil.STATUS_ZOMBIE:
                alive = False
        return alive

    def is_server_alive(self):
        """Detect if a tcp server is alive."""
        state = self.server.state()
        return state != QProcess.NotRunning

    def is_down(self):
        """
        Detect if the transport layer or server are down to inform our
        users about it.
        """
        is_down = False
        if self.transport and not self.is_transport_alive():
            logger.debug(
                "Transport layer for {} is down!!".format(self.language))
            if not self.transport_unresponsive:
                self.transport_unresponsive = True
                self.sig_went_down.emit(self.language)
            is_down = True

        if self.server and not self.is_server_alive():
            logger.debug("LSP server for {} is down!!".format(self.language))
            if not self.server_unresponsive:
                self.server_unresponsive = True
                self.sig_went_down.emit(self.language)
            is_down = True

        if self.stdio_pid and not self.is_stdio_alive():
            logger.debug("LSP server for {} is down!!".format(self.language))
            if not self.server_unresponsive:
                self.server_unresponsive = True
                self.sig_went_down.emit(self.language)
            is_down = True

        return is_down

    def send(self, method, params, kind):
        """Send message to transport."""
        if self.is_down():
            return

        if ClientConstants.CANCEL in params:
            return
        _id = self.request_seq
        if kind == MessageKind.REQUEST:
            msg = {
                'id': self.request_seq,
                'method': method,
                'params': params
            }
            self.req_status[self.request_seq] = method
        elif kind == MessageKind.RESPONSE:
            msg = {
                'id': self.request_seq,
                'result': params
            }
        elif kind == MessageKind.NOTIFICATION:
            msg = {
                'method': method,
                'params': params
            }

        logger.debug('Perform request {0} with id {1}'.format(method, _id))

        # Save requests to check their ordering.
        if running_under_pytest():
            self._requests.append((_id, method))

        # Try sending a message. If the send queue is full, keep trying for a
        # a second before giving up.
        timeout = 1
        start_time = time.time()
        timeout_time = start_time + timeout
        while True:
            try:
                self.zmq_out_socket.send_pyobj(msg, flags=zmq.NOBLOCK)
                self.request_seq += 1
                return int(_id)
            except zmq.error.Again:
                if time.time() > timeout_time:
                    self.sig_went_down.emit(self.language)
                    return
                # The send queue is full! wait 0.1 seconds before retrying.
                if self.initialized:
                    logger.warning("The send queue is full! Retrying...")
                time.sleep(.1)

    @Slot()
    def on_msg_received(self):
        """Process received messages."""
        self.notifier.setEnabled(False)
        while True:
            try:
                # events = self.zmq_in_socket.poll(1500)
                resp = self.zmq_in_socket.recv_pyobj(flags=zmq.NOBLOCK)

                try:
                    method = resp['method']
                    logger.debug(
                        '{} response: {}'.format(self.language, method))
                except KeyError:
                    pass

                if 'error' in resp:
                    logger.debug('{} Response error: {}'
                                 .format(self.language, repr(resp['error'])))
                    if self.language == 'python':
                        # Show PyLS errors in our error report dialog only in
                        # debug or development modes
                        if get_debug_level() > 0 or DEV:
                            message = resp['error'].get('message', '')
                            traceback = (resp['error'].get('data', {}).
                                         get('traceback'))
                            if traceback is not None:
                                traceback = ''.join(traceback)
                                traceback = traceback + '\n' + message
                                self.sig_server_error.emit(traceback)
                        req_id = resp['id']
                        if req_id in self.req_reply:
                            self.req_reply[req_id](None, {'params': []})
                elif 'method' in resp:
                    if resp['method'][0] != '$':
                        if 'id' in resp:
                            self.request_seq = int(resp['id'])
                        if resp['method'] in self.handler_registry:
                            handler_name = (
                                self.handler_registry[resp['method']])
                            handler = getattr(self, handler_name)
                            handler(resp['params'])
                elif 'result' in resp:
                    if resp['result'] is not None:
                        req_id = resp['id']
                        if req_id in self.req_status:
                            req_type = self.req_status[req_id]
                            if req_type in self.handler_registry:
                                handler_name = self.handler_registry[req_type]
                                handler = getattr(self, handler_name)
                                handler(resp['result'], req_id)
                                self.req_status.pop(req_id)
                                if req_id in self.req_reply:
                                    self.req_reply.pop(req_id)
            except RuntimeError:
                # This is triggered when a codeeditor instance has been
                # removed before the response can be processed.
                pass
            except zmq.ZMQError:
                self.notifier.setEnabled(True)
                return

    def perform_request(self, method, params):
        if method in self.sender_registry:
            handler_name = self.sender_registry[method]
            handler = getattr(self, handler_name)
            _id = handler(params)
            if 'response_callback' in params:
                if params['requires_response']:
                    self.req_reply[_id] = params['response_callback']
            return _id

    # ------ LSP initialization methods --------------------------------
    @handles(SERVER_READY)
    @send_request(method=LSPRequestTypes.INITIALIZE)
    def initialize(self, params, *args, **kwargs):
        self.stdio_pid = params['pid']
        pid = self.transport.processId() if not self.external_server else None
        params = {
            'processId': pid,
            'rootUri': pathlib.Path(osp.abspath(self.folder)).as_uri(),
            'capabilities': self.client_capabilites,
            'trace': TRACE
        }
        return params

    @send_request(method=LSPRequestTypes.SHUTDOWN)
    def shutdown(self):
        params = {}
        return params

    @handles(LSPRequestTypes.SHUTDOWN)
    def handle_shutdown(self, response, *args):
        self.ready_to_close = True

    @send_notification(method=LSPRequestTypes.EXIT)
    def exit(self):
        params = {}
        return params

    @handles(LSPRequestTypes.INITIALIZE)
    def process_server_capabilities(self, server_capabilites, *args):
        """
        Register server capabilities and inform other plugins that it's
        available.
        """
        # Update server capabilities with the info sent by the server.
        server_capabilites = server_capabilites['capabilities']

        if isinstance(server_capabilites['textDocumentSync'], int):
            kind = server_capabilites['textDocumentSync']
            server_capabilites['textDocumentSync'] = TEXT_DOCUMENT_SYNC_OPTIONS
            server_capabilites['textDocumentSync']['change'] = kind
        if server_capabilites['textDocumentSync'] is None:
            server_capabilites.pop('textDocumentSync')

        self.server_capabilites.update(server_capabilites)

        # The initialized notification needs to be the first request sent by
        # the client according to the protocol.
        self.initialized = True
        self.initialized_call()

        # This sends a DidChangeConfiguration request to pass to the server
        # the configurations set by the user in our config system.
        self.send_configurations(self.configurations)

        # Inform other plugins that the server is up.
        self.sig_initialize.emit(self.server_capabilites, self.language)

    @send_notification(method=LSPRequestTypes.INITIALIZED)
    def initialized_call(self):
        params = {}
        return params

    # ------ Settings queries --------------------------------
    @property
    def support_multiple_workspaces(self):
        workspace_settings = self.server_capabilites['workspace']
        return workspace_settings['workspaceFolders']['supported']

    @property
    def support_workspace_update(self):
        workspace_settings = self.server_capabilites['workspace']
        return workspace_settings['workspaceFolders']['changeNotifications']
Exemplo n.º 6
0
class CondaProcess(QObject):
    """Conda API modified to work with QProcess instead of popen."""

    # Signals
    sig_finished = Signal(str, object, str)
    sig_partial = Signal(str, object, str)
    sig_started = Signal()

    ENCODING = "ascii"
    ROOT_PREFIX = None

    def __init__(self, parent):
        QObject.__init__(self, parent)
        self._parent = parent
        self._output = None
        self._partial = None
        self._stdout = None
        self._error = None
        self._parse = False
        self._function_called = ""
        self._name = None
        self._process = QProcess()
        self.set_root_prefix()

        # Signals
        self._process.finished.connect(self._call_conda_ready)
        self._process.readyReadStandardOutput.connect(self._call_conda_partial)

    # --- Helpers
    # -------------------------------------------------------------------------
    def _is_running(self):
        return self._process.state() != QProcess.NotRunning

    def _is_not_running(self):
        return self._process.state() == QProcess.NotRunning

    def _call_conda_partial(self):
        """ """
        stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(stdout, CondaProcess.ENCODING)

        stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(stderr, CondaProcess.ENCODING)

        if self._parse:
            try:
                self._output = json.loads(stdout)
            except Exception:
                # Result is a partial json. Can only be parsed when finished
                self._output = stdout
        else:
            self._output = stdout

        self._partial = self._output
        self._stdout = self._output
        self._error = stderr

        self.sig_partial.emit(self._function_called, self._partial, self._error)

    def _call_conda_ready(self):
        """Function called when QProcess in _call_conda finishes task."""
        function = self._function_called

        if self._stdout is None:
            stdout = to_text_string(self._process.readAllStandardOutput(), encoding=CondaProcess.ENCODING)
        else:
            stdout = self._stdout

        if self._error is None:
            stderr = to_text_string(self._process.readAllStandardError(), encoding=CondaProcess.ENCODING)
        else:
            stderr = self._error

        if function == "get_conda_version":
            pat = re.compile(r"conda:?\s+(\d+\.\d\S+|unknown)")
            m = pat.match(stderr.strip())
            if m is None:
                m = pat.match(stdout.strip())
            if m is None:
                raise Exception("output did not match: {0}".format(stderr))
            self._output = m.group(1)
        elif function == "config_path":
            result = self._output
            self._output = result["rc_path"]
        elif function == "config_get":
            result = self._output
            self._output = result["get"]
        elif (
            function == "config_delete"
            or function == "config_add"
            or function == "config_set"
            or function == "config_remove"
        ):
            result = self._output
            self._output = result.get("warnings", [])
        elif function == "pip":
            result = []
            lines = self._output.split("\n")
            for line in lines:
                if "<pip>" in line:
                    temp = line.split()[:-1] + ["pip"]
                    result.append("-".join(temp))
            self._output = result

        if stderr.strip():
            self._error = stderr

        self._parse = False

        self.sig_finished.emit(self._function_called, self._output, self._error)

    def _get_prefix_envname_helper(self, name, envs):
        """ """
        if name == "root":
            return CondaProcess.ROOT_PREFIX

        for prefix in envs:
            if basename(prefix) == name:
                return prefix
        return None

    def _abspath(self, abspath):
        """ """
        if abspath:
            if sys.platform == "win32":
                python = join(CondaProcess.ROOT_PREFIX, "python.exe")
                conda = join(CondaProcess.ROOT_PREFIX, "Scripts", "conda-script.py")
            else:
                python = join(CondaProcess.ROOT_PREFIX, "bin/python")
                conda = join(CondaProcess.ROOT_PREFIX, "bin/conda")
            cmd_list = [python, conda]
        else:
            # Just use whatever conda is on the path
            cmd_list = ["conda"]

        return cmd_list

    def _call_conda(self, extra_args, abspath=True):
        """ """
        if self._is_not_running():
            cmd_list = self._abspath(abspath)
            cmd_list.extend(extra_args)
            self._error, self._output = None, None
            self._process.start(cmd_list[0], cmd_list[1:])
            self.sig_started.emit()

    def _call_and_parse(self, extra_args, abspath=True):
        """ """
        self._parse = True
        self._call_conda(extra_args, abspath=abspath)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        """ """
        cmd_list = []
        if kwargs.get("override_channels", False) and "channel" not in kwargs:
            raise TypeError("conda search: override_channels requires channel")

        if "env" in kwargs:
            cmd_list.extend(["--name", kwargs.pop("env")])
        if "prefix" in kwargs:
            cmd_list.extend(["--prefix", kwargs.pop("prefix")])
        if "channel" in kwargs:
            channel = kwargs.pop("channel")
            if isinstance(channel, str):
                cmd_list.extend(["--channel", channel])
            else:
                cmd_list.append("--channel")
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append("--" + key.replace("_", "-"))

        return cmd_list

    # --- Public api
    # ------------------------------------------------------------------------
    def linked(self, prefix):
        """
        Return the (set of canonical names) of linked packages in `prefix`.
        """
        if not isdir(prefix):
            raise Exception("no such directory: {0}".format(prefix))

        meta_dir = join(prefix, "conda-meta")

        if not isdir(meta_dir):
            # we might have nothing in linked (and no conda-meta directory)
            result = set()

        result = set(fn[:-5] for fn in os.listdir(meta_dir) if fn.endswith(".json"))
        self.sig_finished.emit("linked", result, "")
        return result

    def split_canonical_name(self, cname):
        """
        Split a canonical package name into (name, version, build) strings.
        """
        result = tuple(cname.rsplit("-", 2))
        self.sig_finished.emit("split_canonical_name", result, "")
        return result

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        """
        if prefix:
            CondaProcess.ROOT_PREFIX = prefix
        else:
            # Find conda instance, and then use info() to get 'root_prefix'
            if CondaProcess.ROOT_PREFIX is None:
                info = self.info(abspath=False)
                CondaProcess.ROOT_PREFIX = info["root_prefix"]

    def get_conda_version(self):
        """
        Return the version of conda being used (invoked) as a string.
        """
        if self._is_not_running():
            self._function_called = "get_conda_version"
            self._call_conda(["--version"])

    def get_envs(self):
        """
        Return all of the (named) environment (this does not include the root
        environment), as a list of absolute path to their prefixes.
        """
        if self._is_not_running():
            info = self.info()
            result = info["envs"]
            self.sig_finished.emit("get_envs", result, "")
            return result

    def get_prefix_envname(self, name):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        if self._is_not_running():
            if name == "root":
                pref = CondaProcess.ROOT_PREFIX
            for prefix in self.get_envs():
                if basename(prefix) == name:
                    pref = prefix
                    break
            self.sig_finished.emit("get_prefix_envname", pref, "")
            return pref

    def info(self, abspath=True):
        """
        Return a dictionary with configuration information.

        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
        if self._is_not_running():
            qprocess = QProcess()
            cmd_list = self._abspath(abspath)
            cmd_list.extend(["info", "--json"])
            qprocess.start(cmd_list[0], cmd_list[1:])
            qprocess.waitForFinished()
            output = qprocess.readAllStandardOutput()
            output = handle_qbytearray(output, CondaProcess.ENCODING)
            info = json.loads(output)

            self.sig_finished.emit("info", str(info), "")
            return info

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.

        Structure is {
            'package_name': [{
                'depends': list,
                'version': str,
                'name': str,
                'license': str,
                'fn': ...,
                ...
            }]
        }
        """
        if self._is_not_running():
            self._function_called = "package_info"
            self._call_and_parse(["info", package, "--json"], abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ["search", "--json"]

        if regex and spec:
            raise TypeError("conda search: only one of regex or spec allowed")

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(["--spec", spec])

        if "platform" in kwargs:
            platform = kwargs.pop("platform")
            platforms = ("win-32", "win-64", "osx-64", "linux-32", "linux-64")
            if platform not in platforms:
                raise TypeError("conda search: platform must be one of " + ", ".join(platforms))
            cmd_list.extend(["--platform", platform])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs, ("canonical", "unknown", "use_index_cache", "outdated", "override_channels")
            )
        )

        if self._is_not_running():
            self._function_called = "search"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def share(self, prefix):
        """
        Create a "share package" of the environment located in `prefix`,
        and return a dictionary with (at least) the following keys:
          - 'path': the full path to the created package
          - 'warnings': a list of warnings

        This file is created in a temp directory, and it is the callers
        responsibility to remove this directory (after the file has been
        handled in some way).
        """
        if self._is_not_running:
            self._function_called = "share"
            self._call_and_parse(["share", "--json", "--prefix", prefix])

    def create(self, name=None, path=None, pkgs=None):
        """
        Create an environment either by name or path with a specified set of
        packages
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError("must specify a list of one or more packages to " "install into new environment")

        cmd_list = ["create", "--yes", "--quiet"]
        if name:
            ref = name
            search = [os.path.join(d, name) for d in self.info()["envs_dirs"]]
            cmd_list = ["create", "--yes", "--quiet", "--name", name]
        elif path:
            ref = path
            search = [path]
            cmd_list = ["create", "--yes", "--quiet", "--prefix", path]
        else:
            raise TypeError("must specify either an environment name or a path" " for new environment")

        if any(os.path.exists(path) for path in search):
            raise CondaEnvExistsError("Conda environment [%s] already exists" % ref)

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = "create"
            self._call_conda(cmd_list)

    def install(self, name=None, path=None, pkgs=None, dep=True):
        """
        Install packages into an environment either by name or path with a
        specified set of packages
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError("must specify a list of one or more packages to " "install into existing environment")

        cmd_list = ["install", "--yes", "--json", "--force-pscheck"]
        if name:
            cmd_list.extend(["--name", name])
        elif path:
            cmd_list.extend(["--prefix", path])
        else:  # just install into the current environment, whatever that is
            pass

        cmd_list.extend(pkgs)

        if not dep:
            cmd_list.extend(["--no-deps"])

        if self._is_not_running:
            self._function_called = "install"
            self._call_conda(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ["update", "--json", "--quiet", "--yes"]

        if not pkgs and not kwargs.get("all"):
            raise TypeError(
                "Must specify at least one package to update, \
                            or all=True."
            )

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                (
                    "dry_run",
                    "no_deps",
                    "override_channels",
                    "no_pin",
                    "force",
                    "all",
                    "use_index_cache",
                    "use_local",
                    "alt_hint",
                ),
            )
        )

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = "update"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def remove(self, *pkgs, **kwargs):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        cmd_list = ["remove", "--json", "--quiet", "--yes", "--force-pscheck"]

        if not pkgs and not kwargs.get("all"):
            raise TypeError(
                "Must specify at least one package to remove, \
                            or all=True."
            )

        if kwargs.get("name") and kwargs.get("path"):
            raise TypeError("conda remove: At most one of name, path allowed")

        if kwargs.get("name"):
            cmd_list.extend(["--name", kwargs.pop("name")])

        if kwargs.get("path"):
            cmd_list.extend(["--prefix", kwargs.pop("path")])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs, ("dry_run", "features", "override_channels", "no_pin", "force", "all")
            )
        )

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = "remove"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def remove_environment(self, name=None, path=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
        if self._is_not_running:
            self._function_called = "remove_environment"
            self.remove(name=name, path=path, all=True, **kwargs)

    def clone_environment(self, clone, name=None, path=None, **kwargs):
        """
        Clone the environment ``clone`` into ``name`` or ``path``.
        """
        cmd_list = ["create", "--json", "--quiet"]

        if (name and path) or not (name or path):
            raise TypeError(
                "conda clone_environment: exactly one of name or \
                            path required"
            )

        if name:
            cmd_list.extend(["--name", name])

        if path:
            cmd_list.extend(["--prefix", path])

        cmd_list.extend(["--clone", clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                (
                    "dry_run",
                    "unknown",
                    "use_index_cache",
                    "use_local",
                    "no_pin",
                    "force",
                    "all",
                    "channel",
                    "override_channels",
                    "no_default_packages",
                ),
            )
        )

        if self._is_not_running():
            self._function_called = "clone_environment"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def clone(self, path, prefix):
        """
        Clone a "share package" (located at `path`) by creating a new
        environment at `prefix`, and return a dictionary with (at least) the
        following keys:
          - 'warnings': a list of warnings

        The directory `path` is located in, should be some temp directory or
        some other directory OUTSIDE /opt/anaconda.  After calling this
        function, the original file (at `path`) may be removed (by the caller
        of this function).
        The return object is a list of warnings.
        """
        if self._process.state() == QProcess.NotRunning:
            self._function_called = "clone"
            self._call_and_parse(["clone", "--json", "--prefix", prefix, path])

    def _setup_config_from_kwargs(kwargs):
        cmd_list = ["--json", "--force"]

        if "file" in kwargs:
            cmd_list.extend(["--file", kwargs["file"]])

        if "system" in kwargs:
            cmd_list.append("--system")

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ["config", "--get"]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_path"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ["config", "--get"]
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_get"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ["config", "--set", key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_set"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ["config", "--add", key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_add"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ["config", "--remove", key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_remove"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ["config", "--remove-key", key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = "config_delete"
            self._call_and_parse(cmd_list, abspath=kwargs.get("abspath", True))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ["run", "--json", command]

        if self._is_not_running:
            self._function_called = "run"
            self._call_and_parse(cmd_list, abspath=abspath)

    # --- Additional methods not in conda-api
    # ------------------------------------------------------------------------
    def pip(self, name):
        """Get list of pip installed packages."""
        cmd_list = ["list", "-n", name]

        if self._is_not_running:
            self._function_called = "pip"
            self._call_conda(cmd_list)

    def dependencies(self, name=None, path=None, pkgs=None, dep=True):
        """
        Install packages into an environment either by name or path with a
        specified set of packages.
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError("must specify a list of one or more packages to " "install into existing environment")

        cmd_list = ["install", "--dry-run", "--json"]
        cmd_list = ["install", "--dry-run", "--json", "--force-pscheck"]

        if not dep:
            cmd_list.extend(["--no-deps"])

        if name:
            cmd_list.extend(["--name", name])
        elif path:
            cmd_list.extend(["--prefix", path])
        else:
            pass

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = "install_dry"
            self._call_and_parse(cmd_list)
Exemplo n.º 7
0
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)        
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, enco)
        result = [stdout.encode(enco), stderr.encode(enco)]

        if PY2:
            stderr = stderr.decode()
        result[-1] = ''

        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True
Exemplo n.º 8
0
class PylintWidget(QWidget):
    """
    Pylint widget
    """
    DATAPATH = get_conf_path('pylint.results')
    VERSION = '1.1.0'
    redirect_stdio = Signal(bool)

    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)

        self.setWindowTitle("Pylint")

        self.output = None
        self.error_output = None

        self.max_entries = max_entries
        self.rdata = []
        if osp.isfile(self.DATAPATH):
            try:
                data = pickle.loads(open(self.DATAPATH, 'rb').read())
                if data[0] == self.VERSION:
                    self.rdata = data[1:]
            except (EOFError, ImportError):
                pass

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=ima.icon('run'),
                                              text=_("Analyze"),
                                              tip=_("Run analysis"),
                                              triggered=self.start,
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current analysis"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        self.filecombo.valid.connect(self.show_data)

        browse_button = create_toolbutton(self,
                                          icon=ima.icon('fileopen'),
                                          tip=_('Select Python file'),
                                          triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self,
                                            icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Complete output"),
                                            triggered=self.show_log)
        self.treewidget = ResultsTree(self)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.show_data()

        if self.rdata:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())
        else:
            self.start_button.setEnabled(False)

    def analyze(self, filename):
        filename = to_text_string(filename)  # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()

    @Slot()
    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python file"), getcwd_or_home(),
            _("Python files") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)

    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.rdata = [(filename, data) for filename, data in self.rdata
                      if is_module_or_package(filename)]

    def get_filenames(self):
        return [filename for filename, _data in self.rdata]

    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.rdata):
            if fname == filename:
                return index, data
        else:
            return None, None

    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.rdata.pop(index)
        self.rdata.insert(0, (filename, data))
        self.save()

    def save(self):
        while len(self.rdata) > self.max_entries:
            self.rdata.pop(-1)
        pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2)

    @Slot()
    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=_("Pylint output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    @Slot()
    def start(self):
        filename = to_text_string(self.filecombo.currentText())

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(osp.dirname(filename))
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(
            lambda ec, es=QProcess.ExitStatus: self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        self.output = ''
        self.error_output = ''

        plver = PYLINT_VER
        if plver is not None:
            p_args = ['-m', 'pylint', '--output-format=text']
            if plver.split('.')[0] == '0':
                p_args += ['-i', 'yes']
            else:
                # Option '-i' (alias for '--include-ids') was removed in pylint
                # 1.0
                p_args += ["--msg-template='{msg_id}:{line:3d},"\
                           "{column}: {obj}: {msg}"]
            p_args += [osp.basename(filename)]
        else:
            p_args = [osp.basename(filename)]
        self.process.start(sys.executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        if not self.output:
            if self.error_output:
                QMessageBox.critical(self, _("Error"), self.error_output)
                print("pylint error:\n\n" + self.error_output, file=sys.stderr)
            return

        # Convention, Refactor, Warning, Error
        results = {'C:': [], 'R:': [], 'W:': [], 'E:': []}
        txt_module = '************* Module '

        module = ''  # Should not be needed - just in case something goes wrong
        for line in self.output.splitlines():
            if line.startswith(txt_module):
                # New module
                module = line[len(txt_module):]
                continue
            # Supporting option include-ids: ('R3873:' instead of 'R:')
            if not re.match('^[CRWE]+([0-9]{4})?:', line):
                continue
            i1 = line.find(':')
            if i1 == -1:
                continue
            msg_id = line[:i1]
            i2 = line.find(':', i1 + 1)
            if i2 == -1:
                continue
            line_nb = line[i1 + 1:i2].strip()
            if not line_nb:
                continue
            line_nb = int(line_nb.split(',')[0])
            message = line[i2 + 1:]
            item = (module, line_nb, message, msg_id)
            results[line[0] + ':'].append(item)

        # Rate
        rate = None
        txt_rate = 'Your code has been rated at '
        i_rate = self.output.find(txt_rate)
        if i_rate > 0:
            i_rate_end = self.output.find('/10', i_rate)
            if i_rate_end > 0:
                rate = self.output[i_rate + len(txt_rate):i_rate_end]

        # Previous run
        previous = ''
        if rate is not None:
            txt_prun = 'previous run: '
            i_prun = self.output.find(txt_prun, i_rate_end)
            if i_prun > 0:
                i_prun_end = self.output.find('/10', i_prun)
                previous = self.output[i_prun + len(txt_prun):i_prun_end]

        filename = to_text_string(self.filecombo.currentText())
        self.set_data(filename, (time.localtime(), rate, previous, results))
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        _index, data = self.get_data(filename)
        if data is None:
            text = _('Source code has not been rated yet.')
            self.treewidget.clear_results()
            date_text = ''
        else:
            datetime, rate, previous_rate, results = data
            if rate is None:
                text = _('Analysis did not succeed '
                         '(see output for more details).')
                self.treewidget.clear_results()
                date_text = ''
            else:
                text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
                rate_style = "<span style=\'color: %s\'><b>%s</b></span>"
                prevrate_style = "<span style=\'color: #666666\'>%s</span>"
                color = "#FF0000"
                if float(rate) > 5.:
                    color = "#22AA22"
                elif float(rate) > 3.:
                    color = "#EE5500"
                text = _('Global evaluation:')
                text = (text_style % text) + (rate_style % (color,
                                                            ('%s/10' % rate)))
                if previous_rate:
                    text_prun = _('previous run:')
                    text_prun = ' (%s %s/10)' % (text_prun, previous_rate)
                    text += prevrate_style % text_prun
                self.treewidget.set_results(filename, results)
                date = to_text_string(time.strftime("%d %b %Y %H:%M",
                                                    datetime),
                                      encoding='utf8')
                date_text = text_style % date

        self.ratelabel.setText(text)
        self.datelabel.setText(date_text)
Exemplo n.º 9
0
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first
                and self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, enco)
        result = [stdout.encode(enco), stderr.encode(enco)]

        if PY2:
            stderr = stderr.decode()
        result[-1] = ''

        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True
Exemplo n.º 10
0
class ProcessWorker(QObject):
    """
    """
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs={}):
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(50)

        self._timer.timeout.connect(self._communicate)
        self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """
        """
        if not self._communicate_first:
            if self._process.state() == QProcess.NotRunning:
                self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """
        """
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda cloud api site' not in stderr.lower():
            if stderr.strip() and self._conda:
                raise Exception('{0}:\n'
                                'STDERR:\n{1}\nEND'
                                ''.format(' '.join(self._cmd_list),
                                          stderr))
#            elif stderr.strip() and self._pip:
#                raise PipError(self._cmd_list)
        else:
            result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except ValueError as error:
                result = stdout, error

            if 'error' in result[0]:
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """
        """
        self._process.close()

    def is_finished(self):
        """
        """
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """
        """
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
Exemplo n.º 11
0
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                logger.error('{0}:\nSTDERR:\n{1}\nEND'.format(
                        ' '.join(self._cmd_list), stderr))
            elif stderr.strip() and self._pip:
                logger.error("pip error: {}".format(self._cmd_list))
        result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except Exception as error:
                result = stdout, str(error)

            if 'error' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
Exemplo n.º 12
0
class CondaProcess(QObject):
    """conda-api modified to work with QProcess instead of popen"""
    ENCODING = 'ascii'

    def __init__(self, parent, on_finished=None, on_partial=None):
        QObject.__init__(self, parent)
        self._parent = parent
        self.output = None
        self.partial = None
        self.stdout = None
        self.error = None
        self._parse = False
        self._function_called = ''
        self._name = None
        self._process = QProcess()
        self._on_finished = on_finished

        self._process.finished.connect(self._call_conda_ready)
        self._process.readyReadStandardOutput.connect(self._call_conda_partial)

        if on_finished is not None:
            self._process.finished.connect(on_finished)
        if on_partial is not None:
            self._process.readyReadStandardOutput.connect(on_partial)

        self.set_root_prefix()

    def _call_conda_partial(self):
        """ """
        stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(stdout, CondaProcess.ENCODING)

        stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(stderr, CondaProcess.ENCODING)

        if self._parse:
            self.output = json.loads(stdout)
        else:
            self.output = stdout

        self.partial = self.output
        self.stdout = self.output
        self.error = stderr

#        print(self.partial)
#        print(self.error)

    def _call_conda_ready(self):
        """function called when QProcess in _call_conda finishes task"""
        function = self._function_called

        if self.stdout is None:
            stdout = to_text_string(self._process.readAllStandardOutput(),
                                    encoding=CondaProcess.ENCODING)
        else:
            stdout = self.stdout

        if self.error is None:
            stderr = to_text_string(self._process.readAllStandardError(),
                                    encoding=CondaProcess.ENCODING)
        else:
            stderr = self.error

        if function == 'get_conda_version':
            pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
            m = pat.match(stderr.strip())
            if m is None:
                m = pat.match(stdout.strip())
            if m is None:
                raise Exception('output did not match: %r' % stderr)
            self.output = m.group(1)
#        elif function == 'get_envs':
#            info = self.output
#            self.output = info['envs']
#        elif function == 'get_prefix_envname':
#            name = self._name
#            envs = self.output
#            self.output = self._get_prefix_envname_helper(name, envs)
#            self._name = None
        elif function == 'config_path':
            result = self.output
            self.output = result['rc_path']
        elif function == 'config_get':
            result = self.output
            self.output = result['get']
        elif (function == 'config_delete' or function == 'config_add' or
                function == 'config_set' or function == 'config_remove'):
            result = self.output
            self.output = result.get('warnings', [])
        elif function == 'pip':
            result = []
            lines = self.output.split('\n')
            for line in lines:
                if '<pip>' in line:
                    temp = line.split()[:-1] + ['pip']
                    result.append('-'.join(temp))
            self.output = result

        if stderr.strip():
            self.error = stderr
#            raise Exception('conda %r:\nSTDERR:\n%s\nEND' % (extra_args,
#                                                             stderr.decode()))
        self._parse = False

    def _get_prefix_envname_helper(self, name, envs):
        """ """
        global ROOTPREFIX
        if name == 'root':
            return ROOT_PREFIX
        for prefix in envs:
            if basename(prefix) == name:
                return prefix
        return None

    def _abspath(self, abspath):
        """ """
        if abspath:
            if sys.platform == 'win32':
                python = join(ROOT_PREFIX, 'python.exe')
                conda = join(ROOT_PREFIX,
                             'Scripts', 'conda-script.py')
            else:
                python = join(ROOT_PREFIX, 'bin/python')
                conda = join(ROOT_PREFIX, 'bin/conda')
            cmd_list = [python, conda]
        else:  # just use whatever conda is on the path
            cmd_list = ['conda']
        return cmd_list

    def _call_conda(self, extra_args, abspath=True):
        """ """
        # call conda with the list of extra arguments, and return the tuple
        # stdout, stderr
        global ROOT_PREFIX
#        if abspath:
#            if sys.platform == 'win32':
#                python = join(ROOT_PREFIX, 'python.exe')
#                conda = join(ROOT_PREFIX,
#                             'Scripts', 'conda-script.py')
#            else:
#                python = join(ROOT_PREFIX, 'bin/python')
#                conda = join(ROOT_PREFIX, 'bin/conda')
#            cmd_list = [python, conda]
#        else:  # just use whatever conda is on the path
#            cmd_list = ['conda']
        cmd_list = self._abspath(abspath)
        cmd_list.extend(extra_args)

#        try:
#            p = Popen(cmd_list, stdout=PIPE, stderr=PIPE)
#        except OSError:
#            raise Exception("could not invoke %r\n" % args)
#        return p.communicate()

        # adapted code
        # ------------
        self.error, self.output = None, None
        self._process.start(cmd_list[0], cmd_list[1:])

    def _call_and_parse(self, extra_args, abspath=True):
        """ """
#        stdout, stderr = _call_conda(extra_args, abspath=abspath)
#        if stderr.decode().strip():
#            raise Exception('conda %r:\nSTDERR:\n%s\nEND' % (extra_args,
#                                                             stderr.decode()))
#    return json.loads(stdout.decode())

        # adapted code
        # ------------
        self._parse = True
        self._call_conda(extra_args, abspath=abspath)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        """ """
        cmd_list = []
        if kwargs.get('override_channels', False) and 'channel' not in kwargs:
            raise TypeError('conda search: override_channels requires channel')

        if 'env' in kwargs:
            cmd_list.extend(['--name', kwargs.pop('env')])
        if 'prefix' in kwargs:
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])
        if 'channel' in kwargs:
            channel = kwargs.pop('channel')
            if isinstance(channel, str):
                cmd_list.extend(['--channel', channel])
            else:
                cmd_list.append('--channel')
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append('--' + key.replace('_', '-'))

        return cmd_list

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        This function should only be called once (right after importing
        conda_api).
        """
        global ROOT_PREFIX

        if prefix:
            ROOT_PREFIX = prefix
        # find *some* conda instance, and then use info() to get 'root_prefix'
        else:
            pass
#            i = self.info(abspath=False)
#            self.ROOT_PREFIX = i['root_prefix']
            '''
            plat = 'posix'
            if sys.platform.lower().startswith('win'):
                listsep = ';'
                plat = 'win'
            else:
                listsep = ':'

            for p in os.environ['PATH'].split(listsep):
                if (os.path.exists(os.path.join(p, 'conda')) or
                    os.path.exists(os.path.join(p, 'conda.exe')) or
                    os.path.exists(os.path.join(p, 'conda.bat'))):

                    # TEMPORARY:
                    ROOT_PREFIX = os.path.dirname(p) # root prefix is 1 dir up
                    i = info()
                    # REAL:
                    ROOT_PREFIX = i['root_prefix']
                    break
            else: # fall back to standard install location, which may be wrong
                if plat == 'win':
                    ROOT_PREFIX = 'C:\Anaconda'
                else:
                    ROOT_PREFIX = '/opt/anaconda'
            '''
            # adapted code
            # ------------
            if ROOT_PREFIX is None:
#                qprocess = QProcess()
#                cmd_list = ['conda', 'info', '--json']
#                qprocess.start(cmd_list[0], cmd_list[1:])
#                qprocess.waitForFinished()

#                output = qprocess.readAllStandardOutput()
#                output = handle_qbytearray(output, CondaProcess.ENCODING)
#                info = json.loads(output)
#                ROOT_PREFIX = info['root_prefix']
                info = self.info(abspath=False)
                ROOT_PREFIX = info['root_prefix']



    def get_conda_version(self):
        """
        return the version of conda being used (invoked) as a string
        """
#        pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
#        stdout, stderr = self._call_conda(['--version'])
#        # argparse outputs version to stderr in Python < 3.4.
#        # http://bugs.python.org/issue18920
#        m = pat.match(stderr.decode().strip())
#        if m is None:
#            m = pat.match(stdout.decode().strip())
#
#        if m is None:
#            raise Exception('output did not match: %r' % stderr)
#        return m.group(1)

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'get_conda_version'
            self._call_conda(['--version'])

    def get_envs(self):
        """
        Return all of the (named) environment (this does not include the root
        environment), as a list of absolute path to their prefixes.
        """
#        info = self._call_and_parse(['info', '--json'])
#        return info['envs']

        info = self.info()
        return info['envs']

        # adapted code
        # ------------
#        if self._process.state() == QProcess.NotRunning:
#            self._function_called = 'get_envs'
#            self._call_and_parse(['info', '--json'])

    def get_prefix_envname(self, name):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        if name == 'root':
            return ROOT_PREFIX
        for prefix in self.get_envs():
            if basename(prefix) == name:
                return prefix
        return None

        # adapted code
        # ------------
#        if self._process.state() == QProcess.NotRunning:
#            self._name = name
#            self._function_called = 'get_prefix_envname'
#            self._call_and_parse(['info', '--json'])

    def info(self, abspath=True):
        """
        Return a dictionary with configuration information.
        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
#        return self._call_and_parse(['info', '--json'], abspath=abspath)

        qprocess = QProcess()
        cmd_list = self._abspath(abspath)
        cmd_list.extend(['info', '--json'])
        qprocess.start(cmd_list[0], cmd_list[1:])
        qprocess.waitForFinished()
        output = qprocess.readAllStandardOutput()
        output = handle_qbytearray(output, CondaProcess.ENCODING)
        info = json.loads(output)
        return info

        # adapted code
        # ------------
#        if self._process.state() == QProcess.NotRunning:
#            self._function_called = 'info'
#            self._call_and_parse(['info', '--json'], abspath=abspath)

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.

        Structure is {
            'package_name': [{
                'depends': list,
                'version': str,
                'name': str,
                'license': str,
                'fn': ...,
                ...
            }]
        }
        """
#        return self._call_and_parse(['info', package, '--json'],
#                                    abspath=abspath)

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'package_info'
            self._call_and_parse(['info', package, '--json'], abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ['search', '--json']

        if regex and spec:
            raise TypeError('conda search: only one of regex or spec allowed')

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(['--spec', spec])

        if 'platform' in kwargs:
            platform = kwargs.pop('platform')
            platforms = ('win-32', 'win-64', 'osx-64', 'linux-32', 'linux-64')
            if platform not in platforms:
                raise TypeError('conda search: platform must be one of ' +
                                ', '.join(platforms))
            cmd_list.extend(['--platform', platform])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('canonical', 'unknown', 'use_index_cache', 'outdated',
                 'override_channels')))

#        return self._call_and_parse(cmd_list,
#                                    abspath=kwargs.get('abspath', True))
        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'search'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def share(self, prefix):
        """
        Create a "share package" of the environment located in `prefix`,
        and return a dictionary with (at least) the following keys:
          - 'path': the full path to the created package
          - 'warnings': a list of warnings

        This file is created in a temp directory, and it is the callers
        responsibility to remove this directory (after the file has been
        handled in some way).
        """
#        return self._call_and_parse(['share', '--json', '--prefix', prefix])

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'share'
            self._call_and_parse(['share', '--json', '--prefix', prefix])

    def create(self, name=None, path=None, pkgs=None):
        """
        Create an environment either by name or path with a specified set of
        packages
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into new environment')

        cmd_list = ['create', '--yes', '--quiet']
        if name:
            ref = name
            search = [os.path.join(d, name) for d in self.info()['envs_dirs']]
            cmd_list = ['create', '--yes', '--quiet', '--name', name]
        elif path:
            ref = path
            search = [path]
            cmd_list = ['create', '--yes', '--quiet', '--prefix', path]
        else:
            raise TypeError('must specify either an environment name or a path'
                            ' for new environment')

        if any(os.path.exists(path) for path in search):
            raise CondaEnvExistsError('Conda environment [%s] already exists'
                                      % ref)

        cmd_list.extend(pkgs)
#        (out, err) = self._call_conda(cmd_list)
#        if err.decode().strip():
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               err.decode()))
#        return out

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'create'
            self._call_conda(cmd_list)

    def install(self, name=None, path=None, pkgs=None, dep=True):
        """
        Install packages into an environment either by name or path with a
        specified set of packages
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--yes', '--json', '--force-pscheck']
#        cmd_list = ['install', '--yes', '--quiet']
        if name:
            cmd_list.extend(['--name', name])
        elif path:
            cmd_list.extend(['--prefix', path])
        else:  # just install into the current environment, whatever that is
            pass

        cmd_list.extend(pkgs)

#        (out, err) = self._call_conda(cmd_list)
#        if err.decode().strip():
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               err.decode()))
#        return out

        # adapted code
        # ------------
        if not dep:
            cmd_list.extend(['--no-deps'])

        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'install'
            self._call_conda(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ['update', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to update, \
                            or all=True.")

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'no_deps', 'override_channels',
                 'no_pin', 'force', 'all', 'use_index_cache', 'use_local',
                 'alt_hint')))

        cmd_list.extend(pkgs)

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#
#        return result

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'update'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def remove(self, *pkgs, **kwargs):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        cmd_list = ['remove', '--json', '--quiet', '--yes', '--force-pscheck']
#        cmd_list = ['remove', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to remove, \
                            or all=True.")

        if kwargs.get('name') and kwargs.get('path'):
            raise TypeError('conda remove: At most one of name, path allowed')

        if kwargs.get('name'):
            cmd_list.extend(['--name', kwargs.pop('name')])

        if kwargs.get('path'):
            cmd_list.extend(['--prefix', kwargs.pop('path')])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'features', 'override_channels',
                 'no_pin', 'force', 'all')))

        cmd_list.extend(pkgs)

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#
#        return result

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'remove'
            self._call_and_parse(cmd_list,
                                 abspath=kwargs.get('abspath', True))

    def remove_environment(self, name=None, path=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
#        return self.remove(name=name, path=path, all=True, **kwargs)

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'remove_environment'
            self.remove(name=name, path=path, all=True, **kwargs)

    def clone_environment(self, clone, name=None, path=None, **kwargs):
        """
        Clone the environment ``clone`` into ``name`` or ``path``.
        """
        cmd_list = ['create', '--json', '--quiet']

        if (name and path) or not (name or path):
            raise TypeError("conda clone_environment: exactly one of name or \
                            path required")

        if name:
            cmd_list.extend(['--name', name])

        if path:
            cmd_list.extend(['--prefix', path])

        cmd_list.extend(['--clone', clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'unknown', 'use_index_cache', 'use_local',
                 'no_pin', 'force', 'all', 'channel', 'override_channels',
                 'no_default_packages')))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#
#        return result

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'clone_environment'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

#    def process(self, name=None, path=None, cmd=None, args=None, stdin=None,
#                stdout=None, stderr=None, timeout=None):
#        """
#        Create a Popen process for cmd using the specified args but in the
#        conda environment specified by name or path.
#
#        The returned object will need to be invoked with p.communicate() or
#        similar.
#
#        :param name: name of conda environment
#        :param path: path to conda environment (if no name specified)
#        :param cmd:  command to invoke
#        :param args: argument
#        :param stdin: stdin
#        :param stdout: stdout
#        :param stderr: stderr
#        :return: Popen object
#        """
#
#        if bool(name) == bool(path):
#            raise TypeError('exactly one of name or path must be specified')
#
#        if not cmd:
#            raise TypeError('cmd to execute must be specified')
#
#        if not args:
#            args = []
#
#        if name:
#            path = self.get_prefix_envname(name)
#
#        plat = 'posix'
#        if sys.platform.lower().startswith('win'):
#            listsep = ';'
#            plat = 'win'
#        else:
#            listsep = ':'
#
#        conda_env = dict(os.environ)
#
#        if plat == 'posix':
#            conda_env['PATH'] = path + os.path.sep + 'bin' + listsep + \
#                conda_env['PATH']
#        else: # win
#            conda_env['PATH'] = path + os.path.sep + 'Scripts' + listsep + \
#                conda_env['PATH']
#
#        conda_env['PATH'] = path + listsep + conda_env['PATH']
#
#        cmd_list = [cmd]
#        cmd_list.extend(args)
#
#        try:
#            p = Popen(cmd_list, env=conda_env, stdin=stdin, stdout=stdout,
#                      stderr=stderr)
#        except OSError:
#            raise Exception("could not invoke %r\n" % cmd_list)
#        return p

    def clone(self, path, prefix):
        """
        Clone a "share package" (located at `path`) by creating a new
        environment at `prefix`, and return a dictionary with (at least) the
        following keys:
          - 'warnings': a list of warnings

        The directory `path` is located in, should be some temp directory or
        some other directory OUTSIDE /opt/anaconda.  After calling this
        function, the original file (at `path`) may be removed (by the caller
        of this function).
        The return object is a list of warnings.
        """
#        return self._call_and_parse(['clone', '--json', '--prefix', prefix,
#                                     path])
        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'clone'
            self._call_and_parse(['clone', '--json', '--prefix', prefix, path])

    def _setup_config_from_kwargs(kwargs):
        cmd_list = ['--json', '--force']

        if 'file' in kwargs:
            cmd_list.extend(['--file', kwargs['file']])

        if 'system' in kwargs:
            cmd_list.append('--system')

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result['rc_path']

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_path'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result['get']

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_get'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--set', key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result.get('warnings', [])

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_set'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--add', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result.get('warnings', [])

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_add'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result.get('warnings', [])

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_remove'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove-key', key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

#        result = self._call_and_parse(cmd_list,
#                                      abspath=kwargs.get('abspath', True))
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result.get('warnings', [])

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'config_delete'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ['run', '--json', command]

#        result = self._call_and_parse(cmd_list, abspath=abspath)
#
#        if 'error' in result:
#            raise CondaError('conda %s: %s' % (" ".join(cmd_list),
#                                               result['error']))
#        return result

        # adapted code
        # ------------
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'run'
            self._call_and_parse(cmd_list, abspath=abspath)

#    def test():
#        """
#        Self-test function, which prints useful debug information.
#        This function returns None on success, and will crash the interpreter
#        on failure.
#        """
#        print('sys.version: %r' % sys.version)
#        print('sys.prefix : %r' % sys.prefix)
#        print('conda_api.__version__: %r' % __version__)
#        print('conda_api.ROOT_PREFIX: %r' % ROOT_PREFIX)
#        if isdir(ROOT_PREFIX):
#            conda_version = get_conda_version()
#            print('conda version: %r' % conda_version)
#            print('conda info:')
#            d = info()
#            for kv in d.items():
#                print('\t%s=%r' % kv)
#            assert d['conda_version'] == conda_version
#        else:
#            print('Warning: no such directory: %r' % ROOT_PREFIX)
#        print('OK')

    # ---- Additional methods not in conda-api
    def pip(self, name):
        """Get list of pip installed packages."""
        cmd_list = ['list', '-n', name]

        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'pip'
            self._call_conda(cmd_list)

    def dependencies(self, name=None, path=None, pkgs=None, dep=True):
        """
        Install packages into an environment either by name or path with a
        specified set of packages
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--dry-run', '--json']
        cmd_list = ['install', '--dry-run', '--json', '--force-pscheck']

        if not dep:
            cmd_list.extend(['--no-deps'])

        if name:
            cmd_list.extend(['--name', name])
        elif path:
            cmd_list.extend(['--prefix', path])
        else:  # just install into the current environment, whatever that is
            pass

        cmd_list.extend(pkgs)

        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'install_dry'
            self._call_and_parse(cmd_list)
Exemplo n.º 13
0
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self,
                 cmd_list,
                 parse=False,
                 pip=False,
                 callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        try:
            json_stdout = [json.loads(s) for s in stdout.split('\x00') if s]
            json_stdout = json_stdout[-1]
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first
                and self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                d = {'command': ' '.join(self._cmd_list), 'stderr': stderr}
                # print(d)
                # Should we just ignore any message here???
                logger.warning('Conda command output on stderr', extra=d)
            elif stderr.strip() and self._pip:
                d = {'command': ' '.join(self._cmd_list)}
                # print(d)
                # Should we just ignore any message here???
                logger.warning('Pip command output on stderr', extra=d)
        result[-1] = ''

        if self._parse and stdout:
            json_stdout = []
            json_lines_output = stdout.split('\x00')
            for i, l in enumerate(json_lines_output):
                if l:
                    try:
                        json_stdout.append(json.loads(l))
                    except Exception as error:
                        # An exception here could be product of:
                        # - conda env installing pip stuff that is thrown to
                        #   stdout in non json form
                        # - a post link script might be printing stuff to
                        #   stdout in non json format
                        logger.warning(
                            ('Problem parsing conda json output. '
                             'Line {0}. Data - {1}. Error - {2}'.format(
                                 i, l, str(error))), )

            if json_stdout:
                json_stdout = json_stdout[-1]
            result = json_stdout, result[-1]

            if 'exception_name' in result[0] or 'exception_type' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['message'])

                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            d = {'stderr': result[-1]}
            logger.error('error', extra=d)

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
Exemplo n.º 14
0
class TestRunner(QObject):
    """
    Class for running tests with py.test or nose.

    All communication back to the caller is done via signals.

    Attributes
    ----------
    process : QProcess or None
        Process running the unit test suite.
    resultfilename : str
        Name of file in which test results are stored.

    Signals
    -------
    sig_finished(list of TestResult, str)
        Emitted when test process finishes. First argument contains the test
        results, second argument contains the output of the test process.
    """

    sig_finished = Signal(object, str)

    def __init__(self, widget, resultfilename=None):
        """
        Construct test runner.

        Parameters
        ----------
        widget : UnitTestWidget
            Unit test widget which constructs the test runner.
        resultfilename : str or None
            Name of file in which to store test results. If None, use default.
        """

        QObject.__init__(self, widget)
        self.process = None
        if resultfilename is None:
            self.resultfilename = os.path.join(tempfile.gettempdir(),
                                               'unittest.results')
        else:
            self.resultfilename = resultfilename

    def start(self, config, pythonpath):
        """
        Start process which will run the unit test suite.

        The process is run in the working directory specified in 'config',
        with the directories in `pythonpath` added to the Python path for the
        test process. The test results are written to the file
        `self.resultfilename`. The standard output and error are also recorded.
        Once the process is finished, `self.finished()` will be called.

        Parameters
        ----------
        config : TestConfig
            Unit test configuration.
        pythonpath : list of str
            List of directories to be added to the Python path

        Raises
        ------
        RuntimeError
            If process failed to start.
        """

        framework = config.framework
        wdir = config.wdir

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.finished.connect(self.finished)

        if pythonpath is not None:
            env = [
                to_text_string(_pth)
                for _pth in self.process.systemEnvironment()
            ]
            add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)

        if framework == 'nose':
            executable = 'nosetests'
            p_args = [
                '--with-xunit', '--xunit-file={}'.format(self.resultfilename)
            ]
        elif framework == 'py.test':
            executable = 'py.test'
            p_args = ['--junit-xml', self.resultfilename]
        else:
            raise ValueError('Unknown framework')

        if os.name == 'nt':
            executable += '.exe'

        try:
            os.remove(self.resultfilename)
        except OSError:
            pass

        self.process.start(executable, p_args)
        running = self.process.waitForStarted()
        if not running:
            raise RuntimeError

    def finished(self):
        """
        Called when the unit test process has finished.

        This function reads the results and emits `sig_finished`.
        """
        qbytearray = self.process.readAllStandardOutput()
        locale_codec = QTextCodec.codecForLocale()
        output = to_text_string(locale_codec.toUnicode(qbytearray.data()))
        testresults = self.load_data()
        self.sig_finished.emit(testresults, output)

    def kill_if_running(self):
        """Kill testing process if it is running."""
        if self.process and self.process.state() == QProcess.Running:
            self.process.kill()

    def load_data(self):
        """
        Read and parse unit test results.

        This function reads the unit test results from the file with name
        `self.resultfilename` and parses them. The file should contain the
        test results in JUnitXML format.

        Returns
        -------
        list of TestResult
            Unit test results.
        """
        try:
            data = etree.parse(self.resultfilename).getroot()
        except OSError:
            data = []

        testresults = []
        for testcase in data:
            category = Category.OK
            status = 'ok'
            name = '{0}.{1}'.format(testcase.get('classname'),
                                    testcase.get('name'))
            message = ''
            time = float(testcase.get('time'))
            extras = []

            for child in testcase:
                if child.tag in ('error', 'failure', 'skipped'):
                    if child.tag == 'skipped':
                        category = Category.SKIP
                    else:
                        category = Category.FAIL
                    status = child.tag
                    type_ = child.get('type')
                    message = child.get('message', default='')
                    if type_ and message:
                        message = '{0}: {1}'.format(type_, message)
                    elif type_:
                        message = type_
                    if child.text:
                        extras.append(child.text)
                elif child.tag in ('system-out', 'system-err'):
                    if child.tag == 'system-out':
                        heading = _('Captured stdout')
                    else:
                        heading = _('Captured stderr')
                    contents = child.text.rstrip('\n')
                    extras.append('----- {} -----\n{}'.format(
                        heading, contents))

            extra_text = '\n\n'.join(extras)
            testresults.append(
                TestResult(category, status, name, message, time, extra_text))

        return testresults
Exemplo n.º 15
0
class PylintWidget(QWidget):
    """
    Pylint widget
    """
    DATAPATH = get_conf_path('pylint.results')
    VERSION = '1.1.0'
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)
        
        self.setWindowTitle("Pylint")
        
        self.output = None
        self.error_output = None
        
        self.max_entries = max_entries
        self.rdata = []
        if osp.isfile(self.DATAPATH):
            try:
                data = pickle.loads(open(self.DATAPATH, 'rb').read())
                if data[0] == self.VERSION:
                    self.rdata = data[1:]
            except (EOFError, ImportError):
                pass

        self.filecombo = PythonModulesComboBox(self)
        if self.rdata:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())
        
        self.start_button = create_toolbutton(self, icon=ima.icon('run'),
                                    text=_("Analyze"),
                                    tip=_("Run analysis"),
                                    triggered=self.start, text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current analysis"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        self.filecombo.valid.connect(self.show_data)

        browse_button = create_toolbutton(self, icon=ima.icon('fileopen'),
                               tip=_('Select Python file'),
                               triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self, icon=ima.icon('log'),
                                    text=_("Output"),
                                    text_beside_icon=True,
                                    tip=_("Complete output"),
                                    triggered=self.show_log)
        self.treewidget = ResultsTree(self)
        
        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)
        
        self.process = None
        self.set_running_state(False)
        self.show_data()
        
    def analyze(self, filename):
        filename = to_text_string(filename) # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()

    @Slot()
    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(self, _("Select Python file"),
                           getcwd(), _("Python files")+" (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)
            
    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.rdata = [(filename, data) for filename, data in self.rdata
                      if is_module_or_package(filename)]
        
    def get_filenames(self):
        return [filename for filename, _data in self.rdata]
    
    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.rdata):
            if fname == filename:
                return index, data
        else:
            return None, None
            
    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.rdata.pop(index)
        self.rdata.insert(0, (filename, data))
        self.save()
        
    def save(self):
        while len(self.rdata) > self.max_entries:
            self.rdata.pop(-1)
        pickle.dump([self.VERSION]+self.rdata, open(self.DATAPATH, 'wb'), 2)

    @Slot()
    def show_log(self):
        if self.output:
            TextEditor(self.output, title=_("Pylint output"),
                       readonly=True, size=(700, 500)).exec_()

    @Slot()
    def start(self):
        filename = to_text_string(self.filecombo.currentText())
        
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(osp.dirname(filename))
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
                                          lambda: self.read_output(error=True))
        self.process.finished.connect(lambda ec, es=QProcess.ExitStatus:
                                      self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)
        
        self.output = ''
        self.error_output = ''
        
        plver = PYLINT_VER
        if plver is not None:
            p_args = ['-m', 'pylint', '--output-format=text']
            if plver.split('.')[0] == '0':
                p_args += ['-i', 'yes']
            else:
                # Option '-i' (alias for '--include-ids') was removed in pylint
                # 1.0
                p_args += ["--msg-template='{msg_id}:{line:3d},"\
                           "{column}: {obj}: {msg}"]
            p_args += [osp.basename(filename)]
        else:
            p_args = [osp.basename(filename)]
        self.process.start(sys.executable, p_args)
        
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
    
    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)
        
    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string( locale_codec.toUnicode(qba.data()) )
        if error:
            self.error_output += text
        else:
            self.output += text
        
    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        if not self.output:
            if self.error_output:
                QMessageBox.critical(self, _("Error"), self.error_output)
                print("pylint error:\n\n" + self.error_output, file=sys.stderr)
            return
        
        # Convention, Refactor, Warning, Error
        results = {'C:': [], 'R:': [], 'W:': [], 'E:': []}
        txt_module = '************* Module '
        
        module = '' # Should not be needed - just in case something goes wrong
        for line in self.output.splitlines():
            if line.startswith(txt_module):
                # New module
                module = line[len(txt_module):]
                continue
            # Supporting option include-ids: ('R3873:' instead of 'R:')
            if not re.match('^[CRWE]+([0-9]{4})?:', line):
                continue
            i1 = line.find(':')
            if i1 == -1:
                continue
            msg_id = line[:i1]
            i2 = line.find(':', i1+1)
            if i2 == -1:
                continue
            line_nb = line[i1+1:i2].strip()
            if not line_nb:
                continue
            line_nb = int(line_nb.split(',')[0])
            message = line[i2+1:]
            item = (module, line_nb, message, msg_id)
            results[line[0]+':'].append(item)
            
        # Rate
        rate = None
        txt_rate = 'Your code has been rated at '
        i_rate = self.output.find(txt_rate)
        if i_rate > 0:
            i_rate_end = self.output.find('/10', i_rate)
            if i_rate_end > 0:
                rate = self.output[i_rate+len(txt_rate):i_rate_end]
        
        # Previous run
        previous = ''
        if rate is not None:
            txt_prun = 'previous run: '
            i_prun = self.output.find(txt_prun, i_rate_end)
            if i_prun > 0:
                i_prun_end = self.output.find('/10', i_prun)
                previous = self.output[i_prun+len(txt_prun):i_prun_end]
            
        
        filename = to_text_string(self.filecombo.currentText())
        self.set_data(filename, (time.localtime(), rate, previous, results))
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)
        
    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()
        
    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return
        
        _index, data = self.get_data(filename)
        if data is None:
            text = _('Source code has not been rated yet.')
            self.treewidget.clear_results()
            date_text = ''
        else:
            datetime, rate, previous_rate, results = data
            if rate is None:
                text = _('Analysis did not succeed '
                         '(see output for more details).')
                self.treewidget.clear_results()
                date_text = ''
            else:
                text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
                rate_style = "<span style=\'color: %s\'><b>%s</b></span>"
                prevrate_style = "<span style=\'color: #666666\'>%s</span>"
                color = "#FF0000"
                if float(rate) > 5.:
                    color = "#22AA22"
                elif float(rate) > 3.:
                    color = "#EE5500"
                text = _('Global evaluation:')
                text = (text_style % text)+(rate_style % (color,
                                                          ('%s/10' % rate)))
                if previous_rate:
                    text_prun = _('previous run:')
                    text_prun = ' (%s %s/10)' % (text_prun, previous_rate)
                    text += prevrate_style % text_prun
                self.treewidget.set_results(filename, results)
                date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime),
                                      encoding='utf8')
                date_text = text_style % date
            
        self.ratelabel.setText(text)
        self.datelabel.setText(date_text)
Exemplo n.º 16
0
class CoalaWidget(QWidget):
    """
    coala Widget
    """
    DATAPATH = get_conf_path('coala.results')
    VERSION = ''
    redirect_stdio = Signal(bool)

    def __init__(self,
                 parent,
                 max_entries=100,
                 options_button=None,
                 text_color=None,
                 prevrate_color=None):
        QWidget.__init__(self, parent)
        self.setWindowTitle('coala')

        self.output = None
        self.error_output = None

        self.text_color = text_color
        self.prevrate_color = prevrate_color
        self.max_entries = max_entries
        self.rdata = []
        if osp.isfile(self.DATAPATH):
            try:
                data = pickle.loads(open(self.DATAPATH, 'rb').read())
                self.rdata = data[:]
            except (EOFError, ImportError):
                print('error!!')
                pass
        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=ima.icon('run'),
                                              text=_("Analyze"),
                                              tip=_("Run analysis"),
                                              triggered=self.start,
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current analysis"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        self.filecombo.valid.connect(self.show_data)

        browse_button = create_toolbutton(self,
                                          icon=ima.icon('fileopen'),
                                          tip=_('Select Python file'),
                                          triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self,
                                            icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Complete output"),
                                            triggered=self.show_log)
        self.treewidget = ResultsTree(self)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)
        if options_button:
            hlayout1.addWidget(options_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.show_data()

        if self.rdata:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())
            self.start_button.setEnabled(self.filecombo.is_valid())
        else:
            self.start_button.setEnabled(False)

    def analyze(self, filename):
        filename = to_text_string(filename)  # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()

    @Slot()
    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python file"), getcwd_or_home(),
            _("Python files") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)

    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.rdata = [(filename, data) for filename, data in self.rdata
                      if is_module_or_package(filename)]

    def get_filenames(self):
        return [filename for filename, _data in self.rdata]

    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.rdata):
            if fname == filename:
                return index, data
        else:
            return None, None

    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.rdata.pop(index)
        self.rdata.insert(0, (filename, data))
        self.save()

    def save(self):
        while len(self.rdata) > self.max_entries:
            self.rdata.pop(-1)
        pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2)

    @Slot()
    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=_("coala output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    @Slot()
    def start(self):
        filename = to_text_string(self.filecombo.currentText())

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(osp.dirname(filename))
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(
            lambda ec, es=QProcess.ExitStatus: self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        self.output = ''
        self.error_output = ''

        clver = COALA_VER
        if clver is not None:
            c_args = ['-m', 'run_coala']
        self.process.start(sys.executable, c_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        if not self.output:
            if self.error_output:
                QMessageBox.critical(self, _("Error"), self.error_output)
                print("coala error:\n\n" + self.error_output, file=sys.stderr)
            return

        results = {'C:': []}
        literal_dict = ast.literal_eval(self.output)
        line_numbers = []
        char_numbers = []
        bear_values = []
        msg_values = []
        for line in literal_dict['C']:
            print(line)
            for i in line:
                line_num = re.compile('(.+)~')
                val = line_num.findall(i)
                for line_nb in val:
                    if line_nb:
                        line_numbers.append(line_nb)
            for j in line:
                char_num = re.compile('(.*);')
                val = char_num.findall(j)
                for char_nm in val:
                    if char_nm:
                        char_numbers.append(char_nm)
            for k in line:
                bear_val = re.compile('(.*):')
                val = bear_val.findall(k)
                for bear_val in val:
                    if bear_val:
                        bear_values.append(bear_val)
            for m in line:
                msg_val = re.compile(':(.*)')
                val = msg_val.findall(m)
                for msg_val in val:
                    if msg_val:
                        msg_values.append(msg_val)

        item = list(zip(line_numbers, char_numbers, bear_values, msg_values))
        for i in item:
            results['C:'].append(i)
        filename = to_text_string(self.filecombo.currentText())
        self.set_data(filename, results)
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        _index, data = self.get_data(filename)
        if data is None:
            self.treewidget.clear_results()
        else:
            results = data
            self.treewidget.set_results(filename, results)
class MemoryProfilerWidget(QWidget):
    """
    Memory profiler widget.
    """
    DATAPATH = get_conf_path('memoryprofiler.results')
    VERSION = '0.0.1'
    redirect_stdio = Signal(bool)
    sig_finished = Signal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.setWindowTitle("Memory profiler")

        self.output = None
        self.error_output = None

        self.use_colors = True

        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=get_icon('run.png'),
                                              text=_("Profile memory usage"),
                                              tip=_("Run memory profiler"),
                                              triggered=self.start,
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=get_icon('terminate.png'),
                                             text=_("Stop"),
                                             tip=_("Stop current profiling"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often.

        browse_button = create_toolbutton(self,
                                          icon=get_icon('fileopen.png'),
                                          tip=_('Select Python script'),
                                          triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self,
                                            icon=get_icon('log.png'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Show program's output"),
                                            triggered=self.show_log)

        self.datatree = MemoryProfilerDataTree(self)

        self.collapse_button = create_toolbutton(
            self,
            icon=get_icon('collapse.png'),
            triggered=lambda dD=-1: self.datatree.collapseAll(),
            tip=_('Collapse all'))
        self.expand_button = create_toolbutton(
            self,
            icon=get_icon('expand.png'),
            triggered=lambda dD=1: self.datatree.expandAll(),
            tip=_('Expand all'))

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.start_button.setEnabled(False)

        if not is_memoryprofiler_installed():
            for widget in (self.datatree, self.filecombo, self.log_button,
                           self.start_button, self.stop_button, browse_button,
                           self.collapse_button, self.expand_button):
                widget.setDisabled(True)
            text = _(
                '<b>Please install the <a href="%s">memory_profiler module</a></b>'
            ) % WEBSITE_URL
            self.datelabel.setText(text)
            self.datelabel.setOpenExternalLinks(True)
        else:
            pass  # self.show_data()

    def analyze(self,
                filename,
                wdir=None,
                args=None,
                pythonpath=None,
                use_colors=True):
        self.use_colors = use_colors
        if not is_memoryprofiler_installed():
            return
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index = None  # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)

    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python script"), getcwd(),
            _("Python scripts") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(False)
        if filename:
            self.analyze(filename)

    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=_("Memory profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output,
                       title=_("Memory profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath

        self.datelabel.setText(_('Profiling, please wait...'))

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(self.finished)
        self.stop_button.clicked.connect(self.process.kill)

        if pythonpath is not None:
            env = [
                to_text_string(_pth)
                for _pth in self.process.systemEnvironment()
            ]
            add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)

        self.output = ''
        self.error_output = ''

        # remove previous results, since memory_profiler appends to output file
        # instead of replacing
        if osp.isfile(self.DATAPATH):
            os.remove(self.DATAPATH)

        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid
            # confusion with escape characters (otherwise, for example, '\t'
            # will be interpreted as a tabulation):
            filename = osp.normpath(filename).replace(os.sep, '/')
            p_args = [
                '-m', 'memory_profiler', '-o', '"' + self.DATAPATH + '"',
                '"' + filename + '"'
            ]
            if args:
                p_args.extend(programs.shell_split(args))
            executable = get_python_executable()
            executable += ' ' + ' '.join(p_args)
            executable = executable.replace(os.sep, '/')
            self.process.start(executable)
        else:
            p_args = ['-m', 'memory_profiler', '-o', self.DATAPATH, filename]
            if args:
                p_args.extend(programs.shell_split(args))
            executable = get_python_executable()
            self.process.start(executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)
        self.sig_finished.emit()

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datatree.load_data(self.DATAPATH)
        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()
        self.datatree.show_tree()

        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                               time.localtime())
        self.datelabel.setText(date_text)
Exemplo n.º 18
0
class CondaProcess(QObject):
    """Conda API modified to work with QProcess instead of popen."""

    # Signals
    sig_finished = Signal(str, object, str)
    sig_partial = Signal(str, object, str)
    sig_started = Signal()

    ENCODING = 'ascii'
    ROOT_PREFIX = None

    def __init__(self, parent):
        QObject.__init__(self, parent)
        self._parent = parent
        self._output = None
        self._partial = None
        self._stdout = None
        self._error = None
        self._parse = False
        self._function_called = ''
        self._name = None
        self._process = QProcess()
        self.set_root_prefix()

        # Signals
        self._process.finished.connect(self._call_conda_ready)
        self._process.readyReadStandardOutput.connect(self._call_conda_partial)

    # --- Helpers
    # -------------------------------------------------------------------------
    def _is_running(self):
        return self._process.state() != QProcess.NotRunning

    def _is_not_running(self):
        return self._process.state() == QProcess.NotRunning

    def _call_conda_partial(self):
        """ """
        stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(stdout, CondaProcess.ENCODING)

        stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(stderr, CondaProcess.ENCODING)

        if self._parse:
            try:
                self._output = json.loads(stdout)
            except Exception:
                # Result is a partial json. Can only be parsed when finished
                self._output = stdout
        else:
            self._output = stdout

        self._partial = self._output
        self._stdout = self._output
        self._error = stderr

        self.sig_partial.emit(self._function_called, self._partial,
                              self._error)

    def _call_conda_ready(self):
        """Function called when QProcess in _call_conda finishes task."""
        function = self._function_called

        if self._stdout is None:
            stdout = to_text_string(self._process.readAllStandardOutput(),
                                    encoding=CondaProcess.ENCODING)
        else:
            stdout = self._stdout

        if self._error is None:
            stderr = to_text_string(self._process.readAllStandardError(),
                                    encoding=CondaProcess.ENCODING)
        else:
            stderr = self._error

        if function == 'get_conda_version':
            pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
            m = pat.match(stderr.strip())
            if m is None:
                m = pat.match(stdout.strip())
            if m is None:
                raise Exception('output did not match: {0}'.format(stderr))
            self._output = m.group(1)
        elif function == 'config_path':
            result = self._output
            self._output = result['rc_path']
        elif function == 'config_get':
            result = self._output
            self._output = result['get']
        elif (function == 'config_delete' or function == 'config_add' or
              function == 'config_set' or function == 'config_remove'):
            result = self._output
            self._output = result.get('warnings', [])
        elif function == 'pip':
            result = []
            lines = self._output.split('\n')
            for line in lines:
                if '<pip>' in line:
                    temp = line.split()[:-1] + ['pip']
                    result.append('-'.join(temp))
            self._output = result

        if stderr.strip():
            self._error = stderr

        self._parse = False

        self.sig_finished.emit(self._function_called, self._output,
                               self._error)

    def _abspath(self, abspath):
        """ """
        if abspath:
            if sys.platform == 'win32':
                python = join(CondaProcess.ROOT_PREFIX, 'python.exe')
                conda = join(CondaProcess.ROOT_PREFIX,
                             'Scripts', 'conda-script.py')
            else:
                python = join(CondaProcess.ROOT_PREFIX, 'bin/python')
                conda = join(CondaProcess.ROOT_PREFIX, 'bin/conda')
            cmd_list = [python, conda]
        else:
            # Just use whatever conda/pip is on the path
            cmd_list = ['conda']

        return cmd_list

    def _call_conda(self, extra_args, abspath=True):
        """ """
        if self._is_not_running():
            cmd_list = self._abspath(abspath)
            cmd_list.extend(extra_args)
            self._error, self._output = None, None
            self._process.start(cmd_list[0], cmd_list[1:])
            self.sig_started.emit()

    def _call_pip(self, name=None, prefix=None, extra_args=None):
        """ """
        if self._is_not_running():
            cmd_list = self._pip_cmd(name=name, prefix=prefix)
            cmd_list.extend(extra_args)
            self._error, self._output = None, None
            self._parse = False
            self._process.start(cmd_list[0], cmd_list[1:])
            self.sig_started.emit()

    def _call_and_parse(self, extra_args, abspath=True):
        """ """
        self._parse = True
        self._call_conda(extra_args, abspath=abspath)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        """ """
        cmd_list = []
        if kwargs.get('override_channels', False) and 'channel' not in kwargs:
            raise TypeError('conda search: override_channels requires channel')

        if 'env' in kwargs:
            cmd_list.extend(['--name', kwargs.pop('env')])
        if 'prefix' in kwargs:
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])
        if 'channel' in kwargs:
            channel = kwargs.pop('channel')
            if isinstance(channel, str):
                cmd_list.extend(['--channel', channel])
            else:
                cmd_list.append('--channel')
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append('--' + key.replace('_', '-'))

        return cmd_list

    # --- Public api
    # ------------------------------------------------------------------------
    @staticmethod
    def linked(prefix, as_spec=False):
        """
        Return the (set of canonical names) of linked packages in `prefix`.
        """
        if not isdir(prefix):
            raise Exception('no such directory: {0}'.format(prefix))

        meta_dir = join(prefix, 'conda-meta')

        if not isdir(meta_dir):
            # We might have nothing in linked (and no conda-meta directory)
            result = set()

        result = set(fn[:-5] for fn in os.listdir(meta_dir)
                     if fn.endswith('.json'))

        new_result = []
        if as_spec:
            for r in result:
                n, v, b = CondaProcess.split_canonical_name(r)
                new_result.append("{0}={1}".format(n, v))
            result = "\n".join(new_result)

        return result

    @staticmethod
    def split_canonical_name(cname):
        """
        Split a canonical package name into (name, version, build) strings.
        """
        result = tuple(cname.rsplit('-', 2))
        return result

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        """
        if prefix:
            CondaProcess.ROOT_PREFIX = prefix
        else:
            # Find conda instance, and then use info() to get 'root_prefix'
            if CondaProcess.ROOT_PREFIX is None:
                info = self.info(abspath=False)
                CondaProcess.ROOT_PREFIX = info['root_prefix']

    def get_conda_version(self):
        """
        Return the version of conda being used (invoked) as a string.
        """
        if self._is_not_running():
            self._function_called = 'get_conda_version'
            self._call_conda(['--version'])

    def get_envs(self, emit=False):
        """
        Return all of the (named) environments (this does not include the root
        environment), as a list of absolute paths to their prefixes.
        """
        if self._is_not_running():
            info = self.info()
            result = info['envs']

            if emit:
                self.sig_finished.emit('get_envs', result, "")

            return result

    def get_prefix_envname(self, name, emit=False):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        if self._is_not_running():
            if name == 'root':
                prefix = CondaProcess.ROOT_PREFIX
            for env_prefix in self.get_envs():
                if basename(env_prefix) == name:
                    prefix = env_prefix
                    break
            if emit:
                self.sig_finished.emit('get_prefix_envname', prefix, "")

            return prefix

    def info(self, abspath=True, emit=False):
        """
        Return a dictionary with configuration information.

        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
        if self._is_not_running():
            qprocess = QProcess()
            cmd_list = self._abspath(abspath)
            cmd_list.extend(['info', '--json'])
            qprocess.start(cmd_list[0], cmd_list[1:])
            qprocess.waitForFinished()
            output = qprocess.readAllStandardOutput()
            output = handle_qbytearray(output, CondaProcess.ENCODING)
            info = json.loads(output)

            if emit:
                self.sig_finished.emit("info", str(info), "")

            return info

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.

        Structure is {
            'package_name': [{
                'depends': list,
                'version': str,
                'name': str,
                'license': str,
                'fn': ...,
                ...
            }]
        }
        """
        if self._is_not_running():
            self._function_called = 'package_info'
            self._call_and_parse(['info', package, '--json'], abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ['search', '--json']

        if regex and spec:
            raise TypeError('conda search: only one of regex or spec allowed')

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(['--spec', spec])

        if 'platform' in kwargs:
            platform = kwargs.pop('platform')
            platforms = ('win-32', 'win-64', 'osx-64', 'linux-32', 'linux-64')
            if platform not in platforms:
                raise TypeError('conda search: platform must be one of ' +
                                ', '.join(platforms))
            cmd_list.extend(['--platform', platform])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('canonical', 'unknown', 'use_index_cache', 'outdated',
                 'override_channels')))

        if self._is_not_running():
            self._function_called = 'search'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def share(self, prefix):
        """
        Create a "share package" of the environment located in `prefix`,
        and return a dictionary with (at least) the following keys:
          - 'prefix': the full path to the created package
          - 'warnings': a list of warnings

        This file is created in a temp directory, and it is the callers
        responsibility to remove this directory (after the file has been
        handled in some way).
        """
        if self._is_not_running:
            self._function_called = 'share'
            self._call_and_parse(['share', '--json', '--prefix', prefix])

    def create(self, name=None, prefix=None, pkgs=None):
        """
        Create an environment either by 'name' or 'prefix' with a specified set
        of packages.
        """
        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into new environment')

        cmd_list = ['create', '--yes', '--quiet', '--json', '--mkdir']
        if name:
            ref = name
            search = [os.path.join(d, name) for d in self.info()['envs_dirs']]
            cmd_list.extend(['--name', name])
        elif prefix:
            ref = prefix
            search = [prefix]
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError("Must specify either an environment 'name' or a "
                            "'prefix' for new environment.")

        if any(os.path.exists(path) for path in search):
            raise CondaEnvExistsError('Conda environment [%s] already exists'
                                      % ref)

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        if self._is_not_running:
            self._function_called = 'create'
            self._call_conda(cmd_list)

    def install(self, name=None, prefix=None, pkgs=None, dep=True):
        """
        Install packages into an environment either by 'name' or 'prefix' with
        a specified set of packages
        """        
        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--yes', '--json', '--force-pscheck']
        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:  # just install into the current environment, whatever that is
            pass

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        if not dep:
            cmd_list.extend(['--no-deps'])

        if self._is_not_running:
            self._function_called = 'install'
            self._call_conda(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ['update', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to update, \
                            or all=True.")

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'no_deps', 'override_channels',
                 'no_pin', 'force', 'all', 'use_index_cache', 'use_local',
                 'alt_hint')))

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = 'update'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def remove(self, *pkgs, **kwargs):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        cmd_list = ['remove', '--json', '--quiet', '--yes', '--force-pscheck']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to remove, \
                            or all=True.")

        if kwargs.get('name') and kwargs.get('prefix'):
            raise TypeError("conda remove: At most one of 'name', 'prefix' "
                            "allowed")

        if kwargs.get('name'):
            cmd_list.extend(['--name', kwargs.pop('name')])

        if kwargs.get('prefix'):
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'features', 'override_channels',
                 'no_pin', 'force', 'all')))

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = 'remove'
            self._call_and_parse(cmd_list,
                                 abspath=kwargs.get('abspath', True))

    def remove_environment(self, name=None, prefix=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
        if self._is_not_running:
            self._function_called = 'remove_environment'
            self.remove(name=name, prefix=prefix, all=True, **kwargs)

    def clone_environment(self, clone, name=None, prefix=None, **kwargs):
        """
        Clone the environment ``clone`` into ``name`` or ``prefix``.
        """
        cmd_list = ['create', '--json', '--quiet']

        if (name and prefix) or not (name or prefix):
            raise TypeError("conda clone_environment: exactly one of 'name' "
                            "or 'prefix' required.")

        if name:
            cmd_list.extend(['--name', name])

        if prefix:
            cmd_list.extend(['--prefix', prefix])

        cmd_list.extend(['--clone', clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'unknown', 'use_index_cache', 'use_local',
                 'no_pin', 'force', 'all', 'channel', 'override_channels',
                 'no_default_packages')))

        if self._is_not_running():
            self._function_called = 'clone_environment'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def clone(self, path, prefix):
        """
        Clone a "share package" (located at `path`) by creating a new
        environment at `prefix`, and return a dictionary with (at least) the
        following keys:
          - 'warnings': a list of warnings

        The directory `path` is located in, should be some temp directory or
        some other directory OUTSIDE /opt/anaconda.  After calling this
        function, the original file (at `path`) may be removed (by the caller
        of this function).
        The return object is a list of warnings.
        """
        if self._process.state() == QProcess.NotRunning:
            self._function_called = 'clone'
            self._call_and_parse(['clone', '--json', '--prefix', prefix, path])

    def _setup_config_from_kwargs(kwargs):
        cmd_list = ['--json', '--force']

        if 'file' in kwargs:
            cmd_list.extend(['--file', kwargs['file']])

        if 'system' in kwargs:
            cmd_list.append('--system')

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_path'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_get'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--set', key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_set'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--add', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_add'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_remove'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove-key', key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        if self._is_not_running:
            self._function_called = 'config_delete'
            self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ['run', '--json', command]

        if self._is_not_running:
            self._function_called = 'run'
            self._call_and_parse(cmd_list, abspath=abspath)

    # --- Additional methods not in conda-api
    # ------------------------------------------------------------------------
    def dependencies(self, name=None, prefix=None, pkgs=None, dep=True):
        """
        Get dependenciy list for packages to be installed into an environment
        defined either by 'name' or 'prefix'.
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--dry-run', '--json']
        cmd_list = ['install', '--dry-run', '--json', '--force-pscheck']

        if not dep:
            cmd_list.extend(['--no-deps'])

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            pass

        cmd_list.extend(pkgs)

        if self._is_not_running:
            self._function_called = 'install_dry'
            self._call_and_parse(cmd_list)

    def environment_exists(self, name=None, prefix=None, abspath=True,
                           emit=False):
        """
        Check if an environment exists by 'name' or by 'prefix'. If query is
        by 'name' only the default conda environments directory is searched.
        """
        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        qprocess = QProcess()
        cmd_list = self._abspath(abspath)
        cmd_list.extend(['list', '--json'])

        if name:
            cmd_list.extend(['--name', name])
        else:
            cmd_list.extend(['--prefix', prefix])

        qprocess.start(cmd_list[0], cmd_list[1:])
        qprocess.waitForFinished()
        output = qprocess.readAllStandardOutput()
        output = handle_qbytearray(output, CondaProcess.ENCODING)
        info = json.loads(output)

        if emit:
            self.sig_finished.emit("info", unicode(info), "")

        return 'error' not in info

    def export(self, filename, name=None, prefix=None, emit=False):
        """
        Export environment by 'prefix' or 'name' as yaml 'filename'.
        """
        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if self._is_not_running:
            if name:
                temporal_envname = name

            if prefix:
                temporal_envname = 'tempenv' + int(random.random()*10000000)
                envs_dir = self.info()['envs_dirs'][0]
                os.symlink(prefix, os.sep.join([envs_dir, temporal_envname]))

            cmd = self._abspath(True)
            cmd.extend(['env', 'export', '--name', temporal_envname, '--file',
                        os.path.abspath(filename)])
            qprocess = QProcess()
            qprocess.start(cmd[0], cmd[1:])
            qprocess.waitForFinished()

            if prefix:
                os.unlink(os.sep.join([envs_dir, temporal_envname]))

    def update_environment(self, filename, name=None, prefix=None, emit=False):
        """
        Set environment at 'prefix' or 'name' to match 'filename' spec as yaml.
        """
        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if self._is_not_running:
            if name:
                temporal_envname = name

            if prefix:
                temporal_envname = 'tempenv' + int(random.random()*10000000)
                envs_dir = self.info()['envs_dirs'][0]
                os.symlink(prefix, os.sep.join([envs_dir, temporal_envname]))

            cmd = self._abspath(True)
            cmd.extend(['env', 'update', '--name', temporal_envname, '--file',
                        os.path.abspath(filename)])
            qprocess = QProcess()
            qprocess.start(cmd[0], cmd[1:])
            qprocess.waitForFinished()

            if prefix:
                os.unlink(os.sep.join([envs_dir, 'tempenv']))

    @property
    def error(self):
        return self._error

    @property
    def output(self):
        return self._output

    # --- Pip commands
    # -------------------------------------------------------------------------
    def _pip_cmd(self, name=None, prefix=None):
        """
        Get pip location based on environment `name` or `prefix`.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if name and self.environment_exists(name=name):
            prefix = self.get_prefix_envname(name)

        if sys.platform == 'win32':
            python = join(prefix, 'python.exe')  # FIXME:
            pip = join(prefix, 'pip.exe')    # FIXME:
        else:
            python = join(prefix, 'bin/python')
            pip = join(prefix, 'bin/pip')

        cmd_list = [python, pip]

        return cmd_list

    def pip_list(self, name=None, prefix=None, abspath=True, emit=False):
        """
        Get list of pip installed packages.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if self._is_not_running:
            cmd_list = self._abspath(abspath)
            if name:
                cmd_list.extend(['list', '--name', name])
            if prefix:
                cmd_list.extend(['list', '--prefix', prefix])

            qprocess = QProcess()
            qprocess.start(cmd_list[0], cmd_list[1:])
            qprocess.waitForFinished()
            output = qprocess.readAllStandardOutput()
            output = handle_qbytearray(output, CondaProcess.ENCODING)

            result = []
            lines = output.split('\n')

            for line in lines:
                if '<pip>' in line:
                    temp = line.split()[:-1] + ['pip']
                    result.append('-'.join(temp))

            if emit:
                self.sig_finished.emit("pip", str(result), "")

        return result

    def pip_remove(self, name=None, prefix=None, pkgs=None):
        """
        Remove a pip pacakge in given environment by 'name' or 'prefix'.
        """
        if isinstance(pkgs, list) or isinstance(pkgs, tuple):
            pkg = ' '.join(pkgs)
        else:
            pkg = pkgs

        extra_args = ['uninstall', '--yes', pkg]

        if self._is_not_running():
            self._function_called = 'pip_remove'
            self._call_pip(name=name, prefix=prefix, extra_args=extra_args)
Exemplo n.º 19
0
class ProfilerWidget(QWidget):
    """
    Profiler widget
    """
    DATAPATH = get_conf_path('profiler.results')
    VERSION = '0.0.1'
    redirect_stdio = Signal(bool)

    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)

        self.setWindowTitle("Profiler")

        self.output = None
        self.error_output = None

        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=ima.icon('run'),
                                              text=_("Profile"),
                                              tip=_("Run profiler"),
                                              triggered=lambda: self.start(),
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current profiling"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often.

        browse_button = create_toolbutton(self,
                                          icon=ima.icon('fileopen'),
                                          tip=_('Select Python script'),
                                          triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self,
                                            icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Show program's output"),
                                            triggered=self.show_log)

        self.datatree = ProfilerDataTree(self)

        self.collapse_button = create_toolbutton(
            self,
            icon=ima.icon('collapse'),
            triggered=lambda dD: self.datatree.change_view(-1),
            tip=_('Collapse one level up'))
        self.expand_button = create_toolbutton(
            self,
            icon=ima.icon('expand'),
            triggered=lambda dD: self.datatree.change_view(1),
            tip=_('Expand one level down'))

        self.save_button = create_toolbutton(self,
                                             text_beside_icon=True,
                                             text=_("Save data"),
                                             icon=ima.icon('filesave'),
                                             triggered=self.save_data,
                                             tip=_('Save profiling data'))
        self.load_button = create_toolbutton(
            self,
            text_beside_icon=True,
            text=_("Load data"),
            icon=ima.icon('fileimport'),
            triggered=self.compare,
            tip=_('Load profiling data for comparison'))
        self.clear_button = create_toolbutton(self,
                                              text_beside_icon=True,
                                              text=_("Clear comparison"),
                                              icon=ima.icon('editdelete'),
                                              triggered=self.clear)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        hlayout2.addWidget(self.save_button)
        hlayout2.addWidget(self.load_button)
        hlayout2.addWidget(self.clear_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.start_button.setEnabled(False)
        self.clear_button.setEnabled(False)

        if not is_profiler_installed():
            # This should happen only on certain GNU/Linux distributions
            # or when this a home-made Python build because the Python
            # profilers are included in the Python standard library
            for widget in (self.datatree, self.filecombo, self.start_button,
                           self.stop_button):
                widget.setDisabled(True)
            url = 'http://docs.python.org/library/profile.html'
            text = '%s <a href=%s>%s</a>' % (_('Please install'), url,
                                             _("the Python profiler modules"))
            self.datelabel.setText(text)
        else:
            pass  # self.show_data()

    def save_data(self):
        """Save data"""
        title = _("Save profiler result")
        filename, _selfilter = getsavefilename(
            self, title, getcwd_or_home(),
            _("Profiler result") + " (*.Result)")
        if filename:
            self.datatree.save_data(filename)

    def compare(self):
        filename, _selfilter = getopenfilename(
            self, _("Select script to compare"), getcwd_or_home(),
            _("Profiler result") + " (*.Result)")
        if filename:
            self.datatree.compare(filename)
            self.show_data()
            self.clear_button.setEnabled(True)

    def clear(self):
        self.datatree.compare(None)
        self.datatree.hide_diff_cols(True)
        self.show_data()
        self.clear_button.setEnabled(False)

    def analyze(self, filename, wdir=None, args=None, pythonpath=None):
        if not is_profiler_installed():
            return
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index = None  # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)

    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python script"), getcwd_or_home(),
            _("Python scripts") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)

    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output,
                       title=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath

        self.datelabel.setText(_('Profiling, please wait...'))

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(
            lambda ec, es=QProcess.ExitStatus: self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        if pythonpath is not None:
            env = [
                to_text_string(_pth)
                for _pth in self.process.systemEnvironment()
            ]
            add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)

        self.output = ''
        self.error_output = ''

        p_args = ['-m', 'cProfile', '-o', self.DATAPATH]
        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid
            # confusion with escape characters (otherwise, for example, '\t'
            # will be interpreted as a tabulation):
            p_args.append(osp.normpath(filename).replace(os.sep, '/'))
        else:
            p_args.append(filename)
        if args:
            p_args.extend(shell_split(args))
        executable = sys.executable
        if executable.endswith("spyder.exe"):
            # py2exe distribution
            executable = "python.exe"
        self.process.start(executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()

        self.datatree.load_data(self.DATAPATH)
        self.datatree.show_tree()

        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                               time.localtime())
        self.datelabel.setText(date_text)
Exemplo n.º 20
0
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self,
                 cmd_list,
                 parse=False,
                 pip=False,
                 callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first
                and self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                logger.error('{0}:\nSTDERR:\n{1}\nEND'.format(
                    ' '.join(self._cmd_list), stderr))
            elif stderr.strip() and self._pip:
                logger.error("pip error: {}".format(self._cmd_list))
        result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except Exception as error:
                result = stdout, str(error)

            if 'error' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')