You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mod_python-commits@quetz.apache.org by gr...@apache.org on 2006/03/20 12:18:25 UTC

svn commit: r387186 - in /httpd/mod_python/trunk: lib/python/mod_python/__init__.py lib/python/mod_python/apache.py lib/python/mod_python/importer.py src/include/mpversion.h test/htdocs/tests.py test/test.py

Author: grahamd
Date: Mon Mar 20 03:18:21 2006
New Revision: 387186

URL: http://svn.apache.org/viewcvs?rev=387186&view=rev
Log:
Added new experimental module importer. New module importer does not run
by default but must be enabled explicitly. (MODPYTHON-143)

Added:
    httpd/mod_python/trunk/lib/python/mod_python/importer.py
Modified:
    httpd/mod_python/trunk/lib/python/mod_python/__init__.py
    httpd/mod_python/trunk/lib/python/mod_python/apache.py
    httpd/mod_python/trunk/src/include/mpversion.h
    httpd/mod_python/trunk/test/htdocs/tests.py
    httpd/mod_python/trunk/test/test.py

Modified: httpd/mod_python/trunk/lib/python/mod_python/__init__.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/__init__.py?rev=387186&r1=387185&r2=387186&view=diff
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/__init__.py (original)
+++ httpd/mod_python/trunk/lib/python/mod_python/__init__.py Mon Mar 20 03:18:21 2006
@@ -20,5 +20,5 @@
 __all__ = ["apache", "cgihandler", "psp",
            "publisher", "util", "python22"]
 
-version = "3.3.0-dev-20060319"
+version = "3.3.0-dev-20060320"
 

Modified: httpd/mod_python/trunk/lib/python/mod_python/apache.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/apache.py?rev=387186&r1=387185&r2=387186&view=diff
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/apache.py (original)
+++ httpd/mod_python/trunk/lib/python/mod_python/apache.py Mon Mar 20 03:18:21 2006
@@ -838,6 +838,14 @@
 
     global _callback
     _callback = CallBack()
+
+    options = main_server.get_options()
+    interpreters = options.get('mod_python.future.importer', None)
+    if interpreters:
+        interpreters = map(lambda x: x.strip(), interpreters.split(','))
+        if '*' in interpreters or interpreter in interpreters:
+            from mod_python import importer
+
     return _callback
 
 ## Some functions made public

