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 nl...@apache.org on 2005/05/18 22:24:36 UTC
svn commit: r170815 - in /httpd/mod_python/trunk:
lib/python/mod_python/cache.py lib/python/mod_python/publisher.py
src/include/mpversion.h
Author: nlehuen
Date: Wed May 18 13:24:36 2005
New Revision: 170815
URL: http://svn.apache.org/viewcvs?rev=170815&view=rev
Log:
Implemented MODPYTHON-54 - we need a bit of documentation, now...
Modified:
httpd/mod_python/trunk/lib/python/mod_python/cache.py
httpd/mod_python/trunk/lib/python/mod_python/publisher.py
httpd/mod_python/trunk/src/include/mpversion.h
Modified: httpd/mod_python/trunk/lib/python/mod_python/cache.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/cache.py?rev=170815&r1=170814&r2=170815&view=diff
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/cache.py (original)
+++ httpd/mod_python/trunk/lib/python/mod_python/cache.py Wed May 18 13:24:36 2005
@@ -23,7 +23,7 @@
# -*- coding: CP1252 -*-
from threading import Lock
from os import fstat
-from time import time,strptime
+from time import time, strptime
from calendar import timegm
import urllib2
import re
@@ -33,7 +33,7 @@
class Entry(object):
""" A cache entry, mostly an internal object. """
- def __init__(self,key):
+ def __init__(self, key):
object.__init__(self)
self._key=key
self._value=NOT_INITIALIZED
@@ -42,7 +42,7 @@
class Cache(object):
""" An abstract, multi-threaded cache object. """
- def __init__(self,max_size=0):
+ def __init__(self, max_size=0):
""" Builds a cache with a limit of max_size entries.
If this limit is exceeded, the Least Recently Used entry is discarded.
if max_size==0, the cache is unbounded (no LRU rule is applied).
@@ -58,7 +58,7 @@
self._head._previous=self._head
self._head._next=self._head
- def __setitem__(self,name,value):
+ def __setitem__(self, name, value):
""" Populates the cache with a given name and value. """
self._lock.acquire()
try:
@@ -66,21 +66,21 @@
entry = self._dict.get(key)
if not entry:
entry = Entry(key)
- self._pack(entry,value)
+ self._pack(entry, value)
self._dict[key]=entry
if self._maxsize:
entry._next = entry._previous = None
self._access(entry)
self._checklru()
else:
- self._pack(entry,value)
+ self._pack(entry, value)
if self._maxsize:
self._access(entry)
self.commit()
finally:
self._lock.release()
- def __getitem__(self,name):
+ def __getitem__(self, name):
""" Gets a value from the cache, builds it if required. """
self._lock.acquire()
try:
@@ -102,21 +102,21 @@
try:
value = self._unpack(entry)
if value is NOT_INITIALIZED:
- opened = self.check(name,entry)
- value = self.build(name,opened,entry)
- self._pack(entry,value)
+ opened = self.check(key, name, entry)
+ value = self.build(key, name, opened, entry)
+ self._pack(entry, value)
self.commit()
else:
- opened = self.check(name,entry)
+ opened = self.check(key, name, entry)
if opened is not None:
- value = self.build(name,opened,entry)
- self._pack(entry,value)
+ value = self.build(key, name, opened, entry)
+ self._pack(entry, value)
self.commit()
return value
finally:
entry._lock.release()
- def __delitem__(self,key):
+ def __delitem__(self, key):
self._lock.acquire()
try:
key = self.key(key)
@@ -146,7 +146,7 @@
else:
return None
- def key(self,name):
+ def key(self, name):
""" Override this method to extract a key from the name passed to the [] operator """
return name
@@ -165,7 +165,7 @@
finally:
self._lock.release()
- def check(self,name,entry):
+ def check(self, key, name, entry):
""" Override this method to check whether the entry with the given name is stale. Return None if it is fresh
or an opened resource if it is stale. The object returned will be passed to the 'build' method as the 'opened' parameter.
Use the 'entry' parameter to store meta-data if required. Don't worry about multiple threads accessing the same name,
@@ -173,13 +173,13 @@
"""
return None
- def build(self,name,opened,entry):
+ def build(self, key, name, opened, entry):
""" Build the cached value with the given name from the given opened resource. Use entry to obtain or store meta-data if needed.
Don't worry about multiple threads accessing the same name, as this method is properly isolated.
"""
raise NotImplementedError()
- def _access(self,entry):
+ def _access(self, entry):
" Internal use only, must be invoked within a cache lock. Updates the access list. """
if entry._next is not self._head:
if entry._previous is not None:
@@ -202,11 +202,11 @@
lru._next._previous=lru._previous
del self._dict[lru._key]
- def _pack(self,entry,value):
+ def _pack(self, entry, value):
""" Store the value in the entry. """
entry._value=value
- def _unpack(self,entry):
+ def _unpack(self, entry):
""" Recover the value from the entry, returns NOT_INITIALIZED if it is not OK. """
return entry._value
@@ -216,10 +216,10 @@
computations but letting them go as soon as they are not needed by anybody.
"""
- def _pack(self,entry,value):
- entry._value=weakref.ref(value,lambda ref: self.__delitem__(entry._key))
+ def _pack(self, entry, value):
+ entry._value=weakref.ref(value, lambda ref: self.__delitem__(entry._key))
- def _unpack(self,entry):
+ def _unpack(self, entry):
if entry._value is NOT_INITIALIZED:
return NOT_INITIALIZED
@@ -234,13 +234,13 @@
Whenever the files are modified (according to their modification time) the cache is updated.
Override the build method to obtain more interesting behaviour.
"""
- def __init__(self,max_size=0,mode='rb'):
- Cache.__init__(self,max_size)
+ def __init__(self, max_size=0, mode='rb'):
+ Cache.__init__(self, max_size)
self.mode=mode
- def check(self,name,entry):
+ def check(self, key, name, entry):
""" Checks the modification time to determine whether a file has changed or not. """
- f = file(name,self.mode)
+ f = file(key, self.mode)
fs = fstat(f.fileno())
ts1 = fs[-2]
try:
@@ -254,7 +254,7 @@
else:
return None
- def build(self,name,opened,entry):
+ def build(self, key, name, opened, entry):
""" Return the content of the file as a string. Override this for better behaviour. """
try:
return opened.read()
@@ -262,17 +262,17 @@
opened.close()
def parseRFC822Time(t):
- return timegm(strptime(t,"%a, %d %b %Y %H:%M:%S %Z"))
+ return timegm(strptime(t, "%a, %d %b %Y %H:%M:%S %Z"))
-re_max_age=re.compile('max-age\s*=\s*(\d+)',re.I)
+re_max_age=re.compile('max-age\s*=\s*(\d+)', re.I)
class HTTPEntity(object):
- def __init__(self,entity,metadata):
+ def __init__(self, entity, metadata):
self.entity=entity
self.metadata=metadata
def __repr__(self):
- return 'HTTPEntity(%s,%s)'%(repr(self.entity),self.metadata)
+ return 'HTTPEntity(%s, %s)'%(repr(self.entity), self.metadata)
def __str__(self):
return self.entity
@@ -282,8 +282,8 @@
Uses Expires, ETag and Last-Modified headers to minimize bandwidth usage.
Partial Cache-Control support (only max-age is supported).
"""
- def check(self,name,entry):
- request = urllib2.Request(name)
+ def check(self, key, name, entry):
+ request = urllib2.Request(key)
try:
if time()<entry._expires:
@@ -307,7 +307,7 @@
if match:
entry._expires=time()+int(match.group(1))
expiration = True
- except (KeyError,ValueError):
+ except (KeyError, ValueError):
pass
if not expiration:
try:
@@ -321,13 +321,13 @@
# validator handling
validation = False
try:
- entry._validator='If-None-Match',headers['etag']
+ entry._validator='If-None-Match', headers['etag']
validation = True
except KeyError:
pass
if not validation:
try:
- entry._validator='If-Modified-Since',headers['last-modified']
+ entry._validator='If-Modified-Since', headers['last-modified']
except KeyError:
pass
@@ -339,35 +339,34 @@
else:
raise error
- def build(self,name,opened,entry):
+ def build(self, key, name, opened, entry):
try:
- return HTTPEntity(opened.read(),dict(opened.info()))
+ return HTTPEntity(opened.read(), dict(opened.info()))
finally:
opened.close()
class Module(object):
""" Placeholder object for the module definition. """
- def __init__(self,filename):
+ def __init__(self, filename):
self.__file__=filename
def __repr__(self):
- return '<%s object at 0x%08x from %s>'%(type(self).__name__,id(self),self.__file__)
+ return '<%s object at 0x%08x from %s>'%(type(self).__name__, id(self), self.__file__)
class ModuleCache(FileCache):
""" A module cache. Give it a file name, it returns a module-like object
which results from the execution of the Python script it contains.
"""
- def __init__(self,max_size=0):
- FileCache.__init__(self,max_size,'r')
+ def __init__(self, max_size=0):
+ FileCache.__init__(self, max_size, 'r')
- def build(self,name,opened,entry):
+ def build(self, key, name, opened, entry):
try:
- name = self.key(name)
- module = Module(name)
+ module = Module(key)
exec opened in module.__dict__
return module
# I used to use imp.load_source but right now I'm trying the stuff above
- # return imp.load_source(re.sub('\W','_',name),name,opened)
+ # return imp.load_source(re.sub('\W', '_', name), name, opened)
finally:
opened.close()
@@ -375,34 +374,34 @@
""" A module cache. Give it a file name, it returns a module-like object
which results from the execution of the Python script it contains.
"""
- def __init__(self,max_size=0):
- HTTPCache.__init__(self,max_size)
+ def __init__(self, max_size=0):
+ HTTPCache.__init__(self, max_size)
- def build(self,name,opened,entry):
+ def build(self, key, name, opened, entry):
try:
- module = Module(name)
- text = opened.read().replace('\r\n','\n')
- code = compile(text,name,'exec')
+ module = Module(key)
+ text = opened.read().replace('\r\n', '\n')
+ code = compile(text, name, 'exec')
exec code in module.__dict__
return module
# I used to use imp.load_source but right now I'm trying the stuff above
- # return imp.load_source(re.sub('\W','_',name),name,opened)
+ # return imp.load_source(re.sub('\W', '_', name), name, opened)
finally:
opened.close()
class FunctionCache(Cache):
- def __init__(self,function,max_size=0):
- Cache.__init__(self,max_size)
+ def __init__(self, function, max_size=0):
+ Cache.__init__(self, max_size)
self.function=function
- def __call__(self,*args,**kw):
+ def __call__(self, *args, **kw):
if kw:
- # a dict is not hashable so we build a tuple of (key,value) pairs
+ # a dict is not hashable so we build a tuple of (key, value) pairs
kw = tuple(kw.iteritems())
- return self[args,kw]
+ return self[args, kw]
else:
- return self[args,()]
+ return self[args, ()]
- def build(self,name,opened,entry):
- args,kw = name
- return self.function(*args,**dict(kw))
+ def build(self, key, name, opened, entry):
+ args, kw = key
+ return self.function(*args, **dict(kw))
Modified: httpd/mod_python/trunk/lib/python/mod_python/publisher.py
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/lib/python/mod_python/publisher.py?rev=170815&r1=170814&r2=170815&view=diff
==============================================================================
--- httpd/mod_python/trunk/lib/python/mod_python/publisher.py (original)
+++ httpd/mod_python/trunk/lib/python/mod_python/publisher.py Wed May 18 13:24:36 2005
@@ -33,6 +33,7 @@
import sys
import os
+from os.path import normpath, split, isfile, join, dirname
import imp
import re
import base64
@@ -43,16 +44,19 @@
imp_suffixes = " ".join([x[0][1:] for x in imp.get_suffixes()])
+
+####################### The published page cache ##############################
+
from cache import ModuleCache, NOT_INITIALIZED
class PageCache(ModuleCache):
""" This is the cache for page objects. Handles the automatic reloading of pages. """
- def key(self,req):
- """ Extracts the filename from the request """
+ def key(self, req):
+ """ Extracts the normalized filename from the request """
return req.filename
- def check(self,req,entry):
+ def check(self, key, req, entry):
config = req.get_config()
autoreload=int(config.get("PythonAutoReload", 1))
if autoreload==0 and entry._value is not NOT_INITIALIZED:
@@ -60,45 +64,95 @@
# then we consider it fresh
return None
else:
- return ModuleCache.check(self,req.filename,entry)
+ return ModuleCache.check(self, key, req, entry)
- def build(self,req,opened,entry):
+ def build(self, key, req, opened, entry):
config = req.get_config()
log=int(config.get("PythonDebug", 0))
if log:
if entry._value is NOT_INITIALIZED:
- req.log_error('Publisher loading page %s'%req.filename,apache.APLOG_NOTICE)
+ req.log_error('Publisher loading page %s'%req.filename, apache.APLOG_NOTICE)
else:
- req.log_error('Publisher reloading page %s'%req.filename,apache.APLOG_NOTICE)
- return ModuleCache.build(self,req,opened,entry)
+ req.log_error('Publisher reloading page %s'%req.filename, apache.APLOG_NOTICE)
+ return ModuleCache.build(self, key, req, opened, entry)
page_cache = PageCache()
-class DummyRequest(object):
+class DummyModule(object):
"""
This class is used to simulate a request object to be able to import
an arbitrary module from the page cache.
"""
- def __init__(self,filename):
+ def __init__(self, filename):
self.filename = filename
def get_config(self):
return {}
- def log_error(self,message,level):
- apache.log_error(message,level)
+ def log_error(self, message, level):
+ apache.log_error(message, level)
+
+ def __str__(self):
+ return "<mod_python.publisher.DummyModule at 0x%08x for %s>"%(id(self), self.filename)
-def import_page(absolute_path):
- req = DummyRequest(absolute_path)
- return page_cache[req]
+ def __getattr__(self, name):
+ return getattr(page_cache[self], name)
-def get_page(req,relative_path):
+####################### Interface to the published page cache ##################
+
+def import_page(relative_path, auto_reload=True):
+ """
+ This imports a published page. The relative_path argument is a path
+ relative to the directory of the page where import_page() is called.
+ Hence, if we have index.py and foobar.py in the same directory, index.py
+ can simply do something like :
+
+ import mod_python.publisher
+ foobar = mod_python.publisher.import_page('foobar.py')
+
+ If auto_reload is True (the default), the returned object is not really
+ the module itself, but a placeholder object which allows the real module
+ to be automatically reloaded whenever its source file changes.
+ """
+
+ # this is quite a hack but this is handy.
+ try:
+ raise ZeroDivisionError
+ except ZeroDivisionError:
+ calling_frame = sys.exc_info()[2].tb_frame.f_back
+
+ calling_page = calling_frame.f_code.co_filename
+
+ # we look for the given page in the same directory as the calling page
+ page = normpath(join(dirname(calling_page), relative_path))
+
+ if auto_reload:
+ return DummyModule(page)
+ else:
+ # the DummyModule instance can masquerade itself as a request object
+ # so the PageCache will be happy.
+ return page_cache[DummyModule(page)]
+
+def get_page(req, relative_path):
+ """
+ This imports a published page. The relative_path argument is a path
+ relative to the published page where the request is really handled (not
+ relative to the path given in the URL).
+
+ Warning : in order to maintain consistency in case of module reloading,
+ do not store the resulting module in a place that outlives the request
+ duration.
+ """
+
real_filename = req.filename
+ req.filename = normpath(join(dirname(req.filename), relative_path))
try:
return page_cache[req]
finally:
req.filename = real_filename
+####################### The publisher handler himself ##########################
+
def handler(req):
req.allow_methods(["GET", "POST", "HEAD"])
@@ -112,11 +166,11 @@
# /directory/[module][/func_path]
# we check whether there is a file name or not
- path, filename = os.path.split(req.filename)
+ path, filename = split(req.filename)
if not filename:
# if not, we look for index.py
- req.filename = os.path.join(path,'index.py')
+ req.filename = join(path, 'index.py')
# Now we build the function path
if not req.path_info or req.path_info=='/':
@@ -134,7 +188,7 @@
# First we check if there is a Python module with that name
# just by adding a .py extension
- if os.path.isfile(req.filename+'.py'):
+ if isfile(req.filename+'.py'):
req.filename += '.py'
@@ -151,13 +205,13 @@
func_path = req.path_info[1:]
else:
- # The file does not exist, so it seems we are in the
+ # The file does not exist, so it seems we are in the
# case of a request in the form :
# /directory/func_path
# we'll just insert the module name index.py in the middle
- path, func_path = os.path.split(req.filename)
- req.filename = os.path.join(path,'index.py')
+ path, func_path = split(req.filename)
+ req.filename = join(path, 'index.py')
# I don't know if it's still possible to have a path_info
# but if we have one, we append it to the filename which
@@ -165,8 +219,11 @@
if req.path_info:
func_path = func_path + req.path_info
+ # Normalize the filename to save us from insanity on Win32.
+ req.filename = normpath(req.filename)
+
# Now we turn slashes into dots
- func_path = func_path.replace('/','.')
+ func_path = func_path.replace('/', '.')
# We remove the last dot if any
if func_path[-1:] == ".":
@@ -290,7 +347,7 @@
# note that Opera supposedly doesn't like spaces around "=" below
s = 'Basic realm="%s"' % realm
req.err_headers_out["WWW-Authenticate"] = s
- raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
+ raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
if callable(__auth__):
rc = __auth__(req, user, passwd)
@@ -303,7 +360,7 @@
if not rc:
s = 'Basic realm = "%s"' % realm
req.err_headers_out["WWW-Authenticate"] = s
- raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
+ raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
if found_access:
@@ -328,7 +385,7 @@
tp_rules = {}
# by default, built-in types cannot be traversed, but can be published
-default_builtins_tp_rule = (False,True)
+default_builtins_tp_rule = (False, True)
for t in types.__dict__.values():
if isinstance(t, type):
tp_rules[t]=default_builtins_tp_rule
Modified: httpd/mod_python/trunk/src/include/mpversion.h
URL: http://svn.apache.org/viewcvs/httpd/mod_python/trunk/src/include/mpversion.h?rev=170815&r1=170814&r2=170815&view=diff
==============================================================================
--- httpd/mod_python/trunk/src/include/mpversion.h (original)
+++ httpd/mod_python/trunk/src/include/mpversion.h Wed May 18 13:24:36 2005
@@ -2,4 +2,4 @@
#define MPV_MINOR 2
#define MPV_PATCH 0
#define MPV_BUILD 20050428
-#define MPV_STRING "3.2.0-dev-20050428"
+#define MPV_STRING "3.2.0-dev-20050518"