[Zope-Checkins] SVN: Zope/trunk/lib/python/ZClasses/_pmc.txt Synced
up up with persistentclass.txt from ZODB, which was
Jim Fulton
jim at zope.com
Fri Apr 8 06:08:27 EDT 2005
Log message for revision 29904:
Synced up up with persistentclass.txt from ZODB, which was
originally derived from this test.
Changed:
U Zope/trunk/lib/python/ZClasses/_pmc.txt
-=-
Modified: Zope/trunk/lib/python/ZClasses/_pmc.txt
===================================================================
--- Zope/trunk/lib/python/ZClasses/_pmc.txt 2005-04-07 23:48:40 UTC (rev 29903)
+++ Zope/trunk/lib/python/ZClasses/_pmc.txt 2005-04-08 10:08:26 UTC (rev 29904)
@@ -10,6 +10,8 @@
- They can only contain picklable subobjects
+- They don't live in regular file-system modules
+
Let's look at an example:
>>> def __init__(self, name):
@@ -22,6 +24,7 @@
>>> class C:
... __metaclass__ = ZClasses._pmc.ZClassPersistentMetaClass
... __init__ = __init__
+ ... __module__ = '__zodb__'
... foo = foo
... kind = 'sample'
@@ -30,6 +33,12 @@
persistent class must be picklable. We defined the methods as global
functions to make them picklable.
+Also note that we explictly set the module. Persistent classes don't
+live in normal Python modules. Rather, they live in the database. We
+use information in __module__ to record where in the database. When
+we want to use a database, we will need to supply a custom class
+factory to load instances of the class.
+
The class we created works a lot like other persistent objects. It
has standard standard persistent attributes:
@@ -63,17 +72,15 @@
>>> C._p_changed
False
-Now, we can store the class in a database. We have to be careful,
-however, to use the ZClass-aware class factory so that we can find
-ZClasses, which are stored in the database, rather than in modules:
+Now, we can store the class in a database. We're going to use an
+explicit transaction manager so that we can show parallel transactions
+without having to use threads.
- >>> import Zope2.App.ClassFactory
- >>> some_database.classFactory = Zope2.App.ClassFactory.ClassFactory
-
- >>> connection = some_database.open()
+ >>> import transaction
+ >>> tm = transaction.TransactionManager()
+ >>> connection = some_database.open(txn_mgr=tm)
>>> connection.root()['C'] = C
- >>> import transaction
- >>> transaction.commit()
+ >>> tm.commit()
Now, if we look at the persistence variables, we'll see that they have
values:
@@ -102,7 +109,7 @@
If we abort the transaction:
- >>> transaction.abort()
+ >>> tm.abort()
Then the class will return to it's prior state:
@@ -114,23 +121,15 @@
>>> c.bar()
bar first
-We can open another connection and access the class there. Let's do
-that in another thread:
- >>> import threading
- >>> def run(func):
- ... thread = threading.Thread(target=func)
- ... thread.start()
- ... thread.join()
+We can open another connection and access the class there.
- >>> def read_class():
- ... connection = some_database.open()
- ... C = connection.root()['C']
- ... c = C('other')
- ... c.bar()
- ... connection.close()
+ >>> tm2 = transaction.TransactionManager()
+ >>> connection2 = some_database.open(txn_mgr=tm2)
- >>> run(read_class)
+ >>> C2 = connection2.root()['C']
+ >>> c2 = C2('other')
+ >>> c2.bar()
bar other
If we make changes without commiting them:
@@ -139,28 +138,27 @@
>>> c.bar()
baz first
-Other connections/threads are unaffected:
+ >>> C is C2
+ False
- >>> run(read_class)
+Other connections are unaffected:
+
+ >>> connection2.sync()
+ >>> c2.bar()
bar other
Until we commit:
- >>> transaction.commit()
- >>> run(read_class)
+ >>> tm.commit()
+ >>> connection2.sync()
+ >>> c2.bar()
baz other
-Similarly, we don't see changes made in other connextions:
+Similarly, we don't see changes made in other connections:
- >>> def write_class():
- ... connection = some_database.open()
- ... C = connection.root()['C']
- ... C.color = 'red'
- ... transaction.commit()
- ... connection.close()
+ >>> C2.color = 'red'
+ >>> tm2.commit()
- >>> run(write_class)
-
>>> c.color
Traceback (most recent call last):
...
@@ -172,3 +170,114 @@
>>> c.color
'red'
+Instances of Persistent Classes
+-------------------------------
+
+We can, of course, store instances of perstent classes in the
+database:
+
+ >>> c.color = 'blue'
+ >>> connection.root()['c'] = c
+ >>> tm.commit()
+
+ >>> connection2.sync()
+ >>> connection2.root()['c'].color
+ 'blue'
+
+NOTE: If a non-persistent instance of a persistent class is copied,
+ the class may be copied as well. This is usually not the desired
+ result.
+
+
+Persistent instances of persistent classes
+------------------------------------------
+
+Persistent instances of persistent classes are handled differently
+than normal instances. When we copy a persistent instances of a
+persistent class, we want to avoid copying the class.
+
+Lets create a persistent class that subclasses Persistent:
+
+ >>> import persistent
+ >>> class P(persistent.Persistent, C):
+ ... __module__ = '__zodb__'
+ ... color = 'green'
+
+ >>> connection.root()['P'] = P
+
+ >>> import persistent.mapping
+ >>> connection.root()['obs'] = persistent.mapping.PersistentMapping()
+ >>> p = P('p')
+ >>> connection.root()['obs']['p'] = p
+ >>> tm.commit()
+
+You might be wondering why we didn't just stick 'p' into the root
+object. We created an intermediate persistent object instead. We are
+storing persistent classes in the root object. To create a ghost for a
+persistent instance of a persistent class, we need to be able to be
+able to access the root object and it must be loaded first. If the
+instance was in the root object, we'd be unable to create it while
+loading the root object.
+
+Now, if we try to load it, we get a broken oject:
+
+ >>> connection2.sync()
+ >>> connection2.root()['obs']['p']
+ <persistent broken __zodb__.P instance '\x00\x00\x00\x00\x00\x00\x00\x04'>
+
+because the module, "__zodb__" can't be loaded. We need to provide a
+class factory that knows about this special module. Here we'll supply a
+sample class factory that looks up a class name in the database root
+if the module is "__zodb__". It falls back to the normal class lookup
+for other modules:
+
+ >>> from ZODB.broken import find_global
+ >>> def classFactory(connection, modulename, globalname):
+ ... if modulename == '__zodb__':
+ ... return connection.root()[globalname]
+ ... return find_global(modulename, globalname)
+
+ >>> some_database.classFactory = classFactory
+
+Normally, the classFactory should be set before a database is opened.
+We'll reopen the connections we're using. We'll assign the old
+connections to a variable first to prevent getting them from the
+connection pool:
+
+ >>> old = connection, connection2
+ >>> connection = some_database.open(txn_mgr=tm)
+ >>> connection2 = some_database.open(txn_mgr=tm2)
+
+Now, we can read the object:
+
+ >>> connection2.root()['obs']['p'].color
+ 'green'
+ >>> connection2.root()['obs']['p'].color = 'blue'
+ >>> tm2.commit()
+
+ >>> connection.sync()
+ >>> p = connection.root()['obs']['p']
+ >>> p.color
+ 'blue'
+
+Copying
+-------
+
+If we copy an instance via export/import, the copy and the original
+share the same class:
+
+ >>> file = connection.exportFile(p._p_oid)
+ >>> file.seek(0)
+ >>> cp = connection.importFile(file)
+ >>> cp.color
+ 'blue'
+
+ >>> cp is not p
+ True
+
+ >>> cp.__class__ is p.__class__
+ True
+
+
+
+XXX test abort of import
More information about the Zope-Checkins
mailing list