Source code for aiida.manage.configuration.config

# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved.                     #
# This file is part of the AiiDA code.                                    #
#                                                                         #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file        #
# For further information please visit http://www.aiida.net               #
###########################################################################
"""Module that defines the configuration file of an AiiDA instance and functions to create and load it."""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import io
import os
import shutil

from aiida.common import json

from .migrations import CURRENT_CONFIG_VERSION, OLDEST_COMPATIBLE_CONFIG_VERSION
from .options import get_option, parse_option, NO_DEFAULT
from .profile import Profile
from .settings import DEFAULT_UMASK, DEFAULT_CONFIG_INDENT_SIZE

__all__ = ('Config',)


[docs]class Config(object): # pylint: disable=useless-object-inheritance """Object that represents the configuration file of an AiiDA instance.""" KEY_VERSION = 'CONFIG_VERSION' KEY_VERSION_CURRENT = 'CURRENT' KEY_VERSION_OLDEST_COMPATIBLE = 'OLDEST_COMPATIBLE' KEY_DEFAULT_PROFILE = 'default_profile' KEY_PROFILES = 'profiles'
[docs] def __init__(self, filepath, dictionary): """Instantiate a configuration object from a configuration dictionary and its filepath. If an empty dictionary is passed, the constructor will create the skeleton configuration dictionary. :param filepath: the absolute filepath of the configuration file :param dictionary: the content of the configuration file in dictionary form """ if not dictionary: # Construct the default configuration template dictionary = { self.KEY_VERSION: { self.KEY_VERSION_CURRENT: CURRENT_CONFIG_VERSION, self.KEY_VERSION_OLDEST_COMPATIBLE: OLDEST_COMPATIBLE_CONFIG_VERSION, }, self.KEY_PROFILES: {}, } self._filepath = filepath self._dictionary = dictionary
[docs] def __eq__(self, other): """Two configurations are considered equal, when their dictionaries are equal.""" return self.dictionary == other.dictionary
[docs] def __ne__(self, other): """Two configurations are considered unequal, when their dictionaries are unequal.""" return self.dictionary != other.dictionary
@property def dictionary(self): return self._dictionary @property def version(self): return self._version_settings.get(self.KEY_VERSION_CURRENT, 0) @version.setter def version(self, version): self._version_settings[self.KEY_VERSION_CURRENT] = version @property def version_oldest_compatible(self): return self._version_settings.get(self.KEY_VERSION_OLDEST_COMPATIBLE, 0) @version_oldest_compatible.setter def version_oldest_compatible(self, version_oldest_compatible): self._version_settings[self.KEY_VERSION_OLDEST_COMPATIBLE] = version_oldest_compatible @property def _version_settings(self): return self.dictionary.setdefault(self.KEY_VERSION, {}) @property def filepath(self): return self._filepath @property def dirpath(self): return os.path.dirname(self.filepath) @property def default_profile_name(self): """Return the default profile name. :return: the default profile name or None if not defined """ try: return self.dictionary[self.KEY_DEFAULT_PROFILE] except KeyError: return None @property def current_profile(self): """Return the currently loaded profile. :return: the current profile or None if not defined """ from aiida.backends import settings current_profile_name = settings.AIIDADB_PROFILE if current_profile_name: return self.get_profile(current_profile_name) return None @property def profile_names(self): """Return the list of profile names. :return: list of profile names """ try: profiles = self.dictionary[self.KEY_PROFILES] except KeyError: return [] else: return list(profiles) @property def profiles(self): """Return the list of profiles. :return: the profiles :rtype: list of `Profile` instances """ try: profiles = self.dictionary[self.KEY_PROFILES] except KeyError: return [] else: return [Profile(name, profile) for name, profile in profiles.items()]
[docs] def validate_profile(self, name): """Validate that a profile exists. :param name: name of the profile: :raises aiida.common.ProfileConfigurationError: if the name is not found in the configuration file """ from aiida.common import exceptions if name not in self.profile_names: raise exceptions.ProfileConfigurationError('profile `{}` does not exist'.format(name))
[docs] def get_profile(self, name=None): """Return the profile for the given name or the default one if not specified. :return: the profile instance or None if it does not exist :raises aiida.common.ProfileConfigurationError: if the name is not found in the configuration file """ from aiida.common import exceptions if not name and not self.default_profile_name: raise exceptions.ProfileConfigurationError('no default profile defined') if not name: name = self.default_profile_name self.validate_profile(name) return Profile(name, self.dictionary[self.KEY_PROFILES][name])
[docs] def _get_profile_dictionary(self, name): """Return the internal profile dictionary for the given name or the default one if not specified. :return: the profile instance or None if it does not exist :raises aiida.common.ProfileConfigurationError: if the name is not found in the configuration file """ self.validate_profile(name) return self.dictionary[self.KEY_PROFILES][name]
[docs] def add_profile(self, name, profile): """Add a profile to the configuration. :param name: the name of the profile to remove :param profile: the profile configuration dictionary :return: self """ self.dictionary[self.KEY_PROFILES][name] = profile return self
[docs] def update_profile(self, profile): """Update a profile in the configuration. :param profile: the profile instance to update :return: self """ self.dictionary[self.KEY_PROFILES][profile.name] = profile.dictionary return self
[docs] def remove_profile(self, name): """Remove a profile from the configuration. :param name: the name of the profile to remove :raises aiida.common.ProfileConfigurationError: if the given profile does not exist :return: self """ self.validate_profile(name) self.dictionary[self.KEY_PROFILES].pop(name) return self
[docs] def set_default_profile(self, name, overwrite=False): """Set the given profile as the new default. :param name: name of the profile to set as new default :param overwrite: when True, set the profile as the new default even if a default profile is already defined :raises aiida.common.ProfileConfigurationError: if the given profile does not exist :return: self """ if self.default_profile_name and not overwrite: return self self.validate_profile(name) self.dictionary[self.KEY_DEFAULT_PROFILE] = name return self
[docs] def option_set(self, option_name, option_value, scope=None): """Set a configuration option. :param option_name: the name of the configuration option :param option_value: the option value :param scope: set the option for this profile or globally if not specified """ option, parsed_value = parse_option(option_name, option_value) if scope is not None: dictionary = self._get_profile_dictionary(scope) else: dictionary = self.dictionary if parsed_value: dictionary[option.key] = parsed_value elif option.default is not NO_DEFAULT: dictionary[option.key] = option.default else: pass
[docs] def option_unset(self, option_name, scope=None): """Unset a configuration option. :param option_name: the name of the configuration option :param scope: unset the option for this profile or globally if not specified """ option = get_option(option_name) if scope is not None: dictionary = self._get_profile_dictionary(scope) else: dictionary = self.dictionary dictionary.pop(option.key, None)
[docs] def option_get(self, option_name, scope=None, default=True): """Get a configuration option. :param option_name: the name of the configuration option :param scope: get the option for this profile or globally if not specified :param default: boolean, If True will return the option default, even if not defined within the given scope :return: the option value or None if not set for the given scope """ option = get_option(option_name) if scope is not None: dictionary = self.get_profile(scope).dictionary else: dictionary = self.dictionary return dictionary.get(option.key, option.default if default else None)
[docs] def store(self): """Write the current config to file.""" self._backup() umask = os.umask(DEFAULT_UMASK) try: with io.open(self.filepath, 'wb') as handle: json.dump(self.dictionary, handle, indent=DEFAULT_CONFIG_INDENT_SIZE) finally: os.umask(umask) return self
[docs] def _backup(self): """Create a backup of the current config as it exists on disk.""" if not os.path.isfile(self.filepath): return umask = os.umask(DEFAULT_UMASK) try: shutil.copy(self.filepath, self.filepath + '~') finally: os.umask(umask)