From 964c660865c3c49d2355425e65fa2bd8e77377aa Mon Sep 17 00:00:00 2001 From: Simon Poirier Date: Thu, 29 Oct 2009 01:49:16 +0000 Subject: addon test+documentation+cleanup+module reloading (bug#462149) --- (limited to 'tutorius') diff --git a/tutorius/addon.py b/tutorius/addon.py index 7ac68f7..8aa7b58 100644 --- a/tutorius/addon.py +++ b/tutorius/addon.py @@ -32,6 +32,7 @@ __action__ = { """ import os +import sys import re import logging @@ -41,55 +42,110 @@ PATH = re.sub("addon\\.py[c]$", "", __file__)+"addons" TYPE_ACTION = 'action' TYPE_EVENT = 'event' -_cache = None +STAT_MODIFY = 8 + +# whether to auto reload modules on modifications +AUTO_RELOAD = True + +__cache__ = {} def _reload_addons(): - global _cache - _cache = {} - for addon in filter(lambda x: x.endswith("py"), os.listdir(PATH)): - mod = __import__(PREFIX+'.'+re.sub("\\.py$", "", addon), {}, {}, [""]) - if hasattr(mod, "__action__"): - _cache[mod.__action__['name']] = mod.__action__ - mod.__action__['type'] = TYPE_ACTION - continue - if hasattr(mod, "__event__"): - _cache[mod.__event__['name']] = mod.__event__ - mod.__event__['type'] = TYPE_EVENT + """ + Scan for all add-ons under the tutorius 'addons' folder and (re)cache + all modules found. + """ + __cache__.clear() + for addon in (x for x in os.listdir(PATH) if x.endswith("py")): + mod_name = PREFIX+'.'+re.sub("\\.py$", "", addon) + _reload_module(mod_name) + +def _reload_module(mod_name): + """ + Reload a specific module and update the cache. If the module changed + the module will be reloaded and future calls to create() will use + the new module. Existing instances won't be changed. + + @param mod_name: the python name of the module. + Ex: 'sugar.tutorius.addons.foo' + """ + # ensure module reloaded, by removing sys reference + sys.modules.pop(mod_name, False) + + mod = __import__(name=mod_name, fromlist=[""]) + if hasattr(mod, "__action__"): + __cache__[mod.__action__['name']] = mod.__action__ + mod.__action__['type'] = TYPE_ACTION + mod.__action__['ts'] = os.stat(mod.__file__)[STAT_MODIFY] + elif hasattr(mod, "__event__"): + __cache__[mod.__event__['name']] = mod.__event__ + mod.__event__['type'] = TYPE_EVENT + mod.__event__['ts'] = os.stat(mod.__file__)[STAT_MODIFY] def create(name, *args, **kwargs): - global _cache - if not _cache: + """ + Create an instance of an addon from its name. + If it cannot be instanciated for whatever reason, a message will be logged + to the console. + + Note: properties can usually be initialized kwargs having the same name. + However, args and kwargs are always optionals. + + @param name: the short name of the addon, as found in the 'name' key + of the __action__ or __event__ dict. + @returns: an instance of the add-on or None. + """ + if (not __cache__) or (name not in __cache__): _reload_addons() try: - comp_metadata = _cache[name] + metadata = __cache__[name] + module_name = metadata['class'].__module__ + module_file = sys.modules[module_name].__file__ + if AUTO_RELOAD and metadata['ts'] != os.stat(module_file)[STAT_MODIFY]: + _reload_module(module_name) + metadata = __cache__[name] + try: - return comp_metadata['class'](*args, **kwargs) - except: - logging.error("Could not instantiate %s with parameters %s, %s"%(comp_metadata['name'],str(args), str(kwargs))) - return None + return metadata['class'](*args, **kwargs) + except TypeError: + logging.error("Could not instantiate %s with parameters %s, %s" \ + %(metadata['name'],str(args), str(kwargs))) + return None except KeyError: logging.error("Addon not found for class '%s'", name) return None def list_addons(): - global _cache - if not _cache: + """ + Returns a list of all loaded add-ons, filling the cache if necesary. + + @returns: a list of addon names. + """ + if not __cache__: _reload_addons() - return _cache.keys() + return __cache__.keys() def get_addon_meta(name): - global _cache - if not _cache: + """ + Returns an add-on's meta data from its name. + @param name: the short name of the add-on, as found in the 'name' metadata. + @returns: a reference to the dictionary of meta data. + """ + if not __cache__ or (name not in __cache__): _reload_addons() - return _cache[name] + return __cache__[name] def get_name_from_type(typ): - global _cache - if not _cache: - _reload_addons() - for addon in _cache.keys(): - if typ == _cache[addon]['class']: - return addon - return None + """ + Lookup an addon's name from the type of an object returned by create(). + Note: After refreshing the cache, this function should still return the + correct name of old instances but calls using that name will refer to + the new code. + + @param typ: the type (class) of an add-on. + @returns: the short name of the add-on, as used in this module's functions. + """ + mod = sys.modules[typ.__module__] + meta = getattr(mod, '__action__', None) or getattr(mod, '__event__') + return meta['name'] # vim:set ts=4 sts=4 sw=4 et: -- cgit v0.9.1