# -*- coding: utf-8 -*- # pylint: disable=protected-access # ----------------------------------------------------------------------------- # Copyright (c) 2016-2017 Anaconda, Inc. # # May be copied and distributed freely only as part of an Anaconda or # Miniconda installation. # ----------------------------------------------------------------------------- """Components for environment management.""" __all__ = ['EnvironmentsComponent'] import datetime import os import typing import uuid from qtpy import QtCore from anaconda_navigator.utils import constants from .. import dialogs from ..dialogs import environment as environment_dialogs from ..dialogs import packages as package_dialogs from ..tabs import environments as environment_tabs from . import common if typing.TYPE_CHECKING: import typing_extensions from qtpy import QtWidgets from anaconda_navigator.widgets import main_window from anaconda_navigator.widgets.dialogs.environment import import_dialogs class SelectorInitializer(typing_extensions.Protocol): # pylint: disable=too-few-public-methods """Common interface for SelectorDialogs.""" def __call__( self, parent: typing.Optional[QtWidgets.QWidget] = None, initial: environment_dialogs.SelectorValue = environment_dialogs.SelectorValue.LOCAL, ) -> environment_dialogs.SelectorDialog: """Initialize new SelectorDialog instance.""" class EnvironmentsComponent(common.Component): """Component for environment management.""" __alias__ = 'environments' def __init__(self, parent: 'main_window.MainWindow') -> None: """Initialize new :class:`~EnvironmentsComponent` instance.""" super().__init__(parent=parent) self.__environments: typing.Any = None self.__timer = QtCore.QTimer() # Check for new environments self.__timer.setInterval(16000) self.__timer.timeout.connect(self.check_for_new_environments) self.__tab = environment_tabs.EnvironmentsTab(parent=self.main_window) self.__tab.sig_channels_requested.connect(self.main_window.show_channels) self.__tab.sig_update_index_requested.connect(self.main_window.update_index) self.__tab.sig_item_selected.connect(self.main_window.select_environment) self.__tab.sig_ready.connect(lambda: self.main_window.set_busy_status(conda=False)) self.__tab.sig_cancel_requested.connect(self.main_window.show_cancel_process) self.__tab.sig_create_requested.connect(self.show_create_environment) self.__tab.sig_clone_requested.connect(self.show_clone_environment) self.__tab.sig_backup_requested.connect(self.show_backup_environment) self.__tab.sig_import_requested.connect(self.show_import_environment) self.__tab.sig_remove_requested.connect(self.show_remove_environment) self.__tab.sig_packages_action_requested.connect(self.show_conda_packages_action) self.main_window.stack.addTab(self.__tab, text='Environments') self.main_window.sig_logged_in.connect(lambda: self.__tab.sig_update_index_requested.emit('Login')) self.main_window.sig_logged_out.connect(lambda: self.__tab.sig_update_index_requested.emit('Logout')) @property def environments(self) -> typing.Any: """Collection of detected environments.""" return self.__environments @property def tab(self) -> typing.Any: """Primary tab control.""" return self.__tab def check_for_new_environments(self): """Check for new environments periodically on the system.""" def listener(worker: typing.Any, output: typing.Any, error: str) -> None: # pylint: disable=unused-argument result = output.get('__environments') if (result is not None) and (self.__environments != result): self.__environments = result self.main_window.select_environment(prefix=self.main_window.current_prefix) conda_worker = self.main_window.api.conda_info(prefix=self.main_window.current_prefix) conda_worker.sig_chain_finished.connect(listener) def show_create_environment(self) -> None: """Create new basic environment with selectable python version.""" self.main_window.tracker.track_event('environments', 'create') self.main_window.tracker.track_page('/environments/create', pagetitle='Create new environment dialog') dialog = environment_dialogs.CreateDialog(parent=self.main_window, api=self.main_window.api) worker_info = self.main_window.api.conda_data() worker_info.sig_chain_finished.connect(dialog.setup) if dialog.exec_(): name = dialog.name prefix = dialog.prefix if name and prefix: self.main_window.set_busy_status(conda=True) worker = self.main_window.api.create_environment(prefix=prefix, packages=dialog.packages) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(self.main_window._conda_output_ready) # Common tasks for tabs and widgets on tabs self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) self.main_window.current_prefix = prefix self.main_window.set_widgets_enabled(False) self.__tab.add_temporal_item(name) self.main_window.tracker.track_page('/environments') def show_clone_environment(self) -> None: """Clone currently selected environment.""" clone_from_prefix = self.main_window.current_prefix clone_from_name = os.path.basename(clone_from_prefix) self.main_window.tracker.track_page('/environments/clone', pagetitle='Clone environment dialog') dialog = environment_dialogs.CloneDialog(parent=self.main_window, clone_from_name=clone_from_name) worker_info = self.main_window.api.conda_data() worker_info.sig_chain_finished.connect(dialog.setup) if dialog.exec_(): name = dialog.name prefix = dialog.prefix if name and prefix: self.main_window.set_busy_status(conda=True) worker = self.main_window.api.clone_environment(clone_from_prefix=clone_from_prefix, prefix=prefix) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(self.main_window._conda_output_ready) # Actions on tabs and subwidgets self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) self.main_window.set_widgets_enabled(False) self.__tab.add_temporal_item(name) self.main_window.tracker.track_page('/environments') def show_backup_environment(self) -> None: """Create backup of current active environment.""" self.main_window.tracker.track_page('/environments/backup', pagetitle='Backup Environment') value: environment_dialogs.SelectorValue = environment_dialogs.SelectorValue.NUCLEUS environment_name: str = ( f'{os.path.basename(self.main_window.current_prefix)}' '_' f'{datetime.date.today().strftime("%Y%m%d")}' ) while True: dialog: environment_dialogs.BackupSelectorDialog = environment_dialogs.BackupSelectorDialog( parent=self.main_window, ) dialog.environment_name = environment_name try: dialog.value = value except ValueError: pass dialog.exec_() if dialog.outcome == environment_dialogs.SelectorOutcome.REJECT: break value = dialog.value environment_name = dialog.environment_name if dialog.outcome == environment_dialogs.SelectorOutcome.LOGIN_REQUEST: if value == environment_dialogs.SelectorValue.NUCLEUS: self.main_window.components.accounts.log_into_nucleus() continue raise NotImplementedError(f'Can not login to {value.name}') if dialog.outcome == environment_dialogs.SelectorOutcome.ACCEPT: dialogs.MessageBox( dialogs.MessageBox.INFORMATION_BOX, title='Backup Environment', text='Environment backed up successfully', parent=self.main_window, ).exec_() break raise NotImplementedError() self.main_window.tracker.track_page('/environments') def show_import_environment(self) -> None: # pylint: disable=missing-function-docstring,too-many-statements self.main_window.tracker.track_page('/environments/import', pagetitle='Import Environment') value: environment_dialogs.SelectorValue = environment_dialogs.SelectorValue.NUCLEUS while True: dialog: environment_dialogs.ImportSelectorDialog = environment_dialogs.ImportSelectorDialog( parent=self.main_window, ) try: dialog.value = value except ValueError: pass dialog.exec_() if dialog.outcome == environment_dialogs.SelectorOutcome.REJECT: break value = dialog.value if dialog.outcome == environment_dialogs.SelectorOutcome.LOGIN_REQUEST: if value == environment_dialogs.SelectorValue.NUCLEUS: self.main_window.components.accounts.log_into_nucleus() continue raise NotImplementedError(f'Can not login to {value.name}') if dialog.outcome == environment_dialogs.SelectorOutcome.ACCEPT: conda_details: 'import_dialogs.CondaDetails' = typing.cast( 'import_dialogs.CondaDetails', dialog.conda_details, ) old_prefix: typing.Optional[str] = conda_details.environments.get(dialog.environment_name, None) new_prefix: str = ( old_prefix or os.path.join(conda_details.environment_directories[0], dialog.environment_name) ) file: str = typing.cast(str, dialog.environment_specification) file_temporary: bool = bool(dialog.environment_temporary) def env_validate() -> None: self.main_window.set_busy_status(conda=True) worker = self.main_window.api.import_environment( prefix=os.path.join(conda_details.environment_directories[0], uuid.uuid4().hex), file=file, validate_only=True, ) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(env_remove) self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) self.main_window.set_widgets_enabled(False) def env_remove(worker: typing.Any, output: typing.Any, error: typing.Any) -> None: if error: worker.old_prefix = self.main_window.current_prefix # to prevent switch to default prefix self.main_window._conda_output_ready(worker, output, error) return worker = self.main_window.api.remove_environment(prefix=old_prefix) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(env_import) self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) def env_import(*args: typing.Any) -> None: # pylint: disable=unused-argument self.main_window.set_busy_status(conda=True) worker = self.main_window.api.import_environment(prefix=new_prefix, file=file) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(self.main_window._conda_output_ready) worker.sig_finished.connect(cleanup) self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) if old_prefix is None: self.__tab.add_temporal_item(os.path.basename(new_prefix)) self.main_window.set_widgets_enabled(False) def cleanup(*args: typing.Any) -> None: # pylint: disable=unused-argument if file_temporary: try: os.remove(file) except OSError: pass if old_prefix is not None: env_validate() else: env_import() break raise NotImplementedError() self.main_window.tracker.track_page('/environments') def show_remove_environment(self) -> None: """Clone currently selected environment.""" prefix = self.main_window.current_prefix name = os.path.basename(prefix) if prefix != self.main_window.api.ROOT_PREFIX: self.main_window.tracker.track_page('/environments/remove', pagetitle='Remove environment dialog') dialog = environment_dialogs.RemoveDialog(parent=self.main_window, name=name, prefix=prefix) if dialog.exec_(): self.main_window.set_busy_status(conda=True) if prefix == self.main_window.config.get('main', 'default_env'): self.main_window.config.set('main', 'default_env', self.main_window.api.ROOT_PREFIX) worker = self.main_window.api.remove_environment(prefix=prefix) worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(self.main_window._conda_output_ready) # Actions on tabs and subwidgets self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) self.main_window.set_widgets_enabled(False) self.main_window.tracker.track_page('/environments') def show_conda_packages_action( # pylint: disable=too-many-branches,too-many-locals,too-many-statements self, conda_packages_actions: typing.Mapping[int, typing.Sequence[typing.Mapping[str, typing.Any]]], pip_packages_actions: typing.Mapping[int, typing.Sequence[typing.Mapping[str, typing.Any]]], ) -> None: """Process the coda actions on the packages for current environment.""" install_packages = [] remove_packages = [] update_packages = [] remove_pip_packages = [] for action in [constants.ACTION_DOWNGRADE, constants.ACTION_INSTALL, constants.ACTION_UPGRADE]: pkgs_action = conda_packages_actions[action] for pkg in pkgs_action: name = pkg['name'] version = pkg['version_to'] if version: spec = name + '==' + version else: spec = name install_packages.append(spec) for pkg in conda_packages_actions[constants.ACTION_REMOVE]: remove_packages.append(pkg['name']) for pkg in conda_packages_actions[constants.ACTION_UPDATE]: update_packages.append(pkg['name']) for pkg in pip_packages_actions[constants.ACTION_REMOVE]: remove_pip_packages.append(pkg['name']) self.main_window.tracker.track_page('/environments/packages', pagetitle='Packages actions dialog') self.main_window.set_busy_status(conda=True) if install_packages: pkgs = install_packages dialog = package_dialogs.PackagesDialog( parent=self.main_window, packages=pkgs, ) worker_deps = self.main_window.api.install_packages( prefix=self.main_window.current_prefix, pkgs=pkgs, dry_run=True, ) elif update_packages: pkgs = update_packages dialog = package_dialogs.PackagesDialog( parent=self.main_window, packages=pkgs, update_only=True, ) worker_deps = self.main_window.api.update_packages( prefix=self.main_window.current_prefix, pkgs=pkgs, dry_run=True, ) elif remove_packages: pkgs = remove_packages dialog = package_dialogs.PackagesDialog( parent=self.main_window, packages=pkgs, remove_only=True, pip_packages=remove_pip_packages, ) worker_deps = self.main_window.api.remove_packages( prefix=self.main_window.current_prefix, pkgs=pkgs, dry_run=True, ) worker_deps.prefix = self.main_window.current_prefix worker_deps.sig_finished.connect(dialog.setup) if dialog.exec_(): worker = None if remove_packages: worker = self.main_window.api.remove_packages(prefix=self.main_window.current_prefix, pkgs=pkgs) elif install_packages: worker = self.main_window.api.install_packages(prefix=self.main_window.current_prefix, pkgs=pkgs) elif update_packages: worker = self.main_window.api.update_packages(prefix=self.main_window.current_prefix, pkgs=pkgs) elif remove_pip_packages: pass # Run pip command :-p? if worker: worker.sig_partial.connect(self.main_window._conda_partial_output_ready) worker.sig_finished.connect(self.main_window._conda_output_ready) self.main_window.set_widgets_enabled(False) self.main_window.set_busy_status(conda=True) self.main_window.update_status(action=worker.action_msg, value=0, max_value=0) else: if not worker_deps.is_finished(): self.main_window.api.conda_terminate() self.main_window.set_busy_status(conda=False) self.main_window.tracker.track_page('/environments') # Virtual endpoints def setup(self, worker: typing.Any, output: typing.Any, error: str, initial: bool) -> None: """Perform component configuration from `conda_data`.""" if initial: self.__environments = output.get('processed_info', {}).get('__environments') else: self.__tab.setup(output) def update_style_sheet(self) -> None: """Update style sheet of the tab.""" self.__tab.update_style_sheet() def start_timers(self) -> None: """Start component timers.""" self.__timer.start() def stop_timers(self) -> None: """Stop component timers.""" self.__timer.stop()