Saving Extension State in a Session File

This example shows how an extension can save its state in a Chimera session file. A session file is Python code that is executed by Chimera to restore the state information. The SimpleSession module saves the core Chimera state such as opened molecules and the camera settings. It then invokes a SAVE_SESSION trigger that extensions can receive to save their state.

Example SessionSaveExample.py

The code below saves state variables "some_path" and "some_number" in a session file when the Save Session menu entry is used.

some_path = '/home/smith/tralala.data'
some_number = 3

def restoreState(path, number):
  global some_path, some_number
  some_path = path
  some_number = number

def saveSession(trigger, x, file):
  restoring_code = \
"""
def restoreSessionSaveExample():
  import SessionSaveExample
  SessionSaveExample.restoreState(%s,%s)
try:
  restoreSessionSaveExample()
except:
  reportRestoreError('Error restoring SessionSaveExample')
"""
  state = (repr(some_path), repr(some_number))
  file.write(restoring_code % state)

import chimera
import SimpleSession
chimera.triggers.addHandler(SimpleSession.SAVE_SESSION, saveSession, None)

The last line registers the saveSession routine to be called when the SAVE_SESSION trigger is invoked. This happens when the user saves the session from the Chimera file menu. The saveSession routine writes Python code to the session file that will restore the state of the some_path and some_number global variables.

Large Data Structures

If you need to save data structures whose repr() would be hundreds or thousands of characters long, you should use SimpleSession's sesRepr() function instead which will insert newlines periodically. The resulting session has a better chance of being user-editable and passing through various mailer programs without corruption.

Session File Format

Here is the code written to the session file by the above example.

def restoreSessionSaveExample():
  import SessionSaveExample
  SessionSaveExample.restoreState('/home/smith/tralala.data','3')
try:
  restoreSessionSaveExample()
except:
  reportRestoreError('Error restoring SessionSaveExample')

Code written by other extensions will appear before and after this code. The restoreSessionSaveExample function is defined to keep extra names out of the global namespace of the session file. This is to avoid name conflicts. The restoreSessionSaveExample routine is called within a try statement so that if an error occurs it won't prevent code later in the session file from being called. The reportRestoreError routine is defined at the top of the session file by SimpleSession.

Saving Complex Data

The SessionUtil module helps writing and reading the state data for extensions that have alot of state. It can convert class instances to dictionaries so they can be written to a file. This is similar to the standard Python pickle module but provides better human readable formatted output. It can handle a tree of data structures. Look at the ScaleBar extension code to see how to use SessionUtil.

Restoring References to Molecular Data

You can save references to molecules, residues, atom, bonds, pseudobonds, VRML models, and MSMS surfaces by calling SimpleSession's sessionID() function with the item to save as the only argument. The repr() of the return value can then be written into the session file. During the restore, the written session ID value should be given to SimpleSession's idLookup() function, which will return the corresponding molecular data item.

Custom Molecular Attributes

If you add non-standard attributes to Molecules/Residues/Bonds/Atoms that you want preserved in restored sessions, use SimpleSession's registerAttribute() function, e.g,:
  import chimera
  from SimpleSession import registerAttribute
  registerAttribute(chimera.Molecule, "qsarVal")
Note that the Define Attribute tool automatically does this registration, so it's only attributes that you add directly from your own Python code that need to be registered as above. Also, only attributes whose values are recoverable from their repr() can be saved by this mechanism, so values that are C++ types (Atoms, MaterialColors, etc.) could not be preserved this way.

Restoring and Referring to Special Models

Non-molecular models cannot be located with the sessionID()/ idLookup() mechanism. Instead, SimpleSession has a modelMap dictionary that can be used to map between a id/subid tuple for a saved model and the actual restored model. So, during a session save, you would get the tuple to save from a model with code like:

  refToSave = (model.id, model.subid)

The values in modelMap are actually lists of models with those particular id/subid values, since multiple models may have the same id/subid (e.g. a molecule and its surface). So if you are restoring a special model and want to update modelMap, you would use code like this:

  import SimpleSession
  SimpleSession.modelMap.setdefault(refToSave, []).append(restoredModel)

Keep in mind that the id/subid of the saved model may not be the same as the restored model, particularly if sessions are being merged. The key used by modelMap is always the id/subid of the saved model.

If you are trying to refer to a non-molecular model using modelMap, and that non-molecular model is of class X, you would use code like this:

  import SimpleSession
  restoredModel = filter(lambda m: isinstance(m, X), SimpleSession.modelMap[refToSave])[0]

Session Merging

If you are restoring your own models, you should try to restore them into their original ids/subids if possible so that scripts and so forth will continue to work across session saves. In the case of sessions being merged, this of course could be problematic. SimpleSession has a variable modelOffset (i.e. SimpleSession.modelOffset) which should be added to your model's id to avoid conflicts during session merges. modelOffset is zero during a non-merging session restore.

Special Molecular/VRML Models

Some extensions may create their own Molecule or VRML instances that need to be restored by the extension itself rather than by the automatic save/restore for Molecules/VRML provided by SimpleSession. In order to prevent SimpleSession from attempting to save the instance, use the noAutoRestore() function once the instance has been created, like this:

  import SimpleSession
  SimpleSession.noAutoRestore(instance)

Your extension is responsible for restoring all aspects of the instance, including selection state.

Post-Model Restore

If restoring code should be called only after all models have been restored then the SimpleSession.registerAfterModelsCB routine should be used.

  def afterModelsRestoredCB(arg):
    # do some state restoration now that models have been created

  import SimpleSession
  SimpleSession.registerAfterModelsCB(afterModelsRestoredCB, arg)

The 'arg' can be omitted in both the registration and callback functions.

Saving Colors

Similar to sessionID for molecular data, there is colorID function that returns a value whose repr() can be saved into a session file. During a restore, that value can be given to SimpleSession's getColor function to get a Color object back.

Closing a Session

The Close Session entry in the Chimera file menu is meant to reset the state of Chimera, unloading all currently loaded data. Extensions can reset their state when the session is closed by handling the CLOSE_SESSION trigger as illustrated below.

def closeSession(trigger, a1, a2):
  default_path = '/default/path'
  default_number = 1
  restoreState(default_path, default_number)

import chimera
chimera.triggers.addHandler(chimera.CLOSE_SESSION, closeSession, None)

Changing Behavior During a Session Restore

If an extension needs to behave differently during a session restore than at other times (e.g. react differently to newly opened models), then it can register for the BEGIN_RESTORE_SESSION and END_RESTORE_SESSION triggers, in an analogous manner to the CLOSE_SESSION trigger in the preceding section.

Running the Example

To try the example, save the above sections of code shown in red as file SessionSaveExample.py. Use the Chimera Favorites/Preferences/Tools/Locations interface to add the directory where you have placed the file to the extension search path. Show the Python shell window using the Tools/Programming/IDLE menu entry and type the following command.

>>> import SessionSaveExample

Now save the session with the File/Save Session As... menu entry and take a look at the session file in an editor. You should be able to find the restoreSessionSaveExample code in the file. The current value of the some_path variable can be inspected as follows.

>>> SessionSaveExample.some_path
'/home/smith/tralala.data'

Now close the session with the File/Close Session menu entry and see that the some_path variable has been reset to the default value.

>>> SessionSaveExample.some_path
'/default/path'

Now reload the session with the File/Open Session menu entry and see that the some_path variable has been restored.

>>> SessionSaveExample.some_path
'/home/smith/tralala.data'