Added: httpd/mod_python/trunk/lib/python/mod_python/importer.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/importer.py?rev=387186&view=auto
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/importer.py (added)
+++ httpd/mod_python/trunk/lib/python/mod_python/importer.py Mon Mar 20 03:18:21 2006
@@ -0,0 +1,1455 @@
+ # vim: set sw=4 expandtab :
+ #
+ # Copyright 2004 Apache Software Foundation 
+ # 
+ # Licensed under the Apache License, Version 2.0 (the "License"); you
+ # may not use this file except in compliance with the License.  You
+ # may obtain a copy of the License at
+ #
+ #      http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS,
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ # implied.  See the License for the specific language governing
+ # permissions and limitations under the License.
+ #
+ # Originally developed by Gregory Trubetskoy.
+ # 
+ # The code in this file originally donated by Graham Dumpleton.
+ # 
+ # $Id: cache.py 374268 2006-02-02 05:31:45Z nlehuen $
+
+from mod_python import apache
+from mod_python import publisher
+
+import os
+import sys
+import new
+import types
+import pdb
+import imp
+import md5
+import time
+import string
+
+try:
+    import threading
+except:
+    import dummy_threading as threading
+
+
+# Define a transient per request modules cache. This is
+# not the same as the true global modules cache used by
+# the module importer. Instead, the per request modules
+# cache is where references to modules loaded in order
+# to satisfy the requirements of a specific request are
+# stored for the life of that request. Such a cache is
+# required to ensure that two distinct bits of code that
+# load the same module do in fact use the same instance
+# of the module and that an update of the code for a
+# module on disk doesn't cause the latter handler code
+# to load its own separate instance. This is in part
+# necessary because the module loader does not reload a
+# module on top of the old module, but loads the new
+# instance into a clean module.
+
+_modules_cache = {}
+
+def _cleanup_modules_cache(thread=None):
+    thread = thread or threading.currentThread()
+    _modules_cache.pop(thread, None)
+
+def _setup_modules_cache(req=None, thread=None):
+    thread = thread or threading.currentThread()
+    if not _modules_cache.has_key(thread):
+        _modules_cache[thread] = {}
+        if req:
+            req.register_cleanup(_cleanup_modules_cache, thread)
+
+def _get_modules_cache(thread=None):
+    thread = thread or threading.currentThread()
+    return _modules_cache.get(thread, None)
+
+
+# Define a transient per request config cache into which
+# the currently active configuration and handler root
+# directory pertaining to a request is stored. This is
+# done so that it can be accessed directly from the
+# module importer function to obtain configuration
+# settings indicating if logging and module reloading is
+# enabled and to determine where to look for modules.
+
+_config_cache = {}
+
+def _setup_config_cache(config, directory, thread=None):
+    thread = thread or threading.currentThread()
+    cache  = _config_cache.get(thread, (None, None))
+    if config is not None:
+        if directory:
+            directory = os.path.normpath(directory)
+        _config_cache[thread] = (config, directory)
+    else:
+        del _config_cache[thread]
+    return cache
+
+def get_config(thread=None):
+    thread = thread or threading.currentThread()
+    config, directory = _config_cache.get(thread,
+            (apache.main_server.get_config(), None))
+    return config
+
+def get_directory(thread=None):
+    thread = thread or threading.currentThread()
+    config, directory = _config_cache.get(thread, (None, None))
+    return directory
+
+
+# Define an alternate implementation of the module
+# importer system and substitute it for the standard one
+# in the 'mod_python.apache' module.
+
+apache.ximport_module = apache.import_module
+
+def _parent_context():
+    # Determine the enclosing module which has called
+    # this function. From that module, return the info
+    # stashed in it by the module importer system.
+
+    try:
+        raise Exception
+    except:
+        parent = sys.exc_info()[2].tb_frame.f_back
+        while (parent and parent.f_globals.has_key('__file__') and
+                parent.f_globals['__file__'] == __file__):
+            parent = parent.f_back
+
+    if parent and parent.f_globals.has_key('__info__'):
+        return parent.f_globals['__info__']
+
+def _find_module(module_name, path):
+
+    # Search the specified path for a Python code module
+    # of the specified name. Note that only Python code
+    # files with a '.py' extension will be used. Python
+    # packages will be ignored.
+
+    for directory in path:
+        file = os.path.join(directory, module_name) + '.py'
+        if os.path.exists(file):
+            return file
+
+def import_module(module_name, autoreload=None, log=None, path=None):
+
+    file = None
+    import_path = []
+
+    # Deal with explicit references to a code file.
+    # Allow some shortcuts for referring to code file
+    # relative to handler root or directory of parent
+    # module. Those relative to parent module are not
+    # allowed if parent module not imported using this
+    # module importing system.
+
+    if os.path.isabs(module_name):
+        file = module_name
+
+    elif module_name[:2] == '~/':
+        directory = get_directory()
+        if directory is not None:
+            file = os.path.join(directory, module_name[2:])
+
+    elif module_name[:2] == './':
+        context = _parent_context()
+        if context is not None:
+            directory = os.path.dirname(context.file)
+            file = os.path.join(directory, module_name[2:])
+
+    elif module_name[:3] == '../':
+        context = _parent_context()
+        if context is not None:
+            directory = os.path.dirname(context.file)
+            file = os.path.join(directory, module_name)
+
+    if file is None:
+        # If not an explicit file reference, it is a
+        # module name. Determine the list of directories
+        # that need to be searched for a module code
+        # file. These directories will be, the handler
+        # root directory and any specified search path.
+        # The handler root though is only checked if it
+        # is known and the 'PythonPath' directive is not
+        # specified. The latter check of 'PythonPath' is
+        # made purely to ensure backward compatability
+        # with old code where the handler root would
+        # previously have been added automatically to
+        # 'sys.path'. The danger is where users add the
+        # handler root to 'PythonPath' explicitly. They
+        # need to be educated not to do this with the
+        # new module importing system as it is not
+        # really necessary and will just continue to
+        # cause possible problems if they do so.
+
+        search_path = []
+
+        if path is not None:
+            search_path.extend(path)
+
+        config = get_config()
+        if config and not config.has_key('PythonPath'):
+            directory = get_directory()
+            if directory is not None:
+                if path is None or directory not in path:
+                    search_path.append(directory)
+
+        # Attempt to find the code file for the module
+        # if we have directories to actually search.
+
+        if search_path:
+            file = _find_module(module_name, search_path)
+
+    else:
+
+        # For module imported using explicit path, the
+        # path argument becomes the special embedded
+        # search path for 'import' statement executed
+        # within that module.
+
+        if path is not None:
+            import_path = path
+
+    # Was a Python code file able to be identified.
+
+    if file is not None:
+
+        # Use the module importing and caching system
+        # to load the code from the specified file.
+
+        return _moduleCache.import_module(file, autoreload, log, import_path)
+
+    else:
+        # If a module code file could not be found,
+        # defer to the standard Python module importer.
+        # We should always end up here if the request
+        # was for a package.
+
+        return __import__(module_name, {}, {}, ['*'])
+
+
+apache.import_module = import_module
+
+
+class _CacheInfo:
+
+    def __init__(self, label, file, mtime):
+        self.label = label
+        self.file = file
+        self.mtime = mtime
+        self.module = None
+        self.instance = 0
+        self.generation = 0
+        self.children = {}
+        self.atime = 0
+        self.direct = 0
+        self.indirect = 0
+        self.lock = threading.Lock()
+
+class _InstanceInfo:
+
+    def __init__(self, label, file, cache):
+        self.label = label
+        self.file = file
+        self.cache = cache
+        self.children = {}
+        self.path = []
+
+class _ModuleCache:
+
+    _prefix = "_mp_"
+
+    def __init__(self):
+        self._cache = {}
+        self._lock1 = threading.Lock()
+        self._lock2 = threading.Lock()
+        self._generation = 0
+        self._frozen = False
+        self._directories = {}
+
+    def _log_notice(self, msg):
+        pid = os.getpid()
+        name = apache.interpreter
+        server = apache.main_server
+        flags = apache.APLOG_NOERRNO|apache.APLOG_NOTICE
+        text = "mod_python (pid=%d,interpreter=%s): %s" % (pid, `name`, msg)
+        apache.log_error(text, flags, server)
+
+    def _log_warning(self, msg):
+        pid = os.getpid()
+        name = apache.interpreter
+        server = apache.main_server
+        flags = apache.APLOG_NOERRNO|apache.APLOG_WARNING
+        text = "mod_python (pid=%d,interpreter=%s): %s" % (pid, `name`, msg)
+        apache.log_error(text, flags, server)
+
+    def cached_modules(self):
+        self._lock1.acquire()
+        try:
+            return self._cache.keys()
+        finally:
+            self._lock1.release()
+
+    def module_info(self, label):
+        self._lock1.acquire()
+        try:
+            return self._cache[label]
+        finally:
+            self._lock1.release()
+
+    def freeze_modules(self):
+        self._frozen = True
+
+    def check_directory(self, file):
+
+        directory = os.path.dirname(file)
+
+        if not directory in self._directories:
+            self._directories[directory] = None
+            if directory in sys.path:
+                msg = 'Module directory listed in "sys.path". '
+                msg = msg + 'This may cause problems. Please check code. '
+                msg = msg + 'Code file being imported is "%s".' % file
+                self._log_warning(msg)
+
+    def import_module(self, file, autoreload=None, log=None, path=None):
+
+        # Ensure that file name is normalised so all
+        # lookups against the cache equate where they
+        # are the same file. This isn't necessarily
+        # going to work where symlinks are involved, but
+        # not much else that can be done in that case.
+
+        file = os.path.normpath(file)
+
+        # Determine the default values for the module
+        # autoreloading and logging arguments direct
+        # from the Apache configuration rather than
+        # having fixed defaults.
+
+        if autoreload is None or log is None:
+
+            config = get_config()
+
+            if autoreload is None:
+                autoreload = int(config.get("PythonAutoReload", 1))
+
+            if log is None:
+                log = int(config.get("PythonDebug", 0))
+
+        # Warn of any instances where a code file is
+        # imported from a directory which also appears
+        # in 'sys.path'.
+
+        if log:
+            self.check_directory(file)
+
+        # Retrieve the parent context. That is, the
+        # details stashed into the parent module by the
+        # module importing system itself.
+
+        context = _parent_context()
+
+        # Check for an attempt by the module to import
+        # itself.
+
+        if context:
+            assert(file != context.file), "Import cycle in %s." % file
+
+        # Retrieve the per request modules cache entry.
+
+        modules = _get_modules_cache()
+
+        # Calculate a unique label corresponding to the
+        # name of the file which is the module. This
+        # will be used as the '__name__' attribute of a
+        # module and as key in various tables.
+
+        label = self._module_label(file)
+
+        # See if the requested module has already been
+        # imported previously within the context of this
+        # request or at least visited by way of prior
+        # dependency checks where it was deemed that it
+        # didn't need to be reloaded. If it has we can
+        # skip any additional dependency checks and use
+        # the module already identified. This ensures
+        # the same actual module instance is used. This
+        # check is also required so that we don't get
+        # into cyclical import loops. Still need to at
+        # least record the fact that the module is a
+        # child of the parent.
+
+        if modules:
+            if modules.has_key(label):
+                if context:
+                    context.children[label] = time.time()
+                return modules[label]
+
+        # Now move on to trying to find the actual
+        # module.
+
+        try:
+            cache = None
+
+            # First determine if the module has been loaded
+            # previously. If not already loaded or if a
+            # dependency of the module has been changed on disk
+            # or reloaded since parent was loaded, must load the
+            # module.
+
+            (cache, load) = self._reload_required(modules,
+                    label, file, autoreload)
+
+            # Make sure that the cache entry is locked by the
+            # thread so that other threads in a multithreaded
+            # system don't try and load the same module at the
+            # same time.
+
+            cache.lock.acquire()
+
+            if load:
+
+                # Setup a new empty module to load the code for
+                # the module into.
+
+                cache.instance = cache.instance + 1
+
+                module = imp.new_module(label)
+
+                # If the module was previously loaded we need to
+                # manage the transition to the new instance of
+                # the module that is being loaded to replace it.
+                # This entails calling the special clone method,
+                # if provided within the existing module. Using
+                # this method the existing module can then
+                # selectively indicate what should be transfered
+                # over to the next instance of the module,
+                # including thread locks. If this process fails
+                # the special purge method is called, if
+                # provided, to indicate that the existing module
+                # is being forcibly purged out of the system. In
+                # that case any existing state will not be
+                # transferred.
+
+                if cache.module != None:
+                    if hasattr(cache.module, "__clone__"):
+                        try:
+                            # Migrate any existing state data from
+                            # existing module instance to new module
+                            # instance.
+
+                            if log:
+                                msg = "Cloning module '%s'" % file
+                                self._log_notice(msg)
+
+                            cache.module.__clone__(module)
+
+                        except:
+                            # Forcibly purging module from system.
+
+                            if hasattr(cache.module, "__purge__"):
+                                try:
+                                    cache.module.__purge__()
+                                except:
+                                    pass
+
+                            if log:
+                                msg = "Purging module '%s'" % file
+                                self._log_notice(msg)
+
+                            cache.module = None
+
+                            # Setup a fresh new module yet again.
+
+                            module = imp.new_module(label)
+
+                    if log:
+                        if cache.module == None:
+                            msg = "Importing module '%s'" % file
+                            self._log_notice(msg)
+                        else:
+                            msg = "Reimporting module '%s'" % file
+                            self._log_notice(msg)
+                else:
+                    if log:
+                        msg = "Importing module '%s'" % file
+                        self._log_notice(msg)
+
+                # Must add to the module the path to the modules
+                # file. This ensures that module looks like a
+                # normal module and this path will also be used
+                # in certain contexts when the import statement
+                # is used within the module.
+
+                module.__file__ = file
+
+                # Setup a new instance object to store in the
+                # module. This will refer back to the actual
+                # cache entry and is used to record information
+                # which is specific to this incarnation of the
+                # module when reloading is occuring.
+
+                instance = _InstanceInfo(label, file, cache)
+
+                module.__info__ = instance
+
+                # Cache any additional module search path which
+                # should be used for this instance of the module
+                # or package. The path shouldn't be able to be
+                # changed during the lifetime of the module to
+                # ensure that module imports are always done
+                # against the same path.
+
+                if path is None:
+                    path = []
+
+                instance.path = list(path)
+
+                # Place a reference to the module within the
+                # request specific cache of imported modules.
+                # This makes module lookup more efficient when
+                # the same module is imported more than once
+                # within the context of a request. In the case
+                # of a cyclical import, avoids a never ending
+                # recursive loop.
+
+                if modules:
+                    modules[label] = module
+
+                # Perform actual import of the module.
+
+                try:
+                    execfile(file, module.__dict__)
+
+                except:
+
+                    # Importation of the module has failed for
+                    # some reason. If this is the very first
+                    # import of the module, need to discard the
+                    # cache entry entirely else a subsequent
+                    # attempt to load the module will wrongly
+                    # think it was successfully loaded already.
+                    # If not a new import, need to ensure that
+                    # module will be reloaded again in future.
+
+                    if cache.module is None:
+                        del self._cache[label]
+                    else:
+                        cache.mtime = 0
+
+                    raise
+
+                # If this is a child import of some parent
+                # module, add this module as a child of the
+                # parent.
+
+                atime = time.time()
+
+                if context:
+                    context.children[label] = atime
+
+                # Update the cache.
+
+                cache.module = module
+
+                # Need to also update the list of child modules
+                # stored in the cache entry with the actual
+                # children found during the import. A copy is
+                # made, meaning that any future imports
+                # performed within the context of the request
+                # handler don't result in the module later being
+                # reloaded if they change.
+
+                cache.children = dict(module.__info__.children)
+
+                # Increment the generation count of the global
+                # state of all modules. This is used in the
+                # dependency management scheme for reloading to
+                # determine if a module dependency has been
+                # reloaded since it was loaded.
+
+                self._lock2.acquire()
+                self._generation = self._generation + 1
+                cache.generation = self._generation
+                self._lock2.release()
+
+                # Update access time and reset access counts.
+
+                cache.atime = atime
+                cache.direct = 1
+                cache.indirect = 0
+
+            else:
+
+                # Update the cache.
+
+                module = cache.module
+
+                # Place a reference to the module within the
+                # request specific cache of imported modules.
+                # This makes module lookup more efficient when
+                # the same module is imported more than once
+                # within the context of a request. In the case
+                # of a cyclical import, avoids a never ending
+                # recursive loop.
+
+                if modules:
+                    modules[label] = module
+
+                # If this is a child import of some parent
+                # module, add this module as a child of the
+                # parent.
+
+                atime = time.time()
+
+                if context:
+                    context.children[label] = atime
+
+                # Didn't need to reload the module so simply
+                # increment access counts and last access time.
+
+                cache.atime = atime
+                cache.direct = cache.direct + 1
+
+            return module
+
+        finally:
+            # Lock on cache object can now be released.
+
+            if cache is not None:
+                cache.lock.release()
+
+    def _reload_required(self, modules, label, file, autoreload):
+
+        # Make sure cache lock is always released.
+
+        try:
+            self._lock1.acquire()
+
+            # Check if this is a new module.
+
+            if not self._cache.has_key(label):
+                mtime = os.path.getmtime(file)
+                cache = _CacheInfo(label, file, mtime)
+                self._cache[label] = cache
+                return (cache, True)
+
+            # Grab entry from cache.
+
+            cache = self._cache[label]
+
+            # Check if reloads have been disabled.
+
+            if self._frozen or not autoreload:
+                return (cache, False)
+
+            # Check if module has been marked as dirty.
+
+            if cache.mtime == 0:
+                return (cache, True)
+
+            # Has modification time changed.
+
+            try:
+                mtime = os.path.getmtime(file)
+            except:
+
+                # Must have been removed just then. We return
+                # currently cached module and avoid a reload.
+                # Defunct module would need to be purged later.
+
+                return (cache, False)
+
+            if mtime != cache.mtime:
+                cache.mtime = mtime
+                return (cache, True)
+
+            # Check if children have changed in any way
+            # that would require a reload.
+
+            if cache.children:
+
+                visited = {}
+                ancestors = [label]
+
+                for tag in cache.children:
+
+                    # If the child isn't in the cache any longer
+                    # for some reason, force a reload.
+
+                    if not self._cache.has_key(tag):
+                        return (cache, True)
+
+                    child = self._cache[tag]
+
+                    # Now check the actual child module.
+
+                    if self._check_module(modules, cache, child,
+                            visited, ancestors):
+                        return (cache, True)
+
+            # No need to reload the module. Module
+            # should be cached in the request object by
+            # the caller if required.
+
+            return (cache, False)
+
+        finally:
+            self._lock1.release()
+
+    def _check_module(self, modules, parent, current, visited, ancestors):
+
+        # Update current modules access statistics.
+
+        current.indirect = current.indirect + 1
+        current.atime = time.time()
+
+        # Check if current module has been marked as
+        # dirty.
+
+        if current.mtime == 0:
+            return True
+
+        # Check if current module has been reloaded
+        # since the parent was last loaded.
+
+        if current.generation > parent.generation:
+            return True
+
+        # If the current module has been visited
+        # already, no need to continue further as it
+        # should be up to date.
+
+        if visited.has_key(current.label):
+            return False
+
+        # Check if current module has been modified on
+        # disk since last loaded.
+
+        try:
+
+            mtime = os.path.getmtime(current.file)
+
+            if mtime != current.mtime:
+                return True
+
+        except:
+            # Current module must have been removed.
+            # Don't cause this to force a reload though
+            # as can cause problems. Rely on the parent
+            # being modified to cause a reload.
+
+            if modules:
+                modules[current.label] = current.module
+
+            return False
+
+        # Check to see if all the children of the
+        # current module need updating or are newer than
+        # the current module.
+
+        if current.children:
+
+            ancestors = ancestors + [current.label]
+
+            for label in current.children.keys():
+
+                # Check for a child which refers to one of its
+                # ancestors. Hopefully this will never occur. If
+                # it does we will force a reload every time to
+                # highlight there is a problem.
+
+                if label in ancestors:
+                    return True
+
+                # If the child isn't in the cache any longer for
+                # some reason, force a reload.
+
+                if not self._cache.has_key(label):
+                    return True
+
+                child = self._cache[label]
+
+                # Recurse back into this function to check
+                # child.
+
+                if self._check_module(modules, current, child,
+                        visited, ancestors):
+                    return True
+
+        # No need to reload the current module. Now safe
+        # to mark the current module as having been
+        # visited and cache it into the request object
+        # for quick later lookup if a parent needs to be
+        # reloaded.
+
+        visited[current.label] = current
+
+        if modules:
+            modules[current.label] = current.module
+
+        return False
+
+    def _module_label(self, file):
+
+        # The label is used in the __name__ field of the
+        # module and then used in determining child
+        # module imports. Thus really needs to be
+        # unique. We don't really want to use a module
+        # name which is a filesystem path. Hope MD5 hex
+        # digest is okay.
+
+        stub = os.path.splitext(file)[0]
+        label = md5.new(stub).hexdigest()
+        label = self._prefix + label
+        label = label + "_" + str(len(stub))
+        return label
+
+
+_moduleCache = _ModuleCache()
+
+apache.freeze_modules = _moduleCache.freeze_modules
+
+def ModuleCache():
+    return _moduleCache
+
+
+class _ModuleLoader:
+
+    def __init__(self, file):
+        self.__file = file
+
+    def load_module(self, fullname):
+        return _moduleCache.import_module(self.__file)
+
+class _ModuleImporter:
+
+    def find_module(self, fullname, path=None):
+
+        # Return straight away if requested to import a
+        # sub module of a package.
+
+        if '.' in fullname:
+            return None
+
+        # Retrieve the parent context. That is, the
+        # details stashed into the parent module by the
+        # module importing system itself. Only consider
+        # using the module importing system for 'import'
+        # statement if parent module was imported using
+        # the same.
+
+        context = _parent_context()
+
+        if context is None:
+            return None
+
+        # Determine the list of directories that need to
+        # be searched for a module code file. These
+        # directories will be, the handler root
+        # directory, the directory of the parent and any
+        # specified search path. The handler root though
+        # is only checked if it is known and the
+        # 'PythonPath' directive is not specified. The
+        # latter check of 'PythonPath' is made purely to
+        # ensure backward compatability with old code
+        # where the handler root would previously have
+        # been added automatically to 'sys.path'. The
+        # danger is where users add the handler root to
+        # 'PythonPath' explicitly. They need to be
+        # educated not to do this with the new module
+        # importing system as it is not really necessary
+        # and will just continue to cause possible
+        # problems if they do so.
+
+        search_path = []
+
+        local_directory = os.path.dirname(context.file)
+        search_path.append(local_directory)
+
+        if context.path is not None:
+            search_path.extend(context.path)
+
+        config = get_config()
+        if config and not config.has_key('PythonPath'):
+            root_directory = get_directory()
+            if root_directory is not None:
+                if root_directory != local_directory:
+                    if (context.path is None or
+                            root_directory not in context.path):
+                        search_path.append(root_directory)
+
+        # Return if we have no search path to search.
+
+        if not search_path:
+            return None
+
+        # Attempt to find the code file for the module.
+
+        file = _find_module(fullname, search_path)
+
+        if file is not None:
+            return _ModuleLoader(file)
+
+        return None
+
+
+sys.meta_path.insert(0, _ModuleImporter())
+
+
+
+# Replace mod_python.publisher page cache object with a
+# dummy object which uses new module importer.
+
+class _PageCache:
+    def __getitem__(self,req):
+        return import_module(req.filename)
+
+publisher.xpage_cache = publisher.page_cache
+publisher.page_cache = _PageCache()
+
+
+# Define alternate implementations of the top level
+# mod_python entry points and substitute them for the
+# standard one in the 'mod_python.apache' callback
+# object.
+
+_callback = apache._callback
+
+_callback.xConnectionDispatch = _callback.ConnectionDispatch
+_callback.xFilterDispatch = _callback.FilterDispatch
+_callback.xHandlerDispatch = _callback.HandlerDispatch
+_callback.xIncludeDispatch = _callback.IncludeDispatch
+_callback.xImportDispatch = _callback.ImportDispatch
+
+_result_warning = """Handler has returned result or raised SERVER_RETURN
+exception with argument having non integer type. Type of value returned
+was %s, whereas expected """ + str(types.IntType) + "."
+
+_status_values = {
+    "postreadrequesthandler":   [ apache.DECLINED, apache.OK ],
+    "transhandler":             [ apache.DECLINED ],
+    "maptostoragehandler":      [ apache.DECLINED ],
+    "inithandler":              [ apache.DECLINED, apache.OK ],
+    "headerparserhandler":      [ apache.DECLINED, apache.OK ],
+    "accesshandler":            [ apache.DECLINED, apache.OK ],
+    "authenhandler":            [ apache.DECLINED ],
+    "authzhandler":             [ apache.DECLINED ],
+    "typehandler":              [ apache.DECLINED ],
+    "fixuphandler":             [ apache.DECLINED, apache.OK ],
+    "loghandler":               [ apache.DECLINED, apache.OK ],
+    "handler":                  [ apache.OK ],
+}
+
+def _execute_target(config, req, object, arg):
+
+    try:
+        # Only permit debugging using pdb if Apache has
+        # actually been started in single process mode.
+
+        pdb_debug = config.has_key("PythonEnablePdb")
+        one_process = apache.exists_config_define("ONE_PROCESS")
+
+        if pdb_debug and one_process:
+
+            # Don't use pdb.runcall() as it results in
+            # a bogus 'None' response when pdb session
+            # is quit. With this code the exception
+            # marking that the session has been quit is
+            # propogated back up and it is obvious in
+            # the error message what actually occurred.
+
+            debugger = pdb.Pdb()
+            debugger.reset()
+            sys.settrace(debugger.trace_dispatch)
+
+            try:
+                result = object(arg)
+
+            finally:
+                debugger.quitting = 1
+                sys.settrace(None)
+
+        else:
+            result = object(arg)
+
+    except apache.SERVER_RETURN, value:
+        # For a connection handler, there is no request
+        # object so this form of response is invalid.
+        # Thus exception is reraised to be handled later.
+
+        if not req:
+            raise
+
+        # The SERVER_RETURN exception type when raised
+        # otherwise indicates an abort from below with
+        # value as (result, status) or (result, None) or
+        # result.
+
+        if len(value.args) == 2:
+            (result, status) = value.args
+            if status:
+                req.status = status
+        else:
+            result = value.args[0]
+
+    # Only check type of return value for connection
+    # handlers and request phase handlers. The return
+    # value of filters are ultimately ignored.
+
+    if not req or req == arg:
+        assert (type(result) == types.IntType), _result_warning % type(result)
+
+    return result
+
+def _process_target(config, req, directory, handler, default, arg, silent):
+
+    # Determine module name and target object.
+
+    parts = handler.split('::', 1)
+
+    module_name = parts[0]
+
+    if len(parts) == 1:
+        object_str = default
+    else:
+        object_str = parts[1]
+
+    # Update 'sys.path'. This will only be done if we
+    # have not encountered the current value of the
+    # 'PythonPath' config previously.
+
+    if config.has_key("PythonPath"):
+
+        apache._path_cache_lock.acquire()
+
+        try:
+
+            pathstring = config["PythonPath"]
+            if not apache._path_cache.has_key(pathstring):
+                newpath = eval(pathstring)
+                apache._path_cache[pathstring] = None
+                sys.path[:] = newpath
+
+        finally:
+            apache._path_cache_lock.release()
+
+    # Import module containing target object. Specify
+    # the handler root directory in the search path so
+    # that it is still checked even if 'PythonPath' set.
+
+    path = []
+
+    if directory:
+        path = [directory]
+
+    module = import_module(module_name, path=path)
+
+    # Lookup expected status values that allow us to
+    # continue when multiple handlers exist.
+
+    expected = _status_values.get(default, None)
+
+    # Default to apache.DECLINED unless in content
+    # handler phase.
+
+    if not expected or apache.DECLINED not in expected:
+        result = apache.OK
+    else:
+        result = apache.DECLINED
+
+    # Obtain reference to actual target object.
+
+    object = apache.resolve_object(module, object_str, arg, silent=silent)
+
+    if object is not None or not silent:
+
+        result = _execute_target(config, req, object, arg)
+
+        # Stop iteration when target object returns a
+        # value other than expected values for the phase.
+
+        if expected and result not in expected:
+            return (True, result)
+
+    return (False, result)
+
+def ConnectionDispatch(self, conn):
+    """     
+    This is the dispatcher for connection handlers.
+    """     
+
+    # Determine the default handler name.
+
+    default_handler = "connectionhandler"
+
+    # Be cautious and return server error as default.
+
+    result = apache.HTTP_INTERNAL_SERVER_ERROR
+
+    # Setup transient per request modules cache. Note
+    # that this cache will always be thrown away when
+    # connection handler returns as there is no way to
+    # transfer ownership and responsibility for
+    # discarding the cache entry to latter handlers.
+
+    _setup_modules_cache()
+
+    try:
+
+        try:
+            # Cache the server configuration for the
+            # current request so that it will be
+            # available from within 'import_module()'.
+
+            config = conn.base_server.get_config()
+            cache = _setup_config_cache(config, None)
+
+            (aborted, result) = _process_target(config=config, req=None,
+                    directory=None, handler=conn.hlist.handler,
+                    default=default_handler, arg=conn, silent=0)
+
+        finally:
+            # Restore any previous cached configuration.
+            # There should not actually be any, but this
+            # will cause the configuration cache entry to
+            # be discarded.
+
+            _setup_config_cache(*cache)
+
+            # Also discard the modules cache entry.
+
+            _cleanup_modules_cache()
+
+    except apache.PROG_TRACEBACK, traceblock:
+
+        # Program runtime error flagged by the application.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        try:
+            (exc_type, exc_value, exc_traceback) = traceblock
+            result = self.ReportError(exc_type, exc_value, exc_traceback,
+                    srv=conn.base_server, phase="ConnectionHandler",
+                    hname=conn.hlist.handler, debug=debug)
+
+        finally:
+            exc_traceback = None
+
+    except:
+
+        # Module loading error or some other runtime error.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        try:    
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            result = self.ReportError(exc_type, exc_value, exc_traceback,
+                    srv=conn.base_server, phase="ConnectionHandler",
+                    hname=conn.hlist.handler, debug=debug)
+        finally:
+            exc_traceback = None
+
+    return result
+
+def FilterDispatch(self, filter):
+    """     
+    This is the dispatcher for input/output filters.
+    """     
+
+    # Determine the default handler name.
+
+    if filter.is_input:
+        default_handler = "inputfilter"
+    else:
+        default_handler = "outputfilter"
+
+    # Setup transient per request modules cache. Note
+    # that this will only actually do anything in this
+    # case if no Python request phase handlers have been
+    # specified. A cleanup handler is registered to
+    # later discard the cache entry if it was created.
+
+    _setup_modules_cache(filter.req)
+
+    try:
+
+        try:
+            # Cache the server configuration for the
+            # current request so that it will be
+            # available from within 'import_module()'.
+
+            config = filter.req.get_config()
+            cache = _setup_config_cache(config, filter.dir)
+
+            (aborted, result) = _process_target(config=config,
+                    req=filter.req, directory=filter.dir,
+                    handler=filter.handler, default=default_handler,
+                    arg=filter, silent=0)
+
+            if not filter.closed:
+                filter.flush()
+
+        finally:
+            # Restore any previous cached configuration.
+
+            _setup_config_cache(*cache)
+
+    except apache.PROG_TRACEBACK, traceblock:
+
+        # Program runtime error flagged by the application.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        filter.disable()
+
+        try:
+            (exc_type, exc_value, exc_traceback) = traceblock
+            result = self.ReportError(exc_type, exc_value,
+                    exc_traceback, req=filter.req, filter=filter,
+                    phase="Filter (%s)"%filter.name,
+                    hname=filter.handler, debug=debug)
+
+        finally:
+            exc_traceback = None
+
+    except:
+
+        # Module loading error or some other runtime error.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        filter.disable()
+
+        try:    
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            result = self.ReportError(exc_type, exc_value,
+                    exc_traceback, req=filter.req, filter=filter,
+                    phase="Filter: " + filter.name,
+                    hname=filter.handler, debug=debug)
+        finally:
+            exc_traceback = None
+
+    return apache.OK
+
+def HandlerDispatch(self, req):
+    """     
+    This is the dispatcher for handler phases.
+    """     
+
+    # Cache name of phase in case something changes it.
+
+    phase = req.phase
+
+    # Determine the default handler name.
+
+    default_handler = phase[len("python"):].lower()
+
+    # Be cautious and return server error as default.
+
+    result = apache.HTTP_INTERNAL_SERVER_ERROR
+
+    # Setup transient per request modules cache. Note
+    # that this will only do something if this is the
+    # first Python request handler phase to be executed.
+    # A cleanup handler is registered to later discard
+    # the cache entry if it needed to be created.
+
+    _setup_modules_cache(req)
+
+    # Cache configuration for later.
+
+    config = req.get_config()
+
+    try:
+        # Iterate over the handlers defined for the
+        # current phase and execute each in turn
+        # until the last is reached or prematurely
+        # aborted.
+
+        (aborted, hlist) = False, req.hlist
+
+        while not aborted and hlist.handler is not None:
+
+            try:
+                # Cache the server configuration for the
+                # current request so that it will be
+                # available from within 'import_module()'.
+
+                directory = hlist.directory
+
+                cache = _setup_config_cache(config, directory)
+
+                (aborted, result) = _process_target(config=config, req=req,
+                        directory=directory, handler=hlist.handler,
+                        default=default_handler, arg=req, silent=hlist.silent)
+
+            finally:
+                # Restore any previous cached configuration.
+
+                _setup_config_cache(*cache)
+
+            hlist.next()
+
+    except apache.PROG_TRACEBACK, traceblock:
+
+        # Program runtime error flagged by the application.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        try:
+            (exc_type, exc_value, exc_traceback) = traceblock
+            result = self.ReportError(exc_type, exc_value,
+                    exc_traceback, req=req, phase=phase,
+                    hname=hlist.handler, debug=debug)
+        finally:
+            exc_traceback = None
+
+    except:
+
+        # Module loading error or some other runtime error.
+
+        debug = int(config.get("PythonDebug", 0))
+
+        try:    
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            result = self.ReportError(exc_type, exc_value,
+                    exc_traceback, req=req, phase=phase,
+                    hname=hlist.handler, debug=debug)
+        finally:
+            exc_traceback = None
+
+    return result
+
+_callback.ConnectionDispatch = new.instancemethod(
+        ConnectionDispatch, _callback, apache.CallBack)
+_callback.FilterDispatch = new.instancemethod(
+        FilterDispatch, _callback, apache.CallBack)
+_callback.HandlerDispatch = new.instancemethod(
+        HandlerDispatch, _callback, apache.CallBack)
+
+def IncludeDispatch(self, filter, tag, code):
+
+    # Setup transient per request modules cache. Note
+    # that this will only actually do anything in this
+    # case if no Python request phase handlers have been
+    # specified. A cleanup handler is registered to
+    # later discard the cache entry if it was created.
+
+    _setup_modules_cache(filter.req)
+
+    try:
+
+        try:
+            # Cache the server configuration for the
+            # current request so that it will be
+            # available from within 'import_module()'.
+
+            config = filter.req.get_config()
+            cache = _setup_config_cache(config, None)
+
+            debug = int(config.get("PythonDebug", 0))
+
+            if not hasattr(filter.req,"ssi_globals"):
+                filter.req.ssi_globals = {}
+
+            filter.req.ssi_globals["filter"] = filter
+
+            code = code.rstrip()
+
+            if tag == 'eval':
+                result = eval(code, filter.req.ssi_globals)
+                if result is not None:
+                    filter.write(str(result))
+            elif tag == 'exec':
+                exec(code, filter.req.ssi_globals)
+
+            filter.flush()
+
+        finally:
+
+            filter.req.ssi_globals["filter"] = None
+
+            # Restore any previous cached configuration.
+
+            _setup_config_cache(*cache)
+
+    except:
+        try:
+            exc_type, exc_value, exc_traceback = sys.exc_info()
+            filter.disable()
+            result = self.ReportError(exc_type, exc_value, exc_traceback,
+                                      req=filter.req, filter=filter,
+                                      phase=filter.name,
+                                      hname=filter.req.filename,
+                                      debug=debug)
+        finally:
+            exc_traceback = None
+
+        raise
+
+    return apache.OK
+
+_callback.IncludeDispatch = new.instancemethod(
+        IncludeDispatch, _callback, apache.CallBack)
+
+def ImportDispatch(self, name):
+
+    config = apache.main_server.get_config()
+
+    debug = int(config.get("PythonDebug", "0"))
+
+    # evaluate pythonpath and set sys.path to
+    # resulting value if not already done
+
+    if config.has_key("PythonPath"): 
+        apache._path_cache_lock.acquire() 
+        try: 
+            pathstring = config["PythonPath"] 
+            if not apache._path_cache.has_key(pathstring): 
+                newpath = eval(pathstring) 
+                apache._path_cache[pathstring] = None 
+                sys.path[:] = newpath 
+        finally: 
+            apache._path_cache_lock.release()
+
+    # split module::function
+    l = name.split('::', 1)
+    module_name = l[0]
+    func_name = None
+    if len(l) != 1:
+        func_name = l[1]
+
+    try:
+        # Setup transient per request modules cache.
+        # Note that this cache will always be thrown
+        # away when the module has been imported.
+
+        _setup_modules_cache()
+
+        # Import the module.
+
+        module = import_module(module_name, log=debug)
+
+        # Optionally call function within module.
+
+        if func_name:
+            getattr(module, func_name)()
+
+    finally:
+
+        # Discard the modules cache entry.
+
+        _cleanup_modules_cache()
+
+_callback.ImportDispatch = new.instancemethod(
+        ImportDispatch, _callback, apache.CallBack)

