configfile: Application saved settings support¶
Tools typically do not use this module directly;
they instead use the chimerax.core.settings
module,
which layers additional capabilities on top of this module’s
ConfigFile
class.
This module provides support for accessing persistent configuration information, a.k.a., saved settings. The information is stored in a file in a human-readable form, *i.e., text, but is not necessarily editable.
The configuration information is considered to be an API and has a semantic version associated with it.
Configuration information is kept in properties. And those properties have names and values.
Each tool has its own settings. The MAJOR part of the semantic version is embedded in its filename. For the ChimeraX core, that version does not change during the life of the release, so patches may only introduce additional information. Tools are allowed to change and might or might not implement backwards compatibility.
Accessing Configuration Information¶
Access Tool Configuration:
settings = tool.get_settings()
if settings.PROPERTY == 12: # access a value
pass
settings.PROPERTY = value # set a value
Access ChimeraX Core Configuration:
from chimerax.core.core_settings import settings
# (ibid)
Declaring the Configuration API¶
The fact that there are configuration files is hidden by an object that implements the tool’s configuration API.
Most tools will only have one section. So the ConfigFile
and Section
subclasses (next example) can be combined into one:
_config = None
BlastMatrixArg = cli.EnumOf((
'BLOSUM45', 'BLOSUM62', 'BLOSUM80', 'PAM30', 'PAM70'
))
class _BPPreferences(configfile.ConfigFile):
PROPERTY_INFO = {
'e_exp': configfile.Value(3, cli.PositiveIntArg, str),
'matrix': configfile.Value('BLOSUM62', BlastMatrixArg, str)
'passes': 1,
}
def __init__(self, session):
ConfigFile.__init__(self, session, "Blast Protein")
def get_preferences():
global _prefs
if _prefs is None:
_prefs = _BPPreferences()
return _prefs
# reusing Annotations for command line arguments
@cli.register("blast",
cli.CmdDesc(
keyword=[('evalue', cli.PostiveIntArg),
('matrix', BlastMatrixArg),]
))
def blast(session, e_exp=None, matrix=None):
prefs = get_preferences()
if e_exp is None:
e_exp = prefs.e_exp
if matrix is None:
matrix = prefs.matrix
# process arguments
Property values can either be a Python literal, which is the default value,
or a Value
has three items associated with it:
A default value.
A function that can parse the value or a cli
Annotation
that can parse the value. This allows for error checking in the case where a user hand edits the configuration.A function to convert the value to a string.
If the tool configuration API changes,
then the tool can subclass Preferences
with custom code.
Adding a Property¶
If an additional property is needed, just add it the
PROPERTY_INFO
class attribute,
and document it.
The minor part of the version number should be increased before
the tool is released again.
That way other tools can use the tool’s version number
to tell if the property is available or not.
Renaming a Property¶
Since configuration is an API, properties can not be removed without
changing the major version number. To prepare for that change, document
that the old name is deprecated and that the new name should be used instead.
Then add a Python property to the section class that forwards the
changes to the old property name. For example, to rename e_exp
, in
the previous example, to e_value
, extend the _Params class with:
class _Params(configfile.Section):
PROPERTY_INFO = {
'e_exp': ( cli.PositiveIntArg, str, 3 ),
'matrix': ( BlastMatrixArg, str, 'BLOSUM62' )
}
@property
def e_value(self):
return 10 ** -self.e_exp
@e_value.setter
def e_value(self, value):
import math
self.e_exp = -round(math.log10(value))
Later, when the major version changes,
the existing ConfigFile
subclass
would be renamed with a version suffix
with the version number hardcoded,
and a new subclass would be generated with the e_exp
replaced with e_value
.
Then in the new ConfigFile
subclass, after it is initialized,
it would check if its data was on disk or not, and if not, try opening up
previous configuration versions and migrate the settings.
The migrate_from
methods,
ConfigFile.migrate_from()
and Section.migrate_from()
,
may be replaced or can be made more explicit.
See the next section for an example.
Changing the API - Migrating to a New Configuration¶
Migrating Example:
class _BPPreferences(configfile.ConfigFile):
PROPERTY_INFO = {
'e_exp': ( cli.PositiveIntArg, str, 3 ),
'matrix': ( BlastMatrixArg, str, 'BLOSUM62' )
}
# additional properties removed
def __init__(self, session):
ConfigFile.__init__(self, session, "Blast Protein")
class _BPPreferences(configfile.ConfigFile):
PROPERTY_INFO = {
'e_value': ( float, str, 1e-3 ),
'matrix': ( BlastMatrixArg, str, 'BLOSUM62' )
}
# e_exp is gone
def migrate_from(self, old, version):
configfile.Section.migrate_from(self, old, version)
self.e_value = 10 ** -old._exp
def __init__(self, session):
# add version
ConfigFile.__init__(self, session, "Blast Protein", "2")
if not self.on_disk():
old = _BPPreferences()
self.migrate_from(old, "1")
Migrating a Property Without Changing the Version¶
This is similar to renaming a property, with a more sophisticated getter function:
class _Params(configfile.ConfigFile):
PROPERTY_INFO = {
'e_value': 1e-3,
'matrix': configfile.Value('BLOSUM62', BlastMatrixArg, str)
}
@property
def e_value(self):
def migrate_e_exp(value):
# conversion function
return 10 ** -value
return self.migrate_value('e_value', 'e_exp', cli.PositiveIntArg,
migrate_e_exp)
The migrate_value()
function looks for the new value,
but if it isn’t present,
then it looked for the old value and migrates it.
If the old value isn’t present, then the new default value is used.
- class ConfigFile(session, tool_name, version='1')¶
Bases:
object
Supported API. In-memory handle to persistent configuration information.
- Parameters:
session (
Session
) – (forlogger
)tool_name (the name of the tool)
version (configuration file version, optional) – Only the major version part of the version is used.
- PROPERTY_INFO¶
property_name
must be a legal Python identifier.value
is a Python literal or anItem
.- Type:
dict of property_name: value
- property filename¶
The name of the file used to store the settings
- migrate_from(old, version)¶
Experimental API . Migrate identical settings from old configuration.
- Parameters:
old (instance Section-subclass for old section)
version (old version "number")
- migrate_value(name, old_name, old_from_str, convert)¶
Experimental API . For migrating property from “.old_name” to “.name”.
First look for the new value, but if it isn’t present, then look for the old value and migrate it. If the old value isn’t present, then the new default value is used.
- on_disk()¶
Experimental API . Return True the configuration information was stored on disk.
This information is useful when deciding whether or not to migrate settings from a previous configuration version.
- reset()¶
Experimental API . Revert all properties to their default state
- save()¶
Experimental API . Save configuration information of all sections to disk.
Don’t store property values that match default value.
- update(dict_iter=None, **kw)¶
Experimental API . Update all corresponding items from dict or iterator or keywords.
Treat preferences as a dictionary and
update()
them.- Parameters:
dict_iter (dict/iterator)
**kw (optional name, value items)
- class Value(default, from_str=None, to_str=None)¶
Bases:
object
Placeholder for default value and conversion functions
- Parameters:
default (is the value when the property has not been set.)
from_str (function or Annotation, optional) – can be either a function that takes a string and returns a value of the right type, or a cli
Annotation
. Defaults to py:func:ast.literal_eval.to_str (function or Annotation, optional) – can be either a function that takes a value and returns a string representation of the value, or a cli
Annotation
. Defaults torepr()
.