# spydurus provides a threadsafe, pooled Spyce interface to a durus database.
# Author: Jonathan Ellis
# If you're just trying to get a feel for Spyce, the main thing to
# understand is that this provides (via a subclass) the get method you
# see used (e.g., "data.get(id)") in .spy pages. More than that you don't
# really need to know unless you need to write your own connection
# pool module.
CONNECTIONS = 3 # max connections to put in the pool
import sys, os, os.path, threading, time
import spyce, spyceUtil
# try to start a server:
# in a production environment, you'd keep it running with inittab or something,
# so this step wouldn't be necessary.
# The best approach is to fork and then use StorageServer.serve to start durus;
# that way we don't have to worry about durus being on the path. However,
# since win32 doesn't support fork, AND that's where we're most likely to have
# PATH issues (durus on win32 doesn't modify the path on install),
# in the interest of making this run out of the box for windows people we'll
# take the StorageServer approach, but in a thread, not a new process.
# Please use the inittab approach in production; Durus will be more performant
# in its own process.
from durus.storage_server import StorageServer
from durus.file_storage import FileStorage,TempFileStorage
_runningDurus = False
def maybeStartDurus(db_path):
if spyce.getServer().threaded():
_runningDurus = True
def _maybeStartDurus():
st = db_path and FileStorage(db_path) or TempFileStorage()
try:
StorageServer(st).serve()
except:
# already running
_runningDurus = False
_t = threading.Thread(target=_maybeStartDurus)
_t.start()
def _cleanup():
if _runningDurus:
from durus.run_durus import stop_durus,DEFAULT_HOST,DEFAULT_PORT
stop_durus(DEFAULT_HOST, DEFAULT_PORT)
_t.join()
import atexit
atexit.register(_cleanup)
import Queue
q = Queue.Queue()
def initPool(connections=3, db_path=None):
"if another process takes care of durus, you never need to pass db_path"
from durus.connection import Connection
if spyce.getServer().threaded():
from durus.client_storage import ClientStorage
for i in range(connections * 3):
if q.qsize() >= connections:
break
try:
q.put(Connection(ClientStorage()))
except:
time.sleep(0.5) # sometimes takes a while for server to start
else:
# not a long-running process -- spyce running under [Fast]CGI or mod_python
st = spyceUtil.tryForAwhile(lambda: FileStorage(db_path))
if not st:
raise 'timeout while getting durus connection'
q.put(Connection(st))
if not q.qsize():
raise 'no durus connections created'
# the actual spyceModule is refreshingly short now that starting Durus is out of the way
from spyceModule import spyceModule
class spydurus(spyceModule):
def start(self):
self.conn = None
try:
self.conn = q.get(timeout=10)
except Queue.Empty:
raise 'timeout while getting durus connection'
else:
self.conn.abort() # syncs connection
# spyce automatically calls finish methods at the end of each request.
# this is an excellent way to make sure that our connection always
# returns to the queue.
def finish(self, err):
q.put(self.conn)
def root(self):
return self.conn.get_root()
def get(self, oid):
"""oid should be a formatted oid, not a raw _p_oid"""
return self.conn.get(int(oid))
def commit(self):
self.conn.commit()
# you may wish to inherit from this instead of Persistent directly
from durus.persistent import Persistent
class PersistentWithId(Persistent):
# _p_format_oid is rather cumbersome to type
def id(self):
return self._p_format_oid()
|