def __init__(self, parent=None): """ Initialize main widget. Create a basic layout and add ReportWidget. """ SpyderPluginWidget.__init__(self, parent) self.main = parent # Spyder 3 compatibility self.render_action = None self.save_action = None self.save_as_action = None # Initialize plugin self.initialize_plugin() # Create widget and add to dockwindow self.report_widget = ReportsWidget( self.main, [self.save_action, self.save_as_action]) layout = QVBoxLayout() layout.addWidget(self.report_widget) self.setLayout(layout) self.sig_render_started.connect(self.report_widget.render_started) self.sig_render_progress.connect(self.report_widget.update_progress) self.sig_render_finished.connect(self.report_widget.render_finished) self.report_widget.tabs.currentChanged.connect( self.update_actions_status) # This worker runs in a thread to avoid blocking when rendering # a report self._worker_manager = WorkerManager() # Dict to save reports information: self._reports = defaultdict(Report)
def __init__(self, parent, statusbar, icon=None, interpreter=None): """Status bar widget for displaying the current conda environment.""" self._interpreter = interpreter super(InterpreterStatus, self).__init__(parent, statusbar, icon=icon) self.main = parent self.env_actions = [] self.path_to_env = {} self.envs = {} self.value = '' self.menu = QMenu(self) self.sig_clicked.connect(self.show_menu) # Worker to compute envs in a thread self._worker_manager = WorkerManager(max_threads=1) # Timer to get envs every minute self._get_envs_timer = QTimer(self) self._get_envs_timer.setInterval(60000) self._get_envs_timer.timeout.connect(self.get_envs) self._get_envs_timer.start() # Update the list of envs at startup self.get_envs()
def __init__(self, parent, statusbar): super(VCSStatus, self).__init__(parent, statusbar, icon=ima.icon('code_fork')) self._worker_manager = WorkerManager(max_threads=1) self._git_is_working = None self._git_job_queue = None self._last_git_job = None
class VCSStatus(StatusBarWidget): """Status bar widget for system vcs.""" def __init__(self, parent, statusbar): super(VCSStatus, self).__init__(parent, statusbar, icon=ima.icon('code_fork')) self._worker_manager = WorkerManager(max_threads=1) self._git_is_working = None self._git_job_queue = None self._last_git_job = None def update_vcs_state(self, idx, fname, fname2): """Update vcs status.""" self.update_vcs(fname, None) def update_vcs(self, fname, index, force=False): """Update vcs status.""" if self._last_git_job == (fname, index) and not force: self._git_job_queue = None return if self._git_is_working: self._git_job_queue = (fname, index) else: self._worker_manager.terminate_all() worker = self._worker_manager.create_python_worker( self.get_git_refs, fname) worker.sig_finished.connect(self.process_git_data) self._last_git_job = (fname, index) self._git_job_queue = None self._git_is_working = True worker.start() def get_git_refs(self, fname): """Get Git active branch, state, branches (plus tags).""" return get_git_refs(osp.dirname(fname)) def process_git_data(self, worker, output, error): """Receive data from git and update gui.""" branches, branch, files_modified = output text = branch if branch else '' if len(files_modified): text = text + ' [{}]'.format(len(files_modified)) self.setVisible(bool(branch)) self.set_value(text) self._git_is_working = False if self._git_job_queue: self.update_vcs(*self._git_job_queue) def change_branch(self): """Change current branch.""" pass def get_tooltip(self): """Return localized tool tip for widget.""" return _("Git branch")
class ReportsPlugin(SpyderPluginWidget): """Reports plugin.""" CONF_SECTION = 'reports' CONFIGWIDGET_CLASS = None sig_render_started = Signal(str) # input filename # Success, output filename(str), error(str) # When an error happened returns input filename instead sig_render_finished = Signal(bool, object, object) sig_render_progress = Signal(str) def __init__(self, parent=None): """ Initialize main widget. Create a basic layout and add ReportWidget. """ SpyderPluginWidget.__init__(self, parent) self.main = parent # Spyder 3 compatibility self.render_action = None self.save_action = None self.save_as_action = None # Initialize plugin self.initialize_plugin() # Create widget and add to dockwindow self.report_widget = ReportsWidget( self.main, [self.save_action, self.save_as_action]) layout = QVBoxLayout() layout.addWidget(self.report_widget) self.setLayout(layout) self.sig_render_started.connect(self.report_widget.render_started) self.sig_render_progress.connect(self.report_widget.update_progress) self.sig_render_finished.connect(self.report_widget.render_finished) self.report_widget.tabs.currentChanged.connect( self.update_actions_status) # This worker runs in a thread to avoid blocking when rendering # a report self._worker_manager = WorkerManager() # Dict to save reports information: self._reports = defaultdict(Report) # --- SpyderPluginWidget API ---------------------------------------------- def get_plugin_title(self): """Return widget title.""" return "Reports" def refresh_plugin(self): """Refresh Reports widget.""" pass def get_plugin_actions(self): """Return a list of actions related to plugin.""" self.render_action = create_action(self, "Render report to HTML", icon=self.get_plugin_icon(), triggered=self.run_reports_render) self.save_action = create_action(self, "Save Report...", icon=ima.icon('filesave'), triggered=self.save_report) self.save_as_action = create_action( self, "Save Report as...", icon=ima.icon('filesaveas'), triggered=lambda: self.save_report(new_path=True)) self.main.run_menu_actions += [self.render_action] return [self.render_action, self.save_action, self.save_as_action] def register_plugin(self): """Register plugin in Spyder's main window.""" self.main.add_dockwidget(self) # Render welcome.md in a temp location self.render_report_thread(WELCOME_PATH) self.save_action.setEnabled(False) self.save_as_action.setEnabled(False) def on_first_registration(self): """Action to be performed on first plugin registration.""" self.main.tabify_plugins(self.main.help, self) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" pass def check_compatibility(self): """ Check plugin requirements. - PyQt version is greater or equal to 5. """ messages = [] valid = True if PYQT4 or PYSIDE: messages.append('Spyder-reports does not work with Qt4') valid = False return valid, ", ".join(messages) # ------------------------------------------------------------------------- def update_actions_status(self): """Disable/enable actions, avoiding the welcome page to be saved.""" report = self.report_widget.get_focus_report() enabled = report is not None and report != WELCOME_PATH self.save_action.setEnabled(enabled) self.save_as_action.setEnabled(enabled) def check_create_tmp_dir(self, folder): """Create temp dir if it does not exists.""" if not os.path.exists(folder): os.makedirs(folder) def show_error_message(self, message): """Show error message.""" messageBox = QMessageBox(self) messageBox.setWindowModality(Qt.NonModal) messageBox.setAttribute(Qt.WA_DeleteOnClose) messageBox.setWindowTitle('Render Report Error') messageBox.setText(message) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.show() def save_report(self, new_path=False): """Save report. If the report was already saved, save it in the same path. If the output are several files copy temporary dir to user selected directory. If the output is just one file copy it, to the user selected path. Args: new_path: force saving in a new path """ report_filename = self.report_widget.get_focus_report() if report_filename is None: return report = self._reports[report_filename] output_filename = report.render_dir input_dir, _ = osp.split(report_filename) tmpdir, output_fname = osp.split(output_filename) output = None if new_path else report.save_path # TODO This should be improved because Pweave creates a # figures dir even when there isn't figures causing this # to evaluate always to True if len([name for name in os.listdir(tmpdir)]) > 1: # if there is more than one file save a dir if output is None: output = getexistingdirectory(parent=self, caption='Save Report', basedir=input_dir) if not osp.isdir(output): return report.save_path = output # Using distutils instead of shutil.copytree # because shutil.copytree fails if the dir already exists copy_tree(tmpdir, output) else: if output is None: basedir = osp.join(input_dir, output_fname) output, _ = getsavefilename(parent=self, caption='Save Report', basedir=basedir) if not osp.isfile(output): return report.save_path = output shutil.copy(output_filename, output) def run_reports_render(self): """Call report rendering and displays its output.""" editorstack = self.main.editor.get_current_editorstack() if editorstack.save(): fname = osp.abspath(self.main.editor.get_current_filename()) self.render_report_thread(fname) self.switch_to_plugin() def render_report_thread(self, file_name): """Render report in a thread and update the widget when finished.""" def worker_output(worker, output_file, error): """Receive the worker output, and update the widget.""" if error is None and output_file: self.report_widget.set_html_from_file(output_file, file_name) self.sig_render_finished.emit(True, output_file, None) else: self.show_error_message(to_text_string(error)) self.sig_render_finished.emit(False, file_name, to_text_string(error)) # Before starting a new worker process make sure to end previous # incarnations self._worker_manager.terminate_all() worker = self._worker_manager.create_python_worker( self._render_report, file_name, ) worker.sig_finished.connect(worker_output) self.sig_render_started.emit(file_name) worker.start() def _render_report(self, file, output=None): """ Parse report document using pweave. Args: file: Path of the file to be parsed. Return: Output file path """ if output is None: report = self._reports.get(file) if report is not None: output = report.render_dir if output is None: name = osp.splitext(osp.basename(file))[0] id_ = to_text_string(uuid.uuid4()) output = osp.join(REPORTS_TEMPDIR, id_, '{}.html'.format(name)) self._reports[file].render_dir = output folder = osp.split(output)[0] self.check_create_tmp_dir(folder) doc = Pweb(file, output=output) # TODO Add more formats support if doc.file_ext == '.mdw': _format = 'md2html' elif doc.file_ext == '.md': _format = 'pandoc2html' else: raise Exception("Format not supported ({})".format(doc.file_ext)) f = CaptureStdOutput(self.sig_render_progress) if pweave_version.startswith('0.3'): with redirect_stdout(f): self.sig_render_progress.emit("Readign") doc.read() self.sig_render_progress.emit("Running") doc.run() self.sig_render_progress.emit("Formating") doc.format(doctype=_format) self.sig_render_progress.emit("Writing") doc.write() return doc.sink else: with redirect_stdout(f): doc.setformat(_format) doc.detect_reader() doc.parse() doc.run() doc.format() doc.write() return doc.sink
class InterpreterStatus(BaseTimerStatus): """Status bar widget for displaying the current conda environment.""" ID = 'interpreter_status' CONF_SECTION = 'main_interpreter' sig_open_preferences_requested = Signal() """ Signal to open the main interpreter preferences. """ def __init__(self, parent, icon=None, interpreter=None): """Status bar widget for displaying the current conda environment.""" self._interpreter = interpreter super().__init__(parent) self.main = parent self.env_actions = [] self.path_to_env = {} self.envs = {} self.value = '' # Worker to compute envs in a thread self._worker_manager = WorkerManager(max_threads=1) # Timer to get envs every minute self._get_envs_timer = QTimer(self) self._get_envs_timer.setInterval(60000) self._get_envs_timer.timeout.connect(self.get_envs) self._get_envs_timer.start() # Update the list of envs at startup self.get_envs() # ---- BaseTimerStatus API def get_value(self): """ Switch to default interpreter if current env was removed or update Python version of current one. """ env_dir = self._get_env_dir(self._interpreter) if not osp.isdir(env_dir): # Env was removed on Mac or Linux self._on_interpreter_removed() elif not osp.isfile(self._interpreter): # This can happen on Windows because the interpreter was # renamed to .conda_trash if not osp.isdir(osp.join(env_dir, 'conda-meta')): # If conda-meta is missing, it means the env was removed self._on_interpreter_removed() else: # If not, it means the interpreter is being updated so # we need to update its version self.get_envs() else: # We need to do this in case the Python version was # changed in the env if self._interpreter in self.path_to_env: self.update_interpreter() return self.value # ---- Qt reimplemented methods def closeEvent(self, event): self._get_envs_timer.stop() self._worker_manager.terminate_all() super().closeEvent(event) # ---- Private API def _get_env_dir(self, interpreter): """Get env directory from interpreter executable.""" if os.name == 'nt': return osp.dirname(interpreter) else: return osp.dirname(osp.dirname(interpreter)) def _get_envs(self): """Get the list of environments in the system.""" # Compute info of default interpreter to have it available in # case we need to switch to it. This will avoid lags when # doing that in get_value. if sys.executable not in self.path_to_env: self._get_env_info(sys.executable) # Get envs conda_env = get_list_conda_envs() pyenv_env = get_list_pyenv_envs() return {**conda_env, **pyenv_env} def _get_env_info(self, path): """Get environment information.""" path = path.lower() if os.name == 'nt' else path try: name = self.path_to_env[path] except KeyError: win_app_path = osp.join('AppData', 'Local', 'Programs', 'spyder') if 'Spyder.app' in path or win_app_path in path: name = 'internal' elif 'conda' in path: name = 'conda' elif 'pyenv' in path: name = 'pyenv' else: name = 'custom' version = get_interpreter_info(path) self.path_to_env[path] = name self.envs[name] = (path, version) __, version = self.envs[name] return f'{name} ({version})' def _on_interpreter_removed(self): """ Actions to take when the current custom interpreter is removed outside Spyder. """ # NOTES: # 1. The interpreter will be updated when the option changes below # generate a change in the 'executable' one in the container. # 2. *Do not* change the order in which these options are set or the # interpreter won't be updated correctly. self.set_conf('custom_interpreter', ' ') self.set_conf('custom', False) self.set_conf('default', True) # ---- Public API def get_envs(self): """ Get the list of environments in a thread to keep them up to date. """ self._worker_manager.terminate_all() worker = self._worker_manager.create_python_worker(self._get_envs) worker.sig_finished.connect(self.update_envs) worker.start() def update_envs(self, worker, output, error): """Update the list of environments in the system.""" self.envs.update(**output) for env in list(self.envs.keys()): path, version = self.envs[env] # Save paths in lowercase on Windows to avoid issues with # capitalization. path = path.lower() if os.name == 'nt' else path self.path_to_env[path] = env self.update_interpreter() def open_interpreter_preferences(self): """Request to open the main interpreter preferences.""" self.sig_open_preferences_requested.emit() def update_interpreter(self, interpreter=None): """Set main interpreter and update information.""" if interpreter: self._interpreter = interpreter self.value = self._get_env_info(self._interpreter) self.set_value(self.value)
class InterpreterStatus(BaseTimerStatus): """Status bar widget for displaying the current conda environment.""" def __init__(self, parent, statusbar, icon=None, interpreter=None): """Status bar widget for displaying the current conda environment.""" self._interpreter = interpreter super(InterpreterStatus, self).__init__(parent, statusbar, icon=icon) self.main = parent self.env_actions = [] self.path_to_env = {} self.envs = {} self.value = '' self.menu = QMenu(self) self.sig_clicked.connect(self.show_menu) # Worker to compute envs in a thread self._worker_manager = WorkerManager(max_threads=1) # Timer to get envs every minute self._get_envs_timer = QTimer(self) self._get_envs_timer.setInterval(60000) self._get_envs_timer.timeout.connect(self.get_envs) self._get_envs_timer.start() # Update the list of envs at startup self.get_envs() def import_test(self): pass def get_value(self): """ Switch to default interpreter if current env was removed or update Python version of current one. """ env_dir = self._get_env_dir(self._interpreter) if not osp.isdir(env_dir): # Env was removed on Mac or Linux CONF.set('main_interpreter', 'custom', False) CONF.set('main_interpreter', 'default', True) self.update_interpreter(sys.executable) elif not osp.isfile(self._interpreter): # This can happen on Windows because the interpreter was # renamed to .conda_trash if not osp.isdir(osp.join(env_dir, 'conda-meta')): # If conda-meta is missing, it means the env was removed CONF.set('main_interpreter', 'custom', False) CONF.set('main_interpreter', 'default', True) self.update_interpreter(sys.executable) else: # If not, it means the interpreter is being updated so # we need to update its version self.get_envs() else: # We need to do this in case the Python version was # changed in the env if self._interpreter in self.path_to_env: self.update_interpreter() return self.value def _get_env_dir(self, interpreter): """Get env directory from interpreter executable.""" if os.name == 'nt': return osp.dirname(interpreter) else: return osp.dirname(osp.dirname(interpreter)) def _get_envs(self): """Get the list of environments in the system.""" # Compute info of default interpreter to have it available in # case we need to switch to it. This will avoid lags when # doing that in get_value. if sys.executable not in self.path_to_env: self._get_env_info(sys.executable) # Get envs conda_env = get_list_conda_envs() pyenv_env = get_list_pyenv_envs() return {**conda_env, **pyenv_env} def get_envs(self): """ Get the list of environments in a thread to keep them up to date. """ self._worker_manager.terminate_all() worker = self._worker_manager.create_python_worker(self._get_envs) worker.sig_finished.connect(self.update_envs) worker.start() def update_envs(self, worker, output, error): """Update the list of environments in the system.""" self.envs = output for env in list(self.envs.keys()): path, version = self.envs[env] # Save paths in lowercase on Windows to avoid issues with # capitalization. path = path.lower() if os.name == 'nt' else path self.path_to_env[path] = env self.update_interpreter() def show_menu(self): """Display a menu when clicking on the widget.""" menu = self.menu menu.clear() text = _("Change default environment in Preferences...") change_action = create_action( self, text=text, triggered=self.open_interpreter_preferences, ) add_actions(menu, [change_action]) rect = self.contentsRect() os_height = 7 if os.name == 'nt' else 12 pos = self.mapToGlobal(rect.topLeft() + QPoint(-40, -rect.height() - os_height)) menu.popup(pos) def open_interpreter_preferences(self): """Open the Preferences dialog in the Python interpreter section.""" self.main.show_preferences() dlg = self.main.prefs_dialog_instance index = dlg.get_index_by_name("main_interpreter") dlg.set_current_index(index) def _get_env_info(self, path): """Get environment information.""" path = path.lower() if os.name == 'nt' else path try: name = self.path_to_env[path] except KeyError: win_app_path = osp.join('AppData', 'Local', 'Programs', 'spyder') if 'Spyder.app' in path or win_app_path in path: name = 'internal' elif 'conda' in path: name = 'conda' elif 'pyenv' in path: name = 'pyenv' else: name = 'custom' version = get_interpreter_info(path) self.path_to_env[path] = name self.envs[name] = (path, version) __, version = self.envs[name] return '{env} ({version})'.format(env=name, version=version) def get_tooltip(self): """Override api method.""" return self._interpreter if self._interpreter else '' def update_interpreter(self, interpreter=None): """Set main interpreter and update information.""" if interpreter: self._interpreter = interpreter self.value = self._get_env_info(self._interpreter) self.set_value(self.value) self.update_tooltip()
def __init__(self, parent): super().__init__(parent) self._worker_manager = WorkerManager(max_threads=1) self._git_is_working = None self._git_job_queue = None self._last_git_job = None