Modified: httpd/mod_python/trunk/src/include/mpversion.h
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/src/include/mpversion.h?rev=387186&r1=387185&r2=387186&view=diff
==============================================================================
--- httpd/mod_python/trunk/src/include/mpversion.h (original)
+++ httpd/mod_python/trunk/src/include/mpversion.h Mon Mar 20 03:18:21 2006
@@ -1,5 +1,5 @@
 #define MPV_MAJOR 3
 #define MPV_MINOR 3
 #define MPV_PATCH 0
-#define MPV_BUILD 20060319
-#define MPV_STRING "3.3.0-dev-20060319"
+#define MPV_BUILD 20060320
+#define MPV_STRING "3.3.0-dev-20060320"

Modified: httpd/mod_python/trunk/test/htdocs/tests.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/test/htdocs/tests.py?rev=387186&r1=387185&r2=387186&view=diff
==============================================================================
--- httpd/mod_python/trunk/test/htdocs/tests.py (original)
+++ httpd/mod_python/trunk/test/htdocs/tests.py Mon Mar 20 03:18:21 2006
@@ -352,6 +352,8 @@
             self.fail("get_config return should show PythonDebug 1")
 
         log("req.get_options(): %s" % `req.get_options()`)
+        for option in apache.main_server.get_options().keys():
+            del req.get_options()[option]
         if req.get_options() != apache.table({"testing":"123"}):
             self.fail("get_options() should contain 'testing':'123', contains %s"%req.get_options().items())
 

Modified: httpd/mod_python/trunk/test/test.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/test/test.py?rev=387186&r1=387185&r2=387186&view=diff
==============================================================================
--- httpd/mod_python/trunk/test/test.py (original)
+++ httpd/mod_python/trunk/test/test.py Mon Mar 20 03:18:21 2006
@@ -325,6 +325,7 @@
             Listen(PORT),
             PythonOption('mod_python.mutex_directory %s' % TMP_DIR),
             PythonOption('PythonOptionTest sample_value'),
+            #PythonOption('mod_python.future.importer *'),
             DocumentRoot(DOCUMENT_ROOT),
             LoadModule("python_module %s" % quoteIfSpace(MOD_PYTHON_SO)))
 
@@ -1665,8 +1666,6 @@
                         Directory(DOCUMENT_ROOT,
                                   SetHandler("mod_python"),
                                   PythonHandler("tests"),
-                                  PythonOption('PythonOptionTest ""'),
-                                  PythonOption('mod_python.mutex_directory ""'),
                                   PythonOption("testing 123"),
                                   PythonDebug("On")))
         return str(c)