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._partial_stderr = 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 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 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']))
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: 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(PYLINT_PATH, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start"))
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(150) self._timer.timeout.connect(self._communicate) # self._process.finished.connect(self._communicate) self._process.readyReadStandardOutput.connect(self._partial)
def _create_process( self, installer: InstallerTypes = "pip", ): process = QProcess() if installer != "pip": process.setProgram(installer) else: process.setProgram(self._sys_executable_or_bundled_python()) process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda process=process: self._on_stdout_ready(process)) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join([ user_site_packages(), env.systemEnvironment().value("PYTHONPATH") ]) env.insert("PYTHONPATH", combined_paths) # use path of parent process env.insert("PATH", QProcessEnvironment.systemEnvironment().value("PATH")) process.setProcessEnvironment(env) self.set_output_widget(self._output_widget) process.finished.connect( lambda ec, es: self._on_process_finished(process, ec, es) ) # FIXME connecting lambda to finished signal is bug creating and may end with segfault when garbage # collection will consume Installer object before process end. return process
def _create_process( self, installer: InstallerTypes = "pip", ): process = QProcess() process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda process=process: self._on_stdout_ready(process)) env = QProcessEnvironment.systemEnvironment() if installer == "pip": process.setProgram(self._sys_executable_or_bundled_python()) # patch process path combined_paths = os.pathsep.join([ user_site_packages(), env.systemEnvironment().value("PYTHONPATH"), ]) env.insert("PYTHONPATH", combined_paths) else: process.setProgram(installer) if installer == "mamba": from ..._version import version_tuple # To avoid napari version changing when installing a plugin, we # add a pin to the current napari version, that way we can # restrict any changes to the actual napari application. # Conda/mamba also pin python by default, so we effectively # constrain python and napari versions from changing, when # installing plugins inside the constructor bundled application. # See: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-pkgs.html#preventing-packages-from-updating-pinning napari_version = ".".join(str(v) for v in version_tuple[:3]) if env.contains("CONDA_PINNED_PACKAGES"): # str delimiter is '&' system_pins = f"&{env.value('CONDA_PINNED_PACKAGES')}" else: system_pins = "" env.insert( "CONDA_PINNED_PACKAGES", f"napari={napari_version}{system_pins}", ) if os.name == "nt": # workaround https://github.com/napari/napari/issues/4247, 4484 if not env.contains("TEMP"): temp = gettempdir() env.insert("TMP", temp) env.insert("TEMP", temp) if not env.contains("USERPROFILE"): env.insert("HOME", os.path.expanduser("~")) env.insert("USERPROFILE", os.path.expanduser("~")) process.setProcessEnvironment(env) self.set_output_widget(self._output_widget) process.finished.connect( lambda ec, es: self._on_process_finished(process, ec, es) ) # FIXME connecting lambda to finished signal is bug creating and may end with segfault when garbage # collection will consume Installer object before process end. return process
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() # 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) if os.name == "nt": # 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 PyLSP. if "USERPROFILE" in os.environ: env.insert("USERPROFILE", os.environ["USERPROFILE"]) # The PyLSP can't start on pip installations if APPDATA # is missing and the user has installed their packages on # that directory. # Fixes spyder-ide/spyder#17661 if (not (is_anaconda() or is_pynsist()) and "APPDATA" in os.environ): env.insert("APPDATA", os.environ["APPDATA"]) 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_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_server(self): """Start server.""" # This is not necessary if we're trying to connect to an # external server if self.external_server: return # Set server log file server_log_file = None if get_debug_level() > 0: # Create server log file server_log_fname = 'server_{0}_{1}.log'.format( self.language, os.getpid()) server_log_file = get_conf_path( osp.join('lsp_logs', server_log_fname)) if not osp.exists(osp.dirname(server_log_file)): os.makedirs(osp.dirname(server_log_file)) if self.stdio: if self.language == 'python': self.server_args += ['--log-file', server_log_file] self.transport_args += ['--server-log-file', server_log_file] # Start server with logging options if self.language == 'python': if get_debug_level() == 2: self.server_args.append('-v') elif get_debug_level() == 3: self.server_args.append('-vv') logger.info('Starting server: {0}'.format(' '.join(self.server_args))) # 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. if self.language == 'python': cwd = get_conf_path('empty_cwd') if not osp.exists(cwd): os.mkdir(cwd) else: cwd = None # Setup server self.server = QProcess(self) self.server.errorOccurred.connect(self.handle_process_errors) self.server.setWorkingDirectory(cwd) self.server.setProcessChannelMode(QProcess.MergedChannels) if server_log_file is not None: self.server.setStandardOutputFile(server_log_file) # Start server self.server.start(self.server_args[0], self.server_args[1:])
def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [ to_text_string(_path) for _path in self.process.systemEnvironment() ] processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) add_pathlist_to_PYTHONPATH(env, self.path) self.process.setProcessEnvironment(processEnvironment) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) # Shell arguments if os.name == 'nt': p_args = ['/Q'] else: p_args = ['-i'] if self.arguments: p_args.extend(shell_split(self.arguments)) self.process.readyReadStandardOutput.connect(self.write_output) self.process.finished.connect(self.finished) self.kill_button.clicked.connect(self.process.kill) if os.name == 'nt': self.process.start('cmd.exe', p_args) else: # Using bash: self.process.start('bash', p_args) self.send_to_process('PS1="\\u@\\h:\\w> "\n') running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.shell.setFocus() self.started.emit() return self.process
def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or self.env is None: python_path = osp.dirname(get_module_path('spyder')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass if self.extra_path: try: python_path = osp.pathsep.join([python_path] + self.extra_path) except Exception as e: debug_print("Error when adding extra_path to plugin env") debug_print(e) env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received)
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. """ 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) executable = get_python_executable() p_args = ['-m', self.module] + self.create_argument_list() try: os.remove(self.resultfilename) except OSError: pass self.process.start(executable, p_args) running = self.process.waitForStarted() if not running: raise RuntimeError
def start_process(self, process, args=None, cwd=None, env=None): """ Starts a process interactively. :param process: Process to run :type process: str :param args: List of arguments (list of str) :type args: list :param cwd: Working directory :type cwd: str :param env: environment variables (dict). """ self.setReadOnly(False) if env is None: env = {} if args is None: args = [] if not self._running: self.process = QProcess() self.process.finished.connect(self._on_process_finished) self.process.started.connect(self.process_started.emit) self.process.error.connect(self._write_error) self.process.readyReadStandardError.connect(self._on_stderr) self.process.readyReadStandardOutput.connect(self._on_stdout) if cwd: self.process.setWorkingDirectory(cwd) e = self.process.systemEnvironment() ev = QProcessEnvironment() for v in e: values = v.split('=') ev.insert(values[0], '='.join(values[1:])) for k, v in env.items(): ev.insert(k, v) self.process.setProcessEnvironment(ev) self._running = True self._process_name = process self._args = args if self._clear_on_start: self.clear() self._user_stop = False self._write_started() self.process.start(process, args) self.process.waitForStarted() else: _logger().warning('a process is already running')
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 _start(self): """Start the code analysis.""" self.start_spinner() self.output = "" self.error_output = "" self._process = process = QProcess(self) process.setProcessChannelMode(QProcess.SeparateChannels) process.setWorkingDirectory(getcwd_or_home()) process.readyReadStandardOutput.connect(self._read_output) process.readyReadStandardError.connect( lambda: self._read_output(error=True)) process.finished.connect( lambda ec, es=QProcess.ExitStatus: self._finished(ec, es)) command_args = self.get_command(self.get_filename()) processEnvironment = QProcessEnvironment() processEnvironment.insert("PYTHONIOENCODING", "utf8") # resolve spyder-ide/spyder#14262 if running_in_mac_app(): pyhome = os.environ.get("PYTHONHOME") processEnvironment.insert("PYTHONHOME", pyhome) process.setProcessEnvironment(processEnvironment) process.start(sys.executable, command_args) running = process.waitForStarted() if not running: self.stop_spinner() QMessageBox.critical( self, _("Error"), _("Process failed to start"), )
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: 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(PYLINT_PATH, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start"))
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 = '' 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}" ] pylintrc_path = self.get_pylintrc_path(filename=filename) if pylintrc_path is not None: p_args += ['--rcfile={}'.format(pylintrc_path)] p_args += [filename] processEnvironment = QProcessEnvironment() processEnvironment.insert("PYTHONIOENCODING", "utf8") self.process.setProcessEnvironment(processEnvironment) 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 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() if DEV: # Use local pyls instead of site-packages one 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) 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 restart(self): """Restart the napari application in a detached process.""" process = QProcess() process.setProgram(sys.executable) if not running_as_bundled_app(): process.setArguments(sys.argv) process.startDetached() self.close(quit_app=True)
def start_server(self, filename, interpreter): """ Start a notebook server asynchronously. Start a server which can render the given notebook and return immediately. Assume the server uses the given interpreter. The manager will check periodically whether the server is accepting requests and emit `sig_server_started` or `sig_server_timed_out` when appropriate. Parameters ---------- filename : str File name of notebook to be rendered by the server. interpreter : str File name of Python interpreter to be used. """ home_dir = get_home_dir() if filename.startswith(home_dir): nbdir = home_dir else: nbdir = osp.dirname(filename) logger.debug('Starting new notebook server for %s', nbdir) process = QProcess(None) serverscript = osp.join(osp.dirname(__file__), '../server/main.py') serverscript = osp.normpath(serverscript) arguments = [ serverscript, '--no-browser', '--notebook-dir={}'.format(nbdir), '--NotebookApp.password='******'--KernelSpecManager.kernel_spec_class={}'.format(KERNELSPEC) ] if self.dark_theme: arguments.append('--dark') logger.debug('Arguments: %s', repr(arguments)) if DEV: env = QProcessEnvironment.systemEnvironment() env.insert('PYTHONPATH', osp.dirname(get_module_path('spyder'))) process.setProcessEnvironment(env) server_process = ServerProcess(process, notebook_dir=nbdir, interpreter=interpreter) process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda: self.read_server_output(server_process)) process.errorOccurred.connect( lambda error: self.handle_error(server_process, error)) process.finished.connect(lambda code, status: self.handle_finished( server_process, code, status)) process.start(sys.executable, arguments) self.servers.append(server_process) self._check_server_started(server_process)
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) print(filename) 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, '/') 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(_('Running tests, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(filename) 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()] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) self.process.setEnvironment(env) self.output = '' self.error_output = '' executable = "py.test" p_args = ['--junit-xml', self.DATAPATH] # executable = "nosetests" # p_args = ['--with-xunit', "--xunit-file=%s" % self.DATAPATH] if args: p_args.extend(programs.shell_split(args)) 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 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()) and not running_in_ci(): sys_path = self._clean_sys_path() 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_transport(self): """Start transport layer.""" self.transport_args = list(map(str, self.transport_args)) logger.info('Starting transport: {0}'.format(' '.join( self.transport_args))) self.transport = QProcess(self) self.transport.errorOccurred.connect(self.handle_process_errors) # Modifying PYTHONPATH to run transport in development mode or # tests if DEV or running_under_pytest(): env = QProcessEnvironment() 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 transport log file transport_log_file = None if get_debug_level() > 0: transport_log_fname = 'transport_{0}_{1}.log'.format( self.language, os.getpid()) transport_log_file = get_conf_path( osp.join('lsp_logs', transport_log_fname)) if not osp.exists(osp.dirname(transport_log_file)): os.makedirs(osp.dirname(transport_log_file)) # Set channel properties if self.stdio: self.transport_args += self.server_args self.transport.setProcessChannelMode(QProcess.SeparateChannels) if transport_log_file is not None: self.transport.setStandardErrorFile(transport_log_file) else: self.transport.setProcessChannelMode(QProcess.MergedChannels) if transport_log_file is not None: self.transport.setStandardOutputFile(transport_log_file) # Start transport self.transport.start(self.transport_args[0], self.transport_args[1:])
def __init__(self, output_widget: QTextEdit = None): from ...plugins import plugin_manager # create install process self._output_widget = None self.process = QProcess() self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join([ user_site_packages(), env.systemEnvironment().value("PYTHONPATH") ]) env.insert("PYTHONPATH", combined_paths) self.process.setProcessEnvironment(env) self.process.finished.connect(lambda: plugin_manager.discover()) self.process.finished.connect(lambda: plugin_manager.prune()) self.set_output_widget(output_widget)
def run_command(self, command_list): for qwidget in self.qwidgets_to_disable: qwidget.setEnabled(False) self._busy.setRange(0, 0) self._busy.setVisible(True) self._busy.update() self._timer_label.setVisible(True) self._time0 = time.time() self.process = QProcess(self) self.process.readyReadStandardOutput.connect(self.stdout_ready) self.process.finished.connect(self.on_finished) #self.process.started.connect(lambda: print_func('Started!')) #self.process.finished.connect(lambda: print_func('Finished!')) #self.process.startDetached(command_list[0], command_list[1:]) self.process.start(command_list[0], command_list[1:]) for qwidget in self.qwidgets_to_disable: qwidget.setEnabled(True)
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 __init__(self, parent=None, window_flags=None): super(PyChopGui, self).__init__(parent) if window_flags: self.setWindowFlags(window_flags) self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 # help self.assistant_process = QProcess(self) # pylint: disable=protected-access self.mantidplot_name = 'PyChop'
def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) add_pathlist_to_PYTHONPATH(env, self.path) self.process.setProcessEnvironment(processEnvironment) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) # Shell arguments if os.name == 'nt': p_args = ['/Q'] else: p_args = ['-i'] if self.arguments: p_args.extend( shell_split(self.arguments) ) self.process.readyReadStandardOutput.connect(self.write_output) self.process.finished.connect(self.finished) self.kill_button.clicked.connect(self.process.kill) if os.name == 'nt': self.process.start('cmd.exe', p_args) else: # Using bash: self.process.start('bash', p_args) self.send_to_process('PS1="\\u@\\h:\\w> "\n') running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.shell.setFocus() self.started.emit() return self.process
def launch(project_path, command): """ Handle launching commands from projects. """ logger.debug(str((project_path, command))) command = command.format(PREFIX=project_path) command = command.replace('\\', '/') if os.name == 'nt': command = command.replace('/bin', '/Scripts') if '.ipynb' in command: filename = command.replace('ipython notebook ', '') filename = filename.replace('jupyter notebook ', '') run_notebook(project_path=project_path, filename=filename) elif command.startswith('python '): filename = command.replace('python ', '') run_python_file(project_path, filename=filename) else: proc = QProcess() logger.debug(command) proc.startDetached(command)
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 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 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 _main(args, exec=True): # import pydm # app = QApplication([]) # app = pydm.PyDMApplication() app = QApplication.instance() or QApplication([]) signal.signal(signal.SIGINT, signal.SIG_DFL) from xicam.gui.windows import splash from xicam.core import msg if getattr(args, 'verbose', False): 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() mainwindow.activateWindow() # splash = splash.XicamSplashScreen(args=args) if exec: return app.exec_() else: return mainwindow
def _main(args, exec=True): global mainwindow, splash_proc, show_check_timer # import pydm # app = QApplication([]) # app = pydm.PyDMApplication() app = QApplication.instance() or QApplication([]) signal.signal(signal.SIGINT, signal.SIG_DFL) from xicam.gui.windows import splash from xicam.core import msg if getattr(args, 'verbose', False): 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)]) show_check_timer = QTimer() show_check_timer.timeout.connect(check_show_mainwindow) show_check_timer.start(100) from xicam.gui.windows.mainwindow import XicamMainWindow mainwindow = XicamMainWindow() if exec: return app.exec_() else: return mainwindow
def _prepare_process(self, config, pythonpath): """ Prepare and return process for running the unit test suite. This sets the working directory and environment. """ process = QProcess(self) process.setProcessChannelMode(QProcess.MergedChannels) process.setWorkingDirectory(config.wdir) process.finished.connect(self.finished) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) process.setProcessEnvironment(processEnvironment) return process
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)
def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received)
def _prepare_process(self, config, pythonpath): """ Prepare and return process for running the unit test suite. This sets the working directory and environment. """ process = QProcess(self) process.setProcessChannelMode(QProcess.MergedChannels) process.setWorkingDirectory(config.wdir) process.finished.connect(self.finished) if pythonpath: env = QProcessEnvironment.systemEnvironment() old_python_path = env.value('PYTHONPATH', None) python_path_str = os.pathsep.join(pythonpath) if old_python_path: python_path_str += os.pathsep + old_python_path env.insert('PYTHONPATH', python_path_str) process.setProcessEnvironment(env) return process
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 __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 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
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
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget sig_pdb = Signal(str, int) open_file = Signal(str, int) ipython_kernel_start_error = Signal(str) create_ipython_client = Signal(str) started = Signal() sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, post_mortem=False, path=[], python_args='', ipykernel=False, arguments='', stand_alone=None, umr_enabled=True, umr_namelist=[], umr_verbose=True, pythonstartup=None, pythonexecutable=None, external_interpreter=False, monitor_enabled=True, mpl_backend=None, ets_backend='qt4', qt_api=None, merge_output_channels=False, colorize_sys_stderr=False, autorefresh_timeout=3000, autorefresh_state=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): assert qt_api in (None, 'pyqt', 'pyside', 'pyqt5') self.namespacebrowser = None # namespace browser widget! self.dialog_manager = DialogManager() self.stand_alone = stand_alone # stand alone settings (None: plugin) self.interact = interact self.is_ipykernel = ipykernel self.pythonstartup = pythonstartup self.pythonexecutable = pythonexecutable self.external_interpreter = external_interpreter self.monitor_enabled = monitor_enabled self.mpl_backend = mpl_backend self.ets_backend = ets_backend self.qt_api = qt_api self.merge_output_channels = merge_output_channels self.colorize_sys_stderr = colorize_sys_stderr self.umr_enabled = umr_enabled self.umr_namelist = umr_namelist self.umr_verbose = umr_verbose self.autorefresh_timeout = autorefresh_timeout self.autorefresh_state = autorefresh_state self.namespacebrowser_button = None self.cwd_button = None self.env_button = None self.syspath_button = None self.terminate_button = None self.notification_thread = None ExternalShellBase.__init__(self, parent=parent, fname=fname, wdir=wdir, history_filename='history.py', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) if self.pythonexecutable is None: self.pythonexecutable = get_python_executable() self.python_args = None if python_args: assert is_text_string(python_args) self.python_args = python_args assert is_text_string(arguments) self.arguments = arguments self.connection_file = None if self.is_ipykernel: self.interact = False # Running our custom startup script for IPython kernels: # (see spyderlib/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path( 'spyderlib.widgets.externalshell', 'start_ipython_kernel.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(self.interact) self.debug_action.setChecked(debug) self.introspection_socket = None self.is_interpreter = fname is None if self.is_interpreter: self.terminate_button.hide() self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) # Additional python path list self.path = path self.shell.path = path def set_introspection_socket(self, introspection_socket): self.introspection_socket = introspection_socket if self.namespacebrowser is not None: settings = self.namespacebrowser.get_view_settings() communicate(introspection_socket, 'set_remote_view_settings()', settings=[settings]) def set_autorefresh_timeout(self, interval): if self.introspection_socket is not None: try: communicate(self.introspection_socket, "set_monitor_timeout(%d)" % interval) except socket.error: pass def closeEvent(self, event): self.quit_monitor() ExternalShellBase.closeEvent(self, event) def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) if self.namespacebrowser_button is None \ and self.stand_alone is not None: self.namespacebrowser_button = create_toolbutton(self, text=_("Variables"), icon=ima.icon('dictedit'), tip=_("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer, text_beside_icon=True) if self.terminate_button is None: self.terminate_button = create_toolbutton(self, text=_("Terminate"), icon=ima.icon('stop'), tip=_("Attempts to stop the process. The process\n" "may not exit as a result of clicking this\n" "button (it is given the chance to prompt\n" "the user for any unsaved files, etc).")) buttons = [] if self.namespacebrowser_button is not None: buttons.append(self.namespacebrowser_button) buttons += [self.run_button, self.terminate_button, self.kill_button, self.options_button] return buttons def get_options_menu(self): ExternalShellBase.get_options_menu(self) self.interact_action = create_action(self, _("Interact")) self.interact_action.setCheckable(True) self.debug_action = create_action(self, _("Debug")) self.debug_action.setCheckable(True) self.args_action = create_action(self, _("Arguments..."), triggered=self.get_arguments) self.post_mortem_action = create_action(self, _("Post Mortem Debug")) self.post_mortem_action.setCheckable(True) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action, self.post_mortem_action)) self.cwd_button = create_action(self, _("Working directory"), icon=ima.icon('DirOpenIcon'), tip=_("Set current working directory"), triggered=self.set_current_working_directory) self.env_button = create_action(self, _("Environment variables"), icon=ima.icon('environ'), triggered=self.show_env) self.syspath_button = create_action(self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.show_syspath) actions = [run_settings_menu, self.show_time_action, None, self.cwd_button, self.env_button, self.syspath_button] if self.menu_actions is not None: actions += [None]+self.menu_actions return actions def is_interpreter(self): """Return True if shellwidget is a Python interpreter""" return self.is_interpreter def get_shell_widget(self): if self.stand_alone is None: return self.shell else: self.namespacebrowser = NamespaceBrowser(self) settings = self.stand_alone self.namespacebrowser.set_shellwidget(self) self.namespacebrowser.setup(**settings) self.namespacebrowser.sig_collapse.connect( lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.splitter.splitterMoved.connect(self.splitter_moved) splitter.addWidget(self.shell) splitter.setCollapsible(0, False) splitter.addWidget(self.namespacebrowser) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 0) splitter.setHandleWidth(5) splitter.setSizes([2, 1]) return splitter def get_icon(self): return ima.icon('python') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_action.setEnabled(not state and not self.is_interpreter) self.debug_action.setEnabled(not state and not self.is_interpreter) self.args_action.setEnabled(not state and not self.is_interpreter) self.post_mortem_action.setEnabled(not state and not self.is_interpreter) if state: if self.arguments: argstr = _("Arguments: %s") % self.arguments else: argstr = _("No argument") else: argstr = _("Arguments...") self.args_action.setText(argstr) self.terminate_button.setVisible(not self.is_interpreter and state) if not state: self.toggle_globals_explorer(False) for btn in (self.cwd_button, self.env_button, self.syspath_button): btn.setEnabled(state and self.monitor_enabled) if self.namespacebrowser_button is not None: self.namespacebrowser_button.setEnabled(state) def set_namespacebrowser(self, namespacebrowser): """ Set namespace browser *widget* Note: this method is not used in stand alone mode """ self.namespacebrowser = namespacebrowser self.configure_namespacebrowser() def configure_namespacebrowser(self): """Connect the namespace browser to the notification thread""" if self.notification_thread is not None: self.notification_thread.refresh_namespace_browser.connect( self.namespacebrowser.refresh_table) signal = self.notification_thread.sig_process_remote_view signal.connect(lambda data: self.namespacebrowser.process_remote_view(data)) def create_process(self): self.shell.clear() self.process = QProcess(self) if self.merge_output_channels: self.process.setProcessChannelMode(QProcess.MergedChannels) else: self.process.setProcessChannelMode(QProcess.SeparateChannels) self.shell.wait_for_ready_read.connect( lambda: self.process.waitForReadyRead(250)) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) #-------------------------Python specific------------------------------ # Python arguments p_args = ['-u'] if DEBUG >= 3: p_args += ['-v'] p_args += get_python_args(self.fname, self.python_args, self.interact_action.isChecked(), self.debug_action.isChecked(), self.arguments) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # Set standard input/output encoding for Python consoles # (IPython handles it on its own) # See http://stackoverflow.com/q/26312400/438386, specifically # the comments of Martijn Pieters if not self.is_ipykernel: env.append('PYTHONIOENCODING=UTF-8') # Monitor if self.monitor_enabled: env.append('SPYDER_SHELL_ID=%s' % id(self)) env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout) env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state) from spyderlib.widgets.externalshell import introspection introspection_server = introspection.start_introspection_server() introspection_server.register(self) notification_server = introspection.start_notification_server() self.notification_thread = notification_server.register(self) self.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.new_ipython_kernel.connect( lambda args: self.create_ipython_client.emit(args)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(fname, lineno)) if self.namespacebrowser is not None: self.configure_namespacebrowser() env.append('SPYDER_I_PORT=%d' % introspection_server.port) env.append('SPYDER_N_PORT=%d' % notification_server.port) # External modules options if not self.is_ipykernel: env.append('ETS_TOOLKIT=%s' % self.ets_backend) if self.mpl_backend is not None: backends = {0: 'Automatic', 1: 'None', 2: 'TkAgg'} env.append('SPY_MPL_BACKEND=%s' % backends[self.mpl_backend]) if self.qt_api and not self.is_ipykernel: env.append('QT_API=%s' % self.qt_api) env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr) # # Socket-based alternative (see input hook in sitecustomize.py): # if self.install_qt_inputhook: # from PyQt4.QtNetwork import QLocalServer # self.local_server = QLocalServer() # self.local_server.listen(str(id(self))) # User Module Deleter if self.is_interpreter: env.append('UMR_ENABLED=%r' % self.umr_enabled) env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist)) env.append('UMR_VERBOSE=%r' % self.umr_verbose) env.append('MATPLOTLIB_ION=True') else: if self.interact: env.append('MATPLOTLIB_ION=True') else: env.append('MATPLOTLIB_ION=False') # IPython kernel env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel) # External interpreter env.append('EXTERNAL_INTERPRETER=%r' % self.external_interpreter) # Add sitecustomize path to path list pathlist = [] scpath = osp.dirname(osp.abspath(__file__)) pathlist.append(scpath) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable add_pathlist_to_PYTHONPATH(env, pathlist) #-------------------------Python specific------------------------------ self.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(self.process.kill) #-------------------------Python specific------------------------------ # Fixes for our Mac app: # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app, # but their values are messing sys.path for external interpreters # (e.g. EPD) so we need to remove them from the environment. # 2. Set PYTHONPATH again but without grabbing entries defined in the # environment (Fixes Issue 1321) # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if running_in_mac_app(): if MAC_APP_NAME not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.process.start(self.pythonexecutable, p_args) #-------------------------Python specific------------------------------ running = self.process.waitForStarted(3000) self.set_running_state(running) if not running: if self.is_ipykernel: self.ipython_kernel_start_error.emit( _("The kernel failed to start!! That's all we know... " "Please close this console and open a new one.")) else: QMessageBox.critical(self, _("Error"), _("A Python console failed to start!")) else: self.shell.setFocus() self.started.emit() return self.process def finished(self, exit_code, exit_status): """Reimplement ExternalShellBase method""" if self.is_ipykernel and exit_code == 1: self.ipython_kernel_start_error.emit(self.shell.get_text_with_eol()) ExternalShellBase.finished(self, exit_code, exit_status) self.introspection_socket = None #============================================================================== # Input/Output #============================================================================== def write_error(self): if os.name == 'nt': #---This is apparently necessary only on Windows (not sure though): # emptying standard output buffer before writing error output self.process.setReadChannel(QProcess.StandardOutput) if self.process.waitForReadyRead(1): self.write_output() self.shell.write_error(self.get_stderr()) QApplication.processEvents() def send_to_process(self, text): if not self.is_running(): return if not is_text_string(text): text = to_text_string(text) if self.mpl_backend == 0 and os.name == 'nt' and \ self.introspection_socket is not None: communicate(self.introspection_socket, "toggle_inputhook_flag(True)") # # Socket-based alternative (see input hook in sitecustomize.py): # while self.local_server.hasPendingConnections(): # self.local_server.nextPendingConnection().write('go!') if any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \ any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]): text = 'evalsc(r"%s")\n' % text if not text.endswith('\n'): text += '\n' self.process.write(to_binary_string(text, 'utf8')) self.process.waitForBytesWritten(-1) # Eventually write prompt faster (when hitting Enter continuously) # -- necessary/working on Windows only: if os.name == 'nt': self.write_error() def keyboard_interrupt(self): if self.introspection_socket is not None: communicate(self.introspection_socket, "thread.interrupt_main()") def quit_monitor(self): if self.introspection_socket is not None: try: write_packet(self.introspection_socket, "thread.exit()") except socket.error: pass #============================================================================== # Globals explorer #============================================================================== @Slot(bool) def toggle_globals_explorer(self, state): if self.stand_alone is not None: self.splitter.setSizes([1, 1 if state else 0]) self.namespacebrowser_button.setChecked(state) if state and self.namespacebrowser is not None: self.namespacebrowser.refresh_table() def splitter_moved(self, pos, index): self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] ) #============================================================================== # Misc. #============================================================================== @Slot() def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.redirect_stdio.emit(True) @Slot() def show_env(self): """Show environment variables""" get_func = self.shell.get_env set_func = self.shell.set_env self.dialog_manager.show(RemoteEnvDialog(get_func, set_func)) @Slot() def show_syspath(self): """Show sys.path contents""" editor = CollectionsEditor() editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor)
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)
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)
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"))
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
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)
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)
class AsyncClient(QObject): """ A class which handles a connection to a client through a QProcess. """ # Emitted when the client has initialized. initialized = Signal() # Emitted when the client errors. errored = Signal() # Emitted when a request response is received. received = Signal(object) def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received) def request(self, func_name, *args, **kwargs): """Send a request to the server. The response will be a dictionary the 'request_id' and the 'func_name' as well as a 'result' field with the object returned by the function call or or an 'error' field with a traceback. """ if not self.is_initialized: return request_id = uuid.uuid4().hex request = dict(func_name=func_name, args=args, kwargs=kwargs, request_id=request_id) self._send(request) return request_id def close(self): """Cleanly close the connection to the server. """ self.closing = True self.is_initialized = False self.timer.stop() self.notifier.activated.disconnect(self._on_msg_received) self.notifier.setEnabled(False) del self.notifier self.request('server_quit') self.process.waitForFinished(1000) self.process.close() self.context.destroy() def _on_finished(self): """Handle a finished signal from the process. """ if self.closing: return if self.is_initialized: debug_print('Restarting %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.is_initialized = False self.notifier.setEnabled(False) self.run() else: debug_print('Errored %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.errored.emit() def _on_msg_received(self): """Handle a message trigger from the socket. """ self.notifier.setEnabled(False) while 1: try: resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK) except zmq.ZMQError: self.notifier.setEnabled(True) return if not self.is_initialized: self.is_initialized = True debug_print('Initialized %s' % self.name) self.initialized.emit() self.timer.start(HEARTBEAT) continue resp['name'] = self.name self.received.emit(resp) def _heartbeat(self): """Send a heartbeat to keep the server alive. """ self._send(dict(func_name='server_heartbeat')) def _send(self, obj): """Send an object to the server. """ try: self.socket.send_pyobj(obj) except Exception as e: debug_print(e) self.is_initialized = False self._on_finished()
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)
def create_process(self): self.shell.clear() self.process = QProcess(self) if self.merge_output_channels: self.process.setProcessChannelMode(QProcess.MergedChannels) else: self.process.setProcessChannelMode(QProcess.SeparateChannels) self.shell.wait_for_ready_read.connect( lambda: self.process.waitForReadyRead(250)) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) #-------------------------Python specific------------------------------ # Python arguments p_args = ['-u'] if DEBUG >= 3: p_args += ['-v'] p_args += get_python_args(self.fname, self.python_args, self.interact_action.isChecked(), self.debug_action.isChecked(), self.arguments) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # Set standard input/output encoding for Python consoles # (IPython handles it on its own) # See http://stackoverflow.com/q/26312400/438386, specifically # the comments of Martijn Pieters if not self.is_ipykernel: env.append('PYTHONIOENCODING=UTF-8') # Monitor if self.monitor_enabled: env.append('SPYDER_SHELL_ID=%s' % id(self)) env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout) env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state) from spyderlib.widgets.externalshell import introspection introspection_server = introspection.start_introspection_server() introspection_server.register(self) notification_server = introspection.start_notification_server() self.notification_thread = notification_server.register(self) self.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.new_ipython_kernel.connect( lambda args: self.create_ipython_client.emit(args)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(fname, lineno)) if self.namespacebrowser is not None: self.configure_namespacebrowser() env.append('SPYDER_I_PORT=%d' % introspection_server.port) env.append('SPYDER_N_PORT=%d' % notification_server.port) # External modules options if not self.is_ipykernel: env.append('ETS_TOOLKIT=%s' % self.ets_backend) if self.mpl_backend is not None: backends = {0: 'Automatic', 1: 'None', 2: 'TkAgg'} env.append('SPY_MPL_BACKEND=%s' % backends[self.mpl_backend]) if self.qt_api and not self.is_ipykernel: env.append('QT_API=%s' % self.qt_api) env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr) # # Socket-based alternative (see input hook in sitecustomize.py): # if self.install_qt_inputhook: # from PyQt4.QtNetwork import QLocalServer # self.local_server = QLocalServer() # self.local_server.listen(str(id(self))) # User Module Deleter if self.is_interpreter: env.append('UMR_ENABLED=%r' % self.umr_enabled) env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist)) env.append('UMR_VERBOSE=%r' % self.umr_verbose) env.append('MATPLOTLIB_ION=True') else: if self.interact: env.append('MATPLOTLIB_ION=True') else: env.append('MATPLOTLIB_ION=False') # IPython kernel env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel) # External interpreter env.append('EXTERNAL_INTERPRETER=%r' % self.external_interpreter) # Add sitecustomize path to path list pathlist = [] scpath = osp.dirname(osp.abspath(__file__)) pathlist.append(scpath) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable add_pathlist_to_PYTHONPATH(env, pathlist) #-------------------------Python specific------------------------------ self.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(self.process.kill) #-------------------------Python specific------------------------------ # Fixes for our Mac app: # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app, # but their values are messing sys.path for external interpreters # (e.g. EPD) so we need to remove them from the environment. # 2. Set PYTHONPATH again but without grabbing entries defined in the # environment (Fixes Issue 1321) # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if running_in_mac_app(): if MAC_APP_NAME not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.process.start(self.pythonexecutable, p_args) #-------------------------Python specific------------------------------ running = self.process.waitForStarted(3000) self.set_running_state(running) if not running: if self.is_ipykernel: self.ipython_kernel_start_error.emit( _("The kernel failed to start!! That's all we know... " "Please close this console and open a new one.")) else: QMessageBox.critical(self, _("Error"), _("A Python console failed to start!")) else: self.shell.setFocus() self.started.emit() return self.process
messages.append(message) except zmq.ZMQError: pass finally: self.notifier.setEnabled(True) if messages: self.sig_received.emit(messages) def close(self): """Read any remaining messages and close stream.""" self.received_message() # Flush remaining messages self.notifier.setEnabled(False) self.socket.close() self.context.destroy() if __name__ == '__main__': # For testing, construct a ZMQ stream between two processes and send # the number 42 over the stream if len(sys.argv) == 1: app = QApplication(sys.argv) manager = ZmqStreamReader() manager.sig_received.connect(print) process = QProcess() process.start('python', [sys.argv[0], str(manager.port)]) process.finished.connect(app.quit) sys.exit(app.exec_()) else: worker = ZmqStreamWriter(sys.argv[1]) worker.write(42)
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.')
class ExternalSystemShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = TerminalWidget started = Signal() def __init__(self, parent=None, wdir=None, path=[], light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): ExternalShellBase.__init__(self, parent=parent, fname=None, wdir=wdir, history_filename='.history', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) # Additional python path list self.path = path # For compatibility with the other shells that can live in the external # console self.is_ipykernel = False self.connection_file = None def get_icon(self): return ima.icon('cmdprompt') def finish_process(self): while not self.process.waitForFinished(100): self.process.kill(); def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) add_pathlist_to_PYTHONPATH(env, self.path) self.process.setProcessEnvironment(processEnvironment) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) # Shell arguments if os.name == 'nt': p_args = ['/Q'] else: p_args = ['-i'] if self.arguments: p_args.extend( shell_split(self.arguments) ) self.process.readyReadStandardOutput.connect(self.write_output) self.process.finished.connect(self.finished) self.kill_button.clicked.connect(self.process.kill) if os.name == 'nt': self.process.start('cmd.exe', p_args) else: # Using bash: self.process.start('bash', p_args) self.send_to_process('PS1="\\u@\\h:\\w> "\n') running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.shell.setFocus() self.started.emit() return self.process #=============================================================================== # Input/Output #=============================================================================== def transcode(self, qba): if os.name == 'nt': return to_text_string( CP850_CODEC.toUnicode(qba.data()) ) else: return ExternalShellBase.transcode(self, qba) def send_to_process(self, text): if not is_text_string(text): text = to_text_string(text) if text[:-1] in ["clear", "cls", "CLS"]: self.shell.clear() self.send_to_process(os.linesep) return if not text.endswith('\n'): text += '\n' if os.name == 'nt': self.process.write(text.encode('cp850')) else: self.process.write(LOCALE_CODEC.fromUnicode(text)) self.process.waitForBytesWritten(-1) def keyboard_interrupt(self): # This does not work on Windows: # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe) self.send_ctrl_to_process('c')
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.')
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)