Commit c7ec2c98 authored by Tim Peters's avatar Tim Peters

Merge the Zope part of Zope/branches/jim-fix-zclasses.

Two of the ZClasses tests are disabled here, because they
run afoul of Zope trunk's security machinery.  Jim needs to look
at them.  A blurb also needs to be added to CHANGES.txt.

Here are checkin msgs from the branch relating to Zope code
(the msgs relating to ZODB code were reproduced in the
ZODB 3.4 branch checkin):

    ------------------------------------------------------------------------
    r29869 | jim | 2005-04-04 07:04:21 -0400 (Mon, 04 Apr 2005) | 6 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.txt

    Added configuration of class factory.

    Use explicit tm for second connection rather than separate thread.

    Added copy test

    ------------------------------------------------------------------------
    r29868 | jim | 2005-04-04 07:03:57 -0400 (Mon, 04 Apr 2005) | 2 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/_pmc.txt

    Added configuration of class factory.

    ------------------------------------------------------------------------
    r29776 | jim | 2005-04-01 06:24:31 -0500 (Fri, 01 Apr 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/OFS/tests/testProductInit.py

    Added clean-up code to remove non-exixtent directoris from products
    path.

    ------------------------------------------------------------------------
    r29775 | jim | 2005-04-01 06:24:29 -0500 (Fri, 01 Apr 2005) | 2 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/tests.py

    Added missing abourt to tear-down code

    ------------------------------------------------------------------------
    r29772 | jim | 2005-04-01 06:24:22 -0500 (Fri, 01 Apr 2005) | 2 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/App/ApplicationManager.py
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/Basic.py
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.py
       M /Zope/branches/jim-fix-zclasses/lib/python/Zope2/App/startup.py

    Removed BoboPOS compatibility code.

    ------------------------------------------------------------------------
    r29201 | jim | 2005-02-18 07:18:05 -0500 (Fri, 18 Feb 2005) | 3 lines
    Changed paths:
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/27.fs
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/27.txt
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/tests.py

    Added a test for reading ZClasses and their instances from Zope 2.7
    databases.

    ------------------------------------------------------------------------
    r29149 | jim | 2005-02-15 07:24:49 -0500 (Tue, 15 Feb 2005) | 2 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.txt
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/tests.py

    Made the tests a bit more thorough.

    ------------------------------------------------------------------------
    r29148 | jim | 2005-02-15 07:24:44 -0500 (Tue, 15 Feb 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/OFS/tests/testAppInitializer.py

    Made the test cleanup more robust by cleaning up any non-existent
    directories found in Products.__path__.

    ------------------------------------------------------------------------
    r29147 | jim | 2005-02-15 07:24:37 -0500 (Tue, 15 Feb 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ExtensionClass/ExtensionClass.h
       M /Zope/branches/jim-fix-zclasses/lib/python/ExtensionClass/_ExtensionClass.c

    Fixed code that assumed that there weren't subclasses of the
    ExtensionClass meta class.

    ------------------------------------------------------------------------
    r29136 | jim | 2005-02-13 11:37:33 -0500 (Sun, 13 Feb 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.py
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.txt
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/_pmc.py
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/tests.py

    Integrated persistent metaclass with ZClasses and got basic ZClass
    test to pass.

    ------------------------------------------------------------------------
    r29135 | jim | 2005-02-13 11:15:15 -0500 (Sun, 13 Feb 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ExtensionClass/_ExtensionClass.c
       M /Zope/branches/jim-fix-zclasses/lib/python/ExtensionClass/tests.py

    Fixed bug in Base __getattro__ that caused __of__ to be missed in
    instances of instances of subclasses (meta classes) of ExtensionClass.

    ------------------------------------------------------------------------
    r29072 | jim | 2005-02-07 07:36:07 -0500 (Mon, 07 Feb 2005) | 5 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/Property.py
       M /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.py

    Fixed some code for registering class changes to avoid the
    registration of data managers that are None.  This code will,
    eventually, go away, since new persistent classes will
    take care of their own registration.

    ------------------------------------------------------------------------
    r29070 | jim | 2005-02-07 07:36:03 -0500 (Mon, 07 Feb 2005) | 3 lines
    Changed paths:
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/ZClass.txt

    Created a basic ZClass test. It still isn't used, because the
    persistent meta class hasn't been integrated yet.

    ------------------------------------------------------------------------
    r29069 | jim | 2005-02-07 07:36:01 -0500 (Mon, 07 Feb 2005) | 3 lines
    Changed paths:
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/_pmc.py
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/_pmc.txt
       A /Zope/branches/jim-fix-zclasses/lib/python/ZClasses/tests.py

    Created a meta class for persistent classes.  This is based very
    loosly on the experimental persistent class code from Zope 3.

    ------------------------------------------------------------------------
    r29068 | jim | 2005-02-07 07:35:58 -0500 (Mon, 07 Feb 2005) | 2 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/ExtensionClass/_ExtensionClass.c

    Made the ExtensionClass meta class subclassible.

    ------------------------------------------------------------------------
    r29065 | jim | 2005-02-07 07:35:52 -0500 (Mon, 07 Feb 2005) | 3 lines
    Changed paths:
       M /Zope/branches/jim-fix-zclasses/lib/python/OFS/tests/testAppInitializer.py

    Added missing cleanup code to return Products.__path__ to it's
    original state.
parents 913a1813 d612b6ea
...@@ -495,7 +495,7 @@ def test_zsp_gets_right_roles_for_methods(): ...@@ -495,7 +495,7 @@ def test_zsp_gets_right_roles_for_methods():
""" """
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
...@@ -1527,7 +1527,7 @@ def showaq(m_self, indent=''): ...@@ -1527,7 +1527,7 @@ def showaq(m_self, indent=''):
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_suite(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
......
...@@ -376,11 +376,9 @@ class ApplicationManager(Folder,CacheManager): ...@@ -376,11 +376,9 @@ class ApplicationManager(Folder,CacheManager):
return self._p_jar.db().getName() return self._p_jar.db().getName()
def db_size(self): def db_size(self):
if Globals.DatabaseVersion=='2': s=self._p_jar.db().getSize()
s=os.stat(self.db_name())[6] if type(s) is type(''):
else: return s
s=self._p_jar.db().getSize()
if type(s) is type(''): return s
if s >= 1048576.0: return '%.1fM' % (s/1048576.0) if s >= 1048576.0: return '%.1fM' % (s/1048576.0)
return '%.1fK' % (s/1024.0) return '%.1fK' % (s/1024.0)
......
...@@ -66,7 +66,7 @@ def test_wrapper_support(): ...@@ -66,7 +66,7 @@ def test_wrapper_support():
""" """
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
from ExtensionClass import Base from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute from ComputedAttribute import ComputedAttribute
......
...@@ -175,11 +175,11 @@ static struct ExtensionClassCAPIstruct { ...@@ -175,11 +175,11 @@ static struct ExtensionClassCAPIstruct {
/* The following macro checks whether a type is an extension class: */ /* The following macro checks whether a type is an extension class: */
#define PyExtensionClass_Check(TYPE) \ #define PyExtensionClass_Check(TYPE) \
(((PyObject*)(TYPE))->ob_type == ECExtensionClassType) PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType)
/* The following macro checks whether an instance is an extension instance: */ /* The following macro checks whether an instance is an extension instance: */
#define PyExtensionInstance_Check(INST) \ #define PyExtensionInstance_Check(INST) \
(((PyObject*)(INST))->ob_type->ob_type == ECExtensionClassType) PyObject_TypeCheck(((PyObject*)(INST))->ob_type, ECExtensionClassType)
#define CHECK_FOR_ERRORS(MESS) #define CHECK_FOR_ERRORS(MESS)
...@@ -213,7 +213,7 @@ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \ ...@@ -213,7 +213,7 @@ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \
/* Check whether an object has an __of__ method for returning itself /* Check whether an object has an __of__ method for returning itself
in the context of it's container. */ in the context of it's container. */
#define has__of__(O) ((O)->ob_type->ob_type == ECExtensionClassType \ #define has__of__(O) (PyObject_TypeCheck((O)->ob_type, ECExtensionClassType) \
&& (O)->ob_type->tp_descr_get != NULL) && (O)->ob_type->tp_descr_get != NULL)
/* The following macros are used to check whether an instance /* The following macros are used to check whether an instance
......
...@@ -142,7 +142,8 @@ Base_getattro(PyObject *obj, PyObject *name) ...@@ -142,7 +142,8 @@ Base_getattro(PyObject *obj, PyObject *name)
If the tp_descr_get of res is of_get, If the tp_descr_get of res is of_get,
then call it. */ then call it. */
if (res->ob_type->ob_type == &ExtensionClassType if (PyObject_TypeCheck(res->ob_type,
&ExtensionClassType)
&& res->ob_type->tp_descr_get != NULL) && res->ob_type->tp_descr_get != NULL)
res = res->ob_type->tp_descr_get( res = res->ob_type->tp_descr_get(
res, obj, res, obj,
...@@ -603,6 +604,7 @@ static PyTypeObject ExtensionClassType = { ...@@ -603,6 +604,7 @@ static PyTypeObject ExtensionClassType = {
/* tp_as_buffer */ 0, /* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT /* tp_flags */ Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_GC
| Py_TPFLAGS_BASETYPE
, ,
/* tp_doc */ "Meta-class for extension classes", /* tp_doc */ "Meta-class for extension classes",
/* tp_traverse */ (traverseproc)0, /* tp_traverse */ (traverseproc)0,
......
...@@ -709,7 +709,35 @@ def test_inheriting___doc__(): ...@@ -709,7 +709,35 @@ def test_inheriting___doc__():
""" """
from doctest import DocTestSuite def test___of___w_metaclass_instance():
"""When looking for extension class instances, need to handle meta classes
>>> class C(Base):
... pass
>>> class O(Base):
... def __of__(self, parent):
... print '__of__ called on an O'
>>> class M(ExtensionClass):
... pass
>>> class X:
... __metaclass__ = M
...
>>> class S(X, O):
... pass
>>> c = C()
>>> c.s = S()
>>> c.s
__of__ called on an O
"""
from zope.testing.doctest import DocTestSuite
import unittest import unittest
def test_suite(): def test_suite():
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
$Id$ $Id$
""" """
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_xxx(): def test_xxx():
""" """
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
$Id$ $Id$
""" """
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_suite(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
$Id$ $Id$
""" """
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_suite(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
......
...@@ -74,6 +74,9 @@ class TestInitialization( unittest.TestCase ): ...@@ -74,6 +74,9 @@ class TestInitialization( unittest.TestCase ):
App.config.setConfiguration(original_config) App.config.setConfiguration(original_config)
os.rmdir(TEMPPRODUCTS) os.rmdir(TEMPPRODUCTS)
os.rmdir(TEMPNAME) os.rmdir(TEMPNAME)
import Products
Products.__path__ = [d for d in Products.__path__
if os.path.exists(d)]
def configure(self, text): def configure(self, text):
# We have to create a directory of our own since the existence # We have to create a directory of our own since the existence
......
...@@ -19,6 +19,7 @@ from OFS.Application import Application, AppInitializer, get_products ...@@ -19,6 +19,7 @@ from OFS.Application import Application, AppInitializer, get_products
import Zope2.Startup import Zope2.Startup
import ZConfig import ZConfig
from App.config import getConfiguration, setConfiguration from App.config import getConfiguration, setConfiguration
import Products
TEMPNAME = tempfile.mktemp() TEMPNAME = tempfile.mktemp()
TEMPPRODUCTS = os.path.join(TEMPNAME, "Products") TEMPPRODUCTS = os.path.join(TEMPNAME, "Products")
...@@ -84,6 +85,8 @@ class TestProductInit( unittest.TestCase ): ...@@ -84,6 +85,8 @@ class TestProductInit( unittest.TestCase ):
del self.schema del self.schema
App.config.setConfiguration(original_config) App.config.setConfiguration(original_config)
shutil.rmtree(TEMPNAME) shutil.rmtree(TEMPNAME)
Products.__path__ = [d for d in Products.__path__
if os.path.exists(d)]
def configure(self, text): def configure(self, text):
# We have to create a directory of our own since the existence # We have to create a directory of our own since the existence
......
...@@ -57,7 +57,7 @@ $Id: tests.py,v 1.2 2003/11/28 16:46:39 jim Exp $ ...@@ -57,7 +57,7 @@ $Id: tests.py,v 1.2 2003/11/28 16:46:39 jim Exp $
import ThreadLock, threading, time import ThreadLock, threading, time
import unittest import unittest
from doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
def test_suite(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
......
Support for Zope 2.7 databases
==============================
Let's make sure we can load old ZClasses:
>>> import os
>>> from ZODB.FileStorage import FileStorage
>>> sname = os.path.join(os.path.dirname(__file__), '27.fs')
>>> s = FileStorage(sname, read_only=True)
>>> from ZODB.DB import DB
>>> db = DB(s)
>>> from Zope2.ClassFactory import ClassFactory
>>> db.classFactory = ClassFactory
>>> conn = db.open()
>>> ac = conn.root()['Application'].ac
>>> ac.eek()
'xxx'
>>> ac.y = 2
>>> ac.eek()
'xx'
...@@ -64,7 +64,7 @@ class ZClassBasicSheet(OFS.PropertySheets.PropertySheet, ...@@ -64,7 +64,7 @@ class ZClassBasicSheet(OFS.PropertySheets.PropertySheet,
def classIcon(self): return self.getClassAttr('icon','') def classIcon(self): return self.getClassAttr('icon','')
def show_class_id(self): return Globals.DatabaseVersion=='3' def show_class_id(self): return True
def class_id(self): def class_id(self):
return (self.getClassAttr('__module__','') or '')[1:] return (self.getClassAttr('__module__','') or '')[1:]
......
...@@ -25,13 +25,13 @@ class ClassCaretaker: ...@@ -25,13 +25,13 @@ class ClassCaretaker:
def __setattr__(self, name, v): def __setattr__(self, name, v):
klass=self._k klass=self._k
setattr(klass, name, v) setattr(klass, name, v)
if not getattr(klass,'_p_changed',None): if not getattr(klass,'_p_changed',None) and klass._p_jar is not None:
transaction.get().register(klass) transaction.get().register(klass)
klass._p_changed=1 klass._p_changed=1
def __delattr__(self, name): def __delattr__(self, name):
klass=self._k klass=self._k
delattr(klass, name) delattr(klass, name)
if not getattr(klass,'_p_changed',None): if not getattr(klass,'_p_changed',None) and klass._p_jar is not None:
transaction.get().register(klass) transaction.get().register(klass)
klass._p_changed=1 klass._p_changed=1
...@@ -272,7 +272,7 @@ class ZInstanceSheet(OFS.PropertySheets.FixedSchema, ...@@ -272,7 +272,7 @@ class ZInstanceSheet(OFS.PropertySheets.FixedSchema,
Globals.default__class_init__(ZInstanceSheet) Globals.default__class_init__(ZInstanceSheet)
def rclass(klass): def rclass(klass):
if not getattr(klass, '_p_changed', 0): if not getattr(klass, '_p_changed', 0) and klass._p_jar is not None:
transaction.get().register(klass) transaction.get().register(klass)
klass._p_changed=1 klass._p_changed=1
......
...@@ -22,6 +22,7 @@ from ComputedAttribute import ComputedAttribute ...@@ -22,6 +22,7 @@ from ComputedAttribute import ComputedAttribute
from Products.PythonScripts.PythonScript import PythonScript from Products.PythonScripts.PythonScript import PythonScript
from zExceptions import BadRequest, Redirect from zExceptions import BadRequest, Redirect
import webdav.Collection import webdav.Collection
import ZClasses._pmc
import transaction import transaction
...@@ -93,7 +94,18 @@ from OFS.misc_ import p_ ...@@ -93,7 +94,18 @@ from OFS.misc_ import p_
p_.ZClass_Icon=Globals.ImageFile('class.gif', globals()) p_.ZClass_Icon=Globals.ImageFile('class.gif', globals())
class PersistentClass(Base, webdav.Collection.Collection ): class PersistentClass(Base, webdav.Collection.Collection ):
def __class_init__(self): pass
__metaclass__ = ZClasses._pmc.ZClassPersistentMetaClass
# We need this class to be treated as a normal global class, even
# though it is an instance of ZClassPersistentMetaClass.
# Subclasses should be stored in the database. See
# _pmc._p_DataDescr.__get__.
__global_persistent_class_not_stored_in_DB__ = True
def __class_init__(self):
pass
manage_addZClassForm=Globals.DTMLFile( manage_addZClassForm=Globals.DTMLFile(
'dtml/addZClass', globals(), 'dtml/addZClass', globals(),
...@@ -116,12 +128,6 @@ def find_class(ob, name): ...@@ -116,12 +128,6 @@ def find_class(ob, name):
continue continue
raise AttributeError, name raise AttributeError, name
def dbVersionEquals(ver):
# A helper function to isolate db version checking.
return hasattr(Globals, 'DatabaseVersion') and \
Globals.DatabaseVersion == ver
bad_id=re.compile('[^a-zA-Z0-9_]').search bad_id=re.compile('[^a-zA-Z0-9_]').search
def manage_addZClass(self, id, title='', baseclasses=[], def manage_addZClass(self, id, title='', baseclasses=[],
...@@ -129,6 +135,7 @@ def manage_addZClass(self, id, title='', baseclasses=[], ...@@ -129,6 +135,7 @@ def manage_addZClass(self, id, title='', baseclasses=[],
zope_object=0): zope_object=0):
"""Add a Z Class """Add a Z Class
""" """
if bad_id(id) is not None: if bad_id(id) is not None:
raise BadRequest, ( raise BadRequest, (
'The id %s is invalid as a class name.' % id) 'The id %s is invalid as a class name.' % id)
...@@ -337,8 +344,6 @@ class ZClass( Base ...@@ -337,8 +344,6 @@ class ZClass( Base
changeClassId__roles__ = () # Private changeClassId__roles__ = () # Private
def changeClassId(self, newid=None): def changeClassId(self, newid=None):
if not dbVersionEquals('3'):
return
if newid is None: newid=self._new_class_id() if newid is None: newid=self._new_class_id()
self._unregister() self._unregister()
if newid: if newid:
...@@ -413,16 +418,12 @@ class ZClass( Base ...@@ -413,16 +418,12 @@ class ZClass( Base
self.propertysheets.methods.manage_afterClone(item) self.propertysheets.methods.manage_afterClone(item)
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
if not dbVersionEquals('3'):
return
if not self._zclass_.__module__: if not self._zclass_.__module__:
self.setClassAttr('__module__', self._new_class_id()) self.setClassAttr('__module__', self._new_class_id())
self._register() self._register()
self.propertysheets.methods.manage_afterAdd(item, container) self.propertysheets.methods.manage_afterAdd(item, container)
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
if not dbVersionEquals('3'):
return
self._unregister() self._unregister()
self.propertysheets.methods.manage_beforeDelete(item, container) self.propertysheets.methods.manage_beforeDelete(item, container)
...@@ -517,7 +518,7 @@ class ZClass( Base ...@@ -517,7 +518,7 @@ class ZClass( Base
def setClassAttr(self, name, value): def setClassAttr(self, name, value):
c=self._zclass_ c=self._zclass_
setattr(c, name, value) setattr(c, name, value)
if not c._p_changed: if (not c._p_changed) and (c._p_jar is not None):
transaction.get().register(c) transaction.get().register(c)
c._p_changed=1 c._p_changed=1
...@@ -525,7 +526,7 @@ class ZClass( Base ...@@ -525,7 +526,7 @@ class ZClass( Base
def delClassAttr(self, name): def delClassAttr(self, name):
c=self._zclass_ c=self._zclass_
delattr(c, name) delattr(c, name)
if not c._p_changed: if (not c._p_changed) and (c._p_jar is not None):
transaction.get().register(c) transaction.get().register(c)
c._p_changed=1 c._p_changed=1
......
Basic ZClass Tests
==================
We can create ZClasses from Python, It's a bit complicated, as
ZClasses were designed mainly to be used from the web.
First, we need to install the ZClass-aware class factory in our
database:
>>> import Zope2.App.ClassFactory
>>> some_database.classFactory = Zope2.App.ClassFactory.ClassFactory
To do anything, we need a working Zope object space:
>>> conn = some_database.open()
>>> from OFS.Application import Application
>>> app = Application()
>>> conn.root()['Application'] = app
>>> from OFS.Application import initialize
>>> initialize(app)
Once we have an object space, we need to create a product to hold the ZClass:
>>> app.Control_Panel.Products.manage_addProduct('test', '')
>>> test = app.Control_Panel.Products['test']
Then we can create the ZClass in the product:
>>> test.manage_addZClass('C', zope_object=True, CreateAFactory=True)
Having created a ZClass, we can create an instance:
>>> c = test.C()
>>> c._setId('c')
>>> app._setObject('c', c)
'c'
Now, ZClass instances aren't very interesting by themselves. We can
give them data by defining property sheets:
>>> test.C.propertysheets.common.manage_addCommonSheet('basic', '')
>>> test.C.propertysheets.common['basic'].manage_addProperty(
... 'x', 'hee ', 'string')
>>> app.c.x
'hee '
>>> test.C.propertysheets.common['basic'].manage_addProperty(
... 'y', 42, 'int')
>>> app.c.y
42
Of course, we can change the data:
>>> app.c.x = 'hi '
>>> app.c.y = 3
>>> app.c.x, app.c.y
('hi ', 3)
We can also add methods, such as Python scripts:
>>> test.C.propertysheets.methods.manage_addProduct[
... 'PythonScripts'].manage_addPythonScript('eek')
''
>>> test.C.propertysheets.methods['eek'].ZPythonScript_edit('',
... 'return container.x * container.y')
>>> app.c.eek()
'hi hi hi '
Let's commit our changes:
>>> import transaction
>>> transaction.commit()
We can access the class in another connection. We'll ise an explicit
transaction manager so that we can use the second connection without
creating a separate thread:
>>> tm2 = transaction.TransactionManager()
>>> conn2 = some_database.open(txn_mgr=tm2)
>>> app2 = conn2.root()['Application']
>>> test2 = app2.Control_Panel.Products['test']
>>> c2 = test2.C()
>>> c2._setId('c2')
>>> app2._setObject('c2', c2)
'c2'
>>> app2.c2.x = '*'
>>> print app2.c2.x, app2.c2.y, app2.c2.eek(), '!'
* 42 ****************************************** !
>>> print app.c.x, app.c.y, app.c.eek(), '!'
hi 3 hi hi hi !
>>> tm2.commit()
Of course, we should be able to see the new object created in the
other connection:
>>> conn.sync()
>>> app.c2.eek()
'******************************************'
We can copy instances:
>>> c3 = app.c2._getCopy(app)
>>> c3 is app.c2.aq_base
False
>>> c3.eek()
'******************************************'
>>> c3.__class__ is app.c2.__class__
True
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZCLass Persistent Meta Class
IMPORTANT -- This module is private to ZClasses and experimetal.
It is highly subject to change and likely to move
$Id$
"""
# Notes:
#
# Persistent classes are non-ghostable. This has some interesting
# ramifications:
#
# - When an object is invalidated, it must reload it's state
#
# - When an object is loaded from the database, it's state must be
# loaded. Unfortunately, there isn't a clear signal when an object is
# loaded from the database. This should probably be fixed.
#
# In the mean time, we need to infer. This should be viewed as a
# short term hack.
#
# Here's the strategy we'll use:
#
# - We'll have a need to be loaded flag that we'll set in
# __new__, through an extra argument.
#
# - When setting _p_oid and _p_jar, if both are set and we need to be
# loaded, then we'll load out state.
#
# - We'll use _p_changed is None to indicate that we're in this state.
#
import ExtensionClass
class _p_DataDescr(object):
# Descr used as base for _p_ data. Data are stored in
# _p_class_dict.
def __init__(self, name):
self.__name__ = name
def __get__(self, inst, cls):
if inst is None:
return self
if '__global_persistent_class_not_stored_in_DB__' in inst.__dict__:
raise AttributeError, self.__name__
return inst._p_class_dict.get(self.__name__)
def __set__(self, inst, v):
inst._p_class_dict[self.__name__] = v
def __delete__(self, inst):
raise AttributeError, self.__name__
class _p_oid_or_jar_Descr(_p_DataDescr):
# Special descr for _p_oid and _p_jar that loads
# state when set if both are set and and _p_changed is None
#
# See notes above
def __set__(self, inst, v):
get = inst._p_class_dict.get
if v == get(self.__name__):
return
inst._p_class_dict[self.__name__] = v
jar = get('_p_jar')
if (jar is not None
and get('_p_oid') is not None
and get('_p_changed') is None
):
jar.setstate(inst)
class _p_ChangedDescr(object):
# descriptor to handle special weird emantics of _p_changed
def __get__(self, inst, cls):
if inst is None:
return self
return inst._p_class_dict['_p_changed']
def __set__(self, inst, v):
if v is None:
return
inst._p_class_dict['_p_changed'] = bool(v)
def __delete__(self, inst):
inst._p_invalidate()
class _p_MethodDescr(object):
"""Provide unassignable class attributes
"""
def __init__(self, func):
self.func = func
def __get__(self, inst, cls):
if inst is None:
return cls
return self.func.__get__(inst, cls)
def __set__(self, inst, v):
raise AttributeError, self.__name__
def __delete__(self, inst):
raise AttributeError, self.__name__
special_class_descrs = '__dict__', '__weakref__'
class ZClassPersistentMetaClass(ExtensionClass.ExtensionClass):
_p_jar = _p_oid_or_jar_Descr('_p_jar')
_p_oid = _p_oid_or_jar_Descr('_p_oid')
_p_changed = _p_ChangedDescr()
_p_serial = _p_DataDescr('_p_serial')
def __new__(self, name, bases, cdict, _p_changed=False):
cdict = dict([(k, v) for (k, v) in cdict.items()
if not k.startswith('_p_')])
cdict['_p_class_dict'] = {'_p_changed': _p_changed}
return super(ZClassPersistentMetaClass, self).__new__(
self, name, bases, cdict)
def __getnewargs__(self):
return self.__name__, self.__bases__, {}, None
__getnewargs__ = _p_MethodDescr(__getnewargs__)
def _p_maybeupdate(self, name):
get = self._p_class_dict.get
data_manager = get('_p_jar')
if (
(data_manager is not None)
and
(get('_p_oid') is not None)
and
(get('_p_changed') == False)
):
self._p_changed = True
data_manager.register(self)
def __setattr__(self, name, v):
if not ((name.startswith('_p_') or name.startswith('_v'))):
self._p_maybeupdate(name)
super(ZClassPersistentMetaClass, self).__setattr__(name, v)
def __delattr__(self, name):
if not ((name.startswith('_p_') or name.startswith('_v'))):
self._p_maybeupdate(name)
super(ZClassPersistentMetaClass, self).__delattr__(name)
def _p_deactivate(self):
# persistent classes can't be ghosts
pass
_p_deactivate = _p_MethodDescr(_p_deactivate)
def _p_invalidate(self):
# reset state
self._p_class_dict['_p_changed'] = None
self._p_jar.setstate(self)
_p_invalidate = _p_MethodDescr(_p_invalidate)
def __getstate__(self):
return (self.__bases__,
dict([(k, v) for (k, v) in self.__dict__.items()
if not (k.startswith('_p_')
or k.startswith('_v_')
or k in special_class_descrs
)
]),
)
__getstate__ = _p_MethodDescr(__getstate__)
def __setstate__(self, state):
self.__bases__, cdict = state
cdict = dict([(k, v) for (k, v) in cdict.items()
if not k.startswith('_p_')])
_p_class_dict = self._p_class_dict
self._p_class_dict = {}
to_remove = [k for k in self.__dict__
if ((k not in cdict)
and
(k not in special_class_descrs)
and
(k != '_p_class_dict')
)]
for k in to_remove:
delattr(self, k)
for k, v in cdict.items():
setattr(self, k, v)
self._p_class_dict = _p_class_dict
self._p_changed = False
__setstate__ = _p_MethodDescr(__setstate__)
def _p_activate(self):
self._p_jar.setstate(self)
_p_activate = _p_MethodDescr(_p_activate)
Persistent Extension Classes
============================
The _pmc module provides a meta class that can be used to implement
persistent extension classes for ZClasses.
Persistent classes have the following properties:
- They cannot be turned into ghosts
- They can only contain picklable subobjects
Let's look at an example:
>>> def __init__(self, name):
... self.name = name
>>> def foo(self):
... return self.name, self.kind
>>> import ZClasses._pmc
>>> class C:
... __metaclass__ = ZClasses._pmc.ZClassPersistentMetaClass
... __init__ = __init__
... foo = foo
... kind = 'sample'
This example is obviously a bit contrived. In particular, we defined
the methods outside of the class. Why? Because all of the items in a
persistent class must be picklable. We defined the methods as global
functions to make them picklable.
The class we created works a lot like other persistent objects. It
has standard standard persistent attributes:
>>> C._p_oid
>>> C._p_jar
>>> C._p_serial
>>> C._p_changed
False
Because we haven't saved the object, the jar, oid, and serial are all
None and it's not changed.
We can create and use instances of the class:
>>> c = C('first')
>>> c.foo()
('first', 'sample')
We can modify the class and none of the persistent attributes will
change because the object hasn't been saved.
>>> def bar(self):
... print 'bar', self.name
>>> C.bar = bar
>>> c.bar()
bar first
>>> C._p_oid
>>> C._p_jar
>>> C._p_serial
>>> 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:
>>> import Zope2.App.ClassFactory
>>> some_database.classFactory = Zope2.App.ClassFactory.ClassFactory
>>> connection = some_database.open()
>>> connection.root()['C'] = C
>>> import transaction
>>> transaction.commit()
Now, if we look at the persistence variables, we'll see that they have
values:
>>> C._p_oid
'\x00\x00\x00\x00\x00\x00\x00\x01'
>>> C._p_jar is not None
True
>>> C._p_serial is not None
True
>>> C._p_changed
False
Now, if we modify the class:
>>> def baz(self):
... print 'baz', self.name
>>> C.baz = baz
>>> c.baz()
baz first
We'll see that the class has changed:
>>> C._p_changed
True
If we abort the transaction:
>>> transaction.abort()
Then the class will return to it's prior state:
>>> c.baz()
Traceback (most recent call last):
...
AttributeError: baz
>>> 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()
>>> def read_class():
... connection = some_database.open()
... C = connection.root()['C']
... c = C('other')
... c.bar()
... connection.close()
>>> run(read_class)
bar other
If we make changes without commiting them:
>>> C.bar = baz
>>> c.bar()
baz first
Other connections/threads are unaffected:
>>> run(read_class)
bar other
Until we commit:
>>> transaction.commit()
>>> run(read_class)
baz other
Similarly, we don't see changes made in other connextions:
>>> def write_class():
... connection = some_database.open()
... C = connection.root()['C']
... C.color = 'red'
... transaction.commit()
... connection.close()
>>> run(write_class)
>>> c.color
Traceback (most recent call last):
...
AttributeError: color
until we sync:
>>> connection.sync()
>>> c.color
'red'
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZClass tests
$Id$
"""
import os, sys
import unittest
import ZODB.tests.util
import transaction
from zope.testing import doctest
# XXX need to update files to get newer testing package
class FakeModule:
def __init__(self, name, dict):
self.__dict__ = dict
self.__name__ = name
def setUp(test):
test.globs['some_database'] = ZODB.tests.util.DB()
module = FakeModule('ZClasses.example', test.globs)
sys.modules[module.__name__] = module
def tearDown(test):
transaction.abort()
test.globs['some_database'].close()
del sys.modules['ZClasses.example']
def tearDown27(test):
transaction.abort()
test.globs['db'].close()
# XXX Two tests are disable because they're failing on Zope trunk;
# XXX they didn't fail on Jim's branch:
#
# C:\Code\zt>\python23\python.exe test.py ZClas
# Running unit tests from C:\Code\zt\lib\python
# ======================================================================
# FAIL: Doctest: ZClass.txt
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 2102, in runTest
# raise self.failureException(self.format_failure(new.getvalue()))
# AssertionError: Failed doctest test for ZClass.txt
# File "C:\Code\zt\lib\python\ZClasses\ZClass.txt", line 0
#
# ----------------------------------------------------------------------
# File "C:\Code\zt\lib\python\ZClasses\ZClass.txt", line 88, in ZClass.txt
# Failed example:
# print app2.c2.x, app2.c2.y, app2.c2.eek(), '!'
# Exception raised:
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 1315, in __run
# compileflags, 1) in test.globs
# File "<doctest ZClass.txt[35]>", line 1, in ?
# print app2.c2.x, app2.c2.y, app2.c2.eek(), '!'
# File "C:\Code\zt\lib\python\AccessControl\PermissionMapping.py", line 150, in __call__
# return apply(self, args, kw)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 311, in __call__
# return self._bindAndExec(args, kw, None)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 348, in _bindAndExec
# return self._exec(bound_data, args, kw)
# File "C:\Code\zt\lib\python\Products\PythonScripts\PythonScript.py", line 323, in _exec
# result = f(*args, **kw)
# File "Script (Python)", line 1, in eek
# Unauthorized: You are not allowed to access 'x' in this context
# ----------------------------------------------------------------------
# File "C:\Code\zt\lib\python\ZClasses\ZClass.txt", line 110, in ZClass.txt
# Failed example:
# c3.eek()
# Exception raised:
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 1315, in __run
# compileflags, 1) in test.globs
# File "<doctest ZClass.txt[42]>", line 1, in ?
# c3.eek()
# File "C:\Code\zt\lib\python\AccessControl\PermissionMapping.py", line 150, in __call__
# return apply(self, args, kw)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 311, in __call__
# return self._bindAndExec(args, kw, None)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 348, in _bindAndExec
# return self._exec(bound_data, args, kw)
# File "C:\Code\zt\lib\python\Products\PythonScripts\PythonScript.py", line 323, in _exec
# result = f(*args, **kw)
# File "Script (Python)", line 1, in eek
# Unauthorized: You are not allowed to access 'x' in this context
#
#
# ======================================================================
# FAIL: Doctest: 27.txt
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 2102, in runTest
# raise self.failureException(self.format_failure(new.getvalue()))
# AssertionError: Failed doctest test for 27.txt
# File "C:\Code\zt\lib\python\ZClasses\27.txt", line 0
#
# ----------------------------------------------------------------------
# File "C:\Code\zt\lib\python\ZClasses\27.txt", line 16, in 27.txt
# Failed example:
# ac.eek()
# Exception raised:
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 1315, in __run
# compileflags, 1) in test.globs
# File "<doctest 27.txt[10]>", line 1, in ?
# ac.eek()
# File "C:\Code\zt\lib\python\AccessControl\PermissionMapping.py", line 150, in __call__
# return apply(self, args, kw)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 311, in __call__
# return self._bindAndExec(args, kw, None)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 348, in _bindAndExec
# return self._exec(bound_data, args, kw)
# File "C:\Code\zt\lib\python\Products\PythonScripts\PythonScript.py", line 323, in _exec
# result = f(*args, **kw)
# File "Script (Python)", line 1, in eek
# Unauthorized: You are not allowed to access 'x' in this context
# ----------------------------------------------------------------------
# File "C:\Code\zt\lib\python\ZClasses\27.txt", line 19, in 27.txt
# Failed example:
# ac.eek()
# Exception raised:
# Traceback (most recent call last):
# File "C:\Code\zt\lib\python\zope\testing\doctest.py", line 1315, in __run
# compileflags, 1) in test.globs
# File "<doctest 27.txt[12]>", line 1, in ?
# ac.eek()
# File "C:\Code\zt\lib\python\AccessControl\PermissionMapping.py", line 150, in __call__
# return apply(self, args, kw)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 311, in __call__
# return self._bindAndExec(args, kw, None)
# File "C:\Code\zt\lib\python\Shared\DC\Scripts\Bindings.py", line 348, in _bindAndExec
# return self._exec(bound_data, args, kw)
# File "C:\Code\zt\lib\python\Products\PythonScripts\PythonScript.py", line 323, in _exec
# result = f(*args, **kw)
# File "Script (Python)", line 1, in eek
# Unauthorized: You are not allowed to access 'x' in this context
#
#
# ----------------------------------------------------------------------
# Ran 3 tests in 2.734s
#
# FAILED (failures=2)
def test_suite():
return unittest.TestSuite((
# To do:
# - Test working with old pickles
# - Test proper handling of __of__
# - Test export/import
doctest.DocFileSuite("_pmc.txt", setUp=setUp, tearDown=tearDown),
## XXX doctest.DocFileSuite("ZClass.txt", setUp=setUp, tearDown=tearDown),
## XXX doctest.DocFileSuite("27.txt", tearDown=tearDown27,
## XXX globs=dict(__file__=__file__),
## XXX ),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
...@@ -267,7 +267,7 @@ def testPublisher(): ...@@ -267,7 +267,7 @@ def testPublisher():
pass pass
import doctest from zope.testing import doctest
def test_suite(): def test_suite():
return doctest.DocTestSuite() return doctest.DocTestSuite()
...@@ -40,8 +40,6 @@ import ZPublisher ...@@ -40,8 +40,6 @@ import ZPublisher
def startup(): def startup():
global app global app
Globals.DatabaseVersion='3'
# Import products # Import products
OFS.Application.import_products() OFS.Application.import_products()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment