From f62698e28c8b1525fab7bfaac53b9859d59e5991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Mon, 2 Jun 2008 23:15:08 +0000 Subject: [PATCH] Added docstrings in reST format (also with todos). Commented out 'print' statements related to roster window. A few modifications to make code prettier (PyLint driven). --- epydoc.conf | 4 +- src/plugins/plugin.py | 52 +++++++++++- src/plugins/pluginmanager.py | 157 ++++++++++++++++++++++++++--------- src/roster_window.py | 20 ++--- test/test_pluginmanager.py | 74 ++++++++++++----- 5 files changed, 232 insertions(+), 75 deletions(-) diff --git a/epydoc.conf b/epydoc.conf index 092f7f1a5..580067d0d 100644 --- a/epydoc.conf +++ b/epydoc.conf @@ -8,12 +8,12 @@ verbosity: 3 imports: yes redundant-details: yes docformat: restructuredtext -# top: gajim +top: plugins # The list of modules to document. Modules can be named using # dotted names, module filenames, or package directory names. # This option may be repeated. -modules: src/plugins/*.py +modules: src/plugins/*.py test/*.py # Write html output to the directory "apidocs" #output: pdf diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index d3bfa62c9..c8bb95af9 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -24,16 +24,64 @@ Base class for implementing plugin. :license: GPL ''' -import sys -from plugins.helpers import log, log_calls +from plugins.helpers import log_calls class GajimPlugin(object): + ''' + Base class for implementing Gajim plugins. + ''' name = '' + ''' + Name of plugin. + + Will be shown in plugins management GUI. + + :type: unicode + ''' short_name = '' + ''' + Short name of plugin. + + Used for quick indentification of plugin. + + :type: unicode + + :todo: decide whether we really need this one, because class name (with + module name) can act as such short name + ''' version = '' + ''' + Version of plugin. + + :type: unicode + + :todo: decide how to compare version between each other (which one + is higher). Also rethink: do we really need to compare versions + of plugins between each other? This would be only useful if we detect + same plugin class but with different version and we want only the newest + one to be active - is such policy good? + ''' description = '' + ''' + Plugin description. + + :type: unicode + + :todo: should be allow rich text here (like HTML or reStructuredText)? + ''' authors = [] + ''' + Plugin authors. + + :type: [] of unicode + + :todo: should we decide on any particular format of author strings? + Especially: should we force format of giving author's e-mail? + ''' gui_extension_points = {} + ''' + Extension points that plugin wants to connect with. + ''' @log_calls('GajimPlugin') def __init__(self): diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index 59c3bad11..0498b5fd3 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -16,7 +16,7 @@ ## ''' -Helper code related to plug-ins management system. +Plug-in management related classes. :author: Mateusz BiliƄski :since: 05/30/2008 @@ -32,29 +32,91 @@ import fnmatch import common.gajim as gajim -from helpers import log, log_calls, Singleton -from plugin import GajimPlugin +from plugins.helpers import log, log_calls, Singleton +from plugins.plugin import GajimPlugin class PluginManager(object): + ''' + Main plug-in management class. + + Currently: + - scans for plugins + - activates them + - handles GUI extension points, when called by GUI objects after plugin + is activated (by dispatching info about call to handlers in plugins) + + :todo: add more info about how GUI extension points work + :todo: add list of available GUI extension points + :todo: implement mechanism to dynamically load plugins where GUI extension + points have been already called (i.e. when plugin is activated + after GUI object creation) + :todo: implement mechanism to dynamically deactive plugins (call plugin's + deactivation handler) + + ''' + __metaclass__ = Singleton @log_calls('PluginManager') def __init__(self): self.plugins = [] - self.active = [] + ''' + Detected plugin classes. + + Each class object in list is `GajimPlugin` subclass. + + :type: [] of class objects + ''' + self.active_plugins = [] + ''' + Objectsof active plugins. + + These are object instances of classes held `plugins`, but only those + that were activated. + + :type: [] of `GajimPlugin` based objects + ''' self.gui_extension_points = {} + ''' + Registered GUI extension points. + ''' for path in gajim.PLUGINS_DIRS: - self.plugins.extend(self._scan_dir_for_plugins(path)) + self.plugins.extend(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) - + self._activate_all_plugins() - - log.debug('active: %s'%(self.active)) - + + log.debug('active: %s'%(self.active_plugins)) + @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): + ''' + Invokes all handlers (from plugins) for particular GUI extension point. + + :param gui_extpoint_name: name of GUI extension point. + :type gui_extpoint_name: unicode + :param args: parameters to be passed to extension point handlers + (typically and object that invokes `gui_extension_point`; however, + this can be practically anything) + :type args: tuple + + :todo: GUI extension points must be documented well - names with + parameters that will be passed to handlers (in plugins). Such + documentation must be obeyed both in core and in plugins. This + is a loosely coupled approach and is pretty natural in Python. + + :bug: what if only some handlers are successfully connected? we should + revert all those connections that where successfully made. Maybe + call 'self._deactivate_plugin()' or sth similar. + Looking closer - we only rewrite tuples here. Real check should be + made in method that invokes gui_extpoints handlers. + + ''' + + log.debug(type(args)) + if gui_extpoint_name in self.gui_extension_points: for handlers in self.gui_extension_points[gui_extpoint_name]: handlers[0](*args) @@ -62,36 +124,53 @@ class PluginManager(object): @log_calls('PluginManager') def _activate_plugin(self, plugin): ''' - :param plugin: Plugin to be activated. - :type plugin: class object of GajimPlugin subclass + :param plugin: plugin to be activated + :type plugin: class object of `GajimPlugin` subclass ''' - p = plugin() + plugin_object = plugin() + success = True - - # :fix: what if only some handlers are successfully connected? we should - # revert all those connections that where successfully made. Maybe - # call 'self._deactivate_plugin()' or sth similar. - # Looking closer - we only rewrite tuples here. Real check should be - # made in method that invokes gui_extpoints handlers. + for gui_extpoint_name, gui_extpoint_handlers in \ - p.gui_extension_points.iteritems(): - self.gui_extension_points.setdefault(gui_extpoint_name,[]).append( + plugin_object.gui_extension_points.iteritems(): + self.gui_extension_points.setdefault(gui_extpoint_name, []).append( gui_extpoint_handlers) - + if success: - self.active.append(p) - + self.active_plugins.append(plugin_object) + return success - + @log_calls('PluginManager') def _activate_all_plugins(self): - self.active = [] + ''' + Activates all plugins in `plugins`. + + Activated plugins are appended to `active_plugins` list. + ''' + self.active_plugins = [] for plugin in self.plugins: self._activate_plugin(plugin) + @staticmethod @log_calls('PluginManager') - def _scan_dir_for_plugins(self, path): + def scan_dir_for_plugins(path): + ''' + Scans given directory for plugin classes. + + :param path: directory to scan for plugins + :type path: unicode + + :return: list of found plugin classes (subclasses of `GajimPlugin` + :rtype: [] of class objects + + :note: currently it only searches for plugin classes in '\*.py' files + present in given direcotory `path` (no recursion here) + + :todo: add scanning packages + :todo: add scanning zipped modules + ''' plugins_found = [] if os.path.isdir(path): dir_list = os.listdir(path) @@ -100,30 +179,28 @@ class PluginManager(object): sys.path.insert(0, path) log.debug(sys.path) - for file in fnmatch.filter(dir_list, '*.py'): - log.debug('- "%s"'%(file)) - file_path = os.path.join(path, file) + for file_name in fnmatch.filter(dir_list, '*.py'): + log.debug('- "%s"'%(file_name)) + file_path = os.path.join(path, file_name) log.debug(' "%s"'%(file_path)) if os.path.isfile(file_path): - module_name = os.path.splitext(file)[0] + module_name = os.path.splitext(file_name)[0] module = __import__(module_name) - filter_out_bad_names = \ - lambda x: not (x.startswith('__') or - x.endswith('__')) - for module_attr_name in filter(filter_out_bad_names, - dir(module)): + for module_attr_name in [f_name for f_name in dir(module) + if not (f_name.startswith('__') or + f_name.endswith('__'))]: module_attr = getattr(module, module_attr_name) log.debug('%s : %s'%(module_attr_name, module_attr)) - + try: if issubclass(module_attr, GajimPlugin) and \ - not module_attr is GajimPlugin: + not module_attr is GajimPlugin: log.debug('is subclass of GajimPlugin') plugins_found.append(module_attr) - except TypeError, e: + except TypeError, type_error: log.debug('module_attr: %s, error : %s'%( - module_name+'.'+module_attr_name, - e)) + module_name+'.'+module_attr_name, + type_error)) log.debug(module) diff --git a/src/roster_window.py b/src/roster_window.py index bc5268174..9af3d59b6 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -310,13 +310,13 @@ class RosterWindow: if jids: c4 = time.clock() - print "" - print "--- Add account contacts of %s ---------" % account - print "Total Time", c4-c1 - print "Add contact without draw", c6-c5 - print "Draw groups and account", c10-c9 - print "--- contacts added -----------------------------" - print "" + #print "" + #print "--- Add account contacts of %s ---------" % account + #print "Total Time", c4-c1 + #print "Add contact without draw", c6-c5 + #print "Draw groups and account", c10-c9 + #print "--- contacts added -----------------------------" + #print "" def _add_entity(self, contact, account, groups = None, @@ -1216,9 +1216,9 @@ class RosterWindow: self.draw_contact(jid, account) self.draw_avatar(jid, account) yield True - print "--- Idle draw of %s -----------" % account - print "Draw contact and avatar", time.clock() - t - print "-------------------------------" + #print "--- Idle draw of %s -----------" % account + #print "Draw contact and avatar", time.clock() - t + #print "-------------------------------" yield False t = time.clock() diff --git a/test/test_pluginmanager.py b/test/test_pluginmanager.py index 5c6723bcb..861641ac2 100644 --- a/test/test_pluginmanager.py +++ b/test/test_pluginmanager.py @@ -32,30 +32,62 @@ import unittest gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..') sys.path.append(gajim_root + '/src') +# a temporary version of ~/.gajim for testing +configdir = gajim_root + '/test/tmp' + +import time + +# define _ for i18n +import __builtin__ +__builtin__._ = lambda x: x + +# wipe config directory +import os +if os.path.isdir(configdir): + import shutil + shutil.rmtree(configdir) + +os.mkdir(configdir) + +import common.configpaths +common.configpaths.gajimpaths.init(configdir) +common.configpaths.gajimpaths.init_profile() + +# for some reason common.gajim needs to be imported before xmpppy? +from common import gajim +from common import xmpp + +gajim.DATA_DIR = gajim_root + '/data' + +from common.stanza_session import StanzaSession + +# name to use for the test account +account_name = 'test' + from plugins import PluginManager class PluginManagerTestCase(unittest.TestCase): - def setUp(self): - self.pluginmanager = PluginManager() - - def tearDown(self): - pass - - def test_01_Singleton(self): - """ 1. Checking whether PluginManger class is singleton. """ - self.pluginmanager.test_arg = 1 - secondPluginManager = PluginManager() - - self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), - 'Different IDs in references to PluginManager objects (not a singleton)') - self.failUnlessEqual(secondPluginManager.test_arg, 1, - 'References point to different PluginManager objects (not a singleton') - + def setUp(self): + self.pluginmanager = PluginManager() + + def tearDown(self): + pass + + def test_01_Singleton(self): + """ 1. Checking whether PluginManger class is singleton. """ + self.pluginmanager.test_arg = 1 + secondPluginManager = PluginManager() + + self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager), + 'Different IDs in references to PluginManager objects (not a singleton)') + self.failUnlessEqual(secondPluginManager.test_arg, 1, + 'References point to different PluginManager objects (not a singleton') + def suite(): - suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) - return suite + suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase) + return suite if __name__=='__main__': - runner = unittest.TextTestRunner() - test_suite = suite() - runner.run(test_suite) \ No newline at end of file + runner = unittest.TextTestRunner() + test_suite = suite() + runner.run(test_suite) \ No newline at end of file