python: Cleaned up dispatching in wsgidir.
[ashd.git] / python3 / ashd / wsgidir.py
index cc17b9b..5761761 100644 (file)
@@ -37,7 +37,7 @@ must, of course, be importable). When writing such handler functions,
 you will probably want to use the getmod() function in this module.
 """
 
 you will probably want to use the getmod() function in this module.
 """
 
-import os, threading, types
+import os, threading, types, importlib
 from . import wsgiutil
 
 __all__ = ["application", "wmain", "getmod", "cachedmod"]
 from . import wsgiutil
 
 __all__ = ["application", "wmain", "getmod", "cachedmod"]
@@ -55,12 +55,11 @@ class cachedmod(object):
     Additional data attributes can be arbitrarily added for recording
     any meta-data about the module.
     """
     Additional data attributes can be arbitrarily added for recording
     any meta-data about the module.
     """
-    def __init__(self, mod, mtime):
+    def __init__(self, mod = None, mtime = -1):
         self.lock = threading.Lock()
         self.mod = mod
         self.mtime = mtime
 
         self.lock = threading.Lock()
         self.mod = mod
         self.mtime = mtime
 
-exts = {}
 modcache = {}
 cachelock = threading.Lock()
 
 modcache = {}
 cachelock = threading.Lock()
 
@@ -87,35 +86,81 @@ def getmod(path):
     about the module. See its documentation for details.
     """
     sb = os.stat(path)
     about the module. See its documentation for details.
     """
     sb = os.stat(path)
-    cachelock.acquire()
-    try:
+    with cachelock:
         if path in modcache:
             entry = modcache[path]
         if path in modcache:
             entry = modcache[path]
-            if sb.st_mtime <= entry.mtime:
-                return entry
-        
-        f = open(path)
-        try:
-            text = f.read()
-        finally:
-            f.close()
-        code = compile(text, path, "exec")
-        mod = types.ModuleType(mangle(path))
-        mod.__file__ = path
-        exec(code, mod.__dict__)
-        entry = cachedmod(mod, sb.st_mtime)
-        modcache[path] = entry
+        else:
+            entry = cachedmod()
+            modcache[path] = entry
+    with entry.lock:
+        if entry.mod is None or sb.st_mtime > entry.mtime:
+            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
         return entry
-    finally:
-        cachelock.release()
+
+class handler(object):
+    def __init__(self):
+        self.lock = threading.Lock()
+        self.handlers = {}
+        self.exts = {}
+        self.addext("wsgi", "chain")
+        self.addext("wsgi3", "chain")
+
+    def resolve(self, name):
+        with self.lock:
+            if name in self.handlers:
+                return self.handlers[name]
+            p = name.rfind('.')
+            if p < 0:
+                return globals()[name]
+            mname = name[:p]
+            hname = name[p + 1:]
+            mod = importlib.import_module(mname)
+            ret = getattr(mod, hname)
+            self.handlers[name] = ret
+            return ret
+        
+    def addext(self, ext, handler):
+        self.exts[ext] = self.resolve(handler)
+
+    def handle(self, env, startreq):
+        if not "SCRIPT_FILENAME" in env:
+            return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
+        path = env["SCRIPT_FILENAME"]
+        base = os.path.basename(path)
+        p = base.rfind('.')
+        if p < 0 or not os.access(path, os.R_OK):
+            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.")
+        return(self.exts[ext](env, startreq))
+
+def wmain(*argv):
+    """Main function for ashd(7)-compatible WSGI handlers
+
+    Returns the `application' function. If any arguments are given,
+    they are parsed according to the module documentation.
+    """
+    ret = handler()
+    for arg in argv:
+        if arg[0] == '.':
+            p = arg.index('=')
+            ret.addext(arg[1:p], arg[p + 1:])
+    return ret.handle
 
 def chain(env, startreq):
     path = env["SCRIPT_FILENAME"]
     mod = getmod(path)
     entry = None
     if mod is not None:
 
 def chain(env, startreq):
     path = env["SCRIPT_FILENAME"]
     mod = getmod(path)
     entry = None
     if mod is not None:
-        mod.lock.acquire()
-        try:
+        with mod.lock:
             if hasattr(mod, "entry"):
                 entry = mod.entry
             else:
             if hasattr(mod, "entry"):
                 entry = mod.entry
             else:
@@ -124,46 +169,8 @@ def chain(env, startreq):
                 elif hasattr(mod.mod, "application"):
                     entry = mod.mod.application
                 mod.entry = entry
                 elif hasattr(mod.mod, "application"):
                     entry = mod.mod.application
                 mod.entry = entry
-        finally:
-            mod.lock.release()
     if entry is not None:
         return entry(env, startreq)
     return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Invalid WSGI handler.")
     if entry is not None:
         return entry(env, startreq)
     return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Invalid WSGI handler.")
-exts["wsgi"] = chain
-exts["wsgi3"] = chain
 
 
-def addext(ext, handler):
-    p = handler.rindex('.')
-    mname = handler[:p]
-    hname = handler[p + 1:]
-    mod = __import__(mname, fromlist = ["dummy"])
-    exts[ext] = getattr(mod, hname)
-
-def application(env, startreq):
-    """WSGI handler function
-
-    Handles WSGI requests as per the module documentation.
-    """
-    if not "SCRIPT_FILENAME" in env:
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    path = env["SCRIPT_FILENAME"]
-    base = os.path.basename(path)
-    p = base.rfind('.')
-    if p < 0 or not os.access(path, os.R_OK):
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    ext = base[p + 1:]
-    if not ext in exts:
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    return(exts[ext](env, startreq))
-
-def wmain(*argv):
-    """Main function for ashd(7)-compatible WSGI handlers
-
-    Returns the `application' function. If any arguments are given,
-    they are parsed according to the module documentation.
-    """
-    for arg in argv:
-        if arg[0] == '.':
-            p = arg.index('=')
-            addext(arg[1:p], arg[p + 1:])
-    return application
+application = handler().handle