Preferences storage for Watcher

I wanted to store a few things for Watcher. It had to remember some preferences (the URL for the RSS feed, some timing options) as well as some state (etag, last modified, and the last seen entry from the RSS feed). I figured a nice simple Windows Registry wrapper was just a google search away. Not so much. I found a few abstractions, but they all either exposed an API I didn’t like (like the ConfigFile API, a fine one but too complicated for what I cared about) or were a little too mysterious and unmistakably powerful for my taste. I wonder if this is why Python has so many implementations of little things like this — it’s so easy to write your own that for small things it takes longer to look for an existing implementation than it does to just write it. This applies to things not already in the standard library; the standard library is well enough documented to make it easy enough to find what you want if it’s there.

Before showing the implementation of my registry abstraction, first I will show what using it looks like. For most of the time I was writing the first version I was using the dummy implementation of Preferences so I could put off dealing with the registry and focus on the fun part of the app. Then I finally went back and did the google searching and eventually just wrote the wrapper seen at the bottom of this post.

From Watcher’s main file (watcher.py): […] from preferences import RegistryPreferences […] REGKEY_NAME = r”Softwarexythiansoftwatcher” […] class Preferences(RegistryPreferences): keyname = REGKEY_NAME

    checkInterval = 15*1000*60
    slideInterval = 10*1000
    idleInterval = 15*1000*60
    feedUrl = [... default feed url ... ]
    maximumSlides = 2
    notifySize = 300

    rssEtag = ""
    rssLastModified = 0
    rssLastSeenDate = 0
[....]
class ControlFrame(wx.Frame, AsyncIOWindow):
    def __init__(self, *args):
        [ .. other window init .. ]
        self.preferences = Preferences()
        self.preferences.load()
        [ .. more window init .. ]

    def OnApplyButton(self, event):
        """The method invoked when the Apply button is clicked -- apply/save the prefs"""
        p = self.preferences
        p.checkInterval = self.checkIntervalTB.GetValue()*1000*60
        p.slideInterval = self.photoIntervalTB.GetValue()*1000
        p.feedUrl = self.feedTB.GetValue()
        p.maximumSlides = self.maximumPhotosTB.GetValue()
        p.notifySize = self.sizeTB.GetValue()
        p.rssLastSeenDate = 0
        p.rssLastModified = 0
        p.rssEtag = ""
        p.save()
        [...]

The class definition ties the preferences store to a place in the registry (HKEY_CURRENT_USERSoftwarexythiansoftwatcher) and defines the keys and types to be loaded and saved. The type of the default indicates the type of the key — it uses REG_SZ for strings and REG_DWORD for the ints. Note for strict correctness, it should automatically detect when an int is actually too big to fit in REG_DWORD and use a different registry type.

Using the class defined as Preferences is as simple as reading or setting its properties and calling .load() and .save() to load or save values to/from the registry.

If I change my mind about where prefs can go watcher.py can remain oblivious.

Here’s the wrapper class: import _winreg class Preference(object): “”“Represents a preference stored as a registry key.”“” def init(self, name, defaultValue, regtype=_winreg.REG_SZ): self.name = name self.value = defaultValue self.regtype = regtype def load(self, regkey): try: v, t = _winreg.QueryValueEx(regkey, self.name) if v: self.value = v except: pass

    def save(self, regkey):
        _winreg.SetValueEx(regkey, self.name, 0, self.regtype, self.value)
class IntegerPreference(Preference):
    """Represents an integer pref stored as a REG_DWORD"""
    def __init__(self, name, defaultValue):
        super(IntegerPreference, self).__init__(name, defaultValue, regtype=_winreg.REG_DWORD)
class RegistryPrefsMeta(type):
    def __new__(cls, name, bases, classdict):
        prefslist = []
        for key, v in classdict.items():
            if key[0] == '_':
                continue
            if isinstance(v, int):
                p = IntegerPreference(key, v)
            elif isinstance(v, str):
                p = Preference(key, v)
            else:
                continue
            prefslist.append(p)
        for pref in prefslist:
            def makeproperty(pref):
                def getter(self):
                    return pref.value
                def setter(self, v):
                    pref.value = v
                return property(getter, setter, None)
            classdict[pref.name] = makeproperty(pref)
        classdict['__preflist__'] = prefslist
        return type.__new__(cls, name, bases, classdict)
class RegistryPreferences(object):
    __metaclass__ = RegistryPrefsMeta
    def load(self):
        try:
            regkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, self.__keyname__)
        except:
            return
        try:
            for pref in self.__preflist__:
              pref.load(regkey)
        finally:
            regkey.Close()
    def save(self):
        regkey = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, self.__keyname__)
        try:
            for pref in self.__preflist__:
                pref.save(regkey)
        finally:
            regkey.Close()

RegistryPrefsMeta defines a metaclass for RegistryPreferences which handles picking the name/values defined in the classdef and turning them into Preference or IntegerPreference instances. RegistryPreferences defines load and save methods to loop for its Preferences and call load or save on them appropriately.