def start(self): self.zmq_out_socket = self.context.socket(zmq.PAIR) self.zmq_out_port = self.zmq_out_socket.bind_to_random_port('tcp://*') self.zmq_in_socket = self.context.socket(zmq.PAIR) self.zmq_in_socket.set_hwm(0) self.zmq_in_port = self.zmq_in_socket.bind_to_random_port('tcp://*') self.transport_args += ['--zmq-in-port', self.zmq_out_port, '--zmq-out-port', self.zmq_in_port] self.lsp_server_log = subprocess.PIPE if get_debug_level() > 0: lsp_server_file = 'lsp_server_logfile.log' log_file = get_conf_path(osp.join('lsp_logs', lsp_server_file)) if not osp.exists(osp.dirname(log_file)): os.makedirs(osp.dirname(log_file)) self.lsp_server_log = open(log_file, 'w') if not self.external_server: logger.info('Starting server: {0}'.format( ' '.join(self.server_args))) creation_flags = 0 if WINDOWS: creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP self.lsp_server = subprocess.Popen( self.server_args, stdout=self.lsp_server_log, stderr=subprocess.STDOUT, creationflags=creation_flags) # self.transport_args += self.server_args self.stdout_log = subprocess.PIPE self.stderr_log = subprocess.PIPE if get_debug_level() > 0: stderr_log_file = 'lsp_client_{0}.log'.format(self.language) log_file = get_conf_path(osp.join('lsp_logs', stderr_log_file)) if not osp.exists(osp.dirname(log_file)): os.makedirs(osp.dirname(log_file)) self.stderr_log = open(log_file, 'w') new_env = dict(os.environ) python_path = os.pathsep.join(sys.path)[1:] new_env['PYTHONPATH'] = python_path self.transport_args = map(str, self.transport_args) self.transport_client = subprocess.Popen(self.transport_args, stdout=self.stdout_log, stderr=self.stderr_log, env=new_env) fid = self.zmq_in_socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) # self.notifier.activated.connect(self.debug_print) self.notifier.activated.connect(self.on_msg_received)
def test_get_autosave_filename(editor_bot): """ Test filename returned by `get_autosave_filename`. Test a consistent and unique name for the autosave file is returned. """ editor_stack, editor = editor_bot autosave = editor_stack.autosave expected = os.path.join(get_conf_path('autosave'), 'foo.py') assert autosave.get_autosave_filename('foo.py') == expected editor_stack.new('ham/foo.py', 'utf-8', '') expected2 = os.path.join(get_conf_path('autosave'), 'foo-1.py') assert autosave.get_autosave_filename('foo.py') == expected assert autosave.get_autosave_filename('ham/foo.py') == expected2
def get_root_path(self, language): """ Get root path to pass to the LSP servers. This can be the current project path or the output of getcwd_or_home (except for Python, see below). """ path = None # Get path of the current project if self.main and self.main.projects: path = self.main.projects.get_active_project_path() # If there's no project, use the output of getcwd_or_home. if not path: # We can't use getcwd_or_home for Python because if it # returns home and you have a lot of Python files on it # then computing Rope completions takes a long time # and blocks the PyLS server. # Instead we use an empty directory inside our config one, # just like we did for Rope in Spyder 3. if language == 'python': path = get_conf_path('lsp_root_path') if not osp.exists(path): os.mkdir(path) else: path = getcwd_or_home() return path
def get_autosave_filename(self, filename): """ Get name of autosave file for specified file name. This function uses the dict in `self.name_mapping`. If `filename` is in the mapping, then return the corresponding autosave file name. Otherwise, construct a unique file name and update the mapping. Args: filename (str): original file name """ try: autosave_filename = self.name_mapping[filename] except KeyError: autosave_dir = get_conf_path('autosave') if not osp.isdir(autosave_dir): try: os.mkdir(autosave_dir) except EnvironmentError as error: action = _('Error while creating autosave directory') msgbox = AutosaveErrorDialog(action, error) msgbox.exec_if_enabled() autosave_filename = self.create_unique_autosave_filename( filename, autosave_dir) self.name_mapping[filename] = autosave_filename self.stack.sig_option_changed.emit( 'autosave_mapping', self.name_mapping) logger.debug('New autosave file name') return autosave_filename
def main(): """ Run pytest tests. """ # Remove temp conf_dir before starting the tests from spyder.config.base import get_conf_path conf_dir = get_conf_path() if osp.isdir(conf_dir): shutil.rmtree(conf_dir) pytest_args = ['spyder', 'spyder_profiler', '-x', '-vv', '-rw', '--durations=10', '--cov=spyder', '--cov=spyder_profiler', '--cov-report=term-missing'] if run_slow: pytest_args.append('--run-slow') errno = pytest.main(pytest_args) # sys.exit doesn't work here because some things could be running # in the background (e.g. closing the main window) when this point # is reached. And if that's the case, sys.exit does't stop the # script (as you would expected). if errno != 0: raise SystemExit(errno)
def try_recover_from_autosave(self): """Offer to recover files from autosave.""" autosave_dir = get_conf_path('autosave') autosave_mapping = CONF.get('editor', 'autosave_mapping', {}) dialog = RecoveryDialog(autosave_dir, autosave_mapping, parent=self.editor) dialog.exec_if_nonempty() self.recover_files_to_open = dialog.files_to_open[:]
def test_autosave_updates_name_mapping(editor_bot, mocker, qtbot): """Test that autosave() updates name_mapping.""" editor_stack, editor = editor_bot assert editor_stack.autosave.name_mapping == {} mocker.patch.object(editor_stack, '_write_to_file') with qtbot.wait_signal(editor_stack.sig_option_changed) as blocker: editor_stack.autosave.autosave(0) expected = {'foo.py': os.path.join(get_conf_path('autosave'), 'foo.py')} assert editor_stack.autosave.name_mapping == expected assert blocker.args == ['autosave_mapping', expected]
def __init__(self, plugin, name, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self) # --- Init attrs self.name = name self.history_filename = get_conf_path(history_filename) self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave # --- Other attrs self.options_button = None self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] # --- Widgets self.shellwidget = ShellWidget(config=config_options, additional_options=additional_options, interpreter_versions=interpreter_versions, local_kernel=True) self.shellwidget.hide() self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self.infowidget.setHtml(self.loading_page, QUrl.fromLocalFile(CSS_PATH)) # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Signals # As soon as some content is printed in the console, stop # our loading animation document = self.get_control().document() document.contentsChange.connect(self._stop_loading_animation)
def __init__( self, parent=None, namespace=None, commands=[], message=None, max_line_count=300, font=None, exitfunc=None, profile=False, multithreaded=True, light_background=True, ): PythonShellWidget.__init__(self, parent, get_conf_path("history_internal.py"), profile) self.set_light_background(light_background) self.multithreaded = multithreaded self.setMaximumBlockCount(max_line_count) if font is not None: self.set_font(font) # Allow raw_input support: self.input_loop = None self.input_mode = False # KeyboardInterrupt support self.interrupted = False # used only for not-multithreaded mode self.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Code completion / calltips getcfg = lambda option: CONF.get("internal_console", option) case_sensitive = getcfg("codecompletion/case_sensitive") self.set_codecompletion_case(case_sensitive) # keyboard events management self.eventqueue = [] # Init interpreter self.exitfunc = exitfunc self.commands = commands self.message = message self.interpreter = None self.start_interpreter(namespace) # Clear status bar self.status.emit("") # Embedded shell -- requires the monitor (which installs the # 'open_in_spyder' function in builtins) if hasattr(builtins, "open_in_spyder"): self.go_to_error.connect(self.open_with_external_spyder)
def get_spyderplugins_mods(io=False): """Import modules from plugins package and return the list""" # Create user directory user_plugin_path = osp.join(get_conf_path(), USER_PLUGIN_DIR) if not osp.isdir(user_plugin_path): os.makedirs(user_plugin_path) modlist, modnames = [], [] # The user plugins directory is given the priority when looking for modules for plugin_path in [user_plugin_path] + sys.path: _get_spyderplugins(plugin_path, io, modnames, modlist) return modlist
def __init__(self, parent, history_filename, profile=False, initial_message=None, default_foreground_color=None, error_foreground_color=None, traceback_foreground_color=None, prompt_foreground_color=None, background_color=None): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self, history_filename) BrowseHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History assert is_text_string(history_filename) self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] if initial_message: self.__buffer.append(initial_message) self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth(CONF.get('main', 'cursor/width')) # Adjustments to completion_widget to use it here self.completion_widget.currentRowChanged.disconnect()
def load_plugin(self): """Load the Rope introspection plugin""" if not programs.is_module_installed('rope', ROPE_REQVER): raise ImportError('Requires Rope %s' % ROPE_REQVER) self.project = None self.create_rope_project(root_path=get_conf_path()) submods = get_preferred_submodules() actual = [] for submod in submods: try: imp.find_module(submod) actual.append(submod) except ImportError: pass if self.project is not None: self.project.prefs.set('extension_modules', actual)
def _filename_global(self): """Create a .ini filename located in user home directory. This .ini files stores the global spyder preferences. """ if self.subfolder is None: config_file = osp.join(get_home_dir(), '.%s.ini' % self.name) return config_file else: folder = get_conf_path() # Save defaults in a "defaults" dir of .spyder2 to not pollute it if 'defaults' in self.name: folder = osp.join(folder, 'defaults') if not osp.isdir(folder): os.mkdir(folder) config_file = osp.join(folder, '%s.ini' % self.name) return config_file
def __init__(self, parent, history_filename, profile=False, initial_message=None): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) BrowseHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] if initial_message: self.__buffer.append(initial_message) self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth(CONF.get('main', 'cursor/width'))
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 __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__buffer.append("NOTE: The Python console is going to " "be REMOVED in Spyder 3.2. Please start " "to migrate your work to the " "IPython console instead.\n\n") self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth( CONF.get('main', 'cursor/width') )
def run_tests(self, config=None): """ Run unit tests. First, run `self.pre_test_hook` if it is set, and abort if its return value is `False`. Then, run the unit tests. The process's output is consumed by `read_output()`. When the process finishes, the `finish` signal is emitted. Parameters ---------- config : Config or None configuration for unit tests. If None, use `self.config`. In either case, configuration should be valid. """ if self.pre_test_hook: if self.pre_test_hook() is False: return if config is None: config = self.config pythonpath = self.pythonpath self.testdatamodel.testresults = [] self.testdetails = [] tempfilename = get_conf_path('unittest.results') self.testrunner = self.framework_registry.create_runner( config.framework, self, tempfilename) self.testrunner.sig_finished.connect(self.process_finished) self.testrunner.sig_collected.connect(self.tests_collected) self.testrunner.sig_collecterror.connect(self.tests_collect_error) self.testrunner.sig_starttest.connect(self.tests_started) self.testrunner.sig_testresult.connect(self.tests_yield_result) self.testrunner.sig_stop.connect(self.tests_stopped) try: self.testrunner.start(config, pythonpath) except RuntimeError: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.set_running_state(True) self.set_status_label(_('Running tests ...'))
def get_defaults_path_name_from_version(self, old_version=None): """ Override method. Get defaults location based on version. """ if old_version: if check_version(old_version, '3.0.0', '<='): name = '{}-{}'.format('defaults', old_version) path = self._module_source_path elif check_version(old_version, '52.0.0', '<'): name = '{}-{}'.format(self._defaults_name_prefix, old_version) path = osp.join(get_conf_path(), 'defaults') else: super_class = super(SpyderUserConfig, self) path, name = super_class.get_defaults_path_name_from_version() return path, name
def get_previous_config_fpath(self): """ Override method. Return the last configuration file used if found. """ fpath = self.get_config_fpath() previous_paths = [ # >= 52.0.0 fpath, # < 52.0.0 os.path.join(get_conf_path(), 'spyder.ini'), ] for fpath in previous_paths: if osp.isfile(fpath): break return fpath
def __init__(self, parent=None, namespace=None, commands=[], message=None, max_line_count=300, font=None, exitfunc=None, profile=False, multithreaded=True, light_background=True): PythonShellWidget.__init__(self, parent, get_conf_path('history_internal.py'), profile) self.set_light_background(light_background) self.multithreaded = multithreaded self.setMaximumBlockCount(max_line_count) if font is not None: self.set_font(font) # Allow raw_input support: self.input_loop = None self.input_mode = False # KeyboardInterrupt support self.interrupted = False # used only for not-multithreaded mode self.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Code completion / calltips getcfg = lambda option: CONF.get('internal_console', option) case_sensitive = getcfg('codecompletion/case_sensitive') self.set_codecompletion_case(case_sensitive) # keyboard events management self.eventqueue = [] # Init interpreter self.exitfunc = exitfunc self.commands = commands self.message = message self.interpreter = None self.start_interpreter(namespace) # Clear status bar self.status.emit('') # Embedded shell -- requires the monitor (which installs the # 'open_in_spyder' function in builtins) if hasattr(builtins, 'open_in_spyder'): self.go_to_error.connect(self.open_with_external_spyder)
def save_autosave_mapping(self): """ Writes current autosave mapping to a pidNNN.txt file. This function should be called after updating `self.autosave_mapping`. The NNN in the file name is the pid of the Spyder process. If the current autosave mapping is empty, then delete the file if it exists. """ autosave_dir = get_conf_path('autosave') my_pid = os.getpid() pidfile_name = osp.join(autosave_dir, 'pid{}.txt'.format(my_pid)) if self.name_mapping: with open(pidfile_name, 'w') as pidfile: pidfile.write(pprint.pformat(self.name_mapping)) else: try: os.remove(pidfile_name) except (IOError, OSError): pass
def __init__(self, plugin, name, history_filename, connection_file=None, hostname=None, sshkey=None, password=None, kernel_widget_id=None, menu_actions=None): super(IPythonClient, self).__init__(plugin) SaveHistoryMixin.__init__(self) self.options_button = None # stop button and icon self.stop_button = None self.stop_icon = ima.icon('stop') self.connection_file = connection_file self.kernel_widget_id = kernel_widget_id self.hostname = hostname self.sshkey = sshkey self.password = password self.name = name self.get_option = plugin.get_option self.shellwidget = IPythonShellWidget(config=self.shellwidget_config(), local_kernel=False) self.shellwidget.hide() self.infowidget = WebView(self) self.menu_actions = menu_actions self.history_filename = get_conf_path(history_filename) self.history = [] self.namespacebrowser = None self.set_infowidget_font() self.loading_page = self._create_loading_page() self.infowidget.setHtml(self.loading_page, QUrl.fromLocalFile(CSS_PATH)) vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) self.exit_callback = lambda: plugin.close_client(client=self)
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, parent=None, namespace=None, commands=[], message=None, max_line_count=300, exitfunc=None, profile=False, multithreaded=True): super().__init__(parent, get_conf_path('history_internal.py'), profile=profile) self.multithreaded = multithreaded self.setMaximumBlockCount(max_line_count) # Allow raw_input support: self.input_loop = None self.input_mode = False # KeyboardInterrupt support self.interrupted = False # used only for not-multithreaded mode self.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Code completion / calltips # keyboard events management self.eventqueue = [] # Init interpreter self.exitfunc = exitfunc self.commands = commands self.message = message self.interpreter = None # Clear status bar self.sig_show_status_requested.emit('') # Embedded shell -- requires the monitor (which installs the # 'open_in_spyder' function in builtins) if hasattr(builtins, 'open_in_spyder'): self.sig_go_to_error_requested.connect( self.open_with_external_spyder)
def get_backup_fpath_from_version(self, version=None, old_version=None): """ Override method. Make a backup of the configuration file. """ if old_version and check_version(old_version, '51.0.0', '<'): name = 'spyder.ini' fpath = os.path.join(get_conf_path(), name) if version is None: backup_fpath = "{}{}".format(fpath, self._backup_suffix) else: backup_fpath = "{}-{}{}".format(fpath, version, self._backup_suffix) else: super_class = super(SpyderUserConfig, self) backup_fpath = super_class.get_backup_fpath_from_version( version, old_version) return backup_fpath
def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth( CONF.get('main', 'cursor/width') )
def get_root_path(self, language): """ Get root path to pass to the LSP servers. This can be the current project path or the output of getcwd_or_home (except for Python, see below). """ path = self.current_project_path if not path: # We can't use getcwd_or_home for LSP servers because if it # returns home and you have a lot of files on it # then computing completions takes a long time # and blocks the server. # Instead we use an empty directory inside our config one, # just like we did for Rope in Spyder 3. path = osp.join(get_conf_path(), 'lsp_paths', 'root_path') if not osp.exists(path): os.makedirs(path) return path
def test_remove_autosave_file(editor_bot, mocker, qtbot): """ Test that remove_autosave_file() removes the autosave file. Also, test that it updates `name_mapping`. """ editor_stack, editor = editor_bot autosave = editor_stack.autosave editor.set_text('spam\n') with qtbot.wait_signal(editor_stack.sig_option_changed) as blocker: autosave.maybe_autosave(0) autosave_filename = os.path.join(get_conf_path('autosave'), 'foo.py') assert os.access(autosave_filename, os.R_OK) expected = {'foo.py': autosave_filename} assert autosave.name_mapping == expected assert blocker.args == ['autosave_mapping', expected] with qtbot.wait_signal(editor_stack.sig_option_changed) as blocker: autosave.remove_autosave_file(editor_stack.data[0].filename) assert not os.access(autosave_filename, os.R_OK) assert autosave.name_mapping == {} assert blocker.args == ['autosave_mapping', {}]
def test_remove_autosave_file(editor_bot, mocker, qtbot): """ Test that remove_autosave_file() removes the autosave file. Also, test that it updates `name_mapping`. """ editor_stack, editor = editor_bot autosave = editor_stack.autosave editor.set_text('spam\n') autosave.maybe_autosave(0) autosave_filename = os.path.join(get_conf_path('autosave'), 'foo.py') assert os.access(autosave_filename, os.R_OK) expected = {'foo.py': autosave_filename} assert autosave.name_mapping == expected autosave.remove_autosave_file(editor_stack.data[0].filename) assert not os.access(autosave_filename, os.R_OK) assert autosave.name_mapping == {}
def load_session(filename): """Load Spyder session""" filename = osp.abspath(filename) old_cwd = getcwd() os.chdir(osp.dirname(filename)) error_message = None renamed = False try: tar = tarfile.open(filename, "r") extracted_files = tar.getnames() # Rename original config files for fname in extracted_files: orig_name = get_conf_path(fname) bak_name = get_conf_path(fname + '.bak') if osp.isfile(bak_name): os.remove(bak_name) if osp.isfile(orig_name): os.rename(orig_name, bak_name) renamed = True tar.extractall() for fname in extracted_files: shutil.move(fname, get_conf_path(fname)) except Exception as error: error_message = to_text_string(error) if renamed: # Restore original config files for fname in extracted_files: orig_name = get_conf_path(fname) bak_name = get_conf_path(fname + '.bak') if osp.isfile(orig_name): os.remove(orig_name) if osp.isfile(bak_name): os.rename(bak_name, orig_name) finally: # Removing backup config files for fname in extracted_files: bak_name = get_conf_path(fname + '.bak') if osp.isfile(bak_name): os.remove(bak_name) os.chdir(old_cwd) return error_message
def _get_log_filename(self, kind): """ Get filename to redirect server or transport logs to in debugging mode. Parameters ---------- kind: str It can be "server" or "transport". """ if get_debug_level() == 0: return None fname = '{0}_{1}_{2}.log'.format(kind, self.language, os.getpid()) location = get_conf_path(osp.join('lsp_logs', fname)) # Create directory that contains the file, in case it doesn't # exist if not osp.exists(osp.dirname(location)): os.makedirs(osp.dirname(location)) return location
def get_preferred_submodules(): """ Get all submodules of the main scientific modules and others of our interest """ # Path to the modules database modules_path = get_conf_path('db') # Modules database modules_db = PickleShareDB(modules_path) if 'submodules' in modules_db: return modules_db['submodules'] submodules = [] for m in PREFERRED_MODULES: submods = get_submodules(m) submodules += submods modules_db['submodules'] = submodules return submodules
def test_remove_autosave_file(editor_bot, mocker, qtbot): """ Test that remove_autosave_file() removes the autosave file Also, test that it updates `name_mapping`. """ editor_stack, editor = editor_bot autosave = editor_stack.autosave with qtbot.wait_signal(editor_stack.sig_option_changed) as blocker: autosave.autosave(0) autosave_filename = os.path.join(get_conf_path('autosave'), 'foo.py') assert os.access(autosave_filename, os.R_OK) expected = {'foo.py': autosave_filename} assert autosave.name_mapping == expected assert blocker.args == ['autosave_mapping', expected] with qtbot.wait_signal(editor_stack.sig_option_changed) as blocker: autosave.remove_autosave_file(editor_stack.data[0]) assert not os.access(autosave_filename, os.R_OK) assert autosave.name_mapping == {} assert blocker.args == ['autosave_mapping', {}] with qtbot.assert_not_emitted(editor_stack.sig_option_changed): autosave.autosave(0) assert not os.access(autosave_filename, os.R_OK) assert autosave.name_mapping == {}
def create_debug_log_actions(self): """Create an action for each lsp and debug log file.""" self.menu_debug_logs.clear_actions() files = [os.environ['SPYDER_DEBUG_FILE']] files += glob.glob(os.path.join(get_conf_path('lsp_logs'), '*.log')) debug_logs_actions = [] for file in files: action = self.create_action( file, os.path.basename(file), tip=file, triggered=lambda _, file=file: self.load_log_file(file), overwrite=True, register_action=False ) debug_logs_actions.append(action) # Add Spyder log on its own section self.add_item_to_menu( debug_logs_actions[0], self.menu_debug_logs, section=LogsMenuSections.SpyderLogSection ) # Add LSP logs for action in debug_logs_actions[1:]: self.add_item_to_menu( action, self.menu_debug_logs, section=LogsMenuSections.LSPLogsSection ) # Render menu self.menu_debug_logs._render()
def test_autosave(editor_bot): """Test that autosave() saves text to correct file.""" editor_stack, editor = editor_bot editor_stack.autosave.autosave(0) contents = open(os.path.join(get_conf_path('autosave'), 'foo.py')).read() assert contents.startswith('a = 1')
import shutil import pytest # Local imports from spyder.tests.fixtures.file_fixtures import create_folders_files from spyder.tests.fixtures.bookmark_fixtures import (code_editor_bot, setup_editor) # To activate/deactivate certain things for pytest's only os.environ['SPYDER_PYTEST'] = 'True' # Remove temp conf_dir before starting the tests from spyder.config.base import get_conf_path conf_dir = get_conf_path() if osp.isdir(conf_dir): shutil.rmtree(conf_dir) def pytest_addoption(parser): """Add option to run slow tests.""" parser.addoption("--run-slow", action="store_true", default=False, help="Run slow tests") def pytest_collection_modifyitems(config, items): """Skip tests with the slow marker""" if config.getoption("--run-slow"): # --run-slow given in cli: do not skip slow tests return
def test_add_history(historylog): """ Test the add_history method. Test adding a history file to the history log widget and the code editor properties that are enabled/disabled. """ hl = historylog hw = historylog.get_widget() # No editors yet. assert len(hw.editors) == 1 # Add one file. name1 = 'test_history.py' text1 = 'a = 5\nb= 10\na + b\n' path1 = create_file(name1, text1) tab1 = os.path.basename(path1) hw.set_conf('line_numbers', False) hw.set_conf('wrap', False) hl.add_history(path1) # Check tab and editor were created correctly. assert len(hw.editors) == 2 assert hw.filenames == [get_conf_path('history.py'), path1] assert hw.tabwidget.currentIndex() == 1 assert not hw.editors[0].linenumberarea.isVisible() assert hw.editors[0].wordWrapMode() == QTextOption.NoWrap assert hw.tabwidget.tabText(1) == tab1 assert hw.tabwidget.tabToolTip(1) == path1 # Try to add same file -- does not process filename again, so # linenumbers and wrap doesn't change. hw.add_history(path1) assert hw.tabwidget.currentIndex() == 1 # assert not hw.editors[0].linenumberarea.isVisible() # Add another file. name2 = 'history2.js' text2 = 'random text\nspam line\n\n\n\n' path2 = create_file(name2, text2) tab2 = os.path.basename(path2) hw.set_conf('line_numbers', True) hw.set_conf('wrap', True) hw.add_history(path2) # Check second tab and editor were created correctly. assert len(hw.editors) == 3 assert hw.filenames == [get_conf_path('history.py'), path1, path2] assert hw.tabwidget.currentIndex() == 2 assert hw.editors[2].wordWrapMode() == ( QTextOption.WrapAtWordBoundaryOrAnywhere) assert hw.editors[2].linenumberarea.isVisible() assert hw.tabwidget.tabText(2) == tab2 assert hw.tabwidget.tabToolTip(2) == path2 # Check differences between tabs based on setup. assert hw.editors[1].supported_language assert hw.editors[1].isReadOnly() assert not hw.editors[1].isVisible() assert hw.editors[1].toPlainText() == text1 assert not hw.editors[2].supported_language assert hw.editors[2].isReadOnly() assert hw.editors[2].isVisible() assert hw.editors[2].toPlainText() == text2
class ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('profiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Profile"), tip=_("Run profiler"), triggered=lambda: self.start(), text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=ima.icon('collapse'), triggered=lambda dD: self.datatree.change_view(-1), tip=_('Collapse one level up')) self.expand_button = create_toolbutton( self, icon=ima.icon('expand'), triggered=lambda dD: self.datatree.change_view(1), tip=_('Expand one level down')) self.save_button = create_toolbutton(self, text_beside_icon=True, text=_("Save data"), icon=ima.icon('filesave'), triggered=self.save_data, tip=_('Save profiling data')) self.load_button = create_toolbutton( self, text_beside_icon=True, text=_("Load data"), icon=ima.icon('fileimport'), triggered=self.compare, tip=_('Load profiling data for comparison')) self.clear_button = create_toolbutton(self, text_beside_icon=True, text=_("Clear comparison"), icon=ima.icon('editdelete'), triggered=self.clear) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) hlayout2.addWidget(self.save_button) hlayout2.addWidget(self.load_button) hlayout2.addWidget(self.clear_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) self.clear_button.setEnabled(False) if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) url = 'http://docs.python.org/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) else: pass # self.show_data() def save_data(self): """Save data""" title = _("Save profiler result") filename, _selfilter = getsavefilename( self, title, getcwd_or_home(), _("Profiler result") + " (*.Result)") if filename: self.datatree.save_data(filename) def compare(self): filename, _selfilter = getopenfilename( self, _("Select script to compare"), getcwd_or_home(), _("Profiler result") + " (*.Result)") if filename: self.datatree.compare(filename) self.show_data() self.clear_button.setEnabled(True) def clear(self): self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_button.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): if not is_profiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = sys.executable if executable.endswith("spyder.exe"): # py2exe distribution executable = "python.exe" self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class WorkingDirectory(SpyderPluginV2): """ Working directory changer plugin. """ NAME = 'workingdir' REQUIRES = [Plugins.Console] OPTIONAL = [ Plugins.Editor, Plugins.Explorer, Plugins.IPythonConsole, Plugins.Projects ] CONTAINER_CLASS = WorkingDirectoryContainer CONF_SECTION = NAME CONF_WIDGET_CLASS = WorkingDirectoryConfigPage CONF_FILE = False LOG_PATH = get_conf_path(CONF_SECTION) # --- Signals # ------------------------------------------------------------------------ sig_current_directory_changed = Signal(str) """ This signal is emitted when the current directory has changed. Parameters ---------- new_working_directory: str The new new working directory path. """ # --- SpyderPluginV2 API # ------------------------------------------------------------------------ def get_name(self): return _('Current working directory') def get_description(self): return _('Set the current working directory for various plugins.') def get_icon(self): return self.create_icon('DirOpenIcon') def register(self): container = self.get_container() editor = self.get_plugin(Plugins.Editor) explorer = self.get_plugin(Plugins.Explorer) ipyconsole = self.get_plugin(Plugins.IPythonConsole) self.add_application_toolbar(self.NAME, container.toolbar) container.sig_current_directory_changed.connect( self.sig_current_directory_changed) self.sig_current_directory_changed.connect( lambda path, plugin=None: self.chdir(path, plugin)) container.set_history(self.load_history()) if editor: editor.sig_dir_opened.connect( lambda path, plugin=editor: self.chdir(path, editor)) if ipyconsole: self.sig_current_directory_changed.connect( ipyconsole.set_current_client_working_directory) # TODO: chdir_current_client might follow a better naming # convention ipyconsole.sig_current_directory_changed.connect( lambda path, plugin=ipyconsole: self.chdir(path, plugin)) if explorer: self.sig_current_directory_changed.connect( lambda path: explorer.chdir(path, emit=False)) explorer.sig_dir_opened.connect( lambda path, plugin=explorer: self.chdir(path, plugin)) # --- Public API # ------------------------------------------------------------------------ def chdir(self, directory, sender_plugin=None): """ Change current working directory. Parameters ---------- directory: str The new working directory to set. sender_plugin: spyder.api.plugins.SpyderPluginsV2 The plugin that requested this change: Default is None. """ explorer = self.get_plugin(Plugins.Explorer) ipyconsole = self.get_plugin(Plugins.IPythonConsole) if explorer and sender_plugin != explorer: explorer.chdir(directory, emit=False) explorer.refresh_plugin(directory, force_current=True) if ipyconsole and sender_plugin != ipyconsole: ipyconsole.set_current_client_working_directory(directory) if sender_plugin is not None: container = self.get_container() container.chdir(directory, emit=False) self.save_history() def load_history(self, workdir=None): """ Load history from a text file located in Spyder configuration folder or use `workdir` if there are no directories saved yet. Parameters ---------- workdir: str The working directory to return. Default is None. """ if osp.isfile(self.LOG_PATH): history, _ = encoding.readlines(self.LOG_PATH) history = [name for name in history if osp.isdir(name)] else: if workdir is None: workdir = self.get_workdir() history = [workdir] return history def save_history(self): """ Save history to a text file located in Spyder configuration folder. """ history = self.get_container().get_history() try: encoding.writelines(history, self.LOG_PATH) except EnvironmentError: pass def get_workdir(self): """ Get current working directory. Returns ------- str Current working directory. """ return self.get_container().get_workdir()
def main(): """ Start Spyder application. If single instance mode is turned on (default behavior) and an instance of Spyder is already running, this will just parse and send command line options to the application. """ # Parse command line options if running_under_pytest(): try: from unittest.mock import Mock except ImportError: from mock import Mock # Python 2 options = Mock() options.new_instance = False options.reset_config_files = False options.debug_info = None args = None else: options, args = get_options() # Store variable to be used in self.restart (restart spyder instance) os.environ['SPYDER_ARGS'] = str(sys.argv[1:]) #========================================================================== # Proper high DPI scaling is available in Qt >= 5.6.0. This attibute must # be set before creating the application. #========================================================================== if CONF.get('main', 'high_dpi_custom_scale_factor'): factors = str(CONF.get('main', 'high_dpi_custom_scale_factors')) f = list(filter(None, factors.split(';'))) if len(f) == 1: os.environ['QT_SCALE_FACTOR'] = f[0] else: os.environ['QT_SCREEN_SCALE_FACTORS'] = factors else: os.environ['QT_SCALE_FACTOR'] = '' os.environ['QT_SCREEN_SCALE_FACTORS'] = '' if sys.platform == 'darwin': # Prevent Spyder from crashing in macOS if locale is not defined LANG = os.environ.get('LANG') LC_ALL = os.environ.get('LC_ALL') if bool(LANG) and not bool(LC_ALL): LC_ALL = LANG elif not bool(LANG) and bool(LC_ALL): LANG = LC_ALL else: LANG = LC_ALL = 'en_US.UTF-8' os.environ['LANG'] = LANG os.environ['LC_ALL'] = LC_ALL # Don't show useless warning in the terminal where Spyder # was started # See issue 3730 os.environ['EVENT_NOKQUEUE'] = '1' else: # Prevent our kernels to crash when Python fails to identify # the system locale. # Fixes issue 7051. try: from locale import getlocale getlocale() except ValueError: # This can fail on Windows. See issue 6886 try: os.environ['LANG'] = 'C' os.environ['LC_ALL'] = 'C' except Exception: pass if options.debug_info: levels = {'minimal': '2', 'verbose': '3'} os.environ['SPYDER_DEBUG'] = levels[options.debug_info] if CONF.get('main', 'single_instance') and not options.new_instance \ and not options.reset_config_files and not running_in_mac_app(): # Minimal delay (0.1-0.2 secs) to avoid that several # instances started at the same time step in their # own foots while trying to create the lock file time.sleep(random.randrange(1000, 2000, 90)/10000.) # Lock file creation lock_file = get_conf_path('spyder.lock') lock = lockfile.FilesystemLock(lock_file) # Try to lock spyder.lock. If it's *possible* to do it, then # there is no previous instance running and we can start a # new one. If *not*, then there is an instance already # running, which is locking that file try: lock_created = lock.lock() except: # If locking fails because of errors in the lockfile # module, try to remove a possibly stale spyder.lock. # This is reported to solve all problems with # lockfile (See issue 2363) try: if os.name == 'nt': if osp.isdir(lock_file): import shutil shutil.rmtree(lock_file, ignore_errors=True) else: if osp.islink(lock_file): os.unlink(lock_file) except: pass # Then start Spyder as usual and *don't* continue # executing this script because it doesn't make # sense from spyder.app import mainwindow if running_under_pytest(): return mainwindow.main() else: mainwindow.main() return if lock_created: # Start a new instance from spyder.app import mainwindow if running_under_pytest(): return mainwindow.main() else: mainwindow.main() else: # Pass args to Spyder or print an informative # message if args: send_args_to_spyder(args) else: print("Spyder is already running. If you want to open a new \n" "instance, please pass to it the --new-instance option") else: from spyder.app import mainwindow if running_under_pytest(): return mainwindow.main() else: mainwindow.main()
class PylintWidget(PluginMainWidget): """ Pylint widget. """ DEFAULT_OPTIONS = { "history_filenames": [], "max_entries": 30, "project_dir": None, } ENABLE_SPINNER = True DATAPATH = get_conf_path("pylint.results") VERSION = "1.1.0" # --- Signals sig_edit_goto_requested = Signal(str, int, str) """ This signal will request to open a file in a given row and column using a code editor. Parameters ---------- path: str Path to file. row: int Cursor starting row position. word: str Word to select on given row. """ sig_start_analysis_requested = Signal() """ This signal will request the plugin to start the analysis. This is to be able to interact with other plugins, which can only be done at the plugin level. """ def __init__(self, name=None, plugin=None, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent, options) # Attributes self._process = None self.output = None self.error_output = None self.filename = None self.rdata = [] self.curr_filenames = self.get_option("history_filenames") self.code_analysis_action = None self.browse_action = None # Widgets self.filecombo = PythonModulesComboBox(self) self.ratelabel = QLabel(self) self.datelabel = QLabel(self) self.treewidget = ResultsTree(self) if osp.isfile(self.DATAPATH): try: with open(self.DATAPATH, "rb") as fh: data = pickle.loads(fh.read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass # Widget setup self.filecombo.setInsertPolicy(self.filecombo.InsertAtTop) for fname in self.curr_filenames[::-1]: self.set_filename(fname) # Layout layout = QVBoxLayout() layout.addWidget(self.treewidget) self.setLayout(layout) # Signals self.filecombo.valid.connect(self._check_new_file) self.treewidget.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) # --- Private API # ------------------------------------------------------------------------ @Slot() 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 _read_output(self, error=False): process = self._process if error: process.setReadChannel(QProcess.StandardError) else: process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while process.bytesAvailable(): if error: qba += process.readAllStandardError() else: qba += process.readAllStandardOutput() text = str(qba.data(), "utf-8") if error: self.error_output += text else: self.output += text self.update_actions() def _finished(self, exit_code, exit_status): if not self.output: self.stop_spinner() if self.error_output: QMessageBox.critical( self, _("Error"), self.error_output, ) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return filename = self.get_filename() rate, previous, results = self.parse_output(self.output) self._save_history() self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) self.update_actions() self.stop_spinner() def _check_new_file(self): fname = self.get_filename() if fname != self.filename: self.filename = fname self.show_data() def _is_running(self): process = self._process return process is not None and process.state() == QProcess.Running def _kill_process(self): self._process.kill() self._process.waitForFinished() self.stop_spinner() def _update_combobox_history(self): """Change the number of files listed in the history combobox.""" max_entries = self.get_option("max_entries") if self.filecombo.count() > max_entries: num_elements = self.filecombo.count() diff = num_elements - max_entries for __ in range(diff): num_elements = self.filecombo.count() self.filecombo.removeItem(num_elements - 1) self.filecombo.selected() else: num_elements = self.filecombo.count() diff = max_entries - num_elements for i in range(num_elements, num_elements + diff): if i >= len(self.curr_filenames): break act_filename = self.curr_filenames[i] self.filecombo.insertItem(i, act_filename) def _save_history(self): """Save the current history filenames.""" if self.parent: list_save_files = [] for fname in self.curr_filenames: if _("untitled") not in fname: list_save_files.append(fname) self.curr_filenames = list_save_files[:MAX_HISTORY_ENTRIES] self.set_option("history_filenames", self.curr_filenames) else: self.curr_filenames = [] # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _("Code Analysis") def get_focus_widget(self): return self.treewidget def setup(self, options): change_history_depth_action = self.create_action( PylintWidgetActions.ChangeHistory, text=_("History..."), tip=_("Set history maximum entries"), icon=self.create_icon("history"), triggered=self.change_history_depth, ) self.code_analysis_action = self.create_action( PylintWidgetActions.RunCodeAnalysis, icon_text=_("Analyze"), text=_("Run code analysis"), tip=_("Run code analysis"), icon=self.create_icon("run"), triggered=lambda: self.sig_start_analysis_requested.emit(), context=Qt.ApplicationShortcut, register_shortcut=True) self.browse_action = self.create_action( PylintWidgetActions.BrowseFile, text=_("Select Python file"), tip=_("Select Python file"), icon=self.create_icon("fileopen"), triggered=self.select_file, ) self.log_action = self.create_action( PylintWidgetActions.ShowLog, text=_("Output"), icon_text=_("Output"), tip=_("Complete output"), icon=self.create_icon("log"), triggered=self.show_log, ) options_menu = self.get_options_menu() self.add_item_to_menu( self.treewidget.get_action(OneColumnTreeActions.CollapseAllAction), menu=options_menu, section=PylintWidgetOptionsMenuSections.Global, ) self.add_item_to_menu( self.treewidget.get_action(OneColumnTreeActions.ExpandAllAction), menu=options_menu, section=PylintWidgetOptionsMenuSections.Global, ) self.add_item_to_menu( self.treewidget.get_action( OneColumnTreeActions.CollapseSelectionAction), menu=options_menu, section=PylintWidgetOptionsMenuSections.Section, ) self.add_item_to_menu( self.treewidget.get_action( OneColumnTreeActions.ExpandSelectionAction), menu=options_menu, section=PylintWidgetOptionsMenuSections.Section, ) self.add_item_to_menu( change_history_depth_action, menu=options_menu, section=PylintWidgetOptionsMenuSections.History, ) # Update OneColumnTree contextual menu self.add_item_to_menu( change_history_depth_action, menu=self.treewidget.menu, section=PylintWidgetOptionsMenuSections.History, ) self.treewidget.restore_action.setVisible(False) toolbar = self.get_main_toolbar() for item in [ self.filecombo, self.browse_action, self.code_analysis_action ]: self.add_item_to_toolbar( item, toolbar, section=PylintWidgetMainToolBarSections.Main, ) secondary_toolbar = self.create_toolbar("secondary") for item in [ self.ratelabel, self.create_stretcher(), self.datelabel, self.create_stretcher(), self.log_action ]: self.add_item_to_toolbar( item, secondary_toolbar, section=PylintWidgetMainToolBarSections.Main, ) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.insertItems(0, self.get_filenames()) self.code_analysis_action.setEnabled(self.filecombo.is_valid()) else: self.code_analysis_action.setEnabled(False) # Signals self.filecombo.valid.connect(self.code_analysis_action.setEnabled) def on_option_update(self, option, value): if option == "max_entries": self._update_combobox_history() elif option == "history_filenames": self.curr_filenames = value self._update_combobox_history() def update_actions(self): fm = self.ratelabel.fontMetrics() toolbar = self.get_main_toolbar() width = max([fm.width(_("Stop")), fm.width(_("Analyze"))]) widget = toolbar.widgetForAction(self.code_analysis_action) if widget: widget.setMinimumWidth(width * 1.5) if self._is_running(): self.code_analysis_action.setIconText(_("Stop")) self.code_analysis_action.setIcon(self.create_icon("stop")) else: self.code_analysis_action.setIconText(_("Analyze")) self.code_analysis_action.setIcon(self.create_icon("run")) self.remove_obsolete_items() # --- Public API # ------------------------------------------------------------------------ @Slot() @Slot(int) def change_history_depth(self, value=None): """ Set history maximum entries. Parameters ---------- value: int or None, optional The valur to set the maximum history depth. If no value is provided, an input dialog will be launched. Default is None. """ if value is None: dialog = QInputDialog(self) # Set dialog properties dialog.setModal(False) dialog.setWindowTitle(_("History")) dialog.setLabelText(_("Maximum entries")) dialog.setInputMode(QInputDialog.IntInput) dialog.setIntRange(MIN_HISTORY_ENTRIES, MAX_HISTORY_ENTRIES) dialog.setIntStep(1) dialog.setIntValue(self.get_option("max_entries")) # Connect slot dialog.intValueSelected.connect( lambda value: self.set_option("max_entries", value)) dialog.show() else: self.set_option("max_entries", value) def get_filename(self): """ Get current filename in combobox. """ return str(self.filecombo.currentText()) @Slot(str) def set_filename(self, filename): """ Set current filename in combobox. """ if self._is_running(): self._kill_process() # Don't try to reload saved analysis for filename, if filename # is the one currently displayed. # Fixes spyder-ide/spyder#13347 if self.get_filename() == filename: return filename = str(filename) index, _data = self.get_data(filename) if filename not in self.curr_filenames: self.filecombo.insertItem(0, filename) self.curr_filenames.insert(0, filename) self.filecombo.setCurrentIndex(0) else: try: index = self.filecombo.findText(filename) self.filecombo.removeItem(index) self.curr_filenames.pop(index) except IndexError: self.curr_filenames.remove(filename) self.filecombo.insertItem(0, filename) self.curr_filenames.insert(0, filename) self.filecombo.setCurrentIndex(0) num_elements = self.filecombo.count() if num_elements > self.get_option("max_entries"): self.filecombo.removeItem(num_elements - 1) self.filecombo.selected() def start_code_analysis(self, filename=None): """ Perform code analysis for given `filename`. If `filename` is None default to current filename in combobox. If this method is called while still running it will stop the code analysis. """ if self._is_running(): self._kill_process() else: if filename is not None: self.set_filename(filename) if self.filecombo.is_valid(): self._start() self.update_actions() def stop_code_analysis(self): """ Stop the code analysis process. """ if self._is_running(): self._kill_process() 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 all filenames for which there is data available. """ return [filename for filename, _data in self.rdata] def get_data(self, filename): """ Get and load code analysis data for given `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): """ Set and save code analysis `data` for given `filename`. """ 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)) while len(self.rdata) > self.get_option("max_entries"): self.rdata.pop(-1) with open(self.DATAPATH, "wb") as fh: pickle.dump([self.VERSION] + self.rdata, fh, 2) def show_data(self, justanalyzed=False): """ Show data in treewidget. """ text_color = MAIN_TEXT_COLOR prevrate_color = MAIN_PREVRATE_COLOR if not justanalyzed: self.output = None self.log_action.setEnabled(self.output is not None and len(self.output) > 0) if self._is_running(): self._kill_process() filename = self.get_filename() if not filename: return _index, data = self.get_data(filename) if data is None: text = _("Source code has not been rated yet.") self.treewidget.clear_results() date_text = "" else: datetime, rate, previous_rate, results = data if rate is None: text = _("Analysis did not succeed " "(see output for more details).") self.treewidget.clear_results() date_text = "" else: text_style = "<span style=\"color: %s\"><b>%s </b></span>" rate_style = "<span style=\"color: %s\"><b>%s</b></span>" prevrate_style = "<span style=\"color: %s\">%s</span>" color = DANGER_COLOR if float(rate) > 5.: color = SUCCESS_COLOR elif float(rate) > 3.: color = WARNING_COLOR text = _("Global evaluation:") text = ((text_style % (text_color, text)) + (rate_style % (color, ("%s/10" % rate)))) if previous_rate: text_prun = _("previous run:") text_prun = " (%s %s/10)" % (text_prun, previous_rate) text += prevrate_style % (prevrate_color, text_prun) self.treewidget.set_results(filename, results) date = time.strftime("%Y-%m-%d %H:%M:%S", datetime) date_text = text_style % (text_color, date) self.ratelabel.setText(text) self.datelabel.setText(date_text) @Slot() def show_log(self): """ Show output log dialog. """ if self.output: output_dialog = TextEditor(self.output, title=_("Code analysis output"), parent=self, readonly=True) output_dialog.resize(700, 500) output_dialog.exec_() # --- Python Specific # ------------------------------------------------------------------------ def get_pylintrc_path(self, filename): """ Get the path to the most proximate pylintrc config to the file. """ search_paths = [ # File"s directory osp.dirname(filename), # Working directory getcwd_or_home(), # Project directory self.get_option("project_dir"), # Home directory osp.expanduser("~"), ] return get_pylintrc_path(search_paths=search_paths) @Slot() def select_file(self, filename=None): """ Select filename using a open file dialog and set as current filename. If `filename` is provided, the dialog is not used. """ if filename is None: self.sig_redirect_stdio_requested.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)", ) self.sig_redirect_stdio_requested.emit(True) if filename: self.set_filename(filename) self.start_code_analysis() def get_command(self, filename): """ Return command to use to run code analysis on given filename """ command_args = [] if PYLINT_VER is not None: command_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: command_args += ["--rcfile={}".format(pylintrc_path)] command_args.append(filename) return command_args def parse_output(self, output): """ Parse output and return current revious rate and results. """ # 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 output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ("R3873:" instead of "R:") if not re.match(r"^[CRWE]+([0-9]{4})?:", line): continue items = {} idx_0 = 0 idx_1 = 0 key_names = ["msg_id", "message_name", "line_nb", "message"] for key_idx, key_name in enumerate(key_names): if key_idx == len(key_names) - 1: idx_1 = len(line) else: idx_1 = line.find(":", idx_0) if idx_1 < 0: break item = line[(idx_0):idx_1] if not item: break if key_name == "line_nb": item = int(item.split(",")[0]) items[key_name] = item idx_0 = idx_1 + 1 else: pylint_item = (module, items["line_nb"], items["message"], items["msg_id"], items["message_name"]) results[line[0] + ":"].append(pylint_item) # Rate rate = None txt_rate = "Your code has been rated at " i_rate = output.find(txt_rate) if i_rate > 0: i_rate_end = output.find("/10", i_rate) if i_rate_end > 0: rate = output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = "" if rate is not None: txt_prun = "previous run: " i_prun = output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = output.find("/10", i_prun) previous = output[i_prun + len(txt_prun):i_prun_end] return rate, previous, results
import errno import os import socket import threading # Third party imports from qtpy.QtCore import QThread, Signal # Local imports from spyder.config.base import get_conf_path, DEBUG from spyder.utils.debug import log_last_error from spyder.utils.bsdsocket import read_packet, write_packet from spyder.utils.misc import select_port LOG_FILENAME = get_conf_path('introspection.log') DEBUG_INTROSPECTION = DEBUG >= 2 if DEBUG_INTROSPECTION: import logging logging.basicConfig(filename=get_conf_path('introspection_debug.log'), level=logging.DEBUG) SPYDER_PORT = 20128 class IntrospectionServer(threading.Thread): """Introspection server""" def __init__(self): threading.Thread.__init__(self)
class Help(SpyderDockablePlugin): """ Docstrings viewer widget. """ NAME = 'help' REQUIRES = [Plugins.Preferences, Plugins.Console, Plugins.Editor] OPTIONAL = [Plugins.IPythonConsole, Plugins.Shortcuts, Plugins.MainMenu] TABIFY = Plugins.VariableExplorer WIDGET_CLASS = HelpWidget CONF_SECTION = NAME CONF_WIDGET_CLASS = HelpConfigPage CONF_FILE = False LOG_PATH = get_conf_path(CONF_SECTION) FONT_SIZE_DELTA = DEFAULT_SMALL_DELTA DISABLE_ACTIONS_WHEN_HIDDEN = False CONF_FROM_OPTIONS = { 'editor_shortcut': ('shortcuts', 'editor/inspect current object'), 'console_shortcut': ('shortcuts', 'console/inspect current object'), } # Signals sig_focus_changed = Signal() # TODO: What triggers this? sig_render_started = Signal() """This signal is emitted to inform a help text rendering has started.""" sig_render_finished = Signal() """This signal is emitted to inform a help text rendering has finished.""" # --- SpyderDocakblePlugin API # ----------------------------------------------------------------------- def get_name(self): return _('Help') def get_description(self): return _('Get rich text documentation from the editor and the console') def get_icon(self): return self.create_icon('help') def register(self): widget = self.get_widget() # Plugins internal_console = self.get_plugin(Plugins.Console) editor = self.get_plugin(Plugins.Editor) ipyconsole = self.get_plugin(Plugins.IPythonConsole) shortcuts = self.get_plugin(Plugins.Shortcuts) preferences = self.get_plugin(Plugins.Preferences) preferences.register_plugin_preferences(self) # Expose widget signals on the plugin widget.sig_render_started.connect(self.sig_render_started) widget.sig_render_finished.connect(self.sig_render_finished) # self.sig_focus_changed.connect(self.main.plugin_focus_changed) widget.set_history(self.load_history()) widget.set_internal_console(internal_console) widget.sig_item_found.connect(self.save_history) editor.sig_help_requested.connect(self.set_editor_doc) internal_console.sig_help_requested.connect(self.set_object_text) if ipyconsole: ipyconsole.sig_shellwidget_changed.connect(self.set_shellwidget) ipyconsole.sig_shellwidget_process_started.connect( self.set_shellwidget) ipyconsole.sig_render_plain_text_requested.connect( self.show_plain_text) ipyconsole.sig_render_rich_text_requested.connect( self.show_rich_text) ipyconsole.sig_help_requested.connect(self.set_object_text) if shortcuts: # See: spyder-ide/spyder#6992 shortcuts.sig_shortcuts_updated.connect( lambda: self.show_intro_message()) self.tutorial_action = self.create_action( HelpActions.ShowSpyderTutorialAction, text=_("Spyder tutorial"), triggered=self.show_tutorial, register_shortcut=False, ) # Add actions to main menu (Help menu) self._setup_menus() def update_font(self): color_scheme = self.get_color_scheme() font = self.get_font() rich_font = self.get_font(rich_text=True) widget = self.get_widget() widget.set_plain_text_font(font, color_scheme=color_scheme) widget.set_rich_text_font(rich_font, font) widget.set_plain_text_color_scheme(color_scheme) def on_close(self, cancelable=False): self.save_history() return True def apply_conf(self, options_set): super().apply_conf(options_set) widget = self.get_widget() if 'color_scheme_name' in options_set: widget.set_plain_text_color_scheme(self.get_color_scheme()) # To make auto-connection changes take place instantly editor = self.get_plugin(Plugins.Editor) editor.apply_plugin_settings({'connect_to_oi'}) ipyconsole = self.get_plugin(Plugins.IPythonConsole) if ipyconsole: ipyconsole.apply_plugin_settings({'connect_to_oi'}) # --- Private API # ------------------------------------------------------------------------ def _setup_menus(self): mainmenu = self.get_plugin(Plugins.MainMenu) shortcuts = self.get_plugin(Plugins.Shortcuts) shortcuts_summary_action = None if shortcuts: from spyder.plugins.shortcuts.plugin import ShortcutActions shortcuts_summary_action = shortcuts.get_action( ShortcutActions.ShortcutSummaryAction) if mainmenu: from spyder.plugins.mainmenu.api import (ApplicationMenus, HelpMenuSections) # Documentation actions mainmenu.add_item_to_application_menu( self.tutorial_action, menu_id=ApplicationMenus.Help, section=HelpMenuSections.Documentation, before=shortcuts_summary_action, before_section=HelpMenuSections.Support) # --- Public API # ------------------------------------------------------------------------ def set_shellwidget(self, shellwidget): """ Set IPython Console `shelwidget` as the current shellwidget. Parameters ---------- shellwidget: spyder.plugins.ipyconsole.widgets.shell.ShellWidget The shell widget that is going to be connected to Help. """ shellwidget._control.set_help_enabled( self.get_conf('connect/ipython_console')) self.get_widget().set_shell(shellwidget) def load_history(self, obj=None): """ Load history from a text file in the user configuration directory. """ if os.path.isfile(self.LOG_PATH): with open(self.LOG_PATH, 'r') as fh: lines = fh.read().split('\n') history = [line.replace('\n', '') for line in lines] else: history = [] return history def save_history(self): """ Save history to a text file in the user configuration directory. """ # Don't fail when saving search history to disk # See spyder-ide/spyder#8878 and spyder-ide/spyder#6864 try: search_history = '\n'.join(self.get_widget().get_history()) with open(self.LOG_PATH, 'w') as fh: fh.write(search_history) except (UnicodeEncodeError, UnicodeDecodeError, EnvironmentError): pass def show_tutorial(self): """Show the Spyder tutorial.""" self.switch_to_plugin() self.get_widget().show_tutorial() def show_intro_message(self): """Show the IPython introduction message.""" self.switch_to_plugin() self.get_widget().show_intro_message() def show_rich_text(self, text, collapse=False, img_path=''): """ Show help in rich mode. Parameters ---------- text: str Plain text to display. collapse: bool, optional Show collapsable sections as collapsed/expanded. Default is False. img_path: str, optional Path to folder with additional images needed to correctly display the rich text help. Default is ''. """ self.switch_to_plugin() self.get_widget().show_rich_text(text, collapse=collapse, img_path=img_path) def show_plain_text(self, text): """ Show help in plain mode. Parameters ---------- text: str Plain text to display. """ self.switch_to_plugin() self.get_widget().show_plain_text(text) def set_object_text(self, options_dict): """ Set object's name in Help's combobox. Parameters ---------- options_dict: dict Dictionary of data. See the example for the expected keys. Examples -------- >>> help_data = { 'name': str, 'force_refresh': bool, } See Also -------- :py:meth:spyder.widgets.mixins.GetHelpMixin.show_object_info """ self.switch_to_plugin() self.get_widget().set_object_text( options_dict['name'], ignore_unknown=options_dict['ignore_unknown'], ) def set_editor_doc(self, help_data): """ Set content for help data sent from the editor. Parameters ---------- help_data: dict Dictionary of data. See the example for the expected keys. Examples -------- >>> help_data = { 'obj_text': str, 'name': str, 'argspec': str, 'note': str, 'docstring': str, 'force_refresh': bool, 'path': str, } See Also -------- :py:meth:spyder.plugins.editor.widgets.editor.EditorStack.send_to_help """ force_refresh = help_data.pop('force_refresh', False) self.switch_to_plugin() self.get_widget().set_editor_doc( help_data, force_refresh=force_refresh, )
class MemoryProfilerWidget(QWidget): """ Memory profiler widget. """ DATAPATH = get_conf_path('memoryprofiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("Memory profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=get_icon('run.png'), text=_("Profile memory usage"), tip=_("Run memory profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = MemoryProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_memoryprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( '<b>Please install the <a href="%s">memory_profiler module</a></b>' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_memoryprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw)") self.redirect_stdio.emit(False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(self.finished) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' # remove previous results, since memory_profiler appends to output file # instead of replacing if osp.isfile(self.DATAPATH): os.remove(self.DATAPATH) if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = [ '-m', 'memory_profiler', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"' ] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-m', 'memory_profiler', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) self.sig_finished.emit() def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
def __init__(self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.write_lock = QMutex() self.buffer_lock = QMutex() self.buffer = [] self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility()
# Distributed under the terms of the Modified BSD License # (BSD 3-clause; see NOTICE.txt in the Spyder root directory for details). # ----------------------------------------------------------------------------- """ Module completion auxiliary functions. """ import pkgutil from pickleshare import PickleShareDB from spyder.config.base import get_conf_path # Path to the modules database MODULES_PATH = get_conf_path('db') # Modules database modules_db = PickleShareDB(MODULES_PATH) def get_submodules(mod): """Get all submodules of a given module""" def catch_exceptions(module): pass try: m = __import__(mod) submodules = [mod] submods = pkgutil.walk_packages(m.__path__, m.__name__ + '.', catch_exceptions) for sm in submods:
# Local imports from spyder.utils.misc import fix_reference_name from spyder.utils.debug import log_last_error from spyder.utils.dochelpers import (getargtxt, getdoc, getsource, getobjdir, isdefined) from spyder.utils.bsdsocket import (communicate, read_packet, write_packet, PACKET_NOT_RECEIVED, PICKLE_HIGHEST_PROTOCOL) from spyder.utils.introspection.module_completion import module_completion from spyder.config.base import get_conf_path, get_supported_types, DEBUG from spyder.py3compat import getcwd, is_text_string, pickle, _thread SUPPORTED_TYPES = {} LOG_FILENAME = get_conf_path('monitor.log') DEBUG_MONITOR = DEBUG >= 2 if DEBUG_MONITOR: import logging logging.basicConfig(filename=get_conf_path('monitor_debug.log'), level=logging.DEBUG) REMOTE_SETTINGS = ('check_all', 'exclude_private', 'exclude_uppercase', 'exclude_capitalized', 'exclude_unsupported', 'excluded_names', 'truncate', 'minmax', 'remote_editing', 'autorefresh') def get_remote_data(data, settings, mode, more_excluded_names=None): """
def main(): """ Start Spyder application. If single instance mode is turned on (default behavior) and an instance of Spyder is already running, this will just parse and send command line options to the application. """ # Parse command line options options, args = get_options() # Store variable to be used in self.restart (restart spyder instance) os.environ['SPYDER_ARGS'] = str(sys.argv[1:]) if CONF.get('main', 'single_instance') and not options.new_instance \ and not options.reset_config_files and not running_in_mac_app(): # Minimal delay (0.1-0.2 secs) to avoid that several # instances started at the same time step in their # own foots while trying to create the lock file time.sleep(random.randrange(1000, 2000, 90)/10000.) # Lock file creation lock_file = get_conf_path('spyder.lock') lock = lockfile.FilesystemLock(lock_file) # Try to lock spyder.lock. If it's *possible* to do it, then # there is no previous instance running and we can start a # new one. If *not*, then there is an instance already # running, which is locking that file try: lock_created = lock.lock() except: # If locking fails because of errors in the lockfile # module, try to remove a possibly stale spyder.lock. # This is reported to solve all problems with # lockfile (See issue 2363) try: if os.name == 'nt': if osp.isdir(lock_file): import shutil shutil.rmtree(lock_file, ignore_errors=True) else: if osp.islink(lock_file): os.unlink(lock_file) except: pass # Then start Spyder as usual and *don't* continue # executing this script because it doesn't make # sense from spyder.app import mainwindow mainwindow.main() return if lock_created: # Start a new instance from spyder.app import mainwindow mainwindow.main() else: # Pass args to Spyder or print an informative # message if args: send_args_to_spyder(args) else: print("Spyder is already running. If you want to open a new \n" "instance, please pass to it the --new-instance option") else: from spyder.app import mainwindow mainwindow.main()
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) else: self.start_button.setEnabled(False) def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: p_args = ['-m', 'pylint', '--output-format=text'] if plver.split('.')[0] == '0': p_args += ['-i', 'yes'] else: # Option '-i' (alias for '--include-ids') was removed in pylint # 1.0 p_args += ["--msg-template='{msg_id}:{line:3d},"\ "{column}: {obj}: {msg}"] p_args += [osp.basename(filename)] else: p_args = [osp.basename(filename)] self.process.start(sys.executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match('^[CRWE]+([0-9]{4})?:', line): continue i1 = line.find(':') if i1 == -1: continue msg_id = line[:i1] i2 = line.find(':', i1 + 1) if i2 == -1: continue line_nb = line[i1 + 1:i2].strip() if not line_nb: continue line_nb = int(line_nb.split(',')[0]) message = line[i2 + 1:] item = (module, line_nb, message, msg_id) results[line[0] + ':'].append(item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun + len(txt_prun):i_prun_end] filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: #444444\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: #666666\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = (text_style % text) + (rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime), encoding='utf8') date_text = text_style % date self.ratelabel.setText(text) self.datelabel.setText(date_text)
class ProfilerWidget(PluginMainWidget): """ Profiler widget. """ ENABLE_SPINNER = True DATAPATH = get_conf_path('profiler.results') # --- Signals # ------------------------------------------------------------------------ sig_edit_goto_requested = Signal(str, int, str) """ This signal will request to open a file in a given row and column using a code editor. Parameters ---------- path: str Path to file. row: int Cursor starting row position. word: str Word to select on given row. """ sig_redirect_stdio_requested = Signal(bool) """ This signal is emitted to request the main application to redirect standard output/error when using Open/Save/Browse dialogs within widgets. Parameters ---------- redirect: bool Start redirect (True) or stop redirect (False). """ sig_started = Signal() """This signal is emitted to inform the profiling process has started.""" sig_finished = Signal() """This signal is emitted to inform the profile profiling has finished.""" def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent) self.set_conf('text_color', MAIN_TEXT_COLOR) # Attributes self._last_wdir = None self._last_args = None self._last_pythonpath = None self.error_output = None self.output = None self.running = False self.text_color = self.get_conf('text_color') # Widgets self.process = None self.filecombo = PythonModulesComboBox( self, id_=ProfilerWidgetMainToolbarItems.FileCombo) self.datatree = ProfilerDataTree(self) self.datelabel = QLabel() self.datelabel.ID = ProfilerWidgetInformationToolbarItems.DateLabel # Layout layout = QVBoxLayout() layout.addWidget(self.datatree) self.setLayout(layout) # Signals self.datatree.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Profiler') def get_focus_widget(self): return self.datatree def setup(self): self.start_action = self.create_action( ProfilerWidgetActions.Run, text=_("Run profiler"), tip=_("Run profiler"), icon=self.create_icon('run'), triggered=self.run, ) browse_action = self.create_action( ProfilerWidgetActions.Browse, text='', tip=_('Select Python script'), icon=self.create_icon('fileopen'), triggered=lambda x: self.select_file(), ) self.log_action = self.create_action( ProfilerWidgetActions.ShowOutput, text=_("Output"), tip=_("Show program's output"), icon=self.create_icon('log'), triggered=self.show_log, ) self.collapse_action = self.create_action( ProfilerWidgetActions.Collapse, text=_('Collapse'), tip=_('Collapse one level up'), icon=self.create_icon('collapse'), triggered=lambda x=None: self.datatree.change_view(-1), ) self.expand_action = self.create_action( ProfilerWidgetActions.Expand, text=_('Expand'), tip=_('Expand one level down'), icon=self.create_icon('expand'), triggered=lambda x=None: self.datatree.change_view(1), ) self.save_action = self.create_action( ProfilerWidgetActions.SaveData, text=_("Save data"), tip=_('Save profiling data'), icon=self.create_icon('filesave'), triggered=self.save_data, ) self.load_action = self.create_action( ProfilerWidgetActions.LoadData, text=_("Load data"), tip=_('Load profiling data for comparison'), icon=self.create_icon('fileimport'), triggered=self.compare, ) self.clear_action = self.create_action( ProfilerWidgetActions.Clear, text=_("Clear comparison"), tip=_("Clear comparison"), icon=self.create_icon('editdelete'), triggered=self.clear, ) self.clear_action.setEnabled(False) # Main Toolbar toolbar = self.get_main_toolbar() for item in [self.filecombo, browse_action, self.start_action]: self.add_item_to_toolbar( item, toolbar=toolbar, section=ProfilerWidgetMainToolbarSections.Main, ) # Secondary Toolbar secondary_toolbar = self.create_toolbar( ProfilerWidgetToolbars.Information) for item in [ self.collapse_action, self.expand_action, self.create_stretcher( id_=ProfilerWidgetInformationToolbarItems.Stretcher1), self.datelabel, self.create_stretcher( id_=ProfilerWidgetInformationToolbarItems.Stretcher2), self.log_action, self.save_action, self.load_action, self.clear_action ]: self.add_item_to_toolbar( item, toolbar=secondary_toolbar, section=ProfilerWidgetInformationToolbarSections.Main, ) # Setup 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_action): widget.setDisabled(True) url = 'https://docs.python.org/3/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) def update_actions(self): if self.running: icon = self.create_icon('stop') else: icon = self.create_icon('run') self.start_action.setIcon(icon) self.start_action.setEnabled(bool(self.filecombo.currentText())) # --- Private API # ------------------------------------------------------------------------ def _kill_if_running(self): """Kill the profiling process if it is running.""" if self.process is not None: if self.process.state() == QProcess.Running: self.process.close() self.process.waitForFinished(1000) self.update_actions() def _finished(self, exit_code, exit_status): """ Parse results once the profiling process has ended. Parameters ---------- exit_code: int QProcess exit code. exit_status: str QProcess exit status. """ self.running = False self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output self.datelabel.setText('') self.show_data(justanalyzed=True) self.update_actions() def _read_output(self, error=False): """ Read otuput from QProcess. Parameters ---------- error: bool, optional Process QProcess output or error channels. Default is 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(qba.data(), encoding='utf-8') if error: self.error_output += text else: self.output += text # --- Public API # ------------------------------------------------------------------------ def save_data(self): """Save data.""" title = _("Save profiler result") filename, _selfilter = getsavefilename( self, title, getcwd_or_home(), _("Profiler result") + " (*.Result)", ) if filename: self.datatree.save_data(filename) def compare(self): """Compare previous saved run with last run.""" filename, _selfilter = getopenfilename( self, _("Select script to compare"), getcwd_or_home(), _("Profiler result") + " (*.Result)", ) if filename: self.datatree.compare(filename) self.show_data() self.clear_action.setEnabled(True) def clear(self): """Clear data in tree.""" self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_action.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): """ Start the profiling process. Parameters ---------- wdir: str Working directory path string. Default is None. args: list Arguments to pass to the profiling process. Default is None. pythonpath: str Python path string. Default is None. """ if not is_profiler_installed(): return self._kill_if_running() # TODO: storing data is not implemented yet # index, _data = self.get_data(filename) combo = self.filecombo items = [combo.itemText(idx) for idx in range(combo.count())] index = None if index is None and filename not in items: 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, filename=None): """ Select filename to profile. Parameters ---------- filename: str, optional Path to filename to profile. default is None. Notes ----- If no `filename` is provided an open filename dialog will be used. """ if filename is None: self.sig_redirect_stdio_requested.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw)") self.sig_redirect_stdio_requested.emit(True) if filename: self.analyze(filename) def show_log(self): """Show process output log.""" if self.output: output_dialog = TextEditor( self.output, title=_("Profiler output"), readonly=True, parent=self, ) output_dialog.resize(700, 500) output_dialog.exec_() def show_errorlog(self): """Show process error log.""" if self.error_output: output_dialog = TextEditor( self.error_output, title=_("Profiler output"), readonly=True, parent=self, ) output_dialog.resize(700, 500) output_dialog.exec_() def start(self, wdir=None, args=None, pythonpath=None): """ Start the profiling process. Parameters ---------- wdir: str Working directory path string. Default is None. args: list Arguments to pass to the profiling process. Default is None. pythonpath: str Python path string. Default is 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.process.finished.connect(self.stop_spinner) 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, __, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) processEnvironment.insert("PYTHONIOENCODING", "utf8") self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' self.running = True self.start_spinner() 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 = self.get_conf('executable', section='main_interpreter') self.process.start(executable, p_args) running = self.process.waitForStarted() if not running: QMessageBox.critical( self, _("Error"), _("Process failed to start"), ) self.update_actions() def stop(self): """Stop the running process.""" self.running = False self.process.close() self.process.waitForFinished(1000) self.stop_spinner() self.update_actions() def run(self): """Toggle starting or running the profiling process.""" if self.running: self.stop() else: self.start() def show_data(self, justanalyzed=False): """ Show analyzed data on results tree. Parameters ---------- justanalyzed: bool, optional Default is False. """ if not justanalyzed: self.output = None self.log_action.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: %s\'><b>%s </b></span>" date_text = text_style % (self.text_color, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) self.datelabel.setText(date_text)
def __init__(self, plugin, name, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self) # --- Init attrs self.name = name self.history_filename = get_conf_path(history_filename) self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave # --- Other attrs self.options_button = None self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Signals # As soon as some content is printed in the console, stop # our loading animation document = self.get_control().document() document.contentsChange.connect(self._hide_loading_page)
# Add external dependencies subrepo paths to sys.path # NOTE: Please don't move this from here! HERE = osp.dirname(os.path.realpath(__file__)) DEPS_PATH = osp.join(HERE, 'external-deps') i = 0 for path in os.listdir(DEPS_PATH): external_dep_path = osp.join(DEPS_PATH, path) sys.path.insert(i, external_dep_path) i += 1 import pytest # Remove temp conf_dir before starting the tests from spyder.config.base import get_conf_path conf_dir = get_conf_path() if osp.isdir(conf_dir): shutil.rmtree(conf_dir) def pytest_addoption(parser): """Add option to run slow tests.""" parser.addoption("--run-slow", action="store_true", default=False, help="Run slow tests") def pytest_collection_modifyitems(config, items): """ Decide what tests to run (slow or fast) according to the --run-slow
class WorkingDirectory(QToolBar, SpyderPluginMixin): """ Working directory changer widget """ CONF_SECTION = 'workingdir' CONFIGWIDGET_CLASS = WorkingDirectoryConfigPage LOG_PATH = get_conf_path(CONF_SECTION) sig_option_changed = Signal(str, object) set_previous_enabled = Signal(bool) set_next_enabled = Signal(bool) redirect_stdio = Signal(bool) set_explorer_cwd = Signal(str) refresh_findinfiles = Signal() set_current_console_wd = Signal(str) def __init__(self, parent, workdir=None, **kwds): if PYQT5: super(WorkingDirectory, self).__init__(parent, **kwds) else: QToolBar.__init__(self, parent) SpyderPluginMixin.__init__(self, parent) # Initialize plugin self.initialize_plugin() self.setWindowTitle(self.get_plugin_title()) # Toolbar title self.setObjectName( self.get_plugin_title()) # Used to save Window state # Previous dir action self.history = [] self.histindex = None self.previous_action = create_action(self, "previous", None, ima.icon('previous'), _('Back'), triggered=self.previous_directory) self.addAction(self.previous_action) # Next dir action self.history = [] self.histindex = None self.next_action = create_action(self, "next", None, ima.icon('next'), _('Next'), triggered=self.next_directory) self.addAction(self.next_action) # Enable/disable previous/next actions self.set_previous_enabled.connect(self.previous_action.setEnabled) self.set_next_enabled.connect(self.next_action.setEnabled) # Path combo box adjust = self.get_option('working_dir_adjusttocontents') self.pathedit = PathComboBox(self, adjust_to_contents=adjust) self.pathedit.setToolTip( _("This is the working directory for newly\n" "opened consoles (Python/IPython consoles and\n" "terminals), for the file explorer, for the\n" "find in files plugin and for new files\n" "created in the editor")) self.pathedit.open_dir.connect(self.chdir) self.pathedit.activated[str].connect(self.chdir) self.pathedit.setMaxCount(self.get_option('working_dir_history')) wdhistory = self.load_wdhistory(workdir) if workdir is None: if self.get_option('console/use_project_or_home_directory'): workdir = get_home_dir() else: workdir = self.get_option('console/fixed_directory', default='') if not osp.isdir(workdir): workdir = get_home_dir() self.chdir(workdir) self.pathedit.addItems(wdhistory) self.pathedit.selected_text = self.pathedit.currentText() self.refresh_plugin() self.addWidget(self.pathedit) # Browse action browse_action = create_action(self, "browse", None, ima.icon('DirOpenIcon'), _('Browse a working directory'), triggered=self.select_directory) self.addAction(browse_action) # Parent dir action parent_action = create_action(self, "parent", None, ima.icon('up'), _('Change to parent directory'), triggered=self.parent_directory) self.addAction(parent_action) #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Current working directory') def get_plugin_icon(self): """Return widget icon""" return ima.icon('DirOpenIcon') def get_plugin_actions(self): """Setup actions""" return (None, None) def register_plugin(self): """Register plugin in Spyder's main window""" self.redirect_stdio.connect(self.main.redirect_internalshell_stdio) self.main.console.shell.refresh.connect(self.refresh_plugin) iconsize = 24 self.setIconSize(QSize(iconsize, iconsize)) self.main.addToolBar(self) def refresh_plugin(self): """Refresh widget""" curdir = getcwd_or_home() self.pathedit.add_text(curdir) self.save_wdhistory() self.set_previous_enabled.emit(self.histindex is not None and self.histindex > 0) self.set_next_enabled.emit(self.histindex is not None and \ self.histindex < len(self.history)-1) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" pass def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" return True #------ Public API --------------------------------------------------------- def load_wdhistory(self, workdir=None): """Load history from a text file in user home directory""" if osp.isfile(self.LOG_PATH): wdhistory, _ = encoding.readlines(self.LOG_PATH) wdhistory = [name for name in wdhistory if os.path.isdir(name)] else: if workdir is None: workdir = get_home_dir() wdhistory = [workdir] return wdhistory def save_wdhistory(self): """Save history to a text file in user home directory""" text = [ to_text_string( self.pathedit.itemText(index) ) \ for index in range(self.pathedit.count()) ] encoding.writelines(text, self.LOG_PATH) @Slot() def select_directory(self): """Select directory""" self.redirect_stdio.emit(False) directory = getexistingdirectory(self.main, _("Select directory"), getcwd_or_home()) if directory: self.chdir(directory) self.redirect_stdio.emit(True) @Slot() def previous_directory(self): """Back to previous directory""" self.histindex -= 1 self.chdir(directory='', browsing_history=True) @Slot() def next_directory(self): """Return to next directory""" self.histindex += 1 self.chdir(directory='', browsing_history=True) @Slot() def parent_directory(self): """Change working directory to parent directory""" self.chdir(os.path.join(getcwd_or_home(), os.path.pardir)) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) @Slot(str, bool, bool, bool) def chdir(self, directory, browsing_history=False, refresh_explorer=True, refresh_console=True): """Set directory as working directory""" if directory: directory = osp.abspath(to_text_string(directory)) # Working directory history management if browsing_history: directory = self.history[self.histindex] elif directory in self.history: self.histindex = self.history.index(directory) else: if self.histindex is None: self.history = [] else: self.history = self.history[:self.histindex + 1] self.history.append(directory) self.histindex = len(self.history) - 1 # Changing working directory try: os.chdir(directory) if refresh_explorer: self.set_explorer_cwd.emit(directory) if refresh_console: self.set_current_console_wd.emit(directory) self.refresh_findinfiles.emit() except OSError: self.history.pop(self.histindex) self.refresh_plugin()
# Third party imports from qtpy.QtCore import QObject, QTimer, Signal from qtpy.QtWidgets import QApplication # Local imports from spyder import dependencies from spyder.config.base import _, DEBUG, debug_print, get_conf_path from spyder.utils import sourcecode from spyder.utils.introspection.plugin_client import PluginClient from spyder.utils.introspection.utils import CodeInfo PLUGINS = ['rope', 'jedi', 'fallback'] LOG_FILENAME = get_conf_path('introspection.log') DEBUG_EDITOR = DEBUG >= 3 LEAD_TIME_SEC = 0.25 ROPE_REQVER = '>=0.9.4' dependencies.add('rope', _("Editor's code completion, go-to-definition and help"), required_version=ROPE_REQVER) JEDI_REQVER = '>=0.8.1' dependencies.add('jedi', _("Editor's code completion, go-to-definition and help"), required_version=JEDI_REQVER)
def start(self): self.zmq_out_socket = self.context.socket(zmq.PAIR) self.zmq_out_port = self.zmq_out_socket.bind_to_random_port('tcp://*') self.zmq_in_socket = self.context.socket(zmq.PAIR) self.zmq_in_socket.set_hwm(0) self.zmq_in_port = self.zmq_in_socket.bind_to_random_port('tcp://*') self.transport_args += ['--zmq-in-port', self.zmq_out_port, '--zmq-out-port', self.zmq_in_port] server_log = subprocess.PIPE if get_debug_level() > 0: # Create server log file server_log_fname = 'server_{0}.log'.format(self.language) 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)) server_log = open(server_log_file, 'w') # Start server with logging options if get_debug_level() == 2: self.server_args.append('-v') elif get_debug_level() == 3: self.server_args.append('-vv') if not self.external_server: logger.info('Starting server: {0}'.format( ' '.join(self.server_args))) creation_flags = 0 if os.name == 'nt': creation_flags = (subprocess.CREATE_NEW_PROCESS_GROUP | 0x08000000) # CREATE_NO_WINDOW if os.environ.get('CI') and os.name == 'nt': # The following patching avoids: # # OSError: [WinError 6] The handle is invalid # # while running our tests in CI services on Windows # (they run fine locally). # See this comment for an explanation: # https://stackoverflow.com/q/43966523/ # 438386#comment74964124_43966523 def patched_cleanup(): pass subprocess._cleanup = patched_cleanup self.lsp_server = subprocess.Popen( self.server_args, stdout=server_log, stderr=subprocess.STDOUT, creationflags=creation_flags) client_log = subprocess.PIPE if get_debug_level() > 0: # Client log file client_log_fname = 'client_{0}.log'.format(self.language) client_log_file = get_conf_path(osp.join('lsp_logs', client_log_fname)) if not osp.exists(osp.dirname(client_log_file)): os.makedirs(osp.dirname(client_log_file)) client_log = open(client_log_file, 'w') new_env = dict(os.environ) python_path = os.pathsep.join(sys.path)[1:] new_env['PYTHONPATH'] = python_path self.transport_args = list(map(str, self.transport_args)) logger.info('Starting transport: {0}' .format(' '.join(self.transport_args))) self.transport_client = subprocess.Popen(self.transport_args, stdout=subprocess.PIPE, stderr=client_log, env=new_env) fid = self.zmq_in_socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self.on_msg_received) # This is necessary for tests to pass locally! logger.debug('LSP {} client started!'.format(self.language))