def run_pip_command(self, interpreter, worker_function, package, operation): """ Run a pip command. The command is run on the backend for the current interpreter. :param interpreter: Interpreter which is going to run the backend :param worker_function: The worker function to execute. :param package: The list of packages to install, as a string where each item is separated by a space. :param operation: Operation title (used for the info label) """ self.stop_backend() self.ui.lblInfos.setText(operation) self._worker = worker_function self._package = package self.stop_backend() self.backend = BackendManager(self) process = BackendProcess(self.parent()) process.finished.connect(self._on_process_finished) self.backend._process = process server_script = server.__file__.replace('.pyc', '.py') port = self.backend.pick_free_port() self.backend._port = port if LINUX and self._need_root_perms(interpreter): _logger().info('running pip command with root privileges') auth = get_authentication_program() if 'kdesu' in auth: # no quotes around command when using kdesu cmd = '%s %s %s %s --syspath %s' else: # gksu requires quotes around command cmd = '%s "%s %s %s --syspath %s"' cmd = cmd % (auth, interpreter, server_script, str(port), get_library_zip_path()) process.start(cmd) elif DARWIN and self._need_root_perms(interpreter): cmd = 'sudo %s %s %s --syspath %s' % ( interpreter, server_script, str(port), get_library_zip_path()) auth_process = QtCore.QProcess() auth_process.start('pseudo') auth_process.waitForFinished() process.start(cmd) else: self.backend.start(server.__file__, interpreter=interpreter, args=['-s'] + [get_library_zip_path()]) QtCore.QTimer.singleShot(100, self._run_command)
class BackendManager(Manager): """ The backend controller takes care of controlling the client-server architecture. It is responsible of starting the backend process and the client socket and exposes an API to easily control the backend: - start - stop - send_request """ LAST_PORT = None LAST_PROCESS = None SHARE_COUNT = 0 def __init__(self, editor): super(BackendManager, self).__init__(editor) self._process = None self._sockets = [] self.server_script = None self.interpreter = None self.args = None self._shared = False @staticmethod def pick_free_port(): """ Picks a free port """ test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.bind(('127.0.0.1', 0)) free_port = int(test_socket.getsockname()[1]) test_socket.close() return free_port def start(self, script, interpreter=sys.executable, args=None, error_callback=None, reuse=False): """ Starts the backend process. The backend is a python script that starts a :class:`pyqode.core.backend.JsonServer`. You must write the backend script so that you can apply your own backend configuration. The script can be run with a custom interpreter. The default is to use sys.executable. .. note:: This restart the backend process if it was previously running. :param script: Path to the backend script. :param interpreter: The python interpreter to use to run the backend script. If None, sys.executable is used unless we are in a frozen application (frozen backends do not require an interpreter). :param args: list of additional command line args to use to start the backend process. :param reuse: True to reuse an existing backend process. WARNING: to use this, your application must have one single server script. If you're creating an app which supports multiple programming languages you will need to merge all backend scripts into one single script, otherwise the wrong script might be picked up). """ self._shared = reuse if reuse and BackendManager.SHARE_COUNT: self._port = BackendManager.LAST_PORT self._process = BackendManager.LAST_PROCESS BackendManager.SHARE_COUNT += 1 else: if self.running: self.stop() self.server_script = script self.interpreter = interpreter self.args = args backend_script = script.replace('.pyc', '.py') self._port = self.pick_free_port() if hasattr(sys, "frozen") and not backend_script.endswith('.py'): # frozen backend script on windows/mac does not need an # interpreter program = backend_script pgm_args = [str(self._port)] else: program = interpreter pgm_args = [backend_script, str(self._port)] if args: pgm_args += args self._process = BackendProcess(self.editor) if error_callback: self._process.error.connect(error_callback) self._process.start(program, pgm_args) if reuse: BackendManager.LAST_PROCESS = self._process BackendManager.LAST_PORT = self._port BackendManager.SHARE_COUNT += 1 _logger().debug('starting backend process: %s %s', program, ' '.join(pgm_args)) def stop(self): """ Stops the backend process. """ if self._process is None: return if self._shared: BackendManager.SHARE_COUNT -= 1 if BackendManager.SHARE_COUNT: return _logger().debug('stopping backend process') # close all sockets for s in self._sockets: s._callback = None s.close() self._sockets[:] = [] # prevent crash logs from being written if we are busy killing # the process self._process._prevent_logs = True while self._process.state() != self._process.NotRunning: self._process.waitForFinished(1) if sys.platform == 'win32': # Console applications on Windows that do not run an event # loop, or whose event loop does not handle the WM_CLOSE # message, can only be terminated by calling kill(). self._process.kill() else: self._process.terminate() self._process._prevent_logs = False _logger().debug('backend process terminated') def send_request(self, worker_class_or_function, args, on_receive=None): """ Requests some work to be done by the backend. You can get notified of the work results by passing a callback (on_receive). :param worker_class_or_function: Worker class or function :param args: worker args, any Json serializable objects :param on_receive: an optional callback executed when we receive the worker's results. The callback will be called with one arguments: the results of the worker (object) :raise: backend.NotRunning if the backend process is not running. """ if not self.running: raise NotRunning() else: _logger().debug('sending request, worker=%r' % worker_class_or_function) # create a socket, the request will be send as soon as the socket # has connected socket = JsonTcpClient(self.editor, self._port, worker_class_or_function, args, on_receive=on_receive) socket.finished.connect(self._rm_socket) self._sockets.append(socket) def _rm_socket(self, socket): try: socket.close() self._sockets.remove(socket) except ValueError: pass @property def running(self): """ Tells whether the backend process is running. :return: True if the process is running, otherwise False """ return (self._process is not None and self._process.state() != self._process.NotRunning) @property def connected(self): """ Checks if the client socket is connected to the backend. .. deprecated: Since v2.3, a socket is created per request. Checking for global connection status does not make any sense anymore. This property now returns ``running``. This will be removed in v2.5 """ return self.running @property def exit_code(self): """ Returns the backend process exit status or None if the process is till running. """ if self.running: return None else: return self._process.exitCode()
class BackendManager(Manager): """ The backend controller takes care of controlling the client-server architecture. It is responsible of starting the backend process and the client socket and exposes an API to easily control the backend: - start - stop - send_request """ LAST_PORT = None LAST_PROCESS = None SHARE_COUNT = 0 def __init__(self, editor): super(BackendManager, self).__init__(editor) self._process = None self._sockets = [] self.server_script = None self.interpreter = None self.args = None self._shared = False self._heartbeat_timer = QtCore.QTimer() self._heartbeat_timer.setInterval(1000) self._heartbeat_timer.timeout.connect(self._send_heartbeat) self._heartbeat_timer.start() @staticmethod def pick_free_port(): """ Picks a free port """ test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.bind(('127.0.0.1', 0)) free_port = int(test_socket.getsockname()[1]) test_socket.close() return free_port def start(self, script, interpreter=sys.executable, args=None, error_callback=None, reuse=False): """ Starts the backend process. The backend is a python script that starts a :class:`pyqode.core.backend.JsonServer`. You must write the backend script so that you can apply your own backend configuration. The script can be run with a custom interpreter. The default is to use sys.executable. .. note:: This restart the backend process if it was previously running. :param script: Path to the backend script. :param interpreter: The python interpreter to use to run the backend script. If None, sys.executable is used unless we are in a frozen application (frozen backends do not require an interpreter). :param args: list of additional command line args to use to start the backend process. :param reuse: True to reuse an existing backend process. WARNING: to use this, your application must have one single server script. If you're creating an app which supports multiple programming languages you will need to merge all backend scripts into one single script, otherwise the wrong script might be picked up). """ self._shared = reuse if reuse and BackendManager.SHARE_COUNT: self._port = BackendManager.LAST_PORT self._process = BackendManager.LAST_PROCESS BackendManager.SHARE_COUNT += 1 else: if self.running: self.stop() self.server_script = script self.interpreter = interpreter self.args = args backend_script = script.replace('.pyc', '.py') self._port = self.pick_free_port() if hasattr(sys, "frozen") and not backend_script.endswith('.py'): # frozen backend script on windows/mac does not need an # interpreter program = backend_script pgm_args = [str(self._port)] else: program = interpreter pgm_args = [backend_script, str(self._port)] if args: pgm_args += args self._process = BackendProcess(self.editor) if error_callback: self._process.error.connect(error_callback) self._process.start(program, pgm_args) if reuse: BackendManager.LAST_PROCESS = self._process BackendManager.LAST_PORT = self._port BackendManager.SHARE_COUNT += 1 comm('starting backend process: %s %s', program, ' '.join(pgm_args)) self._heartbeat_timer.start() def stop(self): """ Stops the backend process. """ if self._process is None: return if self._shared: BackendManager.SHARE_COUNT -= 1 if BackendManager.SHARE_COUNT: return comm('stopping backend process') # close all sockets for s in self._sockets: s._callback = None s.close() self._sockets[:] = [] # prevent crash logs from being written if we are busy killing # the process self._process._prevent_logs = True while self._process.state() != self._process.NotRunning: self._process.waitForFinished(1) if sys.platform == 'win32': # Console applications on Windows that do not run an event # loop, or whose event loop does not handle the WM_CLOSE # message, can only be terminated by calling kill(). self._process.kill() else: self._process.terminate() self._process._prevent_logs = False self._heartbeat_timer.stop() comm('backend process terminated') def send_request(self, worker_class_or_function, args, on_receive=None): """ Requests some work to be done by the backend. You can get notified of the work results by passing a callback (on_receive). :param worker_class_or_function: Worker class or function :param args: worker args, any Json serializable objects :param on_receive: an optional callback executed when we receive the worker's results. The callback will be called with one arguments: the results of the worker (object) :raise: backend.NotRunning if the backend process is not running. """ if not self.running: try: # try to restart the backend if it crashed. self.start(self.server_script, interpreter=self.interpreter, args=self.args) except AttributeError: pass # not started yet finally: # caller should try again, later raise NotRunning() else: comm('sending request, worker=%r' % worker_class_or_function) # create a socket, the request will be send as soon as the socket # has connected socket = JsonTcpClient( self.editor, self._port, worker_class_or_function, args, on_receive=on_receive) socket.finished.connect(self._rm_socket) self._sockets.append(socket) # restart heartbeat timer self._heartbeat_timer.start() def _send_heartbeat(self): try: self.send_request(echo_worker, {'heartbeat': True}) except NotRunning: self._heartbeat_timer.stop() def _rm_socket(self, socket): try: socket.close() self._sockets.remove(socket) socket.deleteLater() except ValueError: pass @property def running(self): """ Tells whether the backend process is running. :return: True if the process is running, otherwise False """ try: return (self._process is not None and self._process.state() != self._process.NotRunning) except RuntimeError: return False @property def connected(self): """ Checks if the client socket is connected to the backend. .. deprecated: Since v2.3, a socket is created per request. Checking for global connection status does not make any sense anymore. This property now returns ``running``. This will be removed in v2.5 """ return self.running @property def exit_code(self): """ Returns the backend process exit status or None if the process is till running. """ if self.running: return None else: return self._process.exitCode()
class BackendManager(Manager): """ The backend controller takes care of controlling the client-server architecture. It is responsible of starting the backend process and the client socket and exposes an API to easily control the backend: - start - stop - send_request """ def __init__(self, editor): super(BackendManager, self).__init__(editor) self._process = None self._sockets = [] self.server_script = None self.interpreter = None self.args = None @staticmethod def pick_free_port(): """ Picks a free port """ test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.bind(('127.0.0.1', 0)) free_port = int(test_socket.getsockname()[1]) test_socket.close() return free_port def start(self, script, interpreter=sys.executable, args=None, error_callback=None): """ Starts the backend process. The backend is a python script that starts a :class:`pyqode.core.backend.JsonServer`. You must write the backend script so that you can apply your own backend configuration. The script can be run with a custom interpreter. The default is to use sys.executable. .. note:: This restart the backend process if it was previously running. :param script: Path to the backend script. :param interpreter: The python interpreter to use to run the backend script. If None, sys.executable is used unless we are in a frozen application (frozen backends do not require an interpreter). :param args: list of additional command line args to use to start the backend process. """ if self.running: self.stop() self.server_script = script self.interpreter = interpreter self.args = args backend_script = script.replace('.pyc', '.py') self._port = self.pick_free_port() if hasattr(sys, "frozen") and not backend_script.endswith('.py'): # frozen backend script on windows/mac does not need an interpreter program = backend_script pgm_args = [str(self._port)] else: program = interpreter pgm_args = [backend_script, str(self._port)] if args: pgm_args += args self._process = BackendProcess(self.editor) if error_callback: self._process.error.connect(error_callback) self._process.start(program, pgm_args) _logger().debug('starting backend process: %s %s', program, ' '.join(pgm_args)) def stop(self): """ Stops the backend process. """ if self._process is None: return # close all sockets for socket in self._sockets: socket._callback = None socket.close() t = time.time() while self._process.state() != self._process.NotRunning: self._process.waitForFinished(1) if sys.platform == 'win32': # Console applications on Windows that do not run an event # loop, or whose event loop does not handle the WM_CLOSE # message, can only be terminated by calling kill(). self._process.kill() else: self._process.terminate() _logger().info('stopping backend took %f [s]', time.time() - t) _logger().info('backend process terminated') def send_request(self, worker_class_or_function, args, on_receive=None): """ Requests some work to be done by the backend. You can get notified of the work results by passing a callback (on_receive). :param worker_class_or_function: Worker class or function :param args: worker args, any Json serializable objects :param on_receive: an optional callback executed when we receive the worker's results. The callback will be called with one arguments: the results of the worker (object) :raise: backend.NotRunning if the backend process is not running. """ if not self.running: raise NotRunning() else: # create a socket, the request will be send as soon as the socket # has connected socket = JsonTcpClient( self.editor, self._port, worker_class_or_function, args, on_receive=on_receive) socket.finished.connect(self._rm_socket) self._sockets.append(socket) def _rm_socket(self, socket): self._sockets.remove(socket) @property def running(self): """ Tells whether the backend process is running. :return: True if the process is running, otherwise False """ return (self._process is not None and self._process.state() != self._process.NotRunning) @property def connected(self): """ Checks if the client socket is connected to the backend. .. deprecated: Since v2.3, a socket is created per request. Checking for global connection status does not make any sense anymore. This property now returns ``running``. This will be removed in v2.5 """ return self.running @property def exit_code(self): """ Returns the backend process exit status or None if the process is till running. """ if self.running: return None else: return self._process.exitCode()