python: Added a global wsgidir module attribute to indicate currentness.
[ashd.git] / python3 / ashd / wsgidir.py
index 6b7ece7..9fc3649 100644 (file)
@@ -33,10 +33,12 @@ argument `.fpy=my.module.foohandler' can be given to pass requests for
 functions, you may want to use the getmod() function in this module.
 """
 
 functions, you may want to use the getmod() function in this module.
 """
 
-import os, threading, types, importlib
+import sys, os, threading, types, logging, importlib, getopt
 from . import wsgiutil
 
 from . import wsgiutil
 
-__all__ = ["application", "wmain", "getmod", "cachedmod"]
+__all__ = ["application", "wmain", "getmod", "cachedmod", "chain"]
+
+log = logging.getLogger("wsgidir")
 
 class cachedmod(object):
     """Cache entry for modules loaded by getmod()
 
 class cachedmod(object):
     """Cache entry for modules loaded by getmod()
@@ -56,6 +58,20 @@ class cachedmod(object):
         self.mod = mod
         self.mtime = mtime
 
         self.mod = mod
         self.mtime = mtime
 
+class current(object):
+    def __init__(self):
+        self.cond = threading.Condition()
+        self.current = True
+    def wait(self, timeout=None):
+        with self.cond:
+            self.cond.wait(timeout)
+    def uncurrent(self):
+        with self.cond:
+            self.current = False
+            self.cond.notify_all()
+    def __bool__(self):
+        return self.current
+
 modcache = {}
 cachelock = threading.Lock()
 
 modcache = {}
 cachelock = threading.Lock()
 
@@ -86,19 +102,47 @@ def getmod(path):
         if path in modcache:
             entry = modcache[path]
         else:
         if path in modcache:
             entry = modcache[path]
         else:
-            entry = cachedmod()
+            entry = [threading.Lock(), None]
             modcache[path] = entry
             modcache[path] = entry
-    with entry.lock:
-        if entry.mod is None or sb.st_mtime > entry.mtime:
+    with entry[0]:
+        if entry[1] is None or sb.st_mtime > entry[1].mtime:
             with open(path, "rb") as f:
                 text = f.read()
             code = compile(text, path, "exec")
             mod = types.ModuleType(mangle(path))
             mod.__file__ = path
             with open(path, "rb") as f:
                 text = f.read()
             code = compile(text, path, "exec")
             mod = types.ModuleType(mangle(path))
             mod.__file__ = path
-            exec(code, mod.__dict__)
-            entry.mod = mod
-            entry.mtime = sb.st_mtime
-        return entry
+            mod.__current__ = current()
+            try:
+                exec(code, mod.__dict__)
+            except:
+                mod.__current__.uncurrent()
+                raise
+            else:
+                if entry[1] is not None:
+                    entry[1].mod.__current__.uncurrent()
+                entry[1] = cachedmod(mod, sb.st_mtime)
+        return entry[1]
+
+def importlocal(filename):
+    import inspect
+    cf = inspect.currentframe()
+    if cf is None: raise ImportError("could not get current frame")
+    if cf.f_back is None: raise ImportError("could not get caller frame")
+    cfile = cf.f_back.f_code.co_filename
+    if not os.path.exists(cfile):
+        raise ImportError("caller is not in a proper file")
+    path = os.path.realpath(os.path.join(os.path.dirname(cfile), filename))
+    if '.' not in os.path.basename(path):
+        for ext in [".pyl", ".py"]:
+            if os.path.exists(path + ext):
+                path += ext
+                break
+        else:
+            raise ImportError("could not resolve file: " + filename)
+    else:
+        if not os.path.exists(cfile):
+            raise ImportError("no such file: " + filename)
+    return getmod(path).mod
 
 class handler(object):
     def __init__(self):
 
 class handler(object):
     def __init__(self):
@@ -127,19 +171,27 @@ class handler(object):
 
     def handle(self, env, startreq):
         if not "SCRIPT_FILENAME" in env:
 
     def handle(self, env, startreq):
         if not "SCRIPT_FILENAME" in env:
+            log.error("wsgidir called without SCRIPT_FILENAME set")
             return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
         path = env["SCRIPT_FILENAME"]
         if not os.access(path, os.R_OK):
             return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
         path = env["SCRIPT_FILENAME"]
         if not os.access(path, os.R_OK):
+            log.error("%s: not readable" % path)
             return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
         if "HTTP_X_ASH_PYTHON_HANDLER" in env:
             return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
         if "HTTP_X_ASH_PYTHON_HANDLER" in env:
-            handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"])
+            try:
+                handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"])
+            except Exception:
+                log.error("could not load handler %s" % env["HTTP_X_ASH_PYTHON_HANDLER"], exc_info=sys.exc_info())
+                return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
         else:
             base = os.path.basename(path)
             p = base.rfind('.')
             if p < 0:
         else:
             base = os.path.basename(path)
             p = base.rfind('.')
             if p < 0:
+                log.error("wsgidir called with neither X-Ash-Python-Handler nor a file extension: %s" % path)
                 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
             ext = base[p + 1:]
             if not ext in self.exts:
                 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
             ext = base[p + 1:]
             if not ext in self.exts:
+                log.error("unregistered file extension: %s" % ext)
                 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
             handler = self.exts[ext]
         return handler(env, startreq)
                 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
             handler = self.exts[ext]
         return handler(env, startreq)
@@ -150,12 +202,20 @@ def wmain(*argv):
     Returns the `application' function. If any arguments are given,
     they are parsed according to the module documentation.
     """
     Returns the `application' function. If any arguments are given,
     they are parsed according to the module documentation.
     """
-    ret = handler()
-    for arg in argv:
+    hnd = handler()
+    ret = hnd.handle
+
+    opts, args = getopt.getopt(argv, "-V")
+    for o, a in opts:
+        if o == "-V":
+            import wsgiref.validate
+            ret = wsgiref.validate.validator(ret)
+
+    for arg in args:
         if arg[0] == '.':
             p = arg.index('=')
         if arg[0] == '.':
             p = arg.index('=')
-            ret.addext(arg[1:p], arg[p + 1:])
-    return ret.handle
+            hnd.addext(arg[1:p], arg[p + 1:])
+    return ret
 
 def chain(env, startreq):
     """Chain-loading WSGI handler
 
 def chain(env, startreq):
     """Chain-loading WSGI handler
@@ -173,7 +233,11 @@ def chain(env, startreq):
     object.
     """
     path = env["SCRIPT_FILENAME"]
     object.
     """
     path = env["SCRIPT_FILENAME"]
-    mod = getmod(path)
+    try:
+        mod = getmod(path)
+    except Exception:
+        log.error("Exception occurred when loading %s" % path, exc_info=sys.exc_info())
+        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Could not load WSGI handler.")
     entry = None
     if mod is not None:
         with mod.lock:
     entry = None
     if mod is not None:
         with mod.lock: