Attachment 'plugin.patch'

Download

   1 * looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-488 to compare with
   2 * comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-488
   3 M  MoinMoin/server/standalone.py
   4 M  MoinMoin/server/twistedmoin.py
   5 M  MoinMoin/_tests/test_pysupport.py
   6 M  wiki/server/moinmodpy.py
   7 M  MoinMoin/wikirpc.py
   8 M  wiki/server/moin.fcg
   9 M  MoinMoin/Page.py
  10 M  MoinMoin/multiconfig.py
  11 M  MoinMoin/formatter/base.py
  12 M  MoinMoin/macro/TableOfContents.py
  13 M  MoinMoin/parser/wiki.py
  14 M  MoinMoin/request.py
  15 M  MoinMoin/userform.py
  16 M  MoinMoin/util/pysupport.py
  17 M  MoinMoin/wikiaction.py
  18 M  MoinMoin/wikimacro.py
  19 M  MoinMoin/wikiutil.py
  20 M  MoinMoin/formatter/text_python.py
  21 M  MoinMoin/config.py
  22 
  23 * modified files
  24 
  25 --- orig/MoinMoin/Page.py
  26 +++ mod/MoinMoin/Page.py
  27 @@ -1011,8 +1011,8 @@
  28                      request.write(''.join(pi_formtext))
  29  
  30          # try to load the parser
  31 -        Parser = wikiutil.importPlugin("parser", self.pi_format, "Parser",
  32 -                                       self.cfg.data_dir)
  33 +        Parser = wikiutil.importPlugin(self.request.cfg, "parser",
  34 +                                       self.pi_format, "Parser")
  35          if Parser is None:
  36              # default to plain text formatter (i.e. show the page source)
  37              del Parser
  38 @@ -1090,8 +1090,8 @@
  39              self.getFormatterName() in self.cfg.caching_formats):
  40              # Everything is fine, now check the parser:
  41              if not parser:
  42 -                parser = wikiutil.importPlugin("parser", self.pi_format, "Parser",
  43 -                                               self.cfg.data_dir)
  44 +                parser = wikiutil.importPlugin(self.request.cfg, "parser",
  45 +                                               self.pi_format, "Parser")
  46              return getattr(parser, 'caching', False)
  47  
  48          return False
  49 
  50 
  51 --- orig/MoinMoin/_tests/test_pysupport.py
  52 +++ mod/MoinMoin/_tests/test_pysupport.py
  53 @@ -2,83 +2,107 @@
  54  """
  55      MoinMoin - MoinMoin.util.pysupport Tests
  56  
  57 -    Module names must start with 'test_' to be included in the tests.
  58 -
  59      @copyright: 2004 by Jürgen Hermann <ograf@bitart.de>
  60      @license: GNU GPL, see COPYING for details.
  61  """
  62  
  63 -import unittest, tempfile, os
  64 +import unittest, os
  65  from MoinMoin.util import pysupport
  66  from MoinMoin._tests import request, TestConfig
  67  
  68  
  69 -class ImportNameTestCase(unittest.TestCase):
  70 -    """ Test if importName works correctly
  71 +class ImportNameFromMoinTestCase(unittest.TestCase):
  72 +    """ Test importName of MoinMoin modules
  73 +
  74 +    We don't make any testing for files, assuming that moin package is
  75 +    not broken.
  76      """
  77  
  78 -    def setUp(self):
  79 -        """ Create a dummy plugin path with some module inside
  80 -        """
  81 -        self.plugin_dir = tempfile.mktemp()
  82 -        self.parser_dir = os.path.join(self.plugin_dir, 'plugin', 'parser')
  83 -        os.makedirs(self.parser_dir)
  84 -        open(os.path.join(self.plugin_dir, 'plugin', '__init__.py'), 'w')
  85 -        open(os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.py'), 'w')
  86 -        f = open(os.path.join(self.parser_dir, 'test.py'), 'w')
  87 -        f.write('import sys, os\ndef test():\n    pass\n')
  88 -        f.close()
  89 -    
  90 -    def tearDown(self):
  91 -        """ Remove the plugin dir
  92 -        """
  93 -        for file in (os.path.join(self.plugin_dir, 'plugin', '__init__.py'),
  94 -                     os.path.join(self.plugin_dir, 'plugin', '__init__.pyc'),
  95 -                     os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.py'),
  96 -                     os.path.join(self.plugin_dir, 'plugin', 'parser', '__init__.pyc'),
  97 -                     os.path.join(self.parser_dir, 'test.py'),
  98 -                     os.path.join(self.parser_dir, 'test.pyc')):
  99 -            try:
 100 -                os.unlink(file)
 101 -            except OSError:
 102 -                pass
 103 -        for dir in (os.path.join(self.plugin_dir, 'plugin', 'parser'),
 104 -                    os.path.join(self.plugin_dir, 'plugin'),
 105 -                    self.plugin_dir):
 106 -            os.rmdir(dir)
 107 - 
 108 -    def testImportName1(self):
 109 -        """ pysupport: import nonexistant parser from moin
 110 -        
 111 -        Must return None."""
 112 +    def testNonExisting(self):
 113 +        """ pysupport: import nonexistant moin parser return None """
 114          self.failIf(pysupport.importName('MoinMoin.parser.abcdefghijkl',
 115                                           'Parser'))
 116  
 117 -    def testImportName2(self):
 118 -        """ pysupport: import wiki parser from moin
 119 +    def testExisting(self):
 120 +        """ pysupport: import wiki parser from moin succeed
 121          
 122          This tests if a module can be imported from the package
 123 -        MoinMoin. Should never fail, cause importName uses
 124 -        __import__ to do it."""
 125 +        MoinMoin. Should never fail!
 126 +        """
 127          self.failUnless(pysupport.importName('MoinMoin.parser.wiki',
 128                                               'Parser'))
 129 +   
 130  
 131 -    def testImportName3(self):
 132 -        """ pysupport: import nonexistant parser plugin
 133 +class ImportNameFromPluginTestCase(unittest.TestCase):
 134 +    """ Test if importName form wiki plugin package """
 135 +    
 136 +    def setUp(self):
 137 +        """ Check for valid plugin and parser packages """
 138 +        # Make sure we have valid plugin and parser dir
 139 +        self.plugindir = os.path.join(request.cfg.data_dir, 'plugin')
 140 +        assert os.path.exists(os.path.join(self.plugindir, '__init__.py')), \
 141 +            "Can't run tests, no plugin package"
 142 +        self.parserdir = os.path.join(self.plugindir, 'parser')
 143 +        assert os.path.exists(os.path.join(self.parserdir, '__init__.py')), \
 144 +            "Can't run tests, no plugin.parser package"
 145 +    
 146 +    def testNonEsisting(self):
 147 +        """ pysupport: import nonexistant plugin return None """
 148 +        name = 'abcdefghijkl'
 149 +
 150 +        # Make sure that the file does not exists
 151 +        for suffix in ['.py', '.pyc']:
 152 +            path = os.path.join(self.parserdir, name + suffix)
 153 +            assert not os.path.exists(path), \
 154 +               "Can't run test, path exists: %r" % path
 155          
 156 -        Must return None."""
 157 -        self.failIf(pysupport.importName('plugin.parser.abcdefghijkl',
 158 +        self.failIf(pysupport.importName('plugin.parser.%s' % name,
 159                                           'Parser'))
 160  
 161 -    def testImportName4(self):
 162 -        """ pysupport: import test parser plugin
 163 +    def testExisting(self):
 164 +        """ pysupport: import existing plugin succeed
 165          
 166          Tests if a module can be importet from an arbitary path
 167          like it is done in moin for plugins. Some strange bug
 168          in the old implementation failed on an import of os,
 169          cause os does a from os.path import that will stumple
 170 -        over a poisoned sys.modules."""
 171 -        self.failUnless(pysupport.importName('plugin.parser.test',
 172 -                                             'test',
 173 -                                             self.plugin_dir),
 174 -                        'Failed to import the test plugin!')
 175 +        over a poisoned sys.modules.
 176 +        """
 177 +        # Save a test plugin
 178 +        pluginName = 'MoinMoinTestParser'
 179 +        data = '''
 180 +import sys, os
 181 +
 182 +class Parser:
 183 +    pass
 184 +'''
 185 +        pluginPath = os.path.join(self.parserdir, pluginName + '.py')
 186 +
 187 +        # File must not exists - or we might destroy user data!
 188 +        for suffix in ['.py', '.pyc']:
 189 +            path = os.path.join(self.parserdir, pluginName + suffix)
 190 +            assert not os.path.exists(path), \
 191 +               "Can't run test, path exists: %r" % path
 192 +        
 193 +        try:
 194 +            # Write test plugin
 195 +            f = file(pluginPath, 'w')
 196 +            f.write(data)
 197 +            f.close()
 198 +
 199 +            modulename = request.cfg.siteid + '.plugin.parser.' + pluginName
 200 +            name = 'Parser'
 201 +            plugin = pysupport.importName(modulename, name)
 202 +            self.failUnless(plugin, 'Failed to import the test plugin')
 203 +
 204 +            # Check that we got class
 205 +            self.failUnless(isinstance(plugin(), plugin), \
 206 +                            "Imported name is wrong")
 207 +
 208 +        finally:
 209 +            # Remove the test plugin, including the pyc file.
 210 +            try:
 211 +                os.unlink(pluginPath)
 212 +                os.unlink(pluginPath + 'c')
 213 +            except OSError:
 214 +                pass
 215 
 216 
 217 --- orig/MoinMoin/config.py
 218 +++ mod/MoinMoin/config.py
 219 @@ -4,6 +4,10 @@
 220  
 221  import re
 222  
 223 +# Threads flag - if you write a moin server that use threads, import
 224 +# config in the server and set this flag to True.
 225 +use_threads = False
 226 +
 227  # Charset - we support only 'utf-8'. While older encodings might work,
 228  # we don't have the resources to test them, and there is no real
 229  # benefit for the user.
 230 
 231 
 232 --- orig/MoinMoin/formatter/base.py
 233 +++ mod/MoinMoin/formatter/base.py
 234 @@ -242,14 +242,12 @@
 235              writes out the result instead of returning it!
 236          """
 237          if not is_parser:
 238 -            processor = wikiutil.importPlugin("processor",
 239 -                                              processor_name, "process",
 240 -                                              self.request.cfg.data_dir)
 241 +            processor = wikiutil.importPlugin(self.request.cfg, "processor",
 242 +                                              processor_name, "process")
 243              processor(self.request, self, lines)
 244          else:
 245 -            parser = wikiutil.importPlugin("parser",
 246 -                                           processor_name, "Parser",
 247 -                                           self.request.cfg.data_dir)
 248 +            parser = wikiutil.importPlugin(self.request.cfg, "parser",
 249 +                                           processor_name, "Parser")
 250              args = self._get_bang_args(lines[0])
 251              if args is not None:
 252                  lines=lines[1:]
 253 
 254 
 255 --- orig/MoinMoin/formatter/text_python.py
 256 +++ mod/MoinMoin/formatter/text_python.py
 257 @@ -178,13 +178,11 @@
 258          prints out the result insted of returning it!
 259          """
 260          if not is_parser:
 261 -            Dependencies = wikiutil.importPlugin("processor",
 262 -                                                 processor_name, "Dependencies",
 263 -                                                 self.request.cfg.data_dir)
 264 +            Dependencies = wikiutil.importPlugin(self.request.cfg, "processor",
 265 +                                                 processor_name, "Dependencies")
 266          else:
 267 -            Dependencies = wikiutil.importPlugin("parser",
 268 -                                                 processor_name, "Dependencies",
 269 -                                                 self.request.cfg.data_dir)
 270 +            Dependencies = wikiutil.importPlugin(self.request.cfg, "parser",
 271 +                                                 processor_name, "Dependencies")
 272              
 273          if Dependencies == None:
 274              Dependencies = ["time"]
 275 
 276 
 277 --- orig/MoinMoin/macro/TableOfContents.py
 278 +++ mod/MoinMoin/macro/TableOfContents.py
 279 @@ -62,7 +62,8 @@
 280  
 281      def IncludeMacro(self, *args, **kwargs):
 282          if self.include_macro is None:
 283 -            self.include_macro = wikiutil.importPlugin('macro', "Include")
 284 +            self.include_macro = wikiutil.importPlugin(self.macro.request.cfg,
 285 +                                                       'macro', "Include")
 286          return self.pre_re.sub('',apply(self.include_macro, args, kwargs)).split('\n')
 287  
 288      def run(self):
 289 
 290 
 291 --- orig/MoinMoin/multiconfig.py
 292 +++ mod/MoinMoin/multiconfig.py
 293 @@ -70,9 +70,9 @@
 294              module =  __import__(name, globals(), {})
 295              Config = getattr(module, 'Config', None)
 296              if Config:
 297 -                # Config found, return config instance
 298 -                cfg = Config()
 299 -                cfg.siteid = name
 300 +                # Config found, return config instance using name as
 301 +                # site identifier (name must be uniqe of our url_re).
 302 +                cfg = Config(name)
 303                  return cfg
 304              else:
 305                  # Broken config file, probably old config from 1.2
 306 @@ -267,8 +267,9 @@
 307      url_mappings = {}
 308      SecurityPolicy = None
 309  
 310 -    def __init__(self):
 311 +    def __init__(self, siteid):
 312          """ Init Config instance """
 313 +        self.siteid = siteid
 314          if self.config_check_enabled:
 315              self._config_check()
 316              
 317 @@ -277,6 +278,9 @@
 318  
 319          # Make sure directories are accessible
 320          self._check_directories()
 321 +
 322 +        # Load plugin module
 323 +        self._loadPluginModule()
 324          
 325          # Normalize values
 326          self.default_lang = self.default_lang.lower()
 327 @@ -415,7 +419,52 @@
 328  also the spelling of the directory name.
 329  ''' % {'attr': attr, 'path': path,}
 330                  raise error.ConfigurationError(msg)
 331 +
 332 +    def _loadPluginModule(self):
 333 +        """ import plugin module under configname.plugin """
 334 +        import sys, imp
 335 +
 336 +        # First try to load the module from sys.modules - fast!
 337 +        name = self.siteid + '.plugin'
 338          
 339 +        # Get lock functions - require Python 2.3
 340 +        try:
 341 +            acquire_lock = imp.acquire_lock
 342 +            release_lock = imp.release_lock
 343 +        except AttributeError:
 344 +            def acquire_lock(): pass
 345 +            def release_lock(): pass
 346 +             
 347 +        # Lock other threads while we check and import
 348 +        acquire_lock()
 349 +        try:
 350 +            try:
 351 +                sys.modules[name]
 352 +            except KeyError:
 353 +                # Find module on disk and try to load - slow!
 354 +                fp, path, info = imp.find_module('plugin', [self.data_dir])
 355 +                try:
 356 +                    try:
 357 +                        # Load the module and set in sys.modules             
 358 +                        module = imp.load_module(name, fp, path, info)
 359 +                        sys.modules[self.siteid].plugin = module
 360 +                    finally:
 361 +                        # Make sure fp is closed properly
 362 +                        if fp:
 363 +                            fp.close()
 364 +                except ImportError, err:
 365 +                    msg = '''
 366 +Could not import plugin package from "%(path)s" because of ImportError:
 367 +%(err)s.
 368 +
 369 +Make sure your data directory path is correct, check permissions, and
 370 +that the plugin directory has an __init__.py file.
 371 +''' % {'path': self.data_dir, 'err': str(err)}
 372 +                    raise error.ConfigurationError(msg)
 373 +
 374 +        finally:
 375 +            release_lock()
 376 +
 377      def __getitem__(self, item):
 378          """ Make it possible to access a config object like a dict """
 379          return getattr(self, item)
 380 
 381 
 382 --- orig/MoinMoin/parser/wiki.py
 383 +++ mod/MoinMoin/parser/wiki.py
 384 @@ -273,7 +273,7 @@
 385          # can handle)
 386          base, ext = os.path.splitext(url)
 387          if inline:
 388 -            Parser = wikiutil.getParserForExtension(self.cfg, ext)
 389 +            Parser = wikiutil.getParserForExtension(self.request, ext)
 390              if Parser is not None:
 391                  content = file(fpath, 'r').read()
 392                  # Try to decode text. It might return junk, but we don't
 393 @@ -811,16 +811,12 @@
 394          elif s_word[:2] == '#!':
 395              # first try to find a processor for this (will go away in 1.4)
 396              processor_name = s_word[2:].split()[0]
 397 -            self.processor = wikiutil.importPlugin("processor", 
 398 -                                                   processor_name, 
 399 -                                                   "process", 
 400 -                                                   self.request.cfg.data_dir)
 401 +            self.processor = wikiutil.importPlugin(
 402 +                self.request.cfg, "processor", processor_name, "process")
 403              # now look for a parser with that name
 404              if self.processor is None:
 405 -                self.processor = wikiutil.importPlugin("parser",
 406 -                                                       processor_name,
 407 -                                                       "Parser",
 408 -                                                       self.request.cfg.data_dir)
 409 +                self.processor = wikiutil.importPlugin(
 410 +                    self.request.cfg, "parser", processor_name, "Parser")
 411                  if self.processor:
 412                      self.processor_is_parser = 1
 413  
 414 @@ -965,11 +961,13 @@
 415          if self.cfg.allow_numeric_entities:
 416              rules = ur'(?P<ent_numeric>&#\d{1,5};)|' + rules
 417  
 418 +        self.request.clock.start('compile_huge_and_ugly')        
 419          scan_re = re.compile(rules, re.UNICODE)
 420          number_re = re.compile(self.ol_rule, re.UNICODE)
 421          term_re = re.compile(self.dl_rule, re.UNICODE)
 422          indent_re = re.compile("^\s*", re.UNICODE)
 423          eol_re = re.compile(r'\r?\n', re.UNICODE)
 424 +        self.request.clock.stop('compile_huge_and_ugly')        
 425  
 426          # get text and replace TABs
 427          rawtext = self.raw.expandtabs()
 428 @@ -998,16 +996,13 @@
 429                      processor_name = ''
 430                      if (line.strip()[:2] == "#!"):
 431                          processor_name = line.strip()[2:].split()[0]
 432 -                        self.processor = wikiutil.importPlugin("processor",
 433 -                                                               processor_name,
 434 -                                                               "process",
 435 -                                                               self.request.cfg.data_dir)
 436 +                        self.processor = wikiutil.importPlugin(
 437 +                            self.request.cfg, "processor", processor_name, "process")
 438 +                                                               
 439                          # now look for a parser with that name
 440                          if self.processor is None:
 441 -                            self.processor = wikiutil.importPlugin("parser",
 442 -                                                                   processor_name,
 443 -                                                                   "Parser",
 444 -                                                                   self.request.cfg.data_dir)
 445 +                            self.processor = wikiutil.importPlugin(
 446 +                                self.request.cfg, "parser", processor_name, "Parser") 
 447                              if self.processor:
 448                                  self.processor_is_parser = 1
 449                      if self.processor:
 450 
 451 
 452 --- orig/MoinMoin/request.py
 453 +++ mod/MoinMoin/request.py
 454 @@ -274,21 +274,14 @@
 455          @return: success code
 456          """
 457          fallback = 0
 458 -        try:
 459 -            Theme = wikiutil.importPlugin('theme', theme_name,
 460 -                                          'Theme',
 461 -                                          path=self.cfg.data_dir)
 462 +        Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
 463 +        if Theme is None:
 464 +            fallback = 1
 465 +            Theme = wikiutil.importPlugin(self.cfg, 'theme',
 466 +                                          self.cfg.theme_default, 'Theme')
 467              if Theme is None:
 468 -                fallback = 1
 469 -                Theme = wikiutil.importPlugin('theme', self.cfg.theme_default,
 470 -                                              'Theme',
 471 -                                              path=self.cfg.data_dir)
 472 -                if Theme is None:
 473 -                    raise ImportError
 474 -        except ImportError:
 475 -            fallback = 2
 476 -            from MoinMoin.theme.modern import Theme
 477 -
 478 +                fallback = 2
 479 +                from MoinMoin.theme.modern import Theme
 480          self.theme = Theme(self)
 481  
 482          return fallback
 483 @@ -1592,6 +1585,10 @@
 484              @param req: the mod_python request instance
 485          """
 486          try:
 487 +            # flags if headers sent out contained content-type or status
 488 +            self._have_ct = 0
 489 +            self._have_status = 0
 490 +
 491              req.add_common_vars()
 492              self.mpyreq = req
 493              # some mod_python 2.7.X has no get method for table objects,
 494 @@ -1601,9 +1598,6 @@
 495              else:
 496                  env=req.subprocess_env
 497              self._setup_vars_from_std_env(env)
 498 -            # flags if headers sent out contained content-type or status
 499 -            self._have_ct = 0
 500 -            self._have_status = 0
 501              RequestBase.__init__(self)
 502  
 503          except error.FatalError, err:
 504 
 505 
 506 --- orig/MoinMoin/server/standalone.py
 507 +++ mod/MoinMoin/server/standalone.py
 508 @@ -41,6 +41,12 @@
 509  from MoinMoin.server import Config, switchUID
 510  from MoinMoin.request import RequestStandAlone
 511  
 512 +# Set threads flag, so other code can use proper locking
 513 +from MoinMoin import config
 514 +config.use_threads = True
 515 +del config
 516 +
 517 +# Server globals
 518  httpd = None
 519  config = None
 520  moin_requests_done = 0
 521 
 522 
 523 --- orig/MoinMoin/server/twistedmoin.py
 524 +++ mod/MoinMoin/server/twistedmoin.py
 525 @@ -38,6 +38,12 @@
 526  from MoinMoin.request import RequestTwisted
 527  from MoinMoin.server import Config
 528  
 529 +# Set threads flag, so other code can use proper locking
 530 +from MoinMoin import config
 531 +config.use_threads = True
 532 +del config
 533 +
 534 +# Server globals
 535  config = None
 536  
 537      
 538 
 539 
 540 --- orig/MoinMoin/userform.py
 541 +++ mod/MoinMoin/userform.py
 542 @@ -343,7 +343,7 @@
 543          """ Create theme selection. """
 544          cur_theme = self.request.user.valid and self.request.user.theme_name or self.cfg.theme_default
 545          options = []
 546 -        for theme in wikiutil.getPlugins('theme', self.request.cfg.data_dir):
 547 +        for theme in wikiutil.getPlugins('theme', self.request.cfg):
 548              options.append((theme, theme))
 549                  
 550          return util.web.makeSelection('theme_name', options, cur_theme)
 551 
 552 
 553 --- orig/MoinMoin/util/pysupport.py
 554 +++ mod/MoinMoin/util/pysupport.py
 555 @@ -10,6 +10,9 @@
 556  ### Module import / Plugins
 557  #############################################################################
 558  
 559 +import sys
 560 +
 561 +
 562  def isImportable(module):
 563      """ Check whether a certain module is available.
 564      """
 565 @@ -34,67 +37,46 @@
 566      return modules
 567  
 568  
 569 -def importName(modulename, name, path=None):
 570 -    """ Import a named object from a module in the context of this function,
 571 -        which means you should use fully qualified module paths.
 572 +def importName(modulename, name):
 573 +    """ Import name dynamically from module
 574  
 575 -        Return None on failure.
 576 +    Used to do dynamic import of modules and names that you know their
 577 +    names only in runtime.
 578 +    
 579 +    @param modulename: full qualified mudule name, e.g. x.y.z
 580 +    @param name: name to import from modulename
 581 +    @rtype: any object
 582 +    @return: name from module or None if there is no such name
 583      """
 584 -    if path:
 585 -        #
 586 -        # see Python-src/Demo/imputil/knee.py how import should be done
 587 -        #
 588 -        import imp, sys
 589 -
 590 -        items = modulename.split('.')
 591 -        parent = None
 592 -        real_path = [path]
 593 -        fqname = None
 594 -        for part_name in items:
 595 -            # keep full qualified module name up to date
 596 -            if fqname is None:
 597 -                fqname = part_name
 598 -            else:
 599 -                fqname = fqname + '.' + part_name
 600 -            ## this is the place to check sys.modules if the module
 601 -            ## is already available
 602 -            ## WARNING: this does not work with farm wikis, cause all
 603 -            ## farm plugin paths would map to the same 'plugin' top
 604 -            ## module!
 605 -            ## We need a dummy module ('wiki_'+sha(path)) to keep them
 606 -            ## apart (create with imp.new_module()?)
 607 -            # find & import the module
 608 -            try:
 609 -                fp, pathname, stuff = imp.find_module(part_name, real_path or parent.__path__)
 610 -            except ImportError:
 611 -                # no need to close fp here, cause its only open if no
 612 -                # error occurs
 613 -                return None
 614 -            try:
 615 -                try:
 616 -                    mod = imp.load_module(fqname, fp, pathname, stuff)
 617 -                except ImportError: # ValueError, if fp is no file, not possible
 618 -                    return None
 619 -            finally:
 620 -                if fp: fp.close()
 621 -            # update parent module up to date
 622 -            if parent is None:
 623 -                # we only need real_path for the first import, after
 624 -                # this parent.__path__ is enough
 625 -                real_path = None
 626 -            else:
 627 -                setattr(parent, part_name, mod)
 628 -            parent = mod
 629 -        return getattr(mod, name, None)
 630 -    else:
 631 -        # this part is for MoinMoin imports, we use __import__
 632 -        # which will also use sys.modules as cache, but thats
 633 -        # no harm cause MoinMoin is global anyway.
 634 -        try:
 635 -            module = __import__(modulename, globals(), {}, [name]) # {} was: locals()
 636 -        except ImportError:
 637 -            return None
 638 +    try:
 639 +        module = __import__(modulename, globals(), {}, [name])
 640          return getattr(module, name, None)
 641 +    except ImportError:
 642 +        return None
 643  
 644 -# if you look for importPlugin: see wikiutil.importPlugin
 645  
 646 +def makeThreadSafe(function, lock=None):
 647 +    """ Call with a fuction you want to make thread safe
 648 +
 649 +    Call without lock to make the fuction thread safe using one lock per
 650 +    function. Call with exsiting lock object if you want to make several
 651 +    fuctions use same lock, e.g. all fuctions that change same data
 652 +    structure.
 653 +
 654 +    @param fuction: function to make thread safe
 655 +    @param lock: threading.Lock instance or None
 656 +    @rtype: function
 657 +    @retrun: function decoreated with locking
 658 +    """
 659 +    if lock is None:
 660 +        import threading
 661 +        lock = threading.Lock()
 662 +    
 663 +    def decorated(*args, **kw):
 664 +        lock.acquire()
 665 +        try:
 666 +            return function(*args, **kw)
 667 +        finally:
 668 +            lock.release()
 669 +            
 670 +    return decorated
 671 
 672 
 673 --- orig/MoinMoin/wikiaction.py
 674 +++ mod/MoinMoin/wikiaction.py
 675 @@ -749,9 +749,8 @@
 676          mimetype = u"text/plain"
 677  
 678      # try to load the formatter
 679 -    Formatter = wikiutil.importPlugin("formatter",
 680 -        mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter",
 681 -        path=request.cfg.data_dir)
 682 +    Formatter = wikiutil.importPlugin(request.cfg, "formatter",
 683 +        mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter")
 684      if Formatter is None:
 685          # default to plain text formatter
 686          del Formatter
 687 @@ -846,9 +845,12 @@
 688      if action in request.cfg.excluded_actions:
 689          return None
 690  
 691 -    handler = wikiutil.importPlugin("action", action, identifier, request.cfg.data_dir)
 692 -    if handler: return handler
 693 +    handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
 694 +    if handler is None:
 695 +        handler = globals().get('do_' + action)
 696 +        
 697 +    return handler
 698  
 699 -    return globals().get('do_' + action, None)
 700 +    
 701   
 702  
 703 
 704 
 705 --- orig/MoinMoin/wikimacro.py
 706 +++ mod/MoinMoin/wikimacro.py
 707 @@ -35,7 +35,7 @@
 708          return cfg.macro_names
 709      else:
 710          lnames = names[:]
 711 -        lnames.extend(wikiutil.getPlugins('macro', cfg.data_dir))
 712 +        lnames.extend(wikiutil.getPlugins('macro', cfg))
 713          return lnames
 714  
 715  def _make_index_key(index_letters, additional_html=""):
 716 @@ -96,7 +96,7 @@
 717          self.cfg = self.request.cfg
 718  
 719      def execute(self, macro_name, args):
 720 -        macro = wikiutil.importPlugin('macro', macro_name, path=self.cfg.data_dir)
 721 +        macro = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name)
 722          if macro:
 723              return macro(self, args)
 724  
 725 @@ -129,7 +129,8 @@
 726      def get_dependencies(self, macro_name):
 727          if self.Dependencies.has_key(macro_name):
 728              return self.Dependencies[macro_name]
 729 -        result = wikiutil.importPlugin('macro', macro_name, 'Dependencies', self.cfg.data_dir)
 730 +        result = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name,
 731 +                                       'Dependencies')
 732          if result != None:
 733              return result
 734          else:
 735 @@ -366,7 +367,7 @@
 736          row(_('Global extension macros'), 
 737              ', '.join(macro.extension_macros) or nonestr)
 738          row(_('Local extension macros'), 
 739 -            ', '.join(wikiutil.extensionPlugins('macro', self.cfg.data_dir)) or nonestr)
 740 +            ', '.join(wikiutil.wikiPlugins('macro', self.cfg)) or nonestr)
 741          ext_actions = []
 742          for a in action.extension_actions:
 743              if not a in self.request.cfg.excluded_actions:
 744 
 745 
 746 --- orig/MoinMoin/wikirpc.py
 747 +++ mod/MoinMoin/wikirpc.py
 748 @@ -391,7 +391,8 @@
 749                  fn = getattr(self, 'xmlrpc_' + method)
 750                  response = fn(*params)
 751              except AttributeError:
 752 -                fn = wikiutil.importPlugin('xmlrpc', method, 'execute', self.request.cfg.data_dir)
 753 +                fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc', method,
 754 +                                           'execute')
 755                  response = fn(self, *params)
 756          except:
 757              # report exception back to server
 758 
 759 
 760 --- orig/MoinMoin/wikiutil.py
 761 +++ mod/MoinMoin/wikiutil.py
 762 @@ -5,11 +5,13 @@
 763      @copyright: 2000 - 2004 by Jürgen Hermann <jh@web.de>
 764      @license: GNU GPL, see COPYING for details.
 765  """
 766 -
 767 +    
 768  import os, re, difflib
 769 +
 770  from MoinMoin import util, version, config
 771  from MoinMoin.util import pysupport
 772  
 773 +
 774  # Exceptions
 775  class InvalidFileNameError(Exception):
 776      """ Called when we find an invalid file name """ 
 777 @@ -533,77 +535,121 @@
 778  ### Plugins
 779  #############################################################################
 780  
 781 -def importPlugin(kind, name, function="execute", path=None):
 782 +def importPlugin(cfg, kind, name, function="execute"):
 783 +    """ Import wiki or builtin plugin
 784 +    
 785 +    Returns an object from a plugin module or None if module or
 786 +    'function' is not found.
 787 +
 788 +    kind may be one of 'action', 'formatter', 'macro', 'processor',
 789 +    'parser' or any other directory that exist in MoinMoin or
 790 +    data/plugin
 791 +
 792 +    Wiki plugins will always override builtin plugins. If you want
 793 +    specific plugin, use either importWikiPlugin or importName directly.
 794 +    
 795 +    @param cfg: wiki config instance
 796 +    @param kind: what kind of module we want to import
 797 +    @param name: the name of the module
 798 +    @param function: the function name
 799 +    @rtype: callable
 800 +    @return: "function" of module "name" of kind "kind", or None
 801      """
 802 -    Returns an object from a plugin module or None if module or 'function' is not found
 803 -    kind may be one of 'action', 'formatter', 'macro', 'processor', 'parser'
 804 -    or any other directory that exist in MoinMoin or data/plugin
 805 +    # Try to import from the wiki
 806 +    plugin = importWikiPlugin(cfg, kind, name, function)
 807 +    if plugin is None:
 808 +        # Try to get the plugin from MoinMoin
 809 +        modulename = 'MoinMoin.%s.%s' % (kind, name)
 810 +        plugin = pysupport.importName(modulename, function)
 811 +        
 812 +    return plugin
 813 +
 814 +
 815 +# Here we cache our plugins until we have a wiki object.
 816 +# WARNING: do not access directly in threaded enviromnent!
 817 +_wiki_plugins = {}
 818 +
 819 +def importWikiPlugin(cfg, kind, name, function):
 820 +    """ Import plugin from the wiki data directory
 821      
 822 +    We try to import only ONCE - then cache the plugin, even if we got
 823 +    None. This way we prevent expensive import of existing plugins for
 824 +    each call to a plugin.
 825 +
 826 +    @param cfg: wiki config instance
 827      @param kind: what kind of module we want to import
 828      @param name: the name of the module
 829      @param function: the function name
 830      @rtype: callable
 831 -    @return: "function" of module "name" of kind "kind"
 832 +    @return: "function" of module "name" of kind "kind", or None
 833      """
 834 -    # First try data/plugins
 835 -    result = None
 836 -    if path:
 837 -        result = pysupport.importName("plugin." + kind + "." + name, function, path)
 838 -    if result == None:
 839 -        # then MoinMoin
 840 -        result = pysupport.importName("MoinMoin." + kind + "." + name, function)
 841 -    return result
 842 +    global _wiki_plugins
 843 +
 844 +    # Wiki plugins are located under 'wikiconfigname.plugin' module.
 845 +    modulename = '%s.plugin.%s.%s' % (cfg.siteid, kind, name)
 846 +    key = (modulename, function)
 847 +    try:
 848 +        # Try cache first - fast!
 849 +        plugin = _wiki_plugins[key]
 850 +    except KeyError:
 851 +        # Try to import from disk and cache result - slow!
 852 +        plugin = pysupport.importName(modulename, function)
 853 +        _wiki_plugins[key] = plugin
 854 +
 855 +    return plugin
 856 +
 857 +# If we use threads, make this function thread safe
 858 +if config.use_threads:
 859 +    importWikiPlugin = pysupport.makeThreadSafe(importWikiPlugin)
 860  
 861  def builtinPlugins(kind):
 862 -    """
 863 -    Gets a list of modules in MoinMoin.'kind'
 864 +    """ Gets a list of modules in MoinMoin.'kind'
 865      
 866      @param kind: what kind of modules we look for
 867      @rtype: list
 868      @return: module names
 869      """
 870 -    plugins =  pysupport.importName("MoinMoin." + kind, "modules")
 871 -    if plugins == None:
 872 -        return []
 873 -    else:
 874 -        return plugins
 875 +    modulename = "MoinMoin." + kind
 876 +    plugins = pysupport.importName(modulename, "modules")
 877 +    return plugins or []
 878  
 879 -def extensionPlugins(kind, path):
 880 -    """
 881 -    Gets a list of modules in data/plugin/'kind'
 882 +
 883 +def wikiPlugins(kind, cfg):
 884 +    """ Gets a list of modules in data/plugin/'kind'
 885      
 886      @param kind: what kind of modules we look for
 887      @rtype: list
 888      @return: module names
 889      """
 890 -    plugins =  pysupport.importName("plugin." + kind, "modules", path=path)
 891 -    if plugins == None:
 892 -        return []
 893 -    else:
 894 -        return plugins
 895 +    # Wiki plugins are located in wikiconfig.plugin module
 896 +    modulename = '%s.plugin.%s' % (cfg.siteid, kind)
 897 +    plugins = pysupport.importName(modulename, "modules")
 898 +    return plugins or []
 899  
 900  
 901 -def getPlugins(kind, path):
 902 -    """
 903 -    Gets a list of module names.
 904 +def getPlugins(kind, cfg):
 905 +    """ Gets a list of plugin names of kind
 906      
 907      @param kind: what kind of modules we look for
 908      @rtype: list
 909      @return: module names
 910      """
 911 -    builtin_plugins = builtinPlugins(kind)
 912 -    extension_plugins = extensionPlugins(kind, path)[:] # use a copy to not destroy the value
 913 -    for module in builtin_plugins:
 914 -        if module not in extension_plugins:
 915 -            extension_plugins.append(module)
 916 -    return extension_plugins
 917 +    # Copy names from builtin plugins - so we dont destroy the value
 918 +    all_plugins = builtinPlugins(kind)[:]
 919 +    
 920 +    # Add extension plugins without duplicates
 921 +    for plugin in wikiPlugins(kind, cfg):
 922 +        if plugin not in all_plugins:
 923 +            all_plugins.append(plugin)
 924 +
 925 +    return all_plugins
 926  
 927  
 928  #############################################################################
 929  ### Parsers
 930  #############################################################################
 931  
 932 -def getParserForExtension(cfg, extension):
 933 +def getParserForExtension(request, extension):
 934      """
 935      Returns the Parser class of the parser fit to handle a file
 936      with the given extension. The extension should be in the same
 937 @@ -623,8 +669,8 @@
 938      if not hasattr(cfg, '_EXT_TO_PARSER'):
 939          import types
 940          etp, etd = {}, None
 941 -        for pname in getPlugins('parser', cfg.data_dir):
 942 -            Parser = importPlugin('parser', pname, 'Parser', cfg.data_dir)
 943 +        for pname in getPlugins('parser', cfg):
 944 +            Parser = importPlugin(request.cfg, 'parser', pname, 'Parser')
 945              if Parser is not None:
 946                  if hasattr(Parser, 'extensions'):
 947                      exts=Parser.extensions
 948 @@ -633,9 +679,11 @@
 949                              etp[ext]=Parser
 950                      elif str(exts) == '*':
 951                          etd=Parser
 952 -        # is this setitem thread save?
 953 +        # Cache in cfg for current request - this is not thread safe
 954 +        # when cfg will be cached per wiki in long running process.
 955          cfg._EXT_TO_PARSER=etp
 956          cfg._EXT_TO_PARSER_DEFAULT=etd
 957 +        
 958      return cfg._EXT_TO_PARSER.get(extension,cfg._EXT_TO_PARSER_DEFAULT)
 959  
 960  
 961 
 962 
 963 --- orig/wiki/server/moin.fcg
 964 +++ mod/wiki/server/moin.fcg
 965 @@ -20,16 +20,31 @@
 966  ## sys.path.insert(0, '/path/to/wikiconfig/dir')
 967  ## sys.path.insert(0, '/path/to/farmconfig/dir')
 968  
 969 +# Use threaded version or non-threaded version (default True)?
 970 +threads = True
 971 +
 972 +
 973 +# Code ------------------------------------------------------------------
 974 +
 975 +# Do not touch unless you know what you are doting!
 976 +# TODO: move to server packge?
 977 +
 978 +# Set threads flag, so other code can use proper locking
 979 +from MoinMoin import config
 980 +config.use_threads = threads
 981 +del config
 982  
 983  from MoinMoin.request import RequestFastCGI
 984  from MoinMoin.support import thfcgi
 985  
 986  def handle_request(req, env, form):
 987 -    request = RequestFastCGI(req,env,form)
 988 +    request = RequestFastCGI(req, env, form)
 989      request.run()
 990  
 991  if __name__ == '__main__':
 992 -    # this is a multi-threaded FastCGI
 993 -    # use thfcgi.unTHFCGI for a single-threaded instance
 994 -    fcg = thfcgi.THFCGI(handle_request)
 995 +    if threads:
 996 +        fcg = thfcgi.THFCGI(handle_request)
 997 +    else:
 998 +        fcg = thfcgi.unTHFCGI(handle_request)    
 999 +
1000      fcg.run()
1001 
1002 
1003 --- orig/wiki/server/moinmodpy.py
1004 +++ mod/wiki/server/moinmodpy.py
1005 @@ -42,6 +42,14 @@
1006  ## sys.path.insert(0, '/path/to/farmconfig/dir')
1007  
1008  
1009 +# Set threads flag, so other code can use proper locking.
1010 +# TODO: It seems that modpy does not use threads, so we don't need to
1011 +# set it here. Do we have another method to check this?
1012 +from MoinMoin import config
1013 +config.use_threads = True
1014 +del config
1015 +
1016 +
1017  from MoinMoin.request import RequestModPy
1018  
1019  def handler(request):

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2007-07-04 15:29:57, 7.7 KB) [[attachment:Slideshow.py]]
  • [get | view] (2004-10-20 05:28:16, 72.8 KB) [[attachment:alum.jpg]]
  • [get | view] (2005-04-14 19:56:30, 68.2 KB) [[attachment:farm.tbz]]
  • [get | view] (2005-05-28 17:05:11, 31.2 KB) [[attachment:me-with-naomi.jpg]]
  • [get | view] (2005-04-14 19:55:22, 8.6 KB) [[attachment:multiconfig.patch]]
  • [get | view] (2005-01-16 05:36:39, 41.4 KB) [[attachment:page-state.patch]]
  • [get | view] (2004-12-28 02:41:46, 36.7 KB) [[attachment:plugin.patch]]
  • [get | view] (2004-10-21 18:49:52, 22.1 KB) [[attachment:text_html.py]]
  • [get | view] (2005-04-18 07:20:14, 1.4 KB) [[attachment:wiki_icons.png]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.