Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Poirier <simpoir@gmail.com>2009-10-29 01:49:16 (GMT)
committer Simon Poirier <simpoir@gmail.com>2009-10-29 01:49:16 (GMT)
commit964c660865c3c49d2355425e65fa2bd8e77377aa (patch)
tree9507181267093023cefaff1e8e3dc13d6809118b
parent53af01c95abe622c0490b43c12439a04aa449509 (diff)
addon test+documentation+cleanup+module reloading (bug#462149)addon-test
-rw-r--r--tests/addontests.py24
-rw-r--r--tutorius/addon.py120
2 files changed, 109 insertions, 35 deletions
diff --git a/tests/addontests.py b/tests/addontests.py
index 24fc803..8ea49e9 100644
--- a/tests/addontests.py
+++ b/tests/addontests.py
@@ -19,6 +19,7 @@
import unittest
from sugar.tutorius import addon, properties
+import os, sys
class AddonTest(unittest.TestCase):
def test_create_constructor_fail(self):
@@ -41,13 +42,13 @@ class AddonTest(unittest.TestCase):
assert obj is not None
def test_reload_addons(self):
- addon._cache = None
+ addon.__cache__.clear()
assert len(addon.list_addons()) > 0, "Addons should be reloaded upon cache clear"
def test_get_addon_meta(self):
- addon._cache = None
+ addon.__cache__.clear()
meta = addon.get_addon_meta("BubbleMessage")
- expected = set(['mandatory_props', 'class', 'display_name', 'name', 'type', 'icon'])
+ expected = set(['mandatory_props', 'class', 'display_name', 'name', 'ts', 'type', 'icon'])
assert not set(meta.keys()).difference(expected), "%s == %s"%(meta.keys(), expected)
def test_reverse_lookup(self):
@@ -70,4 +71,21 @@ class AddonTest(unittest.TestCase):
assert not attribs,\
"assignation of attribute(s) %s detected in '%s.__init__'"%(attribs, type(obj).__name__)
+ def test_addon_reimport(self):
+ obj = addon.create("BubbleMessage", message="Hi!", position=[12,31])
+ mod_name = type(obj).__module__
+ # alter module
+ type(obj).testvar = True
+
+ # recreate without touching the module file
+ obj = addon.create("BubbleMessage", message="Hi!", position=[12,31])
+ assert hasattr(type(obj), 'testvar') and type(obj).testvar == True
+
+ # touch the module
+ os.system('touch %s'%sys.modules.get(mod_name).__file__)
+ obj = addon.create("BubbleMessage", message="Hi!", position=[12,31])
+ mod = type(obj).__module__
+ assert not hasattr(mod, 'testvar')
+
+
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: