Source code for schrodinger.utils.preferences

"""
A module to make the interface to the mmpref library more Pythonic.  It contains
a single class that can be created once and then used to access preference data.

This class also provides data type-free .get(), .set() and .getAllPreferences()
methods.

Simple script usage might look like this::

    import preferences

    # Initialize the preference handler
    pref_handler = preferences.Preferences(preferences.SCRIPTS)

    # Store and get all preferences from a group specific to this script
    pref_handler.beginGroup('my_script')

    # Set the preference 'hosts' to 5
    pref_handler.set('hosts', 5)

    # Get the value of the hosts preference
    num_hosts = pref_handler.get('hosts', default=8)

    # Get the value of the jobname preference, with myjob as the default
    # name if no preference is set
    jobname = pref_handler.get('jobname', default='myjob')

Slightly more complex script usage using groups might look like this::

    import preferences

    # Initialize the preference handler
    pref_handler = preferences.Preferences(preferences.SCRIPTS)

    # Store and get all preferences from a group specific to this script
    pref_handler.beginGroup('my_script')

    # Set the preference 'hosts' to 5
    pref_handler.set('hosts', 5)

    # Switch to the group 'my_script/dialogs'
    pref_handler.beginGroup('dialogs')
    # Set the value of the 'my_script/dialogs/import_directory preference
    pref_handler.set('import_directory', directory)

    # Reset the current group to the 'my_script' group
    pref_handler.resetGroup(retain=1)

    # Get the value of the hosts preference
    num_hosts = pref_handler.get('hosts', default=8)

    # Change the value of the 'my_script/dialogs/import_directory' preference
    pref_handler.set('dialogs/import_directory', directory2)

    # Switch to the group 'my_script/dialogs'
    pref_handler.beginGroup('dialogs')

    # Get the import directory
    imdir = pref_handler.get('import_directory')

"""

from contextlib import contextmanager
from typing import Union

import pymmlibs
from schrodinger.infra import mm

CANVAS = pymmlibs.CANVAS
COMBIGLIDE = pymmlibs.COMBIGLIDE
DESMOND = pymmlibs.DESMOND
EPIK = pymmlibs.EPIK
IMPACT = pymmlibs.IMPACT
JAGUAR = pymmlibs.JAGUAR
KNIME = pymmlibs.KNIME
MACROMODEL = pymmlibs.MACROMODEL
MAESTRO = pymmlibs.MAESTRO
PHASE = pymmlibs.PHASE
PSP = pymmlibs.PSP
PYMOL = pymmlibs.PYMOL
QIKPROP = pymmlibs.QIKPROP
SCRIPTS = pymmlibs.SCRIPTS
SHARED = pymmlibs.SHARED
WATERMAP = pymmlibs.WATERMAP

OK = pymmlibs.MMPREF_OK

NO_TYPE = pymmlibs.MMPREF_TYPE_NONE
INT = pymmlibs.MMPREF_TYPE_INT
STRING = pymmlibs.MMPREF_TYPE_STRING
FLOAT = pymmlibs.MMPREF_TYPE_DOUBLE
BOOL = pymmlibs.MMPREF_TYPE_BOOL

DATA_TYPES = [BOOL, INT, STRING, FLOAT]
ALL_TYPES = DATA_TYPES + [NO_TYPE]

# Create a unique marker that won't be used as a default value by a caller.
NODEFAULT = object()

TMPDIR_KEY = pymmlibs.MMPREF_TMPDIR_KEY


[docs]class Preferences: """ A class that allows Pythonic access to the mmpref library. """
[docs] def __init__(self, product): """ Create a new Preferences object :type product: constant :param product: A product name constant supplied in this module or one from the pymmlibs module. :raise ValueError: If product is not a recognized constant :raise IOError: If the preferences file is inaccessible. :raise IOError: If the preferences file is inaccessible. :raise RuntimeError: If another error is encountered. Constants supplied in this module: - CANVAS - COMBIGLIDE - DESMOND - EPIK - IMPACT - JAGUAR - KNIME - MACROMODEL - MAESTRO - PHASE - PSP - PYMOL - QIKPROP - SCRIPTS - SHARED - WATERMAP """ self.product = product self.status, self.handle = pymmlibs.mmpref_new(product) self._check_pref_status()
def __del__(self, _hasattr=hasattr, _delete=pymmlibs.mmpref_delete): """ Delete the handle and write the preferences to the preference file. """ # attributes are saved at function level to protect against deletion # at interpreter exit # handle might be collected, or fail to initialize if _hasattr(self, 'handle') and self.handle is not None: _delete(self.handle)
[docs] def sync(self): """ Sync the settings to file and reload settings modified by other threads/process. """ self.status = pymmlibs.mmpref_sync(self.handle) self._check_pref_status()
[docs] def clear(self): """ This will clear all preferences associated with this handle. """ pymmlibs.mmpref_clear(self.handle)
[docs] def getKeyType(self, key): """ Gets the type of data stored for this key. :type key: str :param key: key or group to remove :rtype: constant :return: A constant indicating the data type stored for this key. Valid constants are INT, STRING, FLOAT, BOOL. If the key is not found, None is returned. """ for ptype in ALL_TYPES: # mmpref_remove returns the number of keys removed, so if it # evaluates to False, no key was removed if self.contains(key, key_type=ptype): return ptype return None
[docs] def remove(self, key, key_type=None): """ Remove the key or group and its values. To remove a group and its associated keys, set type to NO_TYPE. If a group is set using `beginGroup`, the search will happen within the group. :type key: str :param key: key or group to remove :type key_type: constant :param key_type: A constant indicating the data type of this key or if it is a group. If not given, an attempt will be made to remove key if it is found as a key and if not it will be treated as a group. Valid constants are (INT, STRING, FLOAT, BOOL, NO_TYPE (groups)) :rtype: int :return: The number of keys removed :raise ValueError: If key_type is not a recognized type """ total_removed = 0 if key_type is None: key_type = self.getKeyType(key) if key_type is None: # No key found, treat as a group key_type = NO_TYPE if key_type in ALL_TYPES: total_removed = pymmlibs.mmpref_remove(self.handle, key, key_type) else: raise ValueError('Unknown value for key_type argument') return total_removed
[docs] def contains(self, key, key_type=None): """ Check for the presence of key. Check if the given key is available matching the give datatype. If `beginGroup` is called before this function, the given key will be searched for relative to the group. Keys specific to the running platform and non-platform specific keys attribute will be considered in the search. :type key: str :param key: key to search for :type key_type: constant :param key_type: A constant indicating the data type of this key. If not given, an attempt will be made to find a key of any data type. Valid key_type are: (INT, STRING, FLOAT, BOOL). :rtype: bool :return: True if the key is found, False if not :raise ValueError: If key_type is not a valid type """ if key_type is None: for ptype in DATA_TYPES: if pymmlibs.mmpref_contains(self.handle, key, ptype) == OK: return True elif key_type in DATA_TYPES: if pymmlibs.mmpref_contains(self.handle, key, key_type) == OK: return True else: raise ValueError('Unknown value for key_type argument') return False
[docs] def beginGroup(self, group, toplevel=False): """ Indicate the group for future key searches by appending group to the current group path. This group will be used for future group/key searches until `endGroup` is called. This is useful to avoid typing the common path again and again to query sets of keys. A product's keys can be grouped together under various paths. For instance, the SCRIPT product may have a PoseExplorer group, and that group may have Paths and Dialogs subgroups. One could access the 'import' key under the PoseExplorer/Paths group via:: handler = Preferences(SCRIPTS) handler.beginGroup('PoseExplorer') handler.beginGroup('Paths') import_dir = handler.get('import') or:: handler = PreferenceHandler(SCRIPTS) handler.beginGroup('PoseExplorer/Paths') import_dir = handler.get('import') or:: handler = Preferences(SCRIPTS) import_dir = handler.get('PoseExplorer/Paths/import') :type group: str :param group: the group to append to the current group path :type toplevel: bool :param toplevel: If True, treat this group as a toplevel group - i.e. the group path will simply be 'group' after this. If False (default), this group should be appended to the current group path. :raise ValueError: if key contain invalid character :raise MmException: otherwise """ if toplevel: self.resetGroup() try: pymmlibs.mmpref_begin_group(self.handle, group) except mm.MmException as e: if e.rc == mm.MMPREF_KEY_INVALID_CHAR: raise ValueError(e) raise
[docs] def getGroup(self): """ Get the current group set via the `beginGroup` method. :rtype: str :return: The current group, or "" if no group is set """ return pymmlibs.mmpref_get_group(self.handle)
[docs] def endGroup(self): """ Remove the current group set via the `beginGroup` method from the group search path. The new group path will be the same as it was before the last invocation of `beginGroup`. """ return pymmlibs.mmpref_end_group(self.handle)
[docs] @contextmanager def changeGroup(self, group, toplevel=False): """ Indicate the group to use for key searches by appending group to the current group path. :param str group: the group to append to the current group path :param bool toplevel: If True, treat this group as a toplevel group - i.e. the group path will simply be 'group' after this. If False (default), this group should be appended to the current group path. """ self.beginGroup(group, toplevel=toplevel) # Preference is yielded with the applied group path here yield self.endGroup()
[docs] def resetGroup(self, retain=0): """ Unset the group path so that searches begin at the top level for the product. :type retain: int :param retain: The number of groups in the path to retain below the top level. For instance, if the current group is a/b/c/d, self.resetGroup(retain=1) would set the current group to a. """ current_group = self.getGroup() retain = current_group.split('/')[:retain] while self.getGroup(): self.endGroup() for group in retain: self.beginGroup(group)
[docs] def set(self, key: str, value: Union[int, bool, float, str], os_specific: bool = False): """ Set the value of key. Note that the underlying library stores keys in a type specific way, but this function and the `get` function abstract that away. If key does not exist, it is created. If the group path has been set via `beginGroup`, it is honored. :param key: The key to set the value for. Can be a key name or a group path/key name. :param value: The value to set key to :param os_specific: Adds the key with os specific attribute if set to True. This ties the key to the platform it is invoked. :raise ValueError: if key contain invalid character :raise TypeError: if type of existing key is being changed :raise MmException: otherwise """ try: if os_specific: mm.mmpref_begin_os_attribute(self.handle) if isinstance(value, str): mm.mmpref_set_string(self.handle, key, value) elif isinstance(value, bool): mm.mmpref_set_bool(self.handle, key, value) elif isinstance(value, int): mm.mmpref_set_int(self.handle, key, value) elif isinstance(value, float): mm.mmpref_set_double(self.handle, key, value) else: msg = ( 'Key value %s is a %s, but may only be a str, bool, int, ' 'or float' % (value, type(value))) raise TypeError(msg) except mm.MmException as e: if e.rc == mm.MMPREF_KEY_INVALID_CHAR: raise ValueError(e) elif e.rc == mm.MMPREF_ERR: raise TypeError(e) else: raise finally: mm.mmpref_end_os_attribute(self.handle)
[docs] def get(self, key, default=NODEFAULT): """ Get the value of key. Note that the underlying library stores keys in a type specific way, but this function and the `set` function abstract that away. If key does not exist, the value of the default argument is returned if it is given. If the key does not exist and default is not given, a KeyError is raised. If the group path has been set via `beginGroup`, it is honored. If the OS has been set via `beginOS`, it is honored. OS-specific key values get higher preference when searching for keys. The user-specific preference file is checked for the key first, then the default installation files are checked. :type key: str :param key: The key to get the value for. Can be a key name or a group path/key name. :raise KeyError: If the key is not found and default is not given """ status, value = pymmlibs.mmpref_get_string(self.handle, key) if status == OK: return value status, value = pymmlibs.mmpref_get_bool(self.handle, key) if status == OK: return value status, value = pymmlibs.mmpref_get_int(self.handle, key) if status == OK: return value status, value = pymmlibs.mmpref_get_double(self.handle, key) if status == OK: return value # Key was never found if default is NODEFAULT: raise KeyError('Key not found and no default specified') else: return default
[docs] def getAllPreferences(self, group=""): """ Get a dictionary of all preferences in the current group path. The dictionary keys will be preference names and the dictionary values will be preference values. Note that preference names will include the relative group path starting at the current group. If the OS has been set via `beginOS`, it is honored. OS-specific key values get higher preference when searching for keys. :type group: str :param group: Append this group path to the current group path before searching for keys. Note that this persists during this operation only, after returning the dictionary the group path returns to its previous value. :rtype: dict :return: dictionary of preferences with preference names as keys and preference values as values. """ pref_dict = {} # Cycle through all the various types and merge them together status, type_dict = pymmlibs.mmpref_get_string_list(self.handle, group) pref_dict.update(type_dict) status, type_dict = pymmlibs.mmpref_get_bool_list(self.handle, group) pref_dict.update(type_dict) status, type_dict = pymmlibs.mmpref_get_int_list(self.handle, group) pref_dict.update(type_dict) status, type_dict = pymmlibs.mmpref_get_double_list(self.handle, group) pref_dict.update(type_dict) return pref_dict
def _check_pref_status(self): """ Raise appropriate exception based on status. """ if self.status == pymmlibs.MMPREF_UNRECOGNIZED_PRODUCT: raise ValueError('Unrecognized product "%s"' % self.product) elif self.status == pymmlibs.MMPREF_FORMAT_ERROR: raise SyntaxError('Format error in using settings file.') elif self.status == pymmlibs.MMPREF_ACCESS_ERROR: raise OSError("Access error in using settings file. This might be " "due to permissions or a full disk.") elif self.status != OK: raise RuntimeError('Unknown error: %s' % self.status)
[docs]def get_preference(key, group, product=SCRIPTS, default=NODEFAULT): """ Get the value of key in the specified group under product. See documentation for `Preferences.get` for more information. :param str key: The key to get the value for. Can be a key name or a group path/key name. :param str group: the group path to append to the product :param constant product: A product name constant supplied in this module or one from the pymmlibs module. :type default: any :param default: The default value to return if the key does not exist. :rtype: bool, int, float, str, or default :return: The value for the key. :raise KeyError: If the key is not found and default is not given """ handler = Preferences(product) handler.beginGroup(group) value = handler.get(key, default=default) handler.endGroup() return value
[docs]def set_preference(key, group, value, product=SCRIPTS): """ Set the value of key in the specified group under product. See documentation for `Preferences.set` for more information. :param str key: The key to set the value for. Can be a key name or a group path/key name. :param str group: the group path to append to the product :type value: bool, int, float, or str :param value: The value to set key to :param constant product: A product name constant supplied in this module or one from the pymmlibs module. """ handler = Preferences(product) handler.beginGroup(group) handler.set(key, value) handler.endGroup()