Author: phd
Date: 2005-01-12 12:14:08 +0000 (Wed, 12 Jan 2005)
New Revision: 533
Modified:
home/phd/SQLObject/inheritance/sqlobject/cache.py
home/phd/SQLObject/inheritance/sqlobject/col.py
Log:
Merged patches from revisions 528:532
Modified: home/phd/SQLObject/inheritance/sqlobject/cache.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/cache.py 2005-01-11 21:20:59 UTC (rev 532)
+++ home/phd/SQLObject/inheritance/sqlobject/cache.py 2005-01-12 12:14:08 UTC (rev 533)
@@ -1,3 +1,11 @@
+"""
+This implements the instance caching in SQLObject. Caching is
+relatively aggressive. All objects are retained so long as they are
+in memory, by keeping weak references to objects. We also keep other
+objects in a cache that doesn't allow them to be garbage collected
+(unless caching is turned off).
+"""
+
import threading
from weakref import ref
from time import time as now
@@ -46,6 +54,9 @@
self.lock = threading.Lock()
def tryGet(self, id):
+ """
+ This returns None, or the object in cache.
+ """
value = self.expiredCache.get(id)
if value:
# it's actually a weakref:
@@ -55,7 +66,29 @@
return self.cache.get(id)
def get(self, id):
+ """
+ This method can cause deadlocks! tryGet is safer
+ This returns the object found in cache, or None. If None,
+ then the cache will remain locked! This is so that the
+ calling function can create the object in a threadsafe manner
+ before releasing the lock. You should use this like (note
+ that ``cache`` is actually a CacheSet object in this
+ example)::
+
+ obj = cache.get(some_id, my_class)
+ if obj is None:
+ try:
+ obj = create_object(some_id)
+ cache.put(some_id, my_class, obj)
+ finally:
+ cache.finishPut(cls)
+
+ This method checks both the main cache (which retains
+ references) and the 'expired' cache, which retains only weak
+ references.
+ """
+
if self.doCache:
if self.cullCount > self.cullFrequency:
# Two threads could hit the cull in a row, but
@@ -109,21 +142,40 @@
return val
def put(self, id, obj):
+ """
+ Puts an object into the cache. Should only be called after
+ .get(), so that duplicate objects don't end up in the cache.
+ """
if self.doCache:
self.cache[id] = obj
else:
self.expiredCache[id] = ref(obj)
def finishPut(self):
+ """
+ Releases the lock that is retained when .get() is called and
+ returns None.
+ """
self.lock.release()
def created(self, id, obj):
+ """
+ Inserts and object into the cache. Should be used when no one
+ else knows about the object yet, so there cannot be any object
+ already in the cache. After a database INSERT is an example
+ of this situation.
+ """
if self.doCache:
self.cache[id] = obj
else:
self.expiredCache[id] = ref(obj)
def cull(self):
+ """
+ Runs through the cache and expires objects. E.g., if
+ ``cullFraction`` is 3, then every third object is moved to
+ the 'expired' (aka weakref) cache.
+ """
self.lock.acquire()
try:
keys = self.cache.keys()
@@ -139,11 +191,19 @@
self.lock.release()
def clear(self):
+ """
+ Removes everything from the cache. Warning! This can cause
+ duplicate objects in memory.
+ """
if self.doCache:
self.cache.clear()
self.expiredCache.clear()
def expire(self, id):
+ """
+ Expires a single object. Typically called after a delete.
+ Doesn't even keep a weakref. (@@: bad name?)
+ """
if not self.doCache:
return
self.lock.acquire()
@@ -156,6 +216,10 @@
self.lock.release()
def expireAll(self):
+ """
+ Expires all objects, moving them all into the expired/weakref
+ cache.
+ """
if not self.doCache:
return
self.lock.acquire()
@@ -167,6 +231,9 @@
self.lock.release()
def allIDs(self):
+ """
+ Returns the IDs of all objects in the cache.
+ """
if self.doCache:
all = self.cache.keys()
else:
@@ -178,6 +245,14 @@
class CacheSet(object):
+ """
+ A CacheSet is used to collect and maintain a series of caches. In
+ SQLObject, there is one CacheSet per connection, and one Cache
+ in the CacheSet for each class, since IDs are not unique across
+ classes. It contains methods similar to Cache, but that take
+ a ``cls`` argument.
+ """
+
def __init__(self, *args, **kw):
self.caches = {}
self.args = args
Modified: home/phd/SQLObject/inheritance/sqlobject/col.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-11 21:20:59 UTC (rev 532)
+++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-12 12:14:08 UTC (rev 533)
@@ -1,5 +1,21 @@
"""
-Col
+Col -- SQLObject columns
+
+Note that each column object is named BlahBlahCol, and these are used
+in class definitions. But there's also a corresponding SOBlahBlahCol
+object, which is used in SQLObject *classes*.
+
+An explanation: when a SQLObject subclass is created, the metaclass
+looks through your class definition for any subclasses of Col. It
+collects them together, and indexes them to do all the database stuff
+you like, like the magic attributes and whatnot. It then asks the Col
+object to create an SOCol object (usually a subclass, actually). The
+SOCol object contains all the interesting logic, as well as a record
+of the attribute name you used and the class it is bound to (set by
+the metaclass).
+
+So, in summary: Col objects are what you define, but SOCol objects
+are what gets used.
"""
import re, time
|