Mercurial > moin > 1.9
changeset 51:54d5932d5a03
merge moin--main--1.3--patch-930: fix error handling in plugins, fix broken chart action
Patches applied:
* arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-930
fix error handling in plugins, fix broken chart action
* nirs@freeshell.org--2005/moin--fix--1.3--patch-50
merge from main
* nirs@freeshell.org--2005/moin--fix--1.3--patch-51
fix error handling in plugins
* nirs@freeshell.org--2005/moin--fix--1.3--patch-52
merge from main
* nirs@freeshell.org--2005/moin--fix--1.3--patch-53
update changes with chart action fix
imported from: moin--main--1.5--patch-52
author | Nir Soffer <nirs@freeshell.org> |
---|---|
date | Thu, 29 Sep 2005 00:36:27 +0000 |
parents | 69dfbae549ac |
children | 80b3d4e891ff |
files | ChangeLog MoinMoin/Page.py MoinMoin/_tests/test_pysupport.py MoinMoin/formatter/text_python.py MoinMoin/i18n/__init__.py MoinMoin/macro/StatsChart.py MoinMoin/parser/wiki.py MoinMoin/request.py MoinMoin/util/pysupport.py MoinMoin/wikiaction.py MoinMoin/wikimacro.py MoinMoin/wikirpc.py MoinMoin/wikiutil.py docs/CHANGES |
diffstat | 14 files changed, 246 insertions(+), 140 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Thu Sep 29 00:09:43 2005 +0000 +++ b/ChangeLog Thu Sep 29 00:36:27 2005 +0000 @@ -36,6 +36,43 @@ MoinMoin/wikiaction.py +2005-09-04 00:24:17 GMT Nir Soffer <nirs@freeshell.org> patch-930 + + Summary: + fix error handling in plugins, fix broken chart action + Revision: + moin--main--1.3--patch-930 + + Patches applied: + + * nirs@freeshell.org--2005/moin--fix--1.3--patch-50 + merge from main + + * nirs@freeshell.org--2005/moin--fix--1.3--patch-51 + fix error handling in plugins + + * nirs@freeshell.org--2005/moin--fix--1.3--patch-52 + merge from main + + * nirs@freeshell.org--2005/moin--fix--1.3--patch-53 + update changes with chart action fix + + + modified files: + ChangeLog MoinMoin/Page.py MoinMoin/_tests/test_pysupport.py + MoinMoin/formatter/text_python.py MoinMoin/i18n/__init__.py + MoinMoin/macro/StatsChart.py MoinMoin/parser/wiki.py + MoinMoin/request.py MoinMoin/util/pysupport.py + MoinMoin/wikiaction.py MoinMoin/wikimacro.py + MoinMoin/wikirpc.py MoinMoin/wikiutil.py docs/CHANGES + + new patches: + nirs@freeshell.org--2005/moin--fix--1.3--patch-50 + nirs@freeshell.org--2005/moin--fix--1.3--patch-51 + nirs@freeshell.org--2005/moin--fix--1.3--patch-52 + nirs@freeshell.org--2005/moin--fix--1.3--patch-53 + + 2005-09-03 16:54:10 GMT Nir Soffer <nirs@freeshell.org> patch-929 Summary:
--- a/MoinMoin/Page.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/Page.py Thu Sep 29 00:36:27 2005 +0000 @@ -1172,13 +1172,14 @@ ) % {'pagename': self.formatter.text(self.page_name)}) request.write(''.join(pi_formtext)) - # try to load the parser - Parser = wikiutil.importPlugin(self.request.cfg, "parser", - self.pi_format, "Parser") - if Parser is None: - # default to plain text formatter (i.e. show the page source) - del Parser - from parser.plain import Parser + # Load the parser, or default to plain text parser that will + # just show the page raw source. + # TODO: do we need this magic? any effect on debugging? + try: + Parser = wikiutil.importPlugin(self.request.cfg, "parser", + self.pi_format, "Parser") + except ImportError: + from MoinMoin.parser.plain import Parser # start wiki content div request.write(self.formatter.startContent(content_id)) @@ -1258,11 +1259,13 @@ not self._raw_body_modified and self.getFormatterName() in self.cfg.caching_formats): # Everything is fine, now check the parser: - if not parser: - parser = wikiutil.importPlugin(self.request.cfg, "parser", - self.pi_format, "Parser") + if parser is None: + try: + parser = wikiutil.importPlugin(self.request.cfg, "parser", + self.pi_format, "Parser") + except ImportError: + pass return getattr(parser, 'caching', False) - return False def send_page_content(self, request, Parser, body, format_args='',
--- a/MoinMoin/_tests/test_pysupport.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/_tests/test_pysupport.py Thu Sep 29 00:36:27 2005 +0000 @@ -18,19 +18,21 @@ not broken. """ - def testNonExisting(self): - """ pysupport: import nonexistent moin plugin fail """ - self.failIf(pysupport.importName('MoinMoin.parser.abcdefghijkl', - 'Parser')) + def testNonExistingModule(self): + """ pysupport: import nonexistent module raises ImportError """ + self.assertRaises(ImportError, pysupport.importName, + 'MoinMoin.parser.abcdefghijkl','Parser') + + def testNonExistingAttribute(self): + """ pysupport: import nonexistent attritbue raises AttributeError """ + self.assertRaises(AttributeError, pysupport.importName, + 'MoinMoin.parser.wiki','NoSuchParser') def testExisting(self): - """ pysupport: import existing moin plugin - - This tests if a module can be imported from the package - MoinMoin. Should never fail! - """ - self.failUnless(pysupport.importName('MoinMoin.parser.wiki', - 'Parser')) + """ pysupport: import name from existing module """ + from MoinMoin.parser import wiki + Parser = pysupport.importName('MoinMoin.parser.wiki', 'Parser') + self.failUnless(Parser is wiki.Parser) class ImportNameFromPlugin(unittest.TestCase): @@ -67,7 +69,8 @@ """ pysupport: import nonexistent wiki plugin fail """ if self.pluginExists(): raise TestSkiped('plugin exists: %s' % self.plugin) - self.failIf(pysupport.importName(self.pluginModule, self.name)) + self.assertRaises(ImportError, pysupport.importName, + self.pluginModule, self.name) class ImportExisting(ImportNameFromPlugin):
--- a/MoinMoin/formatter/text_python.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/formatter/text_python.py Thu Sep 29 00:36:27 2005 +0000 @@ -20,6 +20,8 @@ Page must be assembled after the parsing to get working python code. """ + defaultDependencies = ["time"] + def __init__(self, request, static = [], formatter = None, **kw): if formatter: self.formatter = formatter @@ -174,19 +176,17 @@ (self.__adjust_formatter_state(), self.__formatter, name, args)) - def processor(self, processor_name, lines, is_parser = 0): + def processor(self, processor_name, lines, is_parser=0): """ processor_name MUST be valid! prints out the result insted of returning it! """ - if not is_parser: - Dependencies = wikiutil.importPlugin(self.request.cfg, "processor", - processor_name, "Dependencies") - else: - Dependencies = wikiutil.importPlugin(self.request.cfg, "parser", - processor_name, "Dependencies") - - if Dependencies == None: - Dependencies = ["time"] + type = ["processor", "parser"][is_parser] + try: + Dependencies = wikiutil.importPlugin(self.request.cfg, type, + processor_name, + "Dependencies") + except AttributeError: + Dependencies = self.defaultDependencies if self.__is_static(Dependencies): return self.formatter.processor(processor_name, lines, is_parser) else:
--- a/MoinMoin/i18n/__init__.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/i18n/__init__.py Thu Sep 29 00:36:27 2005 +0000 @@ -134,8 +134,10 @@ if needsupdate: from MoinMoin.util import pysupport lang_module = "MoinMoin.i18n." + filename(lang) - texts = pysupport.importName(lang_module, "text") - if not texts: + try: + # Language module without text dict will raise AttributeError + texts = pysupport.importName(lang_module, "text") + except ImportError: return (None, None) meta = pysupport.importName(lang_module, "meta") encoding = meta['encoding']
--- a/MoinMoin/macro/StatsChart.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/macro/StatsChart.py Thu Sep 29 00:36:27 2005 +0000 @@ -21,8 +21,10 @@ formatter.text(_('You need to provide a chart type!')) + formatter.sysmsg(0)) - func = pysupport.importName("MoinMoin.stats." + args, "linkto") - if not func: + try: + # stats module without 'linkto' will raise AttributeError + func = pysupport.importName("MoinMoin.stats." + args, "linkto") + except ImportError: return (formatter.sysmsg(1) + formatter.text(_('Bad chart type "%s"!') % args) + formatter.sysmsg(0))
--- a/MoinMoin/parser/wiki.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/parser/wiki.py Thu Sep 29 00:36:27 2005 +0000 @@ -766,16 +766,9 @@ self.in_pre = 3 return self._closeP() + self.formatter.preformatted(1) elif s_word[:2] == '#!': - # first try to find a processor for this (will go away in 2.0) + # First try to find a processor for this (will go away in 2.0) processor_name = s_word[2:].split()[0] - self.processor = wikiutil.importPlugin( - self.request.cfg, "processor", processor_name, "process") - # now look for a parser with that name - if self.processor is None: - self.processor = wikiutil.importPlugin( - self.request.cfg, "parser", processor_name, "Parser") - if self.processor: - self.processor_is_parser = 1 + self.setProcessor(processor_name) if self.processor: self.processor_name = processor_name @@ -963,15 +956,8 @@ processor_name = '' if (line.strip()[:2] == "#!"): processor_name = line.strip()[2:].split()[0] - self.processor = wikiutil.importPlugin( - self.request.cfg, "processor", processor_name, "process") - - # now look for a parser with that name - if self.processor is None: - self.processor = wikiutil.importPlugin( - self.request.cfg, "parser", processor_name, "Parser") - if self.processor: - self.processor_is_parser = 1 + self.setProcessor(processor_name) + if self.processor: self.in_pre = 2 self.colorize_lines = [line] @@ -1090,4 +1076,20 @@ if self.formatter.in_p: self.request.write(self.formatter.paragraph(0)) if self.in_table: self.request.write(self.formatter.table(0)) - + # -------------------------------------------------------------------- + # Private helpers + + def setProcessor(self, name): + """ Set processer to either processor or parser named 'name' """ + cfg = self.request.cfg + try: + self.processor = wikiutil.importPlugin(cfg, "processor", name, + "process") + self.processor_is_parser = 0 + except ImportError: + try: + self.processor = wikiutil.importPlugin(cfg, "parser", name, + "Parser") + self.processor_is_parser = 1 + except ImportError: + self.processor = None
--- a/MoinMoin/request.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/request.py Thu Sep 29 00:36:27 2005 +0000 @@ -476,16 +476,20 @@ fallback = 0 if theme_name == "<default>": theme_name = self.cfg.theme_default - Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme') - if Theme is None: + + try: + Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, + 'Theme') + except ImportError: fallback = 1 - Theme = wikiutil.importPlugin(self.cfg, 'theme', - self.cfg.theme_default, 'Theme') - if Theme is None: + try: + Theme = wikiutil.importPlugin(self.cfg, 'theme', + self.cfg.theme_default, 'Theme') + except ImportError: fallback = 2 from MoinMoin.theme.modern import Theme + self.theme = Theme(self) - return fallback def setContentLanguage(self, lang):
--- a/MoinMoin/util/pysupport.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/util/pysupport.py Thu Sep 29 00:36:27 2005 +0000 @@ -57,17 +57,16 @@ Used to do dynamic import of modules and names that you know their names only in runtime. + + Any error raised here must be handled by the caller. @param modulename: full qualified mudule name, e.g. x.y.z @param name: name to import from modulename @rtype: any object - @return: name from module or None if there is no such name + @return: name from module """ - try: - module = __import__(modulename, globals(), {}, [name]) - return getattr(module, name, None) - except ImportError: - return None + module = __import__(modulename, globals(), {}, [name]) + return getattr(module, name) def makeThreadSafe(function, lock=None):
--- a/MoinMoin/wikiaction.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/wikiaction.py Thu Sep 29 00:36:27 2005 +0000 @@ -830,13 +830,14 @@ mimetype = u"text/plain" # try to load the formatter - Formatter = wikiutil.importPlugin(request.cfg, "formatter", - mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter") - if Formatter is None: + formatterName = mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}) + try: + Formatter = wikiutil.importPlugin(request.cfg, "formatter", + formatterName, "Formatter") + except ImportError: # default to plain text formatter - del Formatter mimetype = "text/plain" - from formatter.text_plain import Formatter + from MoinMoin.formatter.text_plain import Formatter if "xml" in mimetype: mimetype = "text/xml" @@ -847,10 +848,31 @@ def do_chart(pagename, request): - if request.user.may.read(pagename) and request.cfg.chart_options: - chart_type = request.form['type'][0] - func = pysupport.importName("MoinMoin.stats." + chart_type, "draw") - func(pagename, request) + """ Show page charts + + TODO: add support for text charts? + """ + _ = request.getText + if not request.user.may.read(pagename): + msg = _("You are not allowed to view this page.") + return request.page.send_page(request, msg=msg) + + if not request.cfg.chart_options: + msg = _("Charts are not available!") + return request.page.send_page(request, msg=msg) + + chart_type = request.form.get('type', [''])[0].strip() + if not chart_type: + msg = _('You need to provide a chart type!') + return request.page.send_page(request, msg=msg) + + try: + func = pysupport.importName("MoinMoin.stats." + chart_type, 'draw') + except (ImportError, AttributeError): + msg = _('Bad chart type "%s"!') % chart_type + return request.page.send_page(request, msg=msg) + + func(pagename, request) raise MoinMoinNoFooter @@ -929,8 +951,10 @@ from MoinMoin.formatter.text_html import Formatter request.formatter = Formatter(request) - handler = wikiutil.importPlugin(request.cfg, "action", action, identifier) - if handler is None: + try: + handler = wikiutil.importPlugin(request.cfg, "action", action, + identifier) + except ImportError: handler = globals().get('do_' + action) return handler
--- a/MoinMoin/wikimacro.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/wikimacro.py Thu Sep 29 00:36:27 2005 +0000 @@ -61,6 +61,7 @@ * External macros - implemented in either MoinMoin.macro package, or in the specific wiki instance in the plugin/macro directory """ + defaultDependency = ["time"] Dependencies = { "TitleSearch" : ["namespace"], @@ -94,24 +95,31 @@ self.formatter = self.request.formatter self._ = self.request.getText self.cfg = self.request.cfg + + # Initialized on execute + self.name = None def execute(self, macro_name, args): - macro = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name) - if macro: - return macro(self, args) + """ Get and execute a macro + + Try to get a plugin macro, or a builtin macro or a language + macro, or just raise ImportError. + """ + self.name = macro_name + try: + execute = wikiutil.importPlugin(self.cfg, 'macro', macro_name) + except ImportError: + try: + builtins = self.__class__ + execute = getattr(builtins, '_macro_' + macro_name) + except AttributeError: + if macro_name in i18n.languages: + execute = builtins._m_lang + else: + raise ImportError("Cannot load macro %s" % macro_name) + return execute(self, args) - builtins = vars(self.__class__) - # builtin macro - if builtins.has_key('_macro_' + macro_name): - return builtins['_macro_' + macro_name](self, args) - - # language pseudo macro - if i18n.languages.has_key(macro_name): - return self._m_lang(macro_name, args) - - raise ImportError("Cannot load macro %s" % macro_name) - - def _m_lang(self, lang_name, text): + def _m_lang(self, text): """ Set the current language for page content. Language macro are used in two ways: @@ -119,22 +127,21 @@ * [lang(text)] - insert text with specific lang inside page """ if text: - return (self.formatter.lang(1, lang_name) + + return (self.formatter.lang(1, self.name) + self.formatter.text(text) + - self.formatter.lang(0, lang_name)) + self.formatter.lang(0, self.name)) - self.request.current_lang = lang_name + self.request.current_lang = self.name return '' def get_dependencies(self, macro_name): - if self.Dependencies.has_key(macro_name): + if macro_name in self.Dependencies: return self.Dependencies[macro_name] - result = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name, - 'Dependencies') - if result != None: - return result - else: - return ["time"] + try: + return wikiutil.importPlugin(self.request.cfg, 'macro', + macro_name, 'Dependencies') + except (ImportError, AttributeError): + return self.defaultDependency def _macro_TitleSearch(self, args): return self._m_search("titlesearch")
--- a/MoinMoin/wikirpc.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/wikirpc.py Thu Sep 29 00:36:27 2005 +0000 @@ -408,9 +408,10 @@ try: fn = getattr(self, 'xmlrpc_' + method) except AttributeError: - fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc', method, - 'execute') - if fn is None: + try: + fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc', + method, 'execute') + except ImportError: response = xmlrpclib.Fault(1, "No such method: %s." % method) else:
--- a/MoinMoin/wikiutil.py Thu Sep 29 00:09:43 2005 +0000 +++ b/MoinMoin/wikiutil.py Thu Sep 29 00:36:27 2005 +0000 @@ -568,8 +568,9 @@ def importPlugin(cfg, kind, name, function="execute"): """ Import wiki or builtin plugin - Returns an object from a plugin module or None if module or - 'function' is not found. + Returns function from a plugin module. If the module can not be + imported, raise ImportError. If function is not there, raise + AttributeError. kind may be one of 'action', 'formatter', 'macro', 'processor', 'parser' or any other directory that exist in MoinMoin or @@ -585,18 +586,20 @@ @rtype: callable @return: "function" of module "name" of kind "kind", or None """ - # Try to import from the wiki - plugin = importWikiPlugin(cfg, kind, name, function) - if plugin is None: - # Try to get the plugin from MoinMoin + try: + plugin = importWikiPlugin(cfg, kind, name, function) + except ImportError: modulename = 'MoinMoin.%s.%s' % (kind, name) plugin = pysupport.importName(modulename, function) - return plugin def importWikiPlugin(cfg, kind, name, function): - """ Import plugin from the wiki data directory + """ Import and cache plugin from the wiki data directory + Returns function from a plugin module. If the module can not be + imported, raise ImportError. If function is not there, raise + AttributeError. + We try to import only ONCE - then cache the plugin, even if we got None. This way we prevent expensive import of existing plugins for each call to a plugin. @@ -606,28 +609,32 @@ @param name: the name of the module @param function: the function name @rtype: callable - @return: "function" of module "name" of kind "kind", or None + @return: "function" of module "name" of kind "kind" """ - + try: + wikiPlugins = cfg._wiki_plugins + except AttributeError: + wikiPlugins = cfg._wiki_plugins = {} + # Wiki plugins are located under 'wikiconfigname.plugin' module. modulename = '%s.plugin.%s.%s' % (cfg.siteid, kind, name) - key = (modulename, function) + # Try cache or import once from disk try: - # Try cache first - fast! - plugin = cfg._wiki_plugins[key] - except (KeyError, AttributeError): - # Try to import from disk and cache result - slow! - plugin = pysupport.importName(modulename, function) + module = wikiPlugins[modulename] + except KeyError: try: - cfg._wiki_plugins[key] = plugin - except AttributeError: - cfg._wiki_plugins = {key: plugin} - return plugin + module = __import__(modulename, globals(), {}, ['dummy']) + except ImportError: + module = wikiPlugins[modulename] = None + if module is None: + raise ImportError + return getattr(module, function) # If we use threads, make this function thread safe if config.use_threads: importWikiPlugin = pysupport.makeThreadSafe(importWikiPlugin) + def builtinPlugins(kind): """ Gets a list of modules in MoinMoin.'kind' @@ -636,12 +643,14 @@ @return: module names """ modulename = "MoinMoin." + kind - plugins = pysupport.importName(modulename, "modules") - return plugins or [] + return pysupport.importName(modulename, "modules") def wikiPlugins(kind, cfg): """ Gets a list of modules in data/plugin/'kind' + + Require valid plugin directory. e.g missing 'parser' directory or + missing '__init__.py' file will raise errors. @param kind: what kind of modules we look for @rtype: list @@ -649,8 +658,7 @@ """ # Wiki plugins are located in wikiconfig.plugin module modulename = '%s.plugin.%s' % (cfg.siteid, kind) - plugins = pysupport.importName(modulename, "modules") - return plugins or [] + return pysupport.importName(modulename, "modules") def getPlugins(kind, cfg): @@ -692,15 +700,17 @@ import types etp, etd = {}, None for pname in getPlugins('parser', cfg): - Parser = importPlugin(cfg, 'parser', pname, 'Parser') - if Parser is not None: - if hasattr(Parser, 'extensions'): - exts = Parser.extensions - if type(exts) == types.ListType: - for ext in Parser.extensions: - etp[ext] = Parser - elif str(exts) == '*': - etd = Parser + try: + Parser = importPlugin(cfg, 'parser', pname, 'Parser') + except ImportError: + continue + if hasattr(Parser, 'extensions'): + exts = Parser.extensions + if type(exts) == types.ListType: + for ext in Parser.extensions: + etp[ext] = Parser + elif str(exts) == '*': + etd = Parser cfg._EXT_TO_PARSER = etp cfg._EXT_TO_PARSER_DEFAULT = etd
--- a/docs/CHANGES Thu Sep 29 00:09:43 2005 +0000 +++ b/docs/CHANGES Thu Sep 29 00:36:27 2005 +0000 @@ -151,6 +151,14 @@ manual url mapping. See HelpOnConfiguration/IntegratingWithApache +Developer notes: + + * Plugin API was improved. When plugin module is missing, an + ImportError is raised. When trying to import a missing name from a + plugin module, an AttributeError is raised. You must update any + code that use wikiutil.importPlugin or util.pysupport.importName. + Errors in your plugin should raise now correct tracebacks. + See http://moinmoin.wikiwikiweb.de/ErrorHandlingInPlugins Internal Changes: * request.formatter (html) is available for actions now @@ -199,6 +207,10 @@ the user that he entered an unknown email address. * Fixed SystemInfo, it now also lists parsers in data/plugin/parser dir. * Fix error handling on failure, improved error display + * Fix error handling when importing plugins or importing modules + dynamically. The fix is not backward compatible with older plugins. + * Fix chart action, returns a page with error message when chart + can not be created. Version 1.4: