"""
